Refactor Ticket Controller, move uploaded images to storage
This commit is contained in:
parent
c031281345
commit
86e5c02c43
20 changed files with 830 additions and 356 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
<span class="nav-link">{{ __('Charge') }} :
|
||||
@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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();">⌫</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();">⌫</button>
|
||||
<button class="btn btn-outline-primary" type="submit">🔍</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> -
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue