diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 1fae538..a477dd0 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -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; + } } diff --git a/app/Http/Controllers/SiteController.php b/app/Http/Controllers/SiteController.php index 112dd17..0549f13 100644 --- a/app/Http/Controllers/SiteController.php +++ b/app/Http/Controllers/SiteController.php @@ -13,6 +13,9 @@ class SiteController extends Controller index as crudIndex; } + /** + * SiteController + */ public function __construct() { parent::__construct(); @@ -23,8 +26,19 @@ class SiteController extends Controller /** @var Site */ public static $modelClass = Site::class; + /** + * Crud Layout + * + * @var string + */ public static $layout = 'layout.app'; + /** + * Site.index crud + * + * @param CrudIndexRequest $request + * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory + */ public function index(CrudIndexRequest $request) { /** @var \Illuminate\View\View */ diff --git a/app/Http/Controllers/TicketController.php b/app/Http/Controllers/TicketController.php index 6f4c17f..6a1ac4f 100644 --- a/app/Http/Controllers/TicketController.php +++ b/app/Http/Controllers/TicketController.php @@ -6,39 +6,48 @@ use App\Enums\TicketStatus; use App\Enums\TicketType; use App\Events\NotifySubscriptions; use App\Events\TicketStatusChanged; +use App\Http\Requests\AssignTicketToUserRequest; 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'); + $tickets = Ticket::with(['user', 'assignedUser']); if ($request->s) { $tickets = $tickets->where('text', 'LIKE', "%$request->s%") ->orWhere('id', $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,14 +95,387 @@ 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->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]))); + + /** @var \App\Models\User */ + $user = auth()->user(); + $user->subscriptions()->toggle($ticket->id); + + return back()->with('warning', trans('Votre ticket à été réouvert.')); + } + + /** + * Assign ticket to user ticket + * + * @param AssignTicketToUserRequest $request + * @param Ticket $ticket + * @return \Illuminate\Http\RedirectResponse + */ + public function assignTo(AssignTicketToUserRequest $request, Ticket $ticket) + { + /** @var \App\Models\User */ + $user = auth()->user(); + + /** @var \App\Models\User|null */ + if ($assignedUser = User::whereId($request->assign)->first()) { + $message = trans(':admin à assigné le ticket n°:ticketNumber à :assignedAdmin', [ + 'admin' => $user->name, + 'ticketNumber' => $ticket->id, + 'assignedAdmin' => $assignedUser->name, + ]); + $ticket->assignedUser()->associate($assignedUser); + } else { + $message = trans( + ':admin modifié l\'assignation du ticket n°:ticketNumber, il n\'est plus assigné à quelqu\'un.', + [ + 'admin' => $user->name, + 'ticketNumber' => $ticket->id, + ] + ); + $ticket->assignedUser()->dissociate(); + } + $ticket->saveQuietly(); + + + /** @var \App\Enums\TicketStatus */ + $oldStatus = $ticket->status; + event(new TicketStatusChanged($ticket, $oldStatus, null, $message)); + event(new NotifySubscriptions($ticket, $message, collect([$ticket->user]))); + + /** @var \App\Models\User */ + $user = auth()->user(); + $user->subscriptions()->toggle($ticket->id); + + if ($assignedUser) { + return back()->with('info', trans('Le ticket à été assigné à :assignedUser', [ + 'assignedUser' => $assignedUser->name + ])); + } + return back()->with('info', trans('Le ticket n\'est plus assigné à personne')); + } + + // * PRIVATES + + /** + * Filter Tickets request constructor + * + * @param Builder $tickets + * @param Request $request + * @return Builder + */ private function filterTickets(Builder $tickets, Request $request): Builder { if ($request->rst) { + Session::remove('tickets.ass'); Session::remove('tickets.st'); Session::remove('tickets.pr'); Session::remove('tickets.sort_col'); Session::remove('tickets.sort_way'); } + if ($request->assr) { + Session::remove('tickets.ass'); + } if ($request->prs) { Session::remove('tickets.pr'); } @@ -113,14 +494,23 @@ class TicketController extends Controller if ($request->st) { Session::put('tickets.st', $request->st); } + if ($request->ass) { + Session::put('tickets.ass', $request->ass); + } Session::save(); if (Session::has('tickets.pr')) { - $tickets = $tickets->where('site_id', Session::get('tickets.pr')); + $tickets->where('site_id', Session::get('tickets.pr')); + } + + if (Session::has('tickets.ass')) { + $tickets->whereHas('assignedUser', function ($q) { + $q->whereId(Session::get('tickets.ass')); + }); } if (Session::has('tickets.st')) { - $tickets = $tickets->where('status', Session::get('tickets.st')); + $tickets->where('status', Session::get('tickets.st')); } if ( @@ -128,264 +518,65 @@ class TicketController extends Controller Session::has('tickets.sort_way') and in_array(Session::get('tickets.sort_col'), [ 'id', 'site_id', 'created_at', 'created_by', - 'type', 'status' + 'type', 'status', 'assigned_to' ]) ) { - $tickets = $tickets->orderBy( + $tickets->orderBy( Session::get('tickets.sort_col'), Session::get('tickets.sort_way') ); } else { - $tickets = $tickets->orderBy('id', 'DESC'); + $tickets->orderBy('id', 'DESC'); } 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'); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index e4d9604..60ad4c2 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -14,7 +14,7 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - \App\Http\Middleware\StartSession::class, + \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, \Fruitcake\Cors\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, @@ -32,9 +32,12 @@ class Kernel extends HttpKernel 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \App\Http\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, + \App\Http\Middleware\SecureHeaders::class, + 'xss' ], 'api' => [ @@ -61,5 +64,6 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'xss' => \App\Http\Middleware\XssSanitizer::class, ]; } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index aeb9d80..7e84ea7 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -9,11 +9,12 @@ class Authenticate extends Middleware /** * Get the path the user should be redirected to when they are not authenticated. * - * @param \Illuminate\Http\Request $request + * @param mixed $request * @return string|null */ protected function redirectTo($request) { + /** @var \Illuminate\Http\Request $request */ if (! $request->expectsJson()) { return route('login'); } diff --git a/app/Http/Middleware/PreventRequestsDuringMaintenance.php b/app/Http/Middleware/PreventRequestsDuringMaintenance.php index 402ea32..467b7e2 100644 --- a/app/Http/Middleware/PreventRequestsDuringMaintenance.php +++ b/app/Http/Middleware/PreventRequestsDuringMaintenance.php @@ -11,6 +11,5 @@ class PreventRequestsDuringMaintenance extends Middleware * * @var array */ - protected $except = [ - ]; + protected $except = []; } diff --git a/app/Http/Middleware/SecureHeaders.php b/app/Http/Middleware/SecureHeaders.php new file mode 100644 index 0000000..1a04dc5 --- /dev/null +++ b/app/Http/Middleware/SecureHeaders.php @@ -0,0 +1,71 @@ +removeUnwantedHeaders($this->unwantedHeaderList); + $response = $next($request); + $response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade'); + $response->headers->set('X-Content-Type-Options', 'nosniff'); + $response->headers->set('X-XSS-Protection', '1; mode=block'); + $response->headers->set('X-Frame-Options', 'SAMEORIGIN'); + $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains;'); + $cspPolicies = [ + // phpcs:ignore Generic.Files.LineLength.TooLong + "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;", + // phpcs:ignore Generic.Files.LineLength.TooLong + "script-src 'self' $jsDev;" + ]; + $response->headers->set( + 'Content-Security-Policy', + implode(' ', $cspPolicies) . $reportUri + ); + $response->headers->set('Feature-Policy', 'fullscreen \'self\';'); + return $response; + } + + /** + * Remove unwanted headers + * + * @param array $headerList + * @return void + */ + private function removeUnwantedHeaders(array $headerList) + { + foreach ($headerList as $header) { + header_remove($header); + } + } +} diff --git a/app/Http/Middleware/StartSession.php b/app/Http/Middleware/StartSession.php index 6c8563e..c8e1451 100644 --- a/app/Http/Middleware/StartSession.php +++ b/app/Http/Middleware/StartSession.php @@ -9,11 +9,12 @@ class StartSession extends MiddlewareStartSession /** * Save the session data to storage. * - * @param \Illuminate\Http\Request $request + * @param mixed $request * @return void */ protected function saveSession($request) { + /** @var \Illuminate\Http\Request $request */ if (!$request->is('/ping')) { parent::saveSession($request); } diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php index 5a50e7b..e763dfb 100644 --- a/app/Http/Middleware/TrimStrings.php +++ b/app/Http/Middleware/TrimStrings.php @@ -9,7 +9,7 @@ class TrimStrings extends Middleware /** * The names of the attributes that should not be trimmed. * - * @var array + * @var array */ protected $except = [ 'password', diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php index b0550cf..7186414 100644 --- a/app/Http/Middleware/TrustHosts.php +++ b/app/Http/Middleware/TrustHosts.php @@ -9,7 +9,7 @@ class TrustHosts extends Middleware /** * Get the host patterns that should be trusted. * - * @return array + * @return array */ public function hosts() { diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index 03b7890..059153b 100644 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -19,5 +19,10 @@ class TrustProxies extends Middleware * * @var integer */ - protected $headers = Request::HEADER_X_FORWARDED_ALL; + protected $headers = + Request::HEADER_X_FORWARDED_FOR | + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_AWS_ELB; } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index ed591f2..70e23e1 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -9,8 +9,7 @@ class VerifyCsrfToken extends Middleware /** * The URIs that should be excluded from CSRF verification. * - * @var array + * @var array */ - protected $except = [ - ]; + protected $except = []; } diff --git a/app/Http/Middleware/XssSanitizer.php b/app/Http/Middleware/XssSanitizer.php new file mode 100644 index 0000000..955d285 --- /dev/null +++ b/app/Http/Middleware/XssSanitizer.php @@ -0,0 +1,46 @@ +all(); + array_walk_recursive($inputs, function (&$input, $name) use ($params) { + if (!is_string($input)) { + return; + } + if (in_array($name, $params)) { + // ? Is a json input ? + if ($isJson = \json_decode($input, true) and is_array($isJson)) { + // ! Decode each json field recursive. + \array_walk_recursive($isJson, function (&$item) { + if (\is_string($item)) { + $item = clean($item); + $item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item); + } + }); + } else { + $input = clean($input); + $input = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($input); + } + } else { + $input = strip_tags($input); + } + }); + $request->merge($inputs); + return $next($request); + } +} diff --git a/app/Http/Requests/AssignTicketToUserRequest.php b/app/Http/Requests/AssignTicketToUserRequest.php new file mode 100644 index 0000000..6caaa12 --- /dev/null +++ b/app/Http/Requests/AssignTicketToUserRequest.php @@ -0,0 +1,42 @@ +user(); + return auth()->check() and $user->isAdmin(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'assign' => [ + 'nullable', 'exists:users,id', + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + /** @var \App\Models\User $user */ + if ($user = User::whereId($value)->first() and !$user->isAdmin()) { + $fail(trans('Cet utilisateur ne peut se voir assigner un ticket')); + } + } + ] + ]; + } +} diff --git a/app/Http/Requests/CreateMessage.php b/app/Http/Requests/CreateMessage.php index 3d85442..ad93dfb 100644 --- a/app/Http/Requests/CreateMessage.php +++ b/app/Http/Requests/CreateMessage.php @@ -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)); } /** diff --git a/app/Http/Requests/StoreTicket.php b/app/Http/Requests/StoreTicket.php index 0cce664..a5bbb8b 100644 --- a/app/Http/Requests/StoreTicket.php +++ b/app/Http/Requests/StoreTicket.php @@ -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' ]; } } diff --git a/app/Http/Requests/UpdateTicket.php b/app/Http/Requests/UpdateTicket.php index 0ec1413..b7fa8e6 100644 --- a/app/Http/Requests/UpdateTicket.php +++ b/app/Http/Requests/UpdateTicket.php @@ -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 + ); } } diff --git a/app/Lib/ToolboxHelper.php b/app/Lib/ToolboxHelper.php new file mode 100644 index 0000000..ab9ca09 --- /dev/null +++ b/app/Lib/ToolboxHelper.php @@ -0,0 +1,255 @@ +[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,})|(?P@\w*)|(?P(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/', + function ($matches) { + $match = collect($matches)->first() ?? ''; + if (filter_var($match, FILTER_VALIDATE_EMAIL)) { + return strlen($match) ? "$match" : $match; + } elseif (isset($matches['user']) and strlen($matches['user'])) { + return sprintf( + '%s', + $matches['user'] + ); + } else { + $url = $match; + if (\strpos($url, 'http') === false and \strpos($url, 'https') === false) { + $url = sprintf( + '%s://%s', + config('app.env') !== 'local' ? 'https' : 'http', + $url + ); + } + $fsligthbox = self::isImageFile($url) ? 'data-fslightbox="gallery"' : ''; + return strlen($match) ? "$match" : $match; + } + }, + $text + ); + return nl2br($text); + } + + /** + * Check uri points to image file + * + * @param string $url + * @return boolean + */ + public static function isImageFile(string $url): bool + { + $m = []; + return \preg_match('/(\.png|\.jpg|\.jpeg|\.gif)$/', $url, $m) === 1; + } + + /** + * delais 7 jours 168h + * travaillé 5 jours 37h + * temps tickets 1 jours 7.4h + * + * personnes 4 (TpP) 3.7h / semaine * prs + * + * délais moyen ticket (Dmt) 0.75h + * + * délais max attente pour 1 ticket (Dam) 7jours + * + * niveaux de charge 4: - faible + * - stable + * - élevée + * - saturation + * + * états tickets(Et) : - nouveaux (non traités) + * - en attente (charge pendante) + * - traités + * + * Algo : linéaire + * + * TpP : 3.7h + * P : 4 + * + * Et(nvx) * Dam + * + * Nv Charge = (P * TpP) / (Et(nvx) * Dam) - (P * TpP) / (Et(nvx) * DmT) - (Et(att) * DmT) + * + * + * Si nv Charge < 0 => saturation + * + * @return \App\Enums\LoadLevel + */ + public static function computeWorkload(): LoadLevel + { + $Tnew = View::shared('ticketsNew'); + $Tprogress = View::shared('ticketsInProgress'); + $Twaiting = View::shared('ticketsWaiting'); + + $worked = config('tickets.workedHoursInAWeek'); + $workedDays = config('tickets.workedDaysInAWeek'); + // * in hours + $morningWorkTime = ($worked / $workedDays / 2); + // * Devs handlings tickets per week + $workingDevsPerWeek = config('tickets.workingDevsPerWeek'); + // * Worktime per week to handle tickets + $timePerWeekDev = $workingDevsPerWeek * $morningWorkTime; + // * 45min for a ticket + $avgTimeForTicket = config('tickets.avgTimeForTicket'); + // * The time needed to handle new ticket (average) + $todoListDelay = $Tnew * $avgTimeForTicket; + // * The time needed to handle new ticket (average) + $waitingListDelay = $Twaiting * $avgTimeForTicket; + // * The time needed to handle new ticket (average) + $progressListDelay = $Tprogress * $avgTimeForTicket; + $totalTicketsDelay = $todoListDelay + $progressListDelay + $waitingListDelay; + // * in weeks + $ETAForAnewTicket = $totalTicketsDelay / $timePerWeekDev; + switch ((int)round($ETAForAnewTicket)) { + case LoadLevel::weak()->value: + case LoadLevel::stable()->value: + case LoadLevel::high()->value: + return LoadLevel::make((int)round($ETAForAnewTicket)); + default: + return LoadLevel::overload(); + } + } + + /** + * Strip scheme (https) on oembed urls + * Only embed allowed are for MP4 self hosted videos right now + * + * https is force by ckeditor MediaElements which natively supports + * only cloud hosted medias (youtube, spotify etc..) + * + * @param string $input + * @return string + */ + public static function stripCkEditorSchemesOnMediaInputs(string $input): string + { + $input = \preg_replace('/(?<=)/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(?: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; + } +} diff --git a/app/Lib/Utils.php b/app/Lib/Utils.php deleted file mode 100644 index 6bc83bf..0000000 --- a/app/Lib/Utils.php +++ /dev/null @@ -1,130 +0,0 @@ -[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,})|(?P@\w*)|(?P(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/', - function ($matches) { - $match = collect($matches)->first() ?? ''; - if (filter_var($match, FILTER_VALIDATE_EMAIL)) { - return strlen($match) ? "$match" : $match; - } elseif (isset($matches['user']) and strlen($matches['user'])) { - return sprintf( - '%s', - $matches['user'] - ); - } else { - $url = $match; - if (\strpos($url, 'http') === false and \strpos($url, 'https') === false) { - $url = sprintf( - '%s://%s', - config('app.env') !== 'local' ? 'https' : 'http', - $url - ); - } - $fsligthbox = self::isImageFile($url) ? 'data-fslightbox="gallery"' : ''; - return strlen($match) ? "$match" : $match; - } - }, - $text - ); - return nl2br($text); - } - - /** - * Check uri points to image file - * - * @param string $url - * @return boolean - */ - public static function isImageFile(string $url): bool - { - $m = []; - return \preg_match('/(\.png|\.jpg|\.jpeg|\.gif)$/', $url, $m) === 1; - } - - /** - * delais 7 jours 168h - * travaillé 5 jours 37h - * temps tickets 1 jours 7.4h - * - * personnes 4 (TpP) 3.7h / semaine * prs - * - * délais moyen ticket (Dmt) 0.75h - * - * délais max attente pour 1 ticket (Dam) 7jours - * - * niveaux de charge 4: - faible - * - stable - * - élevée - * - saturation - * - * états tickets(Et) : - nouveaux (non traités) - * - en attente (charge pendante) - * - traités - * - * Algo : linéaire - * - * TpP : 3.7h - * P : 4 - * - * Et(nvx) * Dam - * - * Nv Charge = (P * TpP) / (Et(nvx) * Dam) - (P * TpP) / (Et(nvx) * DmT) - (Et(att) * DmT) - * - * - * Si nv Charge < 0 => saturation - * - * @return \App\Enums\LoadLevel - */ - public static function computeWorkload(): LoadLevel - { - $Tnew = View::shared('ticketsNew'); - $Tprogress = View::shared('ticketsInProgress'); - $Twaiting = View::shared('ticketsWaiting'); - - $worked = config('tickets.workedHoursInAWeek'); - $workedDays = config('tickets.workedDaysInAWeek'); - // * in hours - $morningWorkTime = ($worked / $workedDays / 2); - // * Devs handlings tickets per week - $workingDevsPerWeek = config('tickets.workingDevsPerWeek'); - // * Worktime per week to handle tickets - $timePerWeekDev = $workingDevsPerWeek * $morningWorkTime; - // * 45min for a ticket - $avgTimeForTicket = config('tickets.avgTimeForTicket'); - // * The time needed to handle new ticket (average) - $todoListDelay = $Tnew * $avgTimeForTicket; - // * The time needed to handle new ticket (average) - $waitingListDelay = $Twaiting * $avgTimeForTicket; - // * The time needed to handle new ticket (average) - $progressListDelay = $Tprogress * $avgTimeForTicket; - $totalTicketsDelay = $todoListDelay + $progressListDelay + $waitingListDelay; - // * in weeks - $ETAForAnewTicket = $totalTicketsDelay / $timePerWeekDev; - switch ((int)round($ETAForAnewTicket)) { - case LoadLevel::weak()->value: - case LoadLevel::stable()->value: - case LoadLevel::high()->value: - return LoadLevel::make((int)round($ETAForAnewTicket)); - default: - return LoadLevel::overload(); - } - } -} diff --git a/app/Mail/TicketMention.php b/app/Mail/TicketMention.php index c131173..e45f258 100644 --- a/app/Mail/TicketMention.php +++ b/app/Mail/TicketMention.php @@ -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')); } diff --git a/app/Mail/TicketUpdate.php b/app/Mail/TicketUpdate.php index 0b2bdc2..3fb5d83 100644 --- a/app/Mail/TicketUpdate.php +++ b/app/Mail/TicketUpdate.php @@ -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')); } diff --git a/app/Models/Message.php b/app/Models/Message.php index 170b601..be98669 100644 --- a/app/Models/Message.php +++ b/app/Models/Message.php @@ -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); - } } diff --git a/app/Models/Site.php b/app/Models/Site.php index c0bb1e6..dba17f9 100644 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -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); } diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index 219d0ba..372b5a4 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -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,62 @@ 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'); } + /** + * BelongsTo User relation + * The user that is in charge of the ticket + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function assignedUser() + { + return $this->belongsTo(User::class, 'assigned_to'); + } + + /** + * 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 +154,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 +172,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( diff --git a/app/Models/User.php b/app/Models/User.php index 0ae64c5..b78b5d3 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,10 +5,12 @@ namespace App\Models; use App\Enums\Role; use App\Jobs\SendUserCreatedEmail; use App\Notifications\ResetPassword; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; use Kwaadpepper\CrudPolicies\Enums\CrudAction; @@ -24,17 +26,29 @@ class User extends Authenticatable use Notifiable; use SoftDeletes; + /** + * The attributes that should be cast to Enums. + * + * @var array + */ protected $enumCasts = [ 'role' => Role::class ]; + /** + * Crud Label + * + * @var string + */ protected $crudLabelColumn = 'name'; + /** + * Crud Value + * + * @var string + */ protected $crudValueColumn = 'id'; - // stores unhashed pwd - public $tmpPass = null; - /** * The attributes that are mass assignable. * @@ -67,7 +81,12 @@ class User extends Authenticatable 'email_verified_at' => 'datetime', ]; - public function __construct($attributes = []) + /** + * User + * + * @param array $attributes + */ + public function __construct(array $attributes = []) { self::$editableProperties = [ 'name' => [ @@ -148,6 +167,11 @@ class User extends Authenticatable parent::__construct($attributes); } + /** + * Perform any actions required after the model boots. + * + * @return void + */ protected static function booted() { static::creating(function (User $user) { @@ -158,46 +182,89 @@ class User extends Authenticatable if (Hash::needsRehash($user->password)) { $user->password = Hash::make($user->password); } + Cache::flush('AdminUsers'); }); static::updating(function (User $user) { if (Hash::needsRehash($user->password)) { $user->password = Hash::make($user->password); } + Cache::flush('AdminUsers'); }); } + // * RELATIONSHIPS + + /** + * HasMany Ticket relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ public function tickets() { return $this->hasMany(Ticket::class, 'created_by'); } + /** + * HasMany Message relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ public function messages() { - return $this->hasMany(Messages::class, 'user_id'); + return $this->hasMany(Message::class, 'user_id'); } + /** + * BelongsToMany Ticket relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ public function subscriptions() { return $this->belongsToMany(Ticket::class, 'subscriptions'); } + /** + * BelongsToMany Site relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ public function sites() { return $this->belongsToMany(Site::class); } + /** + * HasMany Schedule relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ public function schedules() { return $this->hasMany(Schedule::class); } + // * METHODS + + /** + * Send User creation confirm mail + * + * @return void + */ public function sendMailConfirm() { dispatch(new SendUserCreatedEmail($this)); } + /** + * Send User reset password mail + * + * @param mixed $token + * @return void + */ public function sendPasswordResetNotification($token) { + /** @var string $token */ $this->notify(new ResetPassword($token)); } @@ -211,4 +278,16 @@ class User extends Authenticatable return ($this->role->equals(Role::developper()) or $this->role->equals(Role::admin())); } + + /** + * Get All Admin users + * + * @return Collection + */ + public static function adminUsers(): Collection + { + return Cache::rememberForever('AdminUsers', function () { + return self::whereIn('role', [Role::admin(), Role::developper()])->get(); + }); + } } diff --git a/app/Notifications/ResetPassword.php b/app/Notifications/ResetPassword.php index afee7cb..14412de 100644 --- a/app/Notifications/ResetPassword.php +++ b/app/Notifications/ResetPassword.php @@ -13,7 +13,7 @@ class ResetPassword extends ResetPasswordNotification /** * Get the reset password notification mail message for the given URL. * - * @param string $url + * @param mixed $url * @return \Illuminate\Notifications\Messages\MailMessage */ protected function buildMailMessage($url) @@ -22,6 +22,7 @@ class ResetPassword extends ResetPasswordNotification ->subject(trans('Reinitialisation de mot de passe')) //phpcs:ignore Generic.Files.LineLength.TooLong ->line(trans('Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte.')) + /** @var string $url */ ->action(trans('Réinitialiser le mot de passe'), $url) //phpcs:ignore Generic.Files.LineLength.TooLong ->line(trans('Ce lien de réinitialisation de mot de passe expirera dans :count minutes. ', ['count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')])) diff --git a/app/Policies/Rules/TicketStaticRules.php b/app/Policies/Rules/TicketStaticRules.php index bafd3f0..f9cf42a 100644 --- a/app/Policies/Rules/TicketStaticRules.php +++ b/app/Policies/Rules/TicketStaticRules.php @@ -12,7 +12,24 @@ use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules; */ abstract class TicketStaticRules extends StaticRules { - protected static function ticketIsNotArchived(Ticket $ticket) + /** + * Ticket is not in production + * + * @param Ticket $ticket + * @return boolean + */ + protected static function ticketIsNotInProduction(Ticket $ticket): bool + { + return !$ticket->status->equals(TicketStatus::production()); + } + + /** + * Ticket is not archived + * + * @param Ticket $ticket + * @return boolean + */ + protected static function ticketIsNotArchived(Ticket $ticket): bool { return !$ticket->status->equals(TicketStatus::archived()); } diff --git a/app/Policies/TicketPolicy.php b/app/Policies/TicketPolicy.php index 8396cf1..5dbdb0c 100644 --- a/app/Policies/TicketPolicy.php +++ b/app/Policies/TicketPolicy.php @@ -18,11 +18,10 @@ class TicketPolicy extends RootPolicy /** * Init policies on call * - * @param User $user + * @param User $user * @param Model $model * @return void */ - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed public function setPolicies(User $user, Model $model = null) { /** @@ -51,7 +50,12 @@ class TicketPolicy extends RootPolicy URR::make('userIsAdmin', $user), TicketStaticRules::make('ticketIsNotArchived', $model), ]), - 'reopen' => URR::make('userIsClient', $user) + 'reopen' => URR::make('userIsClient', $user), + 'assignTo' => StaticRules::and([ + URR::make('userIsAdmin', $user), + TicketStaticRules::make('ticketIsNotInProduction', $model), + TicketStaticRules::make('ticketIsNotArchived', $model) + ]) ] ]; } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 9f141d0..be41a7d 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -35,6 +35,8 @@ class RouteServiceProvider extends ServiceProvider { $this->configureRateLimiting(); + $this->cspReportRoute(); + $this->routes(function () { Route::prefix('api') ->middleware('api') @@ -70,4 +72,42 @@ class RouteServiceProvider extends ServiceProvider return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); }); } + + /** + * * Register CSP report URI + * + * Put this function inside RouteServiceProvider, + * and call it in RouteServiceProvider::boot + * + * ! Use this in conjonction with kwaadpepper/exception-handler + * + * @return void + */ + private function cspReportRoute() + { + // * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware + \Illuminate\Support\Facades\Route::post('/csp-report', function () { + $isCspRequest = request()->header('Content-Type') === 'application/csp-report'; + if (!$isCspRequest or !request()->json('csp-report') or !is_array(request()->json('csp-report'))) { + abort(401); + } + // * Flatten if needed + $cspReport = collect(request()->json('csp-report')) + ->flatMap(function ($v, $k) { + return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v]; + })->all(); + // * Filter array keys + $cspReport = collect(array_intersect_key($cspReport, [ + 'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '', + 'referrer' => '', 'violated-directive' => '' + ])); + report(new \Illuminate\Http\Client\HttpClientException(sprintf( + "CSP REPORT for %s : \n %s", + $cspReport->get('document-uri'), + $cspReport->implode("\n") + ))); + })->middleware('throttle:5,2') + ->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class) + ->name('cspReportUri'); + } } diff --git a/config/app.php b/config/app.php index f0559ff..b2c68ba 100644 --- a/config/app.php +++ b/config/app.php @@ -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, diff --git a/config/purifier.php b/config/purifier.php index 25d357f..29fd2ba 100644 --- a/config/purifier.php +++ b/config/purifier.php @@ -9,7 +9,7 @@ * if ( ! $this->config->get('purifier.finalize')) { * $config->autoFinalize = false; * } - * $config->loadArray($this->getConfig()); + * $config->load [$this->getConfig()); * * You must NOT delete the default settings * anything in settings should be compacted with params that needed to instance HTMLPurifier_Config. @@ -25,26 +25,26 @@ return [ 'cacheFileMode' => 0755, 'settings' => [ 'default' => [ + 'HTML.SafeIframe' => true, + // * For iframes + 'URI.SafeIframeRegexp' => "%^(http://|https://|//)(vimeo.com)%", 'HTML.Doctype' => 'HTML 4.01 Transitional', - 'HTML.Allowed' => 'h1,h2,h3,h4,h5,h6,div,b,strong,i,em,u,a[href|title],ul,ol,li,p[style],br,span[style],img[width|height|alt|src]', - 'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align', + 'HTML.Allowed' => 'h1[style],h2[style],h3[style],h4[style],h5[style],h6[style],figure[class|style],oembed[url],div[style],b,strong,i,em,u,a[href|title|target|rel],ul[style],ol[style],dl[style],li,p[style],br,span[style|class],img[width|height|alt|src|style|class|title],iframe[src|width|height|class|frameborder|style],table[style|align|border|cellspacing|cellpadding|width],thead,tbody,th[colspan|align|valign|style],tr[align|valign|style],td[style|colspan|rowspan|scope|align|valign],hr,dl,dt', + 'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align,list-style-type,float,width,height,vertical-align,border-color', 'AutoFormat.AutoParagraph' => false, 'AutoFormat.RemoveEmpty' => false, 'AutoFormat.RemoveEmpty.RemoveNbsp' => false, ], - 'test' => [ - 'Attr.EnableID' => 'true', - ], "youtube" => [ "HTML.SafeIframe" => 'true', - "URI.SafeIframeRegexp" => "%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/)%", + "URI.SafeIframeRegexp" => "%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/|vimeo.com/)%", ], 'custom_definition' => [ 'id' => 'html5-definitions', 'rev' => 1, - 'debug' => false, + 'debug' => true, 'elements' => [ - // http://developers.whatwg.org/sections.html + // * http://developers.whatwg.org/sections.html ['section', 'Block', 'Flow', 'Common'], ['nav', 'Block', 'Flow', 'Common'], ['article', 'Block', 'Flow', 'Common'], @@ -52,15 +52,16 @@ return [ ['header', 'Block', 'Flow', 'Common'], ['footer', 'Block', 'Flow', 'Common'], - // Content model actually excludes several tags, not modelled here + // * Content model actually excludes several tags, not modelled here ['address', 'Block', 'Flow', 'Common'], ['hgroup', 'Block', 'Required: h1 | h2 | h3 | h4 | h5 | h6', 'Common'], - // http://developers.whatwg.org/grouping-content.html + // * http://developers.whatwg.org/grouping-content.html ['figure', 'Block', 'Optional: (figcaption, Flow) | (Flow, figcaption) | Flow', 'Common'], ['figcaption', 'Inline', 'Flow', 'Common'], + ['oembed', 'Inline', 'Flow', 'Common'], - // http://developers.whatwg.org/the-video-element.html#the-video-element + // * http://developers.whatwg.org/the-video-element.html#the-video-element ['video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [ 'src' => 'URI', 'type' => 'Text', @@ -75,7 +76,7 @@ return [ 'type' => 'Text', ]], - // http://developers.whatwg.org/text-level-semantics.html + // * http://developers.whatwg.org/text-level-semantics.html ['s', 'Inline', 'Inline', 'Common'], ['var', 'Inline', 'Inline', 'Common'], ['sub', 'Inline', 'Inline', 'Common'], @@ -83,7 +84,7 @@ return [ ['mark', 'Inline', 'Inline', 'Common'], ['wbr', 'Inline', 'Empty', 'Core'], - // http://developers.whatwg.org/edits.html + // * http://developers.whatwg.org/edits.html ['ins', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']], ['del', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']], ], @@ -95,14 +96,24 @@ return [ ['tr', 'width', 'Text'], ['tr', 'height', 'Text'], ['tr', 'border', 'Text'], + ['figure', 'class', 'Text'], + ['figure', 'style', 'Text'], + ['oembed', 'url', 'Text'], + ['span', 'style', 'Text'], + ['img', 'style', 'Text'], ], ], 'custom_attributes' => [ ['a', 'target', 'Enum#_blank,_self,_target,_top'], + ['ol', 'style', 'Text'], + ['figure', 'style', 'Text'], + ['img', 'style', 'Text'], + ['span', 'style', 'Text'], + ['oembed', 'url', 'Text'], ], 'custom_elements' => [ ['u', 'Inline', 'Inline', 'Common'], + ['oembed', 'Inline', 'Inline', 'Common'], ], ], - ]; diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php deleted file mode 100644 index a292f3d..0000000 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ /dev/null @@ -1,36 +0,0 @@ -id(); - $table->string('name'); - $table->string('email')->unique(); - $table->string('password'); - $table->integer('role')->default(2); - $table->rememberToken(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('users'); - } -} diff --git a/database/migrations/2020_11_13_152248_create_projects.php b/database/migrations/2020_11_13_152248_create_projects.php deleted file mode 100644 index 132a0b7..0000000 --- a/database/migrations/2020_11_13_152248_create_projects.php +++ /dev/null @@ -1,32 +0,0 @@ -id(); - $table->string('name'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('projects'); - } -} diff --git a/database/migrations/2020_11_13_152321_create_tickets.php b/database/migrations/2020_11_13_152321_create_tickets.php deleted file mode 100644 index 290fbfb..0000000 --- a/database/migrations/2020_11_13_152321_create_tickets.php +++ /dev/null @@ -1,37 +0,0 @@ -id(); - $table->foreignId('created_by')->constrained('users'); - $table->foreignId('project_id')->constrained('projects'); - $table->integer('type'); - $table->text('text'); - $table->string('file')->nullable(); - $table->integer('status')->default(1); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('tickets'); - } -} diff --git a/database/migrations/2020_11_17_140637_update_users.php b/database/migrations/2020_11_17_140637_update_users.php deleted file mode 100644 index 9948a72..0000000 --- a/database/migrations/2020_11_17_140637_update_users.php +++ /dev/null @@ -1,32 +0,0 @@ -foreignId('project_id')->nullable()->after('role')->constrained('projects'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('users', function (Blueprint $table) { - $table->dropConstrainedForeignId('project_id'); - }); - } -} diff --git a/database/migrations/2020_11_25_104931_create_messages.php b/database/migrations/2020_11_25_104931_create_messages.php deleted file mode 100644 index 5756b1f..0000000 --- a/database/migrations/2020_11_25_104931_create_messages.php +++ /dev/null @@ -1,35 +0,0 @@ -id(); - $table->foreignId('ticket_id')->constrained('tickets'); - $table->foreignId('user_id')->nullable()->constrained('users'); - $table->text('message'); - $table->string('file')->nullable(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('messages'); - } -} diff --git a/database/migrations/2020_11_25_141438_alter_tickets.php b/database/migrations/2020_11_25_141438_alter_tickets.php deleted file mode 100644 index 626456b..0000000 --- a/database/migrations/2020_11_25_141438_alter_tickets.php +++ /dev/null @@ -1,32 +0,0 @@ -string('file2')->nullable()->after('file'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('tickets', function(Blueprint $table) { - $table->dropColumn('file2'); - }); - } -} diff --git a/database/migrations/2020_11_26_132557_create_subscriptions.php b/database/migrations/2020_11_26_132557_create_subscriptions.php deleted file mode 100644 index 6a22058..0000000 --- a/database/migrations/2020_11_26_132557_create_subscriptions.php +++ /dev/null @@ -1,32 +0,0 @@ -foreignId('ticket_id')->constrained('tickets'); - $table->foreignId('user_id')->constrained('users'); - $table->primary(['ticket_id', 'user_id']); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('subscriptions'); - } -} diff --git a/database/migrations/2021_04_21_132132_update_tickets_status_with_enum_status.php b/database/migrations/2021_04_21_132132_update_tickets_status_with_enum_status.php deleted file mode 100644 index bbe8a74..0000000 --- a/database/migrations/2021_04_21_132132_update_tickets_status_with_enum_status.php +++ /dev/null @@ -1,33 +0,0 @@ -get() as $ticket) { - $ticket->status = TicketStatus::archived(); - $ticket->saveQuietly(); - } - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status2.php b/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status2.php deleted file mode 100644 index f9cb55b..0000000 --- a/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status2.php +++ /dev/null @@ -1,41 +0,0 @@ -get() as $ticket) { - $ticket->status = TicketStatus::production(); - $ticket->saveQuietly(); - } - foreach (Ticket::where('status', 4)->get() as $ticket) { - $ticket->status = TicketStatus::recette(); - $ticket->saveQuietly(); - } - foreach (Ticket::where('status', 3)->get() as $ticket) { - $ticket->status = TicketStatus::processed(); - $ticket->saveQuietly(); - } - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status3.php b/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status3.php deleted file mode 100644 index 16ce2f8..0000000 --- a/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status3.php +++ /dev/null @@ -1,54 +0,0 @@ -get() as $ticket) { - $ticket->status = TicketStatus::archived(); - $ticket->saveQuietly(); - } - foreach (Ticket::where('status', 6)->get() as $ticket) { - $ticket->status = TicketStatus::production(); - $ticket->saveQuietly(); - } - foreach (Ticket::where('status', 5)->get() as $ticket) { - $ticket->status = TicketStatus::recette(); - $ticket->saveQuietly(); - } - foreach (Ticket::where('status', 4)->get() as $ticket) { - $ticket->status = TicketStatus::processed(); - $ticket->saveQuietly(); - } - foreach (Ticket::where('status', 3)->get() as $ticket) { - $ticket->status = TicketStatus::waiting(); - $ticket->saveQuietly(); - } - foreach (Ticket::where('status', 2)->get() as $ticket) { - $ticket->status = TicketStatus::inprogress(); - $ticket->saveQuietly(); - } - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2021_06_17_173912_create_project_user_table.php b/database/migrations/2021_06_17_173912_create_project_user_table.php deleted file mode 100644 index dd2a18b..0000000 --- a/database/migrations/2021_06_17_173912_create_project_user_table.php +++ /dev/null @@ -1,64 +0,0 @@ -foreignId('project_id')->constrained('projects'); - $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); - $table->unique(['project_id', 'user_id']); - }); - /** @var \App\Models\User */ - foreach (User::all() as $user) { - if ($user->project_id) { - $uP = new SiteUser(); - $uP->project_id = $user->project_id; - $uP->user_id = $user->id; - $uP->saveOrFail(); - } - } - Schema::table('users', function (Blueprint $table) { - $table->dropForeign(['project_id']); - $table->dropColumn('project_id'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('users', function (Blueprint $table) { - $table->foreignId('project_id')->nullable() - ->after('role')->constrained('projects'); - }); - /** @var \App\Models\SiteUser */ - foreach (SiteUser::all() as $uP) { - try { - /** @var \App\Models\User */ - $user = User::whereId($uP->user_id)->firstOrFail(); - $user->project_id = $uP->project_id; - $user->saveOrFail(); - //phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch - } catch (ModelNotFoundException $e) { - // ignore - } - } - Schema::dropIfExists('project_user'); - } -} diff --git a/database/migrations/2021_06_28_163754_add_soft_delete_on_users.php b/database/migrations/2021_06_28_163754_add_soft_delete_on_users.php deleted file mode 100644 index cf19b99..0000000 --- a/database/migrations/2021_06_28_163754_add_soft_delete_on_users.php +++ /dev/null @@ -1,32 +0,0 @@ -softDeletes(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('users', function (Blueprint $table) { - $table->dropSoftDeletes(); - }); - } -} diff --git a/database/migrations/2021_06_28_163755_add_soft_delete_on_projects.php b/database/migrations/2021_06_28_163755_add_soft_delete_on_projects.php deleted file mode 100644 index b58a5fe..0000000 --- a/database/migrations/2021_06_28_163755_add_soft_delete_on_projects.php +++ /dev/null @@ -1,32 +0,0 @@ -softDeletes(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('projects', function (Blueprint $table) { - $table->dropSoftDeletes(); - }); - } -} diff --git a/database/migrations/2021_07_23_111336_rename_projects_table_into_sites_table.php b/database/migrations/2021_07_23_111336_rename_projects_table_into_sites_table.php deleted file mode 100644 index 9669a8f..0000000 --- a/database/migrations/2021_07_23_111336_rename_projects_table_into_sites_table.php +++ /dev/null @@ -1,58 +0,0 @@ -dropForeign('project_user_project_id_foreign'); - $table->dropUnique(['project_id', 'user_id']); - }); - Schema::rename('project_user', 'site_user'); - Schema::table('site_user', function (Blueprint $table) { - $table->renameColumn('project_id', 'site_id'); - $table->foreign('site_id')->references('id')->on('sites'); - $table->unique(['site_id', 'user_id']); - }); - Schema::table('tickets', function (Blueprint $table) { - $table->dropForeign('tickets_project_id_foreign'); - $table->renameColumn('project_id', 'site_id'); - $table->foreign('site_id')->references('id')->on('sites'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::rename('sites', 'projects'); - Schema::table('site_user', function (Blueprint $table) { - $table->dropForeign('site_user_site_id_foreign'); - $table->dropUnique(['site_id', 'user_id']); - }); - Schema::rename('site_user', 'project_user'); - Schema::table('project_user', function (Blueprint $table) { - $table->renameColumn('site_id', 'project_id'); - $table->foreign('project_id')->references('id')->on('projects'); - $table->unique(['project_id', 'user_id']); - }); - Schema::table('tickets', function (Blueprint $table) { - $table->dropForeign('tickets_site_id_foreign'); - $table->renameColumn('site_id', 'project_id'); - $table->foreign('project_id')->references('id')->on('projects'); - }); - } -} diff --git a/database/migrations/2021_07_23_150120_create_projects_table.php b/database/migrations/2021_07_23_150120_create_projects_table.php deleted file mode 100644 index bd7fcc7..0000000 --- a/database/migrations/2021_07_23_150120_create_projects_table.php +++ /dev/null @@ -1,35 +0,0 @@ -id(); - $table->string('name'); - $table->string('status'); - $table->text('description'); - $table->text('todo')->nullable(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('projects'); - } -} diff --git a/database/migrations/2021_07_23_152102_create_project_site_table.php b/database/migrations/2021_07_23_152102_create_project_site_table.php deleted file mode 100644 index a0f73e5..0000000 --- a/database/migrations/2021_07_23_152102_create_project_site_table.php +++ /dev/null @@ -1,32 +0,0 @@ -foreignId('project_id')->constrained('projects')->cascadeOnDelete(); - $table->foreignId('site_id')->constrained('sites'); - $table->unique(['project_id', 'site_id']); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('project_site'); - } -} diff --git a/database/migrations/2021_07_23_152810_create_logs_table.php b/database/migrations/2021_07_23_152810_create_logs_table.php deleted file mode 100644 index c064fd0..0000000 --- a/database/migrations/2021_07_23_152810_create_logs_table.php +++ /dev/null @@ -1,34 +0,0 @@ -id(); - $table->text('entry'); - $table->foreignId('user_id')->constrained('users'); - $table->morphs('parent'); // can be Site or Projet - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('logs'); - } -} diff --git a/database/migrations/2021_07_23_153446_create_files_table.php b/database/migrations/2021_07_23_153446_create_files_table.php deleted file mode 100644 index 6c34ca4..0000000 --- a/database/migrations/2021_07_23_153446_create_files_table.php +++ /dev/null @@ -1,37 +0,0 @@ -id(); - $table->string('name'); - $table->string('uri', 2000)->nullable(); // max uri length is 2k - $table->string('file')->nullable(); - $table->smallInteger('type'); - $table->smallInteger('category'); - $table->morphs('parent'); // can be Site or Projet - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('files'); - } -} diff --git a/database/migrations/2021_07_27_144557_add_todo_on_sites_table.php b/database/migrations/2021_07_27_144557_add_todo_on_sites_table.php deleted file mode 100644 index 4e94070..0000000 --- a/database/migrations/2021_07_27_144557_add_todo_on_sites_table.php +++ /dev/null @@ -1,36 +0,0 @@ -string('status')->after('name'); - $table->text('description')->after('status'); - $table->text('todo')->after('description')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('sites', function (Blueprint $table) { - $table->dropColumn('status'); - $table->dropColumn('description'); - $table->dropColumn('todo'); - }); - } -} diff --git a/database/migrations/2021_08_01_140823_create_password_resets_table.php b/database/migrations/2021_08_01_140823_create_password_resets_table.php deleted file mode 100644 index 0ee0a36..0000000 --- a/database/migrations/2021_08_01_140823_create_password_resets_table.php +++ /dev/null @@ -1,32 +0,0 @@ -string('email')->index(); - $table->string('token'); - $table->timestamp('created_at')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('password_resets'); - } -} diff --git a/database/migrations/2021_10_01_090053_create_week_plannings_table.php b/database/migrations/2021_10_01_090053_create_week_plannings_table.php deleted file mode 100644 index b65939b..0000000 --- a/database/migrations/2021_10_01_090053_create_week_plannings_table.php +++ /dev/null @@ -1,37 +0,0 @@ -id(); - - $table->date('start'); - $table->text('planning'); - - $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); - - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('week_plannings'); - } -} diff --git a/database/migrations/2021_12_03_195755_create_schedules_table.php b/database/migrations/2021_12_03_195755_create_schedules_table.php deleted file mode 100644 index ade7153..0000000 --- a/database/migrations/2021_12_03_195755_create_schedules_table.php +++ /dev/null @@ -1,92 +0,0 @@ -id(); - $table->foreignId('user_id')->constrained('users'); - $table->date('date'); - $table->smallInteger('slot'); - $table->string('activity')->nullable(); - $table->text('description'); - $table->bigInteger('id_lie')->nullable(); - $table->string('model_lie')->nullable(); - $table->timestamps(); - $table->unique(['user_id','date','slot'], 'user_day_slot_unique'); - }); - - //migrate data from week plannings - $week_plannings = WeekPlanning::all(); - - foreach ($week_plannings as $oneweek) { - $user_id = $oneweek->user_id; - $plan = $oneweek->planning; - foreach ($plan as $time => $data) { - $date = Carbon::createFromTimestamp($time); - foreach ($data as $dayslot => $cell) { - $activite = $this->convertColorToType($cell["type"]); - $description = $cell["text"]; - $id_objetlie = $cell["foreign"]; - $type_objetlie = $cell["cellModel"]; //c'est une table d'après l'Enum CellModel - - $collected = [ - 'user_id' => $user_id, - 'date' => $date, - 'slot' => $dayslot, - 'activity' => $activite, - 'description' => $description, - 'id_lie' => $id_objetlie, - 'model_lie' => $type_objetlie - ]; - //si activite null et description null ne pas enregistrer - if (!is_null($activite) && !is_null($description)) { - $schedule = new Schedule(); - $schedule->fill($collected); - $schedule->save(); - } - } - } - } - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('schedules'); - } - - - /** - * convertir une value de couleur en type - */ - private function convertColorToType($value) { - $converter = array( - '#c6e0b4' => 'support', - '#b4c6e7' => 'rnd', - '#00b0f0' => 'dev', - '#ffe699' => 'projects', - '#f4b084' => 'management', - '#aeaaaa' => 'absence', - 'abscence' => 'absence' - ); - return $converter[$value] ?? null; - } -} diff --git a/database/migrations/2022_03_08_101614_migrate_schedules_to_eight_slots_per_day.php b/database/migrations/2022_03_08_101614_migrate_schedules_to_eight_slots_per_day.php deleted file mode 100644 index 5f82835..0000000 --- a/database/migrations/2022_03_08_101614_migrate_schedules_to_eight_slots_per_day.php +++ /dev/null @@ -1,40 +0,0 @@ - 0; $i--) { - $schedules = Schedule::where('slot', $i)->get(); - - foreach ($schedules as $oneschedule) { - $newschedule = $oneschedule->replicate(); - $newschedule->slot = $i * 2; - $newschedule->save(); - $oneschedule->slot = ($i * 2) - 1; - $oneschedule->save(); - } - } - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - //not reversible - } -} diff --git a/database/migrations/2022_03_15_164122_drop_week_plannings_table.php b/database/migrations/2022_03_15_164122_drop_week_plannings_table.php deleted file mode 100644 index f3dccd4..0000000 --- a/database/migrations/2022_03_15_164122_drop_week_plannings_table.php +++ /dev/null @@ -1,37 +0,0 @@ -id(); - - $table->date('start'); - $table->text('planning'); - - $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); - - $table->timestamps(); - }); - } -} diff --git a/database/migrations/2022_04_26_095605_create_markers_table.php b/database/migrations/2022_04_26_095605_create_markers_table.php deleted file mode 100644 index 4c2687c..0000000 --- a/database/migrations/2022_04_26_095605_create_markers_table.php +++ /dev/null @@ -1,37 +0,0 @@ -id(); - - $table->string('label')->comment('Le libellé du marqueur.'); - $table->dateTime('marker')->comment('Le point dans le temps du marqueur.'); - - $table->foreignIdFor(Project::class)->constrained() - ->onUpdate('cascade')->onDelete('cascade'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('macro_markers'); - } -} diff --git a/database/migrations/2022_04_26_095636_create_activities_table.php b/database/migrations/2022_04_26_095636_create_activities_table.php deleted file mode 100644 index a55c007..0000000 --- a/database/migrations/2022_04_26_095636_create_activities_table.php +++ /dev/null @@ -1,39 +0,0 @@ -id(); - $table->dateTime('start')->comment('La date de début de l\'activité.'); - $table->dateTime('end')->comment('La date de fin de l\'activité.'); - $table->unsignedSmallInteger('activity')->comment('Enum MacroSchedule/Activity'); - $table->unsignedSmallInteger('load')->comment('Le nombre d\'hommes qui vont être mobilisé pour la tâche'); - $table->string('comment')->comment('Commentaire sur la tâche, (255 chars max)'); - - $table->foreignIdFor(Project::class)->constrained() - ->onUpdate('cascade')->onDelete('cascade'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('macro_activities'); - } -} diff --git a/database/migrations/2022_04_26_102155_change_status_on_projects_table.php b/database/migrations/2022_04_26_102155_change_status_on_projects_table.php deleted file mode 100644 index 562e5f1..0000000 --- a/database/migrations/2022_04_26_102155_change_status_on_projects_table.php +++ /dev/null @@ -1,35 +0,0 @@ -update(['status' => '0']); - Schema::table('projects', function (Blueprint $table) { - $table->unsignedSmallInteger('status')->default(0) - ->comment('Enum ProjectStatus')->change(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('projects', function (Blueprint $table) { - $table->string('status')->default('')->change(); - }); - } -} diff --git a/database/migrations/2022_02_23_121545_add_is_internal_on_tickets_table.php b/database/migrations/2022_05_24_104346_assign_user_to_tickets.php similarity index 56% rename from database/migrations/2022_02_23_121545_add_is_internal_on_tickets_table.php rename to database/migrations/2022_05_24_104346_assign_user_to_tickets.php index 51efd88..542f53b 100644 --- a/database/migrations/2022_02_23_121545_add_is_internal_on_tickets_table.php +++ b/database/migrations/2022_05_24_104346_assign_user_to_tickets.php @@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class AddIsInternalOnTicketsTable extends Migration +class AssignUserToTickets extends Migration { /** * Run the migrations. @@ -14,7 +14,12 @@ class AddIsInternalOnTicketsTable extends Migration public function up() { Schema::table('tickets', function (Blueprint $table) { - $table->tinyInteger('is_internal')->default(0)->after('status'); + $table->foreignId('assigned_to') + ->comment('La personne a qui est assigné le ticket.') + ->nullable() + ->after('created_by') + ->constrained('users') + ->nullOnDelete(); }); } @@ -26,7 +31,8 @@ class AddIsInternalOnTicketsTable extends Migration public function down() { Schema::table('tickets', function (Blueprint $table) { - $table->dropColumn('is_internal')->default(0); + $table->dropForeign(['assigned_to']); + $table->dropColumn('assigned_to'); }); } } diff --git a/database/migrations/2022_05_24_122728_move_images_to_storage.php b/database/migrations/2022_05_24_122728_move_images_to_storage.php new file mode 100644 index 0000000..c404e2d --- /dev/null +++ b/database/migrations/2022_05_24_122728_move_images_to_storage.php @@ -0,0 +1,59 @@ +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) { + }); + } +} diff --git a/database/schema/mysql-schema.dump b/database/schema/mysql-schema.dump new file mode 100644 index 0000000..92c5495 --- /dev/null +++ b/database/schema/mysql-schema.dump @@ -0,0 +1,266 @@ +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +DROP TABLE IF EXISTS `files`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `files` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `uri` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `file` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `type` smallint(6) NOT NULL, + `category` smallint(6) NOT NULL, + `parent_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `parent_id` bigint(20) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `files_parent_type_parent_id_index` (`parent_type`,`parent_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `logs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `logs` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `entry` text COLLATE utf8mb4_unicode_ci NOT NULL, + `user_id` bigint(20) unsigned NOT NULL, + `parent_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `parent_id` bigint(20) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `logs_user_id_foreign` (`user_id`), + KEY `logs_parent_type_parent_id_index` (`parent_type`,`parent_id`), + CONSTRAINT `logs_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `macro_activities`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `macro_activities` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `start` datetime NOT NULL COMMENT 'La date de début de l''activité.', + `end` datetime NOT NULL COMMENT 'La date de fin de l''activité.', + `activity` smallint(5) unsigned NOT NULL COMMENT 'Enum MacroSchedule/Activity', + `load` smallint(5) unsigned NOT NULL COMMENT 'Le nombre d''hommes qui vont être mobilisé pour la tâche', + `comment` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Commentaire sur la tâche, (255 chars max)', + `project_id` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `macro_activities_project_id_foreign` (`project_id`), + CONSTRAINT `macro_activities_project_id_foreign` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `macro_markers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `macro_markers` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `label` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Le libellé du marqueur.', + `marker` datetime NOT NULL COMMENT 'Le point dans le temps du marqueur.', + `project_id` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `macro_markers_project_id_foreign` (`project_id`), + CONSTRAINT `macro_markers_project_id_foreign` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `messages`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `messages` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `ticket_id` bigint(20) unsigned NOT NULL, + `user_id` bigint(20) unsigned DEFAULT NULL, + `message` text COLLATE utf8mb4_unicode_ci NOT NULL, + `file` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `messages_ticket_id_foreign` (`ticket_id`), + KEY `messages_user_id_foreign` (`user_id`), + CONSTRAINT `messages_ticket_id_foreign` FOREIGN KEY (`ticket_id`) REFERENCES `tickets` (`id`), + CONSTRAINT `messages_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `migrations`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `migrations` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `batch` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `password_resets`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `password_resets` ( + `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + KEY `password_resets_email_index` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_site`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `project_site` ( + `project_id` bigint(20) unsigned NOT NULL, + `site_id` bigint(20) unsigned NOT NULL, + UNIQUE KEY `project_site_project_id_site_id_unique` (`project_id`,`site_id`), + KEY `project_site_site_id_foreign` (`site_id`), + CONSTRAINT `project_site_project_id_foreign` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `project_site_site_id_foreign` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `projects`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `projects` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `status` smallint(5) unsigned NOT NULL DEFAULT 0 COMMENT 'Enum ProjectStatus', + `description` text COLLATE utf8mb4_unicode_ci NOT NULL, + `todo` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `schedules`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `schedules` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `user_id` bigint(20) unsigned NOT NULL, + `date` date NOT NULL, + `slot` smallint(6) NOT NULL, + `activity` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `description` text COLLATE utf8mb4_unicode_ci NOT NULL, + `id_lie` bigint(20) DEFAULT NULL, + `model_lie` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `user_day_slot_unique` (`user_id`,`date`,`slot`), + CONSTRAINT `schedules_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `site_user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `site_user` ( + `site_id` bigint(20) unsigned NOT NULL, + `user_id` bigint(20) unsigned NOT NULL, + UNIQUE KEY `site_user_site_id_user_id_unique` (`site_id`,`user_id`), + KEY `project_user_user_id_foreign` (`user_id`), + CONSTRAINT `project_user_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + CONSTRAINT `site_user_site_id_foreign` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `sites`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sites` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `description` text COLLATE utf8mb4_unicode_ci NOT NULL, + `todo` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `subscriptions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `subscriptions` ( + `ticket_id` bigint(20) unsigned NOT NULL, + `user_id` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`ticket_id`,`user_id`), + KEY `subscriptions_user_id_foreign` (`user_id`), + CONSTRAINT `subscriptions_ticket_id_foreign` FOREIGN KEY (`ticket_id`) REFERENCES `tickets` (`id`), + CONSTRAINT `subscriptions_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `tickets`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `tickets` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `created_by` bigint(20) unsigned NOT NULL, + `site_id` bigint(20) unsigned NOT NULL, + `type` int(11) NOT NULL, + `text` text COLLATE utf8mb4_unicode_ci NOT NULL, + `file` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `file2` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `status` int(11) NOT NULL DEFAULT 1, + `is_internal` tinyint(4) NOT NULL DEFAULT 0, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `tickets_created_by_foreign` (`created_by`), + KEY `tickets_project_id_foreign` (`site_id`), + CONSTRAINT `tickets_created_by_foreign` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`), + CONSTRAINT `tickets_site_id_foreign` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `users` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `role` int(11) NOT NULL DEFAULT 2, + `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `users_email_unique` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +INSERT INTO `migrations` VALUES (1,'2014_10_12_000000_create_users_table',1); +INSERT INTO `migrations` VALUES (2,'2020_11_13_152248_create_projects',1); +INSERT INTO `migrations` VALUES (3,'2020_11_13_152321_create_tickets',1); +INSERT INTO `migrations` VALUES (4,'2020_11_17_140637_update_users',1); +INSERT INTO `migrations` VALUES (5,'2020_11_25_104931_create_messages',2); +INSERT INTO `migrations` VALUES (6,'2020_11_25_141438_alter_tickets',2); +INSERT INTO `migrations` VALUES (7,'2020_11_26_132557_create_subscriptions',3); +INSERT INTO `migrations` VALUES (8,'2021_04_21_132132_update_tickets_status_with_enum_status',4); +INSERT INTO `migrations` VALUES (9,'2021_04_21_132140_update_tickets_status_with_enum_status2',5); +INSERT INTO `migrations` VALUES (10,'2021_04_21_132140_update_tickets_status_with_enum_status3',6); +INSERT INTO `migrations` VALUES (11,'2021_06_17_173912_create_project_user_table',7); +INSERT INTO `migrations` VALUES (12,'2021_06_28_163754_add_soft_delete_on_users',8); +INSERT INTO `migrations` VALUES (13,'2021_06_28_163755_add_soft_delete_on_projects',9); +INSERT INTO `migrations` VALUES (14,'2021_07_23_111336_rename_projects_table_into_sites_table',10); +INSERT INTO `migrations` VALUES (15,'2021_07_23_150120_create_projects_table',11); +INSERT INTO `migrations` VALUES (16,'2021_07_23_152102_create_project_site_table',12); +INSERT INTO `migrations` VALUES (17,'2021_07_23_152810_create_logs_table',13); +INSERT INTO `migrations` VALUES (18,'2021_07_23_153446_create_files_table',14); +INSERT INTO `migrations` VALUES (19,'2021_07_27_144557_add_todo_on_sites_table',15); +INSERT INTO `migrations` VALUES (20,'2021_08_01_140823_create_password_resets_table',16); +INSERT INTO `migrations` VALUES (21,'2021_10_01_090053_create_week_plannings_table',17); +INSERT INTO `migrations` VALUES (22,'2021_12_03_195755_create_schedules_table',18); +INSERT INTO `migrations` VALUES (23,'2022_02_23_121545_add_is_internal_on_tickets_table',19); +INSERT INTO `migrations` VALUES (24,'2022_03_08_101614_migrate_schedules_to_eight_slots_per_day',20); +INSERT INTO `migrations` VALUES (25,'2022_03_15_164122_drop_week_plannings_table',21); +INSERT INTO `migrations` VALUES (26,'2022_04_26_095605_create_markers_table',22); +INSERT INTO `migrations` VALUES (27,'2022_04_26_095636_create_activities_table',23); +INSERT INTO `migrations` VALUES (28,'2022_04_26_102155_change_status_on_projects_table',24); diff --git a/package-lock.json b/package-lock.json index ff20fe7..f641f34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,18 +6,22 @@ "": { "devDependencies": { "@babel/plugin-transform-async-to-generator": "^7.16.8", + "autoprefixer": "10.4.5", "axios": "^0.21.1", - "bootstrap": "^5.0.2", + "bootstrap": "^5.1.3", + "clean-webpack-plugin": "^4.0.0", "cross-env": "^7.0", "del-cli": "^4.0.1", "fslightbox": "^3.2.3", "laravel-mix": "^6.0.16", + "laravel-mix-clean": "^0.1.0", "laravel-mix-polyfill": "^3.0.1", "lodash": "^4.17.19", "moment": "^2.29.3", "resolve-url-loader": "^3.1.0", "sass": "^1.15.2", "sass-loader": "^8.0.0", + "sweetalert2": "^11.4.16", "toastify-js": "^1.11.2", "vue": "^2.6.14", "vue-loader": "^15.9.7", @@ -2378,6 +2382,15 @@ "node": ">=8" } }, + "node_modules/aggregate-error/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2501,12 +2514,24 @@ "dev": true }, "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, "node_modules/arrify": { @@ -2574,9 +2599,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", - "integrity": "sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", + "integrity": "sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==", "dev": true, "funding": [ { @@ -2589,8 +2614,8 @@ } ], "dependencies": { - "browserslist": "^4.20.3", - "caniuse-lite": "^1.0.30001335", + "browserslist": "^4.20.2", + "caniuse-lite": "^1.0.30001332", "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -3205,6 +3230,21 @@ "node": ">=6" } }, + "node_modules/clean-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==", + "dev": true, + "dependencies": { + "del": "^4.1.1" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": ">=4.0.0 <6.0.0" + } + }, "node_modules/cli-table3": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", @@ -3986,6 +4026,53 @@ } }, "node_modules/del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/del-cli": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-4.0.1.tgz", + "integrity": "sha512-KtR/6cBfZkGDAP2NA7z+bP4p1OMob3wjN9mq13+SWvExx6jT9gFWfLgXEeX8J2B47OKeNCq9yTONmtryQ+m+6g==", + "dev": true, + "dependencies": { + "del": "^6.0.0", + "meow": "^10.1.0" + }, + "bin": { + "del": "cli.js", + "del-cli": "cli.js" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del-cli/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/del-cli/node_modules/del": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/del/-/del-6.1.0.tgz", "integrity": "sha512-OpcRktOt7G7HBfyxP0srBH4Djg4824EQORX8E1qvIhIzthNNArxxhrB/Mm7dRMiLi1nvFyUpDhzD2cTtbBhV8A==", @@ -4007,26 +4094,65 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/del-cli": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-4.0.1.tgz", - "integrity": "sha512-KtR/6cBfZkGDAP2NA7z+bP4p1OMob3wjN9mq13+SWvExx6jT9gFWfLgXEeX8J2B47OKeNCq9yTONmtryQ+m+6g==", + "node_modules/del-cli/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { - "del": "^6.0.0", - "meow": "^10.1.0" - }, - "bin": { - "del": "cli.js", - "del-cli": "cli.js" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { - "node": ">=12.20" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/del-cli/node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/del-cli/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del-cli/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4974,23 +5100,28 @@ } }, "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" + } + }, + "node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/graceful-fs": { @@ -5460,6 +5591,15 @@ "node": ">=8" } }, + "node_modules/imagemin/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/imagemin/node_modules/globby": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", @@ -5562,12 +5702,15 @@ } }, "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/inflight": { @@ -5703,13 +5846,28 @@ "node": ">=6" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", "dev": true, + "dependencies": { + "is-path-inside": "^2.1.0" + }, "engines": { - "node": ">=8" + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.2" + }, + "engines": { + "node": ">=6" } }, "node_modules/is-plain-obj": { @@ -5972,6 +6130,12 @@ "webpack-cli": "^4.9.1" } }, + "node_modules/laravel-mix-clean": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/laravel-mix-clean/-/laravel-mix-clean-0.1.0.tgz", + "integrity": "sha512-iOgVELzL0zJ2SNEVEmzCoduZ7tBQNWa/ViWXSTg9Z2QVK05Sld2efzTybR82Lu6ohZaA4btr+KsBBw7kt8aqTQ==", + "dev": true + }, "node_modules/laravel-mix-polyfill": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/laravel-mix-polyfill/-/laravel-mix-polyfill-3.0.1.tgz", @@ -6901,18 +7065,12 @@ } }, "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/p-pipe": { @@ -7051,6 +7209,12 @@ "node": ">=0.10.0" } }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -7115,6 +7279,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -8072,18 +8266,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/redent/node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -8409,18 +8591,15 @@ "dev": true }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ripemd160": { @@ -9197,6 +9376,16 @@ "node": ">=10.13.0" } }, + "node_modules/sweetalert2": { + "version": "11.4.16", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.16.tgz", + "integrity": "sha512-6xXd7kQps2M0okOJp5D9kR/TVL+DPWiQGQFoUIwkRPN/FAF/RrWkV/VUT0/a+VXjjkxDQDedEs/PK0/ptEo/7A==", + "dev": true, + "funding": { + "type": "individual", + "url": "https://sweetalert2.github.io/#donations" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -9992,6 +10181,21 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/webpack-dev-server/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -12177,6 +12381,14 @@ "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } } }, "ajv": { @@ -12271,9 +12483,18 @@ "dev": true }, "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true }, "arrify": { @@ -12336,13 +12557,13 @@ "dev": true }, "autoprefixer": { - "version": "10.4.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", - "integrity": "sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", + "integrity": "sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==", "dev": true, "requires": { - "browserslist": "^4.20.3", - "caniuse-lite": "^1.0.30001335", + "browserslist": "^4.20.2", + "caniuse-lite": "^1.0.30001332", "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -12810,6 +13031,15 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, + "clean-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==", + "dev": true, + "requires": { + "del": "^4.1.1" + } + }, "cli-table3": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", @@ -13415,19 +13645,18 @@ } }, "del": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.0.tgz", - "integrity": "sha512-OpcRktOt7G7HBfyxP0srBH4Djg4824EQORX8E1qvIhIzthNNArxxhrB/Mm7dRMiLi1nvFyUpDhzD2cTtbBhV8A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", "dev": true, "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" } }, "del-cli": { @@ -13438,6 +13667,68 @@ "requires": { "del": "^6.0.0", "meow": "^10.1.0" + }, + "dependencies": { + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "del": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.0.tgz", + "integrity": "sha512-OpcRktOt7G7HBfyxP0srBH4Djg4824EQORX8E1qvIhIzthNNArxxhrB/Mm7dRMiLi1nvFyUpDhzD2cTtbBhV8A==", + "dev": true, + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "depd": { @@ -14178,17 +14469,24 @@ "dev": true }, "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "graceful-fs": { @@ -14535,6 +14833,12 @@ "replace-ext": "^1.0.0" }, "dependencies": { + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "globby": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", @@ -14611,9 +14915,9 @@ } }, "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true }, "inflight": { @@ -14713,11 +15017,23 @@ "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "dev": true }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } }, "is-plain-obj": { "version": "1.1.0", @@ -15000,6 +15316,12 @@ } } }, + "laravel-mix-clean": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/laravel-mix-clean/-/laravel-mix-clean-0.1.0.tgz", + "integrity": "sha512-iOgVELzL0zJ2SNEVEmzCoduZ7tBQNWa/ViWXSTg9Z2QVK05Sld2efzTybR82Lu6ohZaA4btr+KsBBw7kt8aqTQ==", + "dev": true + }, "laravel-mix-polyfill": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/laravel-mix-polyfill/-/laravel-mix-polyfill-3.0.1.tgz", @@ -15618,13 +15940,10 @@ } }, "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true }, "p-pipe": { "version": "3.1.0", @@ -15732,6 +16051,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -15781,6 +16106,27 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -16405,14 +16751,6 @@ "requires": { "indent-string": "^5.0.0", "strip-indent": "^4.0.0" - }, - "dependencies": { - "indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true - } } }, "regenerate": { @@ -16676,9 +17014,9 @@ "dev": true }, "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { "glob": "^7.1.3" @@ -17289,6 +17627,12 @@ "stable": "^0.1.8" } }, + "sweetalert2": { + "version": "11.4.16", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.16.tgz", + "integrity": "sha512-6xXd7kQps2M0okOJp5D9kR/TVL+DPWiQGQFoUIwkRPN/FAF/RrWkV/VUT0/a+VXjjkxDQDedEs/PK0/ptEo/7A==", + "dev": true + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -17908,6 +18252,15 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", diff --git a/package.json b/package.json index 9f27a90..0bd4333 100644 --- a/package.json +++ b/package.json @@ -12,18 +12,22 @@ }, "devDependencies": { "@babel/plugin-transform-async-to-generator": "^7.16.8", + "autoprefixer": "10.4.5", "axios": "^0.21.1", - "bootstrap": "^5.0.2", + "bootstrap": "^5.1.3", + "clean-webpack-plugin": "^4.0.0", "cross-env": "^7.0", "del-cli": "^4.0.1", "fslightbox": "^3.2.3", "laravel-mix": "^6.0.16", + "laravel-mix-clean": "^0.1.0", "laravel-mix-polyfill": "^3.0.1", "lodash": "^4.17.19", "moment": "^2.29.3", "resolve-url-loader": "^3.1.0", "sass": "^1.15.2", "sass-loader": "^8.0.0", + "sweetalert2": "^11.4.16", "toastify-js": "^1.11.2", "vue": "^2.6.14", "vue-loader": "^15.9.7", diff --git a/resources/images/supportweb_128.png b/resources/assets/images/supportweb_128.png similarity index 100% rename from resources/images/supportweb_128.png rename to resources/assets/images/supportweb_128.png diff --git a/resources/images/supportweb.png b/resources/imageSources/supportweb.png similarity index 100% rename from resources/images/supportweb.png rename to resources/imageSources/supportweb.png diff --git a/resources/images/supportweb.xcf b/resources/imageSources/supportweb.xcf similarity index 100% rename from resources/images/supportweb.xcf rename to resources/imageSources/supportweb.xcf diff --git a/resources/images/supportweb_favico.png b/resources/imageSources/supportweb_favico.png similarity index 100% rename from resources/images/supportweb_favico.png rename to resources/imageSources/supportweb_favico.png diff --git a/resources/js/app.js b/resources/js/app.js index 5c27a2c..b165fd3 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,53 +1,4 @@ -require('./bootstrap'); -require('./session_watch'); -//window.Vue = require('vue/dist/vue.min.js'); -import Vue from 'vue' -import moment from 'moment' -import 'moment/locale/fr' -import vSelect from 'vue-select' - -moment.locale('fr') - -// v-select -Vue.component('v-select', vSelect) - -Vue.prototype.moment = moment -Vue.prototype.bootstrap = bootstrap - -// Register modules -const modules = require.context("./modules", true, /\.js$/i); -modules.keys().map(key => Vue.mixin(modules(key).default)); - -// Register components -const vues = require.context("./components", true, /\.vue$/i); -vues.keys().map(key => - Vue.component( - key - .split("/") - .pop() - .split(".")[0] - .replace(/\.?([A-Z])/g, function (x, y) { - return "-" + y.toLowerCase(); - }) - .replace(/^-/, ""), - vues(key).default - ) -); - -// Register vue function -Vue.filter('str_limit', function (value, size) { - if (!value) return ''; - value = value.toString(); - if (value.length <= size) { - return value; - } - return value.substr(0, size) + '...'; -}); -Vue.filter('ucfirst', function (value) { - return value.charAt(0).toUpperCase() + value.slice(1) -}); - -for (let el of document.getElementsByClassName('vueComponent')) { - new Vue({ el: el }); -} - +require('./bootstrap') +require('./session_watch') +require('./vue') +require('./files_add') diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index 46dc22f..b4c3ce6 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -1,10 +1,16 @@ -window._ = require('lodash'); +// SweetAlert +import Swal from 'sweetalert2' -/** - * We'll load jQuery and the Bootstrap jQuery plugin which provides support - * for JavaScript based Bootstrap features such as modals and tabs. This - * code may be modified to fit the specific needs of your application. - */ +// bootstrap styling +window.Swal = Swal.mixin({ + customClass: { + confirmButton: 'btn btn-lg btn-success ms-3 me-3', + cancelButton: 'btn btn-lg btn-danger ms-3 me-3' + }, + buttonsStyling: false +}) + +window._ = require('lodash') /** * We'll load the axios HTTP library which allows us to easily issue requests @@ -12,26 +18,10 @@ window._ = require('lodash'); * CSRF token as a header based on the value of the "XSRF" token cookie. */ -window.axios = require('axios'); +window.axios = require('axios') window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; -window.axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); +window.axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content') -/** - * Echo exposes an expressive API for subscribing to channels and listening - * for events that are broadcast by Laravel. Echo and event broadcasting - * allows your team to easily build robust real-time web applications. - */ -// import Echo from 'laravel-echo'; - -// window.Pusher = require('pusher-js'); - -// window.Echo = new Echo({ -// broadcaster: 'pusher', -// key: process.env.MIX_PUSHER_APP_KEY, -// cluster: process.env.MIX_PUSHER_APP_CLUSTER, -// forceTLS: true -// }); - -require('fslightbox'); +require('fslightbox') diff --git a/resources/js/components/MacroPlanningComponent.vue b/resources/js/components/MacroPlanningComponent.vue index 11f3a98..3a44763 100644 --- a/resources/js/components/MacroPlanningComponent.vue +++ b/resources/js/components/MacroPlanningComponent.vue @@ -193,42 +193,21 @@ import Toastify from 'toastify-js' import axios from 'axios'; export default { - props: { - isAdmin: { - type: Number, - }, - dbLoadCollection: { - type: String, - }, - dbMacroscheduleConfig: { - type: String, - }, - dbProjects: { - type: String, - }, - dbActivitiesEnum: { - type: String, - }, - dbProjectStatusEnum: { - type: String, - }, - today: { - type: String, - }, - startDay: { - type: String, - } - }, mounted() { let self = this window.Toastify = Toastify - this.projects = JSON.parse(this.dbProjects) - this.loadCollection = JSON.parse(this.dbLoadCollection) - this.macroscheduleConfig = JSON.parse(this.dbMacroscheduleConfig) - this.activitiesEnum = JSON.parse(this.dbActivitiesEnum) - this.projectStatusEnum = JSON.parse(this.dbProjectStatusEnum) + + let data = JSON.parse(this.$parent.$options.json) + this.projects = data.projects + this.loadCollection = data.loadCollection + this.macroscheduleConfig = data.macroscheduleConfig + this.activitiesEnum = data.activitiesEnum + this.projectStatusEnum = data.projectStatusEnum + this.today = data.today + this.startDay = data.startDay + this.isAdmin = data.isAdmin this.$nextTick(this.refreshTooltips) - this.modale = new bootstrap.Modal(this.$refs.editModal) + this.modale = new this.bootstrap.Modal(this.$refs.editModal) this.$refs.editModal.addEventListener('hide.bs.modal', function (e) { if(self.modalLoading) { e.preventDefault() @@ -238,6 +217,9 @@ export default { }, data() { return { + isAdmin: false, + today: '', + startDay: '', textLimit: 254, hoverActivityId: null, error: '', @@ -544,7 +526,7 @@ export default { } this.tooltipTriggerList = [].slice.call(this.$refs.macroschedule.querySelectorAll('[data-bs-toggle="tooltip"]')) this.tooltipTriggerList.map((tooltipTriggerEl) => { - return new bootstrap.Tooltip(tooltipTriggerEl, { delay: 500 }) + return new this.bootstrap.Tooltip(tooltipTriggerEl, { delay: 500 }) }) }, limitLines(e) { diff --git a/resources/js/components/MarkerLabelSelectComponent.vue b/resources/js/components/MarkerLabelSelectComponent.vue index 1309daa..1a03756 100644 --- a/resources/js/components/MarkerLabelSelectComponent.vue +++ b/resources/js/components/MarkerLabelSelectComponent.vue @@ -8,8 +8,10 @@ -@endpush diff --git a/resources/views/layout/app.blade.php b/resources/views/layout/app.blade.php index f096b05..31be5ac 100644 --- a/resources/views/layout/app.blade.php +++ b/resources/views/layout/app.blade.php @@ -17,12 +17,9 @@ - - - @crudPoliciesCSS @stack('customCss') @@ -42,15 +39,13 @@ @include('layout.footer') - - @stack('scriptsConstants') @stack('scripts') @crudPoliciesJS() @auth {{-- JS SESSION ALERT IF SESSION TIMEOUT --}} - + @endauth diff --git a/resources/views/layout/footer.blade.php b/resources/views/layout/footer.blade.php index 3eb81af..c8cd2ff 100644 --- a/resources/views/layout/footer.blade.php +++ b/resources/views/layout/footer.blade.php @@ -1,6 +1,6 @@