Refactor Ticket Controller, move uploaded images to storage

This commit is contained in:
Jérémy Munsch 2022-05-24 12:38:30 +02:00
parent c031281345
commit 86e5c02c43
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
20 changed files with 830 additions and 356 deletions

View file

@ -8,6 +8,7 @@ use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use App\Enums\TicketStatus;
use App\Models\Ticket;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\View;
@ -55,9 +56,9 @@ class Controller extends BaseController
/** @var \App\Models\User|null */
$user = Auth::user();
if (!is_null($user) and !$user->isAdmin()) {
$ticketsNew = $ticketsNew->where('is_internal', 0)->whereNotIn('site_id', $ignore);
$ticketsWaiting = $ticketsWaiting->where('is_internal', 0)->whereNotIn('site_id', $ignore);
$ticketsInProgress = $ticketsInProgress->where('is_internal', 0)->whereNotIn('site_id', $ignore);
$ticketsNew = $ticketsNew->where('is_internal', false)->whereNotIn('site_id', $ignore);
$ticketsWaiting = $ticketsWaiting->where('is_internal', false)->whereNotIn('site_id', $ignore);
$ticketsInProgress = $ticketsInProgress->where('is_internal', false)->whereNotIn('site_id', $ignore);
}
View::share('ticketsNew', $ticketsNew->count());
@ -67,4 +68,68 @@ class Controller extends BaseController
return $next($request);
});
}
/**
* Build Search query on specified fields
* splitting search into words or using whole sentence
*
* @param Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields would be concatenate to make the search.
* @return Builder
*/
protected function searchQuery(
Builder $query,
string $search,
callable $searchQuery = null,
string ...$fields
): Builder {
if (count($fields)) {
// Search using words.
$words = explode(' ', $search);
$query->where(function ($query) use ($searchQuery, $words, $search, $fields) {
foreach ($words as $word) {
if (!strlen($word)) {
continue;
}
$this->searchOnFields($query, $word, $fields);
} //end foreach
// Search whole using whole sentence.
$this->searchOnFields($query, $search, $fields);
if ($searchQuery) {
$searchQuery($query);
}
});
} //end if
return $query;
}
/**
* Build query search on all fields
*
* @param Builder $query
* @param string $search
* @param array $fields
* @return Builder
*/
private function searchOnFields(Builder $query, string $search, array $fields): Builder
{
$table = $query->getModel()->getTable();
foreach ($fields as $f) {
$f = str_replace('?', '\?', $f);
if (is_array($f)) {
$query->orWhereRaw(
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($f)->map(function ($q) use ($table) {
return "`$table`.`$q`";
})->implode(', \' \', ')),
["%$search%"]
);
} else {
$query->orWhereRaw("UPPER(`$table`.`$f`) LIKE UPPER(?)", ["%$search%"]);
}
}
return $query;
}
}

View file

@ -8,35 +8,44 @@ use App\Events\NotifySubscriptions;
use App\Events\TicketStatusChanged;
use App\Http\Requests\ChangeTicketStatus;
use App\Http\Requests\CreateMessage;
use App\Http\Requests\UpdateMessage;
use App\Http\Requests\SortTickets;
use App\Http\Requests\StoreTicket;
use App\Http\Requests\UpdateMessage;
use App\Http\Requests\UpdateTicket;
use App\Lib\ToolboxHelper;
use App\Mail\NewTicket;
use App\Models\Message;
use App\Models\Site;
use App\Models\Ticket;
use App\Models\User;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
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
// * Handle Ajax
if ($request->ajax()) {
$tickets = Ticket::with('user');
if ($request->s) {
@ -71,13 +80,12 @@ class TicketController extends Controller
$q->whereId($user->id);
});
});
$tickets = $tickets->where('is_internal', 0);
$tickets = $tickets->where('is_internal', false);
}
$tickets = $tickets->where('status', ($request->st == TicketStatus::archived()) ?
'=' : '<>', TicketStatus::archived());
if ($request->search) {
$tickets = $this->searchTicket($tickets, $request);
}
$tickets = $this->searchTicket($tickets, $request);
$tickets = $this->filterTickets($tickets, $request);
$tickets = $tickets->paginate();
@ -87,6 +95,321 @@ class TicketController extends Controller
return view('ticket.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' => sprintf(
'Ticket %d (%s): %s',
$ticket->id,
$ticket->user ? $ticket->user->email : __('Utilisateur supprimé'),
Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35)
),
'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('ticket.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('sites.create')
->with('warning', trans('Vous devez tout d\'abord créer un site pour y ajouter un ticket'));
}
return view('ticket.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 \Illuminate\Http\UploadedFile|null */
$fileTicket = $request->file_ticket;
/** @var \Illuminate\Http\UploadedFile|null */
$fileTicket2 = $request->file_ticket2;
if ($fileTicket) {
$ticket->file = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket);
}
if ($fileTicket2) {
$ticket->file2 = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket2);
}
$ticket->user()->associate(auth()->user());
$ticket->site()->associate($site);
$ticket->saveOrFail();
$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('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('ticket.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 \Illuminate\Http\UploadedFile|null */
$fileTicket = $request->file_ticket;
/** @var \Illuminate\Http\UploadedFile|null */
$fileTicket2 = $request->file_ticket2;
if ($fileTicket) {
$ticket->file = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket);
}
if ($fileTicket2) {
$ticket->file2 = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket2);
}
$ticket->site()->associate($site);
$ticket->saveOrFail();
return redirect()->route('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 */
$fileMessage = $request->file_message;
$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);
}
$message->saveOrFail();
if ($fileMessage) {
$message->file = ToolboxHelper::storeImage($message->getTable(), $fileMessage);
$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 = TicketStatus::archived();
$ticket->saveOrFail();
return redirect()->route('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::find(Auth::user()->id)->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,
]);
$oldStatus = $ticket->status;
$ticket->status = TicketStatus::inprogress();
$ticket->saveQuietly();
event(new TicketStatusChanged($ticket, $oldStatus, null, $message));
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
User::find(Auth::user()->id)->subscriptions()->toggle($ticket->id);
return back()->with('warning', trans('Votre ticket à été réouvert.'));
}
// * PRIVATES
/**
* Filter Tickets request constructor
*
* @param Builder $tickets
* @param Request $request
* @return Builder
*/
private function filterTickets(Builder $tickets, Request $request): Builder
{
if ($request->rst) {
@ -141,251 +464,52 @@ class TicketController extends Controller
return $tickets;
}
/**
* Search ticket sql request constructor
*
* @param Builder $tickets
* @param Request $request
* @return Builder
*/
private function searchTicket(Builder $tickets, Request $request): Builder
{
$words = explode(' ', $request->search);
return $tickets->where(function ($q) use ($words) {
foreach ($words as $word) {
if (!strlen($word)) {
continue;
}
$q = $q->where(function ($q) use ($word) {
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(TicketStatus::toArray())->filter(function (TicketStatus $t) use ($word) {
return strpos(strtolower($t->label), $word) !== false;
return Str::slug($t->label) === Str::slug($word);
})->map(function (TicketStatus $ticket) {
return $ticket->value;
});
$t = collect(TicketType::toArray())->filter(function (TicketType $t) use ($word) {
return strpos(strtolower($t->label), $word) !== false;
$t = collect(TicketType::toArray())->filter(function (TicketType $s) use ($word) {
return Str::slug($s->label) === Str::slug($word);
})->map(function (TicketType $ticket) {
return $ticket->value;
});
$q->where('id', $word)
->orwhere('text', 'LIKE', "%$word%")
$q->orWhere('id', $word)
->orWhereIn('status', $s)
->orWhereIn('type', $t)
->orWhere('file', 'LIKE', "%$word%")
->orWhere('file2', 'LIKE', "%$word%")
->orWhereHas('user', function ($q) use ($word) {
$q->where('name', 'LIKE', "%$word%");
});
});
}//end foreach
});
}
public function show(Request $request, Ticket $ticket)
{
if ($request->ajax()) {
return \response()->json([
'label' => sprintf(
'Ticket %d (%s): %s',
$ticket->id,
$ticket->user ? $ticket->user->email : __('Utilisateur supprimé'),
Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35)
),
'value' => $ticket->id
]);
}
$user = Auth::user();
if (!$user->can('update', $ticket)) {
abort(403);
}
if ($ticket->is_internal === 1 && !$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('ticket.show', compact('ticket', 'messages', 'isSubscribed'));
}
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('sites.create')
->with('warning', 'Vous devez tout d\'abord créer un site pour y ajouter un ticket');
}
return view('ticket.create', compact('sites'));
}
public function store(StoreTicket $request)
{
$site = Site::findOrFail($request->site_id);
$ticket = new Ticket($request->validated());
if ($request->file_ticket) {
$ticket->file = Ticket::saveFile($site, $ticket, $ticket->type, $request->file_ticket);
}
if ($request->file_ticket2) {
$ticket->file2 = Ticket::saveFile($site, $ticket, $ticket->type, $request->file_ticket2);
}
if ($request->has('is_internal')) {
$ticket->is_internal = 1;
} else {
$ticket->is_internal = 0;
}
$ticket->user()->associate(auth()->user());
$ticket->saveOrFail();
/** @var \App\Models\User */
$user = auth()->user();
$user->subscriptions()->toggle($ticket->id);
if (config('mail.MAIL_ADMIN')) {
Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket));
} else {
throw new Exception('Missing MAIL_ADMIN in .env');
}
return redirect()->route('ticket.show', $ticket)
->with('success', 'Ticket enregistré !');
}
// phpcs:ignore 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('ticket.edit', compact('ticket', 'sites'));
}
public function update(UpdateTicket $request, Ticket $ticket)
{
$site = Site::findOrFail($request->site_id);
$ticket->fill($request->validated());
if ($request->file_ticket) {
if (File::exists($ticket->file)) {
File::delete($ticket->file);
}
$ticket->file = Ticket::saveFile($site, $ticket, $ticket->type, $request->file_ticket);
}
if ($request->file_ticket2) {
if (File::exists($ticket->file2)) {
File::delete($ticket->file2);
}
$ticket->file2 = Ticket::saveFile($site, $ticket, $ticket->type, $request->file_ticket2);
}
if ($request->has('is_internal')) {
$ticket->is_internal = 1;
} else {
$ticket->is_internal = 0;
}
$ticket->save();
return redirect()->route('ticket.edit', $ticket->id)
->with('success', 'Modifications enregistrées !');
}
public function changeStatus(ChangeTicketStatus $request, Ticket $ticket)
{
$ticket->fill($request->validated());
$ticket->save();
return back()
->with('warning', trans('Le status à été modifié'));
}
public function createMessage(CreateMessage $request, Ticket $ticket)
{
$fileMessage = $request->file_message;
$message = new Message($request->validated());
$message->user()->associate(auth()->user());
$message->ticket()->associate($ticket);
/** @var \App\Models\User */
$user = auth()->user();
// Toggle auth user subscription
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);
}
$message->saveOrFail();
// ici $message->id n'est pas connu lors de la création du message.
// on doit donc sauvegarder cette info après le create initial
if ($request->file_message) {
$siteSlug = Str::slug($ticket->site->name);
$fileMessage = $request->file_message;
$fileName = $siteSlug . '-ticket-' . $ticket->id .
'-message-' . $message->id . '.' . $fileMessage->extension();
$fileMessage->move(public_path('fichiers/' . $siteSlug . '/' . $ticket->id . '/'), $fileName);
$filePath = 'fichiers/' . $siteSlug . '/' . $ticket->id . '/' . $fileName;
$message->file = $filePath;
$message->saveOrFail();
}
return back()
->with('success', trans('Votre message à été ajouté'));
}
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é'));
}
public function setToArchive(Ticket $ticket)
{
$ticket->status = TicketStatus::archived();
$ticket->saveOrFail();
return redirect()->route('ticket.index')
->with('warning', trans('Le ticket est archivé'));
}
public function subscribe(Ticket $ticket)
{
/** @var \App\Models\User */
$user = auth()->user();
User::find(Auth::user()->id)->subscriptions()->toggle($ticket->id);
$status = $user->subscriptions()->where('ticket_id', $ticket->id)->exists();
if ($status) {
return back()
->with('success', trans('Vous avez été inscrit'));
}
return back()->with('warning', trans('Vous avez été désinscrit'));
}
public function reopen(Ticket $ticket)
{
$message = "{$ticket->user->name} à réouvert le ticket n°{$ticket->id}";
$oldStatus = $ticket->status;
$ticket->status = TicketStatus::inprogress();
$ticket->saveQuietly();
event(new TicketStatusChanged($ticket, $oldStatus, null, $message));
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
User::find(Auth::user()->id)->subscriptions()->toggle($ticket->id);
return back()->with('warning', trans('Votre ticket à été réouvert.'));
} //end foreach
} //end if
}, 'text', 'file', 'file2');
}
}

View file

@ -2,7 +2,7 @@
namespace App\Http\Middleware;
use App\Lib\Utils;
use App\Lib\ToolboxHelper;
use Illuminate\Http\Request;
class XssSanitizer
@ -29,12 +29,12 @@ class XssSanitizer
\array_walk_recursive($isJson, function (&$item) {
if (\is_string($item)) {
$item = clean($item);
$item = Utils::stripCkEditorSchemesOnMediaInputs($item);
$item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item);
}
});
} else {
$input = clean($input);
$input = Utils::stripCkEditorSchemesOnMediaInputs($input);
$input = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($input);
}
} else {
$input = strip_tags($input);

View file

@ -13,13 +13,28 @@ class CreateMessage extends FormRequest
*/
public function authorize()
{
/** @var \App\Models\User */
$user = auth()->user();
/** @var \App\Models\User|null */
if (!($user = auth()->user())) {
return false;
}
return auth()->check() and auth()->user()->sites and (
$user->isAdmin() or
auth()->user()->sites->contains(request()->route()->ticket->site_id)
);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'subscribe' => $this->subscribe ? true : false
]);
return auth()->check() and ($user->isAdmin() or
auth()->user()->sites->contains(request()->route()->ticket->site_id));
}
/**

View file

@ -15,16 +15,34 @@ class StoreTicket extends FormRequest
*/
public function authorize()
{
/** @var \App\Models\User */
$user = auth()->user();
if (!$user->isAdmin()) {
$this->merge([
'status' => TicketStatus::new()->value,
]);
}
return auth()->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
/** @var \App\Models\User|null */
$user = auth()->user();
if ($user) {
if (!$user->isAdmin()) {
$this->merge([
'status' => $this->site->status->value,
'is_internal' => false,
]);
} else {
$this->merge([
'is_internal' => $this->is_internal ? true : false,
]);
}
}
}
/**
* Get the validation rules that apply to the request.
*
@ -75,6 +93,7 @@ class StoreTicket extends FormRequest
'text' => 'required|string|min:40',
'file_ticket' => 'mimes:jpeg,jpg,png,pdf|max:2048',
'file_ticket2' => 'mimes:jpeg,jpg,png,pdf|max:2048',
'is_internal' => 'required|boolean'
];
}
}

View file

@ -4,15 +4,19 @@ namespace App\Http\Requests;
class UpdateTicket extends StoreTicket
{
public function authorize()
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
/** @var \App\Models\User */
$user = auth()->user();
if (!$user->isAdmin()) {
$this->merge(['status' => $this->site->status->value,
]);
/** @var \App\Models\User|null */
if (!($user = auth()->user())) {
return false;
}
return auth()->check() and ($user->isAdmin() or
request()->route()->ticket->created_by === auth()->user()->id);
optional(request()->route()->ticket)->created_by === $user->id
);
}
}

View file

@ -3,9 +3,16 @@
namespace App\Lib;
use App\Enums\LoadLevel;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class Utils
class ToolboxHelper
{
/**
* Compute plain text to html representation.
@ -143,4 +150,106 @@ class Utils
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
return $input;
}
/**
* Assert That no Back Office url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalBoUrls(string $field, string $text): string
{
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => sprintf(
'%s : Il est interdit de coller une url du back office dans ce champ',
trans($field)
)
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Store an image of different types
*
* @param string $table
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $image
* @return string
* @throws \ErrorException If file copy fails.
*/
public static function storeImage(string $table, $image): string
{
/** @var \Illuminate\Http\UploadedFile $image */
if (\is_object($image) and get_class($image) === UploadedFile::class) {
$newPath = self::prepareMoveFile($table, $image->getClientOriginalName());
$newPath = str_replace(storage_path('app'), '', $newPath);
$image->storePubliclyAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return 'storage' . str_replace('/public', '', $newPath);
}
/** @var \SplFileInfo $image */
if (\is_object($image) and get_class($image) === \SplFileInfo::class) {
$newPath = self::prepareMoveFile($table, $image->getBaseName());
if (!File::copy($image->getRealPath(), $newPath)) {
throw new \ErrorException(sprintf(
'Failed to copy file %s to %s',
$image->getRealPath(),
$newPath
));
}
return \ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
}
if (\is_string($image)) {
return $image;
}
}
/**
* Prepare storage path and return
* the image full path
*
* @param string $table Model table name.
* @param string $filename The basename for the file.
* @return string
*/
private static function prepareMoveFile(string $table, string $filename): string
{
$pathFolder = \storage_path(sprintf('app/public/images/%s', $table));
if (!File::exists($pathFolder)) {
File::makeDirectory($pathFolder, 0755, true);
}
$filename = sprintf('%s-%s', Str::uuid(), $filename);
$fullPath = sprintf('%s/%s', $pathFolder, $filename);
if (File::exists($fullPath)) {
File::delete($fullPath);
}
return $fullPath;
}
}

View file

@ -2,7 +2,7 @@
namespace App\Mail;
use App\Lib\Utils;
use App\Lib\ToolboxHelper;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
@ -38,7 +38,7 @@ class TicketMention extends Mailable
public function build()
{
$ticket = $this->ticket;
$corpsMaj = Utils::plainToHtml($this->corpsMaj);
$corpsMaj = ToolboxHelper::plainToHtml($this->corpsMaj);
return $this->subject("[SupportWeb] Vous avez été mentionné sur le ticket n° $ticket->id")
->view('emails.ticket-mention', compact('ticket', 'corpsMaj'));
}

View file

@ -2,7 +2,7 @@
namespace App\Mail;
use App\Lib\Utils;
use App\Lib\ToolboxHelper;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
@ -38,7 +38,7 @@ class TicketUpdate extends Mailable
public function build()
{
$ticket = $this->ticket;
$corpsMaj = \html_entity_decode(Utils::plainToHtml($this->corpsMaj));
$corpsMaj = \html_entity_decode(ToolboxHelper::plainToHtml($this->corpsMaj));
return $this->subject("[SupportWeb][{$ticket->site->name}] Ticket n°$ticket->id")
->view('emails.ticket-update', compact('ticket', 'corpsMaj'));
}

View file

@ -6,16 +6,25 @@ use App\Events\NotifySubscriptions;
use App\Events\NotifyTicketCreator;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
class Message extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'message'
];
/**
* The attributes that are mass assignable.
*
* @return void
*/
protected static function booted()
{
static::created(function (Message $message) {
@ -50,7 +59,7 @@ class Message extends Model
// https://supportweb.ldc.fr/ticket/583/message/create
// SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '583-8' for key 'PRIMARY'
// $ticket->subscriptions()->attach($messageUser);
// Add subscription
// Add subscription.
if (!$messageUser->subscriptions()->find($ticket->id)) {
$messageUser->subscriptions()->attach($ticket->id);
\session()->flash('info', trans('Vous avez été inscrit.'));
@ -69,28 +78,25 @@ class Message extends Model
});
}
// * RELATIONSHIPS
/**
* BelongsTo Ticket relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function ticket()
{
return $this->belongsTo(Ticket::class, 'ticket_id');
}
/**
* BelongsTo User relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function saveFile(Site $site, Ticket $ticket, UploadedFile $file)
{
$siteAssetFullPath = Site::ticketAssetsFullPath($site->slug_name);
$fileAssetPath = Site::ticketAssetsPath($site->slug_name);
$fileName = sprintf(
'%s-%s-%d-%d.%s',
$site->slug_name,
$ticket->id,
rand(),
$file->extension()
);
$file->move(sprintf('%s/messages/%d', $siteAssetFullPath, $ticket->id), $fileName);
return sprintf('%s/%d/%s', $fileAssetPath, $ticket->id, $fileName);
}
}

View file

@ -18,20 +18,45 @@ class Site extends Model
use IsCrudModel;
use SoftDeletes;
/**
* Crud Label
*
* @var string
*/
protected $crudLabelColumn = 'name';
/**
* Crud Value
*
* @var string
*/
protected $crudValueColumn = 'id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'status',
'description', 'todo'
];
/**
* The model's attributes.
*
* @var array
*/
protected $attributes = [
'status' => '',
];
public function __construct($attributes = [])
/**
* Site
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'name' => [
@ -142,6 +167,11 @@ class Site extends Model
parent::__construct($attributes);
}
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::created(function (Site $site) {
@ -171,32 +201,67 @@ class Site extends Model
});
}
// * RELATIONSHIPS
/**
* HasMany Message relation
* The users of type client that would have access to this site tickets
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function users()
{
return $this->belongsToMany(User::class);
}
/**
* HasMany Ticket relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function tickets()
{
return $this->hasMany(Ticket::class, 'site_id');
}
/**
* BelongsToMany Project relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function projects()
{
return $this->belongsToMany(Project::class);
}
/**
* MorphMany Log relation
*
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function logs()
{
return $this->morphMany(Log::class, 'parent');
}
/**
* MorphMany File relation
*
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function files()
{
return $this->morphMany(File::class, 'parent');
}
public function getSlugNameAttribute()
// * METHODS
/**
* Getter slugName
*
* @return string
*/
public function getSlugNameAttribute(): string
{
return Str::slug($this->name);
}

View file

@ -13,7 +13,6 @@ use App\Mail\TicketMention;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
use Kwaadpepper\Enum\Traits\CastsEnums;
@ -22,11 +21,30 @@ class Ticket extends Model
use HasFactory;
use CastsEnums;
/**
* The attributes that should be cast to Enums.
*
* @var array
*/
protected $enumCasts = [
'type' => TicketType::class,
'status' => TicketStatus::class
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'is_internal' => 'bool'
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'site_id',
'type',
@ -35,10 +53,15 @@ class Ticket extends Model
'is_internal'
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (Ticket $ticket) {
// If evolution force the ticket status on estimateTodo
// If evolution force the ticket status on estimateTodo.
if ($ticket->type->equals(TicketType::evolution())) {
$ticket->status = TicketStatus::estimateTodo();
}
@ -64,41 +87,51 @@ class Ticket extends Model
});
}
// * RELATIONSHIPS
/**
* BelongsTo Site relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function site()
{
return $this->belongsTo(Site::class);
}
/**
* BelongsTo User relation
* The user that created the ticket
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class, 'created_by');
}
/**
* HasMany Message relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function messages()
{
return $this->hasMany(Message::class, 'ticket_id');
}
/**
* BelongsToMany User relation
* The Users that are following this ticket
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function subscriptions()
{
return $this->belongsToMany(User::class, 'subscriptions');
}
public static function saveFile(Site $site, Ticket $ticket, TicketType $type, UploadedFile $file)
{
$siteAssetFullPath = Site::ticketAssetsFullPath($site->slug_name);
$fileAssetPath = Site::ticketAssetsPath($site->slug_name);
$fileName = sprintf(
'%s-%s-%d-%d.%s',
$site->slug_name,
$type->getDefinition(),
$ticket->id,
rand(),
$file->extension()
);
$file->move(sprintf('%s/%d', $siteAssetFullPath, $ticket->id), $fileName);
return sprintf('%s/%d/%s', $fileAssetPath, $ticket->id, $fileName);
}
// * METHODS
/**
* Parse message @user tags and send emails to concerned users
@ -110,7 +143,7 @@ class Ticket extends Model
public function sendBeenMentionnedToUser(Message $message): Collection
{
$ticket = $this;
// find @user + send mail si un seul user est trouvé
// Find @user + send mail si un seul user est trouvé.
$usersNotified = collect([]);
$matches = [];
$ticket->load('site');
@ -128,7 +161,7 @@ class Ticket extends Model
->orWhere('role', Role::developper()->value);
})->firstOrFail();
if ($ticket->is_internal === 1) {
if ($ticket->is_internal) {
if ($user->isAdmin()) {
$usersNotified->add($user);
SendTicketMentionMail::dispatchAfterResponse(

View file

@ -233,7 +233,7 @@ return [
// * Application custom
'TicketType' => App\Enums\TicketType::class,
'TicketStatus' => App\Enums\TicketStatus::class,
'Utils' => App\Lib\Utils::class,
'ToolboxHelper' => App\Lib\ToolboxHelper::class,
'CrudAction' => App\Enums\CrudAction::class,
'CrudType' => App\Enums\CrudType::class,
'Role' => App\Enums\Role::class,

View file

@ -0,0 +1,59 @@
<?php
use App\Models\File as ModelsFile;
use App\Models\Message;
use App\Models\Ticket;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
class MoveImagesToStorage extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
Schema::table('storage', function (Blueprint $table) {
if (File::exists(public_path('fichiers'))) {
File::moveDirectory(public_path('fichiers'), public_path('storage/fichiers'));
Ticket::all()->map(function (Ticket $ticket) {
if ($ticket->file) {
$ticket->file = "storage/{$ticket->file}";
}
if ($ticket->file2) {
$ticket->file2 = "storage/{$ticket->file2}";
}
$ticket->saveQuietly();
});
Message::all()->map(function (Message $message) {
if ($message->file) {
$message->file = "storage/{$message->file}";
}
$message->saveQuietly();
});
ModelsFile::all()->map(function (ModelsFile $fichier) {
if ($fichier->file) {
$fichier->file = "storage/{$fichier->file}";
}
$fichier->saveQuietly();
});
} //end if
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('storage', function (Blueprint $table) {
});
}
}

View file

@ -1,16 +1,16 @@
<span class="nav-link">{{ __('Charge') }}&nbsp;:&nbsp;
@switch(Utils::computeWorkload())
@switch(ToolboxHelper::computeWorkload())
@case(LoadLevel::weak())
<span class="p-1 rounded bg-dark fw-bold text-light">{{ Utils::computeWorkload()->label }}</span>
<span class="p-1 rounded bg-dark fw-bold text-light">{{ ToolboxHelper::computeWorkload()->label }}</span>
@break
@case(LoadLevel::stable())
<span class="p-1 rounded bg-success fw-bold text-dark">{{ Utils::computeWorkload()->label }}</span>
<span class="p-1 rounded bg-success fw-bold text-dark">{{ ToolboxHelper::computeWorkload()->label }}</span>
@break
@case(LoadLevel::high())
<span class="p-1 rounded bg-warning fw-bold text-dark">{{ Utils::computeWorkload()->label }}</span>
<span class="p-1 rounded bg-warning fw-bold text-dark">{{ ToolboxHelper::computeWorkload()->label }}</span>
@break
@case(LoadLevel::overload())
<span class="p-1 rounded bg-danger fw-bold text-dark">{{ Utils::computeWorkload()->label }}</span>
<span class="p-1 rounded bg-danger fw-bold text-dark">{{ ToolboxHelper::computeWorkload()->label }}</span>
@break
@endswitch
</span>

View file

@ -4,13 +4,11 @@
@section('content')
<div class="card">
<div class="card-header">Créer un ticket<a href="{{ route('home') }}" class="float-end btn btn-sm btn-primary">Retour</a></div>
<div class="card-header">{{ __('Créer un ticket') }}<a href="{{ route('home') }}" class="float-end btn btn-sm btn-primary">{{ __('Retour') }}</a></div>
<div class="card-body">
<form method="POST" action="{{ route('ticket.store') }}" enctype="multipart/form-data">
@csrf
@include('ticket.form-inputs')
<div class="form-group text-center">
<input type="submit" class="btn btn-primary" value="Enregistrer">
</div>

View file

@ -4,17 +4,12 @@
@section('content')
<div class="card">
<div class="card-header">Modifier un ticket<a href="{{ route('home') }}" class="float-end btn btn-sm btn-primary">Retour</a></div>
<div class="card-header">{{ __('Modifier un ticket') }}<a href="{{ route('home') }}" class="float-end btn btn-sm btn-primary">{{ __('Retour') }}</a></div>
<div class="card-body">
<form method="POST" action="{{ route('ticket.update', $ticket->id) }}" enctype="multipart/form-data">
@csrf
@method('PUT')
@if (session('success'))
<div class="alert alert-success">{!! session('success') !!}</div>
@endif
@include('ticket.form-inputs')
<div class="form-group text-center">
<input type="submit" class="btn btn-primary" value="Modifier">
</div>

View file

@ -3,7 +3,7 @@ $userPr = Auth::user()->sites ?? collect([]);
@endphp
<div class="form-row">
<div class="form-group col">
<label for="site" class="col-form-label">{{ __('Projet') }}</label>
<label for="site" class="col-form-label">{{ __('Site') }}</label>
<select class="form-select" name="site_id" id="site">
@foreach($sites as $site)
@if (Auth::user()->isAdmin())
@ -62,7 +62,7 @@ $userPr = Auth::user()->sites ?? collect([]);
</div>
@can('changeInternalStatus', App\Model\Ticket::class)
<div class="form-group text-center">
<input type="checkbox" name="is_internal" id="is_internal" @if ((old('is_internal') && old('is_internal')===1) || (isset($ticket) && $ticket->is_internal === 1)) checked @endif>
<input type="checkbox" name="is_internal" id="is_internal" @if(old('is_internal', isset($ticket) and $ticket->is_internal)) checked @endif>
<label for="is_internal" class="col-form-label">Ticket interne</label>
</div>
@endcan
@endcan

View file

@ -45,8 +45,8 @@ $userPr = Auth::user()->sites ?? collect([]);
<div class="col">
<form id="searchTicketForm" class="float-end mt-3 w-75" action="{{ request()->fullUrlWithQuery(['search' => '']) }}">
<div class="input-group mb-3">
<input id="searchTicket" type="text" name="search" class="form-control" placeholder="{{ __('Recherche de ticket..') }}" aria-label="{{ __('Recherche de ticket..') }}" aria-describedby="searchTicket" value="{{ request()->search }}">
<button class="btn btn-outline-danger" type="button" onclick="$('#searchTicket').val('');$('#searchTicketForm').submit();">&#9003;</button>
<input id="searchTicket" type="text" name="search" class="form-control" placeholder="{{ __('Recherche de ticket..') }}" aria-label="{{ __('Recherche de ticket..') }}" aria-describedby="searchTicket" value="{{ session()->get('tickets.search') }}">
<button class="btn btn-outline-danger" type="button" onclick="document.getElementById('searchTicket').value = '';document.getElementById('searchTicketForm').submit();">&#9003;</button>
<button class="btn btn-outline-primary" type="submit">&#128269;</button>
</div>
</form>
@ -120,7 +120,7 @@ $userPr = Auth::user()->sites ?? collect([]);
<tr>
<td scope="row">
{{ $ticket->id }}
@if ($ticket->is_internal === 1)
@if ($ticket->is_internal)
🔒
@endif
</td>
@ -157,7 +157,7 @@ $userPr = Auth::user()->sites ?? collect([]);
<b>{{ $ticket->site->name }}</b> -
<u>
{{__('Ticket n°') }} {{ $ticket->id }}
@if ($ticket->is_internal === 1)
@if ($ticket->is_internal)
🔒
@endif
</u>&nbsp;-&nbsp;

View file

@ -3,31 +3,13 @@
@section('content')
<div class="form-row row">
<h2 class="col">
@php
$back = route('ticket.index');
$url = parse_url(url()->previous());
if (
($url['host'] ?? '') === parse_url(config('app.url'), PHP_URL_HOST) and
($url['path'] ?? '') === '/ticket'
) {
$queries = explode('&', $url['query'] ?? '');
foreach($queries as $q) {
$q = explode('=', $q);
if(($q[0] ?? '') === 'pr' and ctype_digit(($q[1] ?? ''))) {
$back = route('ticket.index', ['pr' => $q[1]]);
}
}
}
@endphp
<a href="{{ $back }}" class="text-decoration-none">←</a>
<a href="{{ route('ticket.index') }}" class="text-decoration-none">←</a>
{{ __('Ticket n° :ticketId : :ticketSiteName - :ticketTypeLabel', [
'ticketId' => $ticket->id,
'ticketSiteName' => $ticket->site->name,
'ticketTypeLabel' => $ticket->type->label
]) }}
@if ($ticket->is_internal === 1)
🔒
@endif
@if ($ticket->is_internal)🔒@endif
<span class="badge bg-info">{{ $ticket->status->label }}</span>
</h2>
<div class="col d-flex flex-row text-end">
@ -35,7 +17,7 @@
<form action="{{ route('ticket.subscribe', $ticket->id) }}" method="POST" id="form_abonnement" class="col">
@csrf
<div class="custom-control custom-checkbox text-right p-2">
<input type="checkbox" name="subscription" class="custom-control-input" id="subscription" @if ($isSubscribed)checked @endif onchange="$('#form_abonnement').submit()">
<input type="checkbox" name="subscription" class="custom-control-input" id="subscription" @if ($isSubscribed)checked @endif onchange="document.getElementById('form_abonnement').submit()">
<label class="custom-control-label" for="subscription">{{ __('Suivre le ticket') }}</label>
</div>
</form>
@ -43,7 +25,7 @@
@can('changeStatus', $ticket)
<form action="{{ route('ticket.changeStatus', $ticket->id) }}" method="POST" id="form_status" class="col">
@csrf
<select class="form-select" name="status" id="status" onchange="$('#form_status').submit();">
<select class="form-select" name="status" id="status" onchange="document.getElementById('form_status').submit();">
@foreach(TicketStatus::toArray() as $ticketStatus)
<option value="{{ $ticketStatus->value }}" @if($ticketStatus->equals($ticket->status))selected @endif>{{ $ticketStatus->label }}</option>
@endforeach
@ -120,9 +102,9 @@
</div>
@else
<div class="@if($message->user_id === Auth::user()->id)offset-md-4 alert-dark @else alert-light @endif col-md-8 alert">
{!! Utils::plainToHtml($message->message) !!}
{!! ToolboxHelper::plainToHtml($message->message) !!}
@if ($message->file !== null)
<br><a @if(Utils::isImageFile($message->file))data-fslightbox="gallery" @endif href="{{ asset($message->file) }}" target="_blank">Ouvrir la pièce jointe</a><br>
<br><a @if(ToolboxHelper::isImageFile($message->file))data-fslightbox="gallery" @endif href="{{ asset($message->file) }}" target="_blank">Ouvrir la pièce jointe</a><br>
@endif<br><br>
<small class="text-muted fw-bold">
{!! __('Créé le :date par <b>:userName</b>', [
@ -172,7 +154,7 @@
<hr>
<div>
<h4>{{__('Description') }}</h4>
<p>{!! clean(Utils::plainToHtml($ticket->text)) !!}</p>
<p>{!! clean(ToolboxHelper::plainToHtml($ticket->text)) !!}</p>
<small class="text-muted">{!! __('Créé le :date par <b>:userName</b>', [
'date' => \Carbon\Carbon::parse($ticket->created_at)->format('d/m/Y à H:i'),
'userName' => $ticket->user ? $ticket->user->name : __('Utilisateur supprimé')
@ -186,13 +168,13 @@
@if (!is_null($ticket->file))
<li>
{{ __('Pièce jointe n°1') }} :
<a @if(Utils::isImageFile($ticket->file))data-fslightbox="gallery" @endif href="{{ asset($ticket->file) }}" target="_blank">{{ __('Ouvrir la pièce jointe') }}</a>
<a @if(ToolboxHelper::isImageFile($ticket->file))data-fslightbox="gallery" @endif href="{{ asset($ticket->file) }}" target="_blank">{{ __('Ouvrir la pièce jointe') }}</a>
</li>
@endif
@if (!is_null($ticket->file2))
<li>
{{ __('Pièce jointe n°2') }} :
<a @if(Utils::isImageFile($ticket->file2))data-fslightbox="gallery" @endif href="{{ asset($ticket->file2) }}" target="_blank">{{ __('Ouvrir la pièce jointe') }}</a>
<a @if(ToolboxHelper::isImageFile($ticket->file2))data-fslightbox="gallery" @endif href="{{ asset($ticket->file2) }}" target="_blank">{{ __('Ouvrir la pièce jointe') }}</a>
</li>
@endif
</ul>