diff --git a/app/Console/Commands/CreateUser.php b/app/Console/Commands/CreateUser.php index 29bd2fd..404d0c9 100644 --- a/app/Console/Commands/CreateUser.php +++ b/app/Console/Commands/CreateUser.php @@ -2,8 +2,10 @@ namespace App\Console\Commands; +use App\Models\Project; use App\Models\User; use Illuminate\Console\Command; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Hash; class CreateUser extends Command @@ -47,7 +49,16 @@ class CreateUser extends Command if ($this->confirm('Is the user an admin?')) { $admin = true; } else { - $projectId = $this->ask("What's the id of the project (s)he's working on?"); + $project = null; + try { + $project = Project::where( + 'id', + $this->ask("What's the id of the project (s)he's working on?") + )->firstOrFail(); + } catch (ModelNotFoundException $e) { + $this->error('The project does not exists'); + return -1; + } } $user = new User(); @@ -56,11 +67,12 @@ class CreateUser extends Command if ($admin) { $user->role = 1; } else { - $user->project_id = $projectId; + $user->project_id = $project->id; } $user->password = Hash::make($pwd); $user->save(); $this->info('User created.'); + return 0; } } diff --git a/app/Enums/TicketStatus.php b/app/Enums/TicketStatus.php new file mode 100644 index 0000000..75f5df7 --- /dev/null +++ b/app/Enums/TicketStatus.php @@ -0,0 +1,34 @@ + 1, + 'inprogress' => 2, + 'processed' => 3, + 'archived' => 4, + ]; + } + + protected static function labels(): array + { + return [ + 'new' => trans('Nouveau'), + 'inprogress' => trans('En cours'), + 'processed' => trans('Traité'), + 'archived' => trans('Archivé'), + ]; + } +} diff --git a/app/Enums/TicketType.php b/app/Enums/TicketType.php new file mode 100644 index 0000000..c6ca449 --- /dev/null +++ b/app/Enums/TicketType.php @@ -0,0 +1,28 @@ + 0, + 'evolution' => 1, + ]; + } + + protected static function labels(): array + { + return [ + 'bug' => trans('Bug'), + 'evolution' => trans('Évolution'), + ]; + } +} diff --git a/app/Events/NotifySubscriptions.php b/app/Events/NotifySubscriptions.php index e9a15ec..1f60396 100644 --- a/app/Events/NotifySubscriptions.php +++ b/app/Events/NotifySubscriptions.php @@ -2,22 +2,29 @@ namespace App\Events; +use App\Models\Ticket; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class NotifySubscriptions { - use Dispatchable, InteractsWithSockets, SerializesModels; + use Dispatchable; + use InteractsWithSockets; + use SerializesModels; - public $ticket, $corpsMaj; + /** @var Ticket */ + public $ticket; + + /** @var string */ + public $corpsMaj; /** * Create a new event instance. * * @return void */ - public function __construct($ticket, $corpsMaj) + public function __construct(Ticket $ticket, string $corpsMaj) { $this->ticket = $ticket; $this->corpsMaj = $corpsMaj; diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 2b39ed5..ef0910d 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -9,6 +9,11 @@ use App\Models\Ticket; class ProjectController extends Controller { + public function __construct() + { + $this->authorizeResource(Project::class, 'project'); + } + public function index() { $projects = Project::all(); diff --git a/app/Http/Controllers/TicketController.php b/app/Http/Controllers/TicketController.php index 3ffed0c..f169250 100644 --- a/app/Http/Controllers/TicketController.php +++ b/app/Http/Controllers/TicketController.php @@ -2,234 +2,160 @@ namespace App\Http\Controllers; +use App\Enums\TicketStatus; +use App\Enums\TicketType; use App\Events\NotifySubscriptions; use App\Events\TicketStatusChanged; +use App\Http\Requests\ChangeTicketStatus; +use App\Http\Requests\CreateMessage; +use App\Http\Requests\SortTickets; +use App\Http\Requests\StoreTicket; +use App\Http\Requests\UpdateTicket; use App\Mail\NewTicket; use App\Models\Message; use App\Models\Project; use App\Models\Ticket; use App\Models\User; -use Illuminate\Http\Request; +use Exception; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; class TicketController extends Controller { - private $status = [1 => "Nouveau", 2 => "En cours", 3 => "Traité", 4 => "Archivé"]; - - public function index(Request $request) + public function __construct() { - if (Auth::check()) { - $tickets = Ticket::where('status', '<>', 4); + $this->authorizeResource(Ticket::class, 'ticket'); + } - $sortway = $request->sort_way == 'asc' ? 'asc' : null; - $sortway = $request->sort_way == 'desc' ? 'desc' : null; + public function index(SortTickets $request) + { + $tickets = Ticket::query(); - if ($request->sort_col and $sortway) { - switch ($request->sort_col) { - case 'id': - $tickets = $tickets->orderBy('id', $sortway); - break; - case 'project': - $tickets = $tickets->orderBy('project_id', $sortway); - break; - case 'created_at': - $tickets = $tickets->orderBy('created_at', $sortway); - break; - case 'created_by': - $tickets = $tickets->orderBy('created_by', $sortway); - break; - case 'type': - $tickets = $tickets->orderBy('type', $sortway); - break; - case 'status': - $tickets = $tickets->orderBy('status', $sortway); - break; - } - } - - $tickets = $tickets->paginate(); - - $status = $this->status; - return view('home', compact('tickets', 'status')); + if ($request->status) { + $tickets = $tickets->where('status', TicketStatus::archived()); } else { - return view('home'); + $tickets = $tickets->where('status', '<>', TicketStatus::archived()); } + + if ($request->sort_col and $request->sort_way) { + switch ($request->sort_col) { + case 'id': + $tickets = $tickets->orderBy('id', $request->sort_way); + break; + case 'project': + $tickets = $tickets->orderBy('project_id', $request->sort_way); + break; + case 'created_at': + $tickets = $tickets->orderBy('created_at', $request->sort_way); + break; + case 'created_by': + $tickets = $tickets->orderBy('created_by', $request->sort_way); + break; + case 'type': + $tickets = $tickets->orderBy('type', $request->sort_way); + break; + case 'status': + $tickets = $tickets->orderBy('status', $request->sort_way); + break; + } + } + + $tickets = $tickets->paginate(); + + return view('ticket.index', compact('tickets')); } - public function show($id) { - $ticket = Ticket::find($id); - $status = $this->status; + public function show(Ticket $ticket) + { $messages = $ticket->messages()->orderBy('created_at', 'DESC')->get(); - $isSubscribed = false; - if (!$ticket) - abort(404); - if ($ticket->subscriptions()->where('user_id', Auth::user()->id)->count() !== 0) - $isSubscribed = true; - return view('ticket.show', compact('ticket', 'status', 'messages', 'isSubscribed')); + $isSubscribed = (bool)$ticket->subscriptions()->where('user_id', Auth::user()->id)->count(); + return view('ticket.show', compact('ticket', 'messages', 'isSubscribed')); } - public function create() { + public function create() + { $projects = Project::all(); + if (!count($projects)) { + return redirect()->route('project.create') + ->with('warning', 'Vous devez tout d\'abord créer un projet pour y ajouter un ticket'); + } return view('ticket.create', compact('projects')); } - public function store(Request $request) { - $request->validate([ - 'text' => 'required|string', - 'file_ticket' => 'mimes:jpeg,jpg,png,pdf|max:2048', - 'file_ticket2' => 'mimes:jpeg,jpg,png,pdf|max:2048', - ]); + public function store(StoreTicket $request) + { + $project = Project::findOrFail($request->project_id); - if (Auth::user()->role === 1) { - $projectId = $request->project_id; - } else { - $projectId = Auth::user()->project_id; + $ticket = new Ticket($request->validated()); + + if ($request->file_ticket) { + $ticket->file = Ticket::saveFile($project, $ticket, $ticket->type, $request->file_ticket); + } + if ($request->file_ticket2) { + $ticket->file2 = Ticket::saveFile($project, $ticket, $ticket->type, $request->file_ticket2); } - $type = $request->type; - $text = $request->text; - $fileTicket = $request->file_ticket; - $fileTicket2 = $request->file_ticket2; - $ticket = new Ticket(); - $ticket->project_id = $projectId; - $ticket->type = $type; - $ticket->text = $text; - $ticket->created_by = Auth::user()->id; + $ticket->user()->associate(auth()->user()); $ticket->save(); - if ($fileTicket !== null) { - $projectSlug = Str::slug(Project::find($projectId)->name); - $fileName = $projectSlug . "-" . ($type === "0" ? "bug" : "evo") . "-" . $ticket->id . "." . $fileTicket->extension(); - $fileTicket->move(public_path('fichiers/' . $projectSlug . "/" . $ticket->id . "/"), $fileName); - $filePath = 'fichiers/' . $projectSlug . "/" . $ticket->id . "/" . $fileName; + if (config('mail.MAIL_ADMIN')) { + Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket)); + } else { + throw new Exception('Missing MAIL_ADMIN in .env'); } - if ($fileTicket2 !== null) { - $projectSlug = Str::slug(Project::find($projectId)->name); - $fileName2 = $projectSlug . "-" . ($type === "0" ? "bug" : "evo") . "-" . $ticket->id . "-2." . $fileTicket2->extension(); - $fileTicket2->move(public_path('fichiers/' . $projectSlug . "/" . $ticket->id . "/"), $fileName2); - $filePath2 = 'fichiers/' . $projectSlug . "/" . $ticket->id . "/" . $fileName2; - } - - - if ($fileTicket !== null) - $ticket->file = $filePath; - - if ($fileTicket2 !== null) - $ticket->file2 = $filePath2; - - if ($fileTicket !== null || $fileTicket2 !== null) - $ticket->save(); - - - Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket)); return redirect()->route('home')->with('success', 'Ticket enregistré !'); } - public function edit($id) { - $ticket = Ticket::findOrFail($id); - if ($ticket->created_by === Auth::user()->id || Auth::user()->role === 1) { - $projects = Project::all(); - $status = $this->status; - return view('ticket.edit', compact('ticket', 'projects', 'status')); - } - return redirect()->back(); + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass + public function edit(Ticket $ticket) + { + $projects = Project::all(); + return view('ticket.edit', compact('ticket', 'projects')); } - public function update(Request $request, $id) { - $request->validate([ - 'text' => 'required|string', - 'file_ticket' => 'mimes:jpeg,jpg,png,pdf|max:2048', - 'file_ticket2' => 'mimes:jpeg,jpg,png,pdf|max:2048', - ]); + public function update(UpdateTicket $request, Ticket $ticket) + { + $project = Project::findOrFail($request->project_id); + $ticket->fill($request->validated()); - if (Auth::user()->role === 1) { - $projectId = $request->project_id; - $status = $request->status; - } else { - $projectId = Auth::user()->project_id; + if ($request->file_ticket) { + if (File::exists($ticket->file)) { + File::delete($ticket->file); + } + $ticket->file = Ticket::saveFile($project, $ticket, $ticket->type, $request->file_ticket); } - $type = $request->type; - $text = $request->text; - $fileTicket = $request->file_ticket; - $fileTicket2 = $request->file_ticket2; - - $ticket = Ticket::findOrFail($id); - - if ($fileTicket !== null) { - if ($ticket->file !== null && file_exists(public_path() . $ticket->file)) - unlink(public_path() . $ticket->file); - $projectSlug = Str::slug(Project::find($projectId)->name); - $fileName = $projectSlug . "-" . ($type === "0" ? "bug" : "evo") . "-" . $ticket->id . "." . $fileTicket->extension(); - $fileTicket->move(public_path('fichiers/' . $projectSlug . "/" . $ticket->id . "/"), $fileName); - $filePath = 'fichiers/' . $projectSlug . "/" . $ticket->id . "/" . $fileName; - } - if ($fileTicket2 !== null) { - if ($ticket->file2 !== null && file_exists(public_path() . $ticket->file2)) - unlink(public_path() . $ticket->file2); - $projectSlug = Str::slug(Project::find($projectId)->name); - $fileName2 = $projectSlug . "-" . ($type === "0" ? "bug" : "evo") . "-" . $ticket->id . "." . $fileTicket2->extension(); - $fileTicket2->move(public_path('fichiers/' . $projectSlug . "/" . $ticket->id . "/"), $fileName2); - $filePath2 = 'fichiers/' . $projectSlug . "/" . $ticket->id . "/" . $fileName2; - } - - $ticket->project_id = $projectId; - $ticket->type = $type; - $ticket->text = $text; - if ($fileTicket !== null) - $ticket->file = $filePath; - if ($fileTicket2 !== null) - $ticket->file2 = $filePath2; - if (Auth::user()->role === 1) { - $ticket->status = $status; - event(new TicketStatusChanged($ticket, $this->status)); + if ($request->file_ticket2) { + if (File::exists($ticket->file2)) { + File::delete($ticket->file2); + } + $ticket->file2 = Ticket::saveFile($project, $ticket, $ticket->type, $request->file_ticket2); } $ticket->save(); - return redirect()->route('edit-ticket', $ticket->id)->with('success', 'Modifications enregistrées !'); + return redirect()->route('ticket.edit', $ticket->id)->with('success', 'Modifications enregistrées !'); } - public function changeStatus(Request $request, $id) { - $ticket = Ticket::find($id); - if (!$ticket) - abort(404); - $ticket->status = $request->status; + public function changeStatus(ChangeTicketStatus $request, Ticket $ticket) + { + $ticket->fill($request->validated()); $ticket->save(); - event(new TicketStatusChanged($ticket, $this->status)); - event(new NotifySubscriptions($ticket, "Le ticket à changé de status : " . $this->status[$ticket->status])); - return back(); } - public function createMessage(Request $request) { - $request->validate([ - 'message' => 'required', - ]); - - if ($request->file_message !== null) { - $request->validate([ - 'file_message' => 'mimes:jpeg,jpg,png,pdf|max:2048', - ]); - } - - $ticketId = $request->ticket_id; + public function createMessage(CreateMessage $request, Ticket $ticket) + { $fileMessage = $request->file_message; - $ticket = Ticket::find($ticketId); - - if (Auth::user()->role !== 1 && (!$ticket || Auth::user()->id !== $ticket->created_by)) - return back()->withErrors("Les données entrées sont incorrects."); - - $message = new Message(); - $message->ticket_id = $ticketId; - $message->user_id = Auth::user()->id; - $message->message = $request->message; + $message = new Message($request->validated()); + $message->user()->associate(auth()->user()); + $message->ticket()->associate($ticket); $message->save(); - if ($request->file_message !== null) { + if ($request->file_message) { $projectSlug = Str::slug($ticket->project->name); $fileMessage = $request->file_message; $fileName = $projectSlug . '-ticket-' . $ticket->id . '-message-' . $message->id . '.' . $fileMessage->extension(); @@ -239,13 +165,19 @@ class TicketController extends Controller $message->save(); } - event(new NotifySubscriptions($ticket, "Nouvelle réponse de " . $message->user->name . " :
" . $message->message)); - return back(); } - public function subscribe(Request $request, $id) { - User::find(Auth::user()->id)->subscriptions()->toggle($id); + public function setToArchive(Ticket $ticket) + { + $ticket->status = TicketStatus::archived(); + $ticket->saveOrFail(); + return redirect()->route('ticket.index'); + } + + public function subscribe(Ticket $ticket) + { + User::find(Auth::user()->id)->subscriptions()->toggle($ticket->id); return back(); } } diff --git a/app/Http/Requests/ChangeTicketStatus.php b/app/Http/Requests/ChangeTicketStatus.php new file mode 100644 index 0000000..4d681d0 --- /dev/null +++ b/app/Http/Requests/ChangeTicketStatus.php @@ -0,0 +1,31 @@ + [ + 'required', + // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + if (!in_array($value, TicketStatus::toValues())) { + $fail(__('Veuillez choisir entre :values', [ + 'value' => implode(',', TicketStatus::toLabels()) + ])); + } + } + ], + ]; + } +} diff --git a/app/Http/Requests/CreateMessage.php b/app/Http/Requests/CreateMessage.php new file mode 100644 index 0000000..7834188 --- /dev/null +++ b/app/Http/Requests/CreateMessage.php @@ -0,0 +1,21 @@ + 'required|min:10', + 'file_message' => 'sometimes|mimes:jpeg,jpg,png,pdf|max:2048' + ]; + } +} diff --git a/app/Http/Requests/SortTickets.php b/app/Http/Requests/SortTickets.php new file mode 100644 index 0000000..3112e19 --- /dev/null +++ b/app/Http/Requests/SortTickets.php @@ -0,0 +1,42 @@ +check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'sort_way' => 'sometimes|in:asc,desc', + 'status' => [ + 'sometimes', + // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + if (!in_array($value, TicketStatus::toValues())) { + $fail(__('Veuillez choisir entre :values', [ + 'value' => implode(',', TicketStatus::toLabels()) + ])); + } + } + ], + ]; + } +} diff --git a/app/Http/Requests/StoreTicket.php b/app/Http/Requests/StoreTicket.php new file mode 100644 index 0000000..78370a9 --- /dev/null +++ b/app/Http/Requests/StoreTicket.php @@ -0,0 +1,63 @@ +user()->project_id) { + $this->merge([ + 'project_id' => auth()->user()->project_id, + 'status' => TicketStatus::new()->value, + ]); + } + return auth()->check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'project_id' => 'required|exists:projects,id', + 'type' => [ + 'required', + // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + if (!in_array($value, TicketType::toValues())) { + $fail(__('Veuillez choisir entre :values', [ + 'value' => implode(',', TicketType::toLabels()) + ])); + } + } + ], + 'status' => [ + 'required', + // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + if (!in_array($value, TicketStatus::toValues())) { + $fail(__('Veuillez choisir entre :values', [ + 'value' => implode(',', TicketStatus::toLabels()) + ])); + } + } + ], + 'text' => 'required|string|min:40', + 'file_ticket' => 'mimes:jpeg,jpg,png,pdf|max:2048', + 'file_ticket2' => 'mimes:jpeg,jpg,png,pdf|max:2048', + ]; + } +} diff --git a/app/Http/Requests/UpdateTicket.php b/app/Http/Requests/UpdateTicket.php new file mode 100644 index 0000000..167da51 --- /dev/null +++ b/app/Http/Requests/UpdateTicket.php @@ -0,0 +1,21 @@ +user()->project_id) { + $this->merge([ + 'project_id' => auth()->user()->project_id, + 'status' => TicketStatus::new()->value, + ]); + } + return auth()->check() or + auth()->user()->role === 1 or + request()->route()->ticket->created_by === auth()->user()->id; + } +} diff --git a/app/Listeners/AddStatusMessages.php b/app/Listeners/AddStatusMessages.php index 1985c35..1c05a72 100644 --- a/app/Listeners/AddStatusMessages.php +++ b/app/Listeners/AddStatusMessages.php @@ -27,7 +27,7 @@ class AddStatusMessages { $message = new Message(); $message->ticket_id = $event->ticket->id; - $message->message = "Le status est passé à : " . $event->status[$event->ticket->status]; + $message->message = "Le status est passé à : " . $event->ticket->status->label; $message->save(); } } diff --git a/app/Listeners/SendNotificationMail.php b/app/Listeners/SendNotificationMail.php index 55fb583..178df4a 100644 --- a/app/Listeners/SendNotificationMail.php +++ b/app/Listeners/SendNotificationMail.php @@ -12,16 +12,6 @@ use Illuminate\Support\Facades\Mail; class SendNotificationMail { - /** - * Create the event listener. - * - * @return void - */ - public function __construct() - { - // - } - /** * Handle the event. * @@ -30,8 +20,10 @@ class SendNotificationMail */ public function handle(NotifySubscriptions $event) { - foreach($event->ticket->subscriptions as $sub) - if ($sub->email !== Auth::user()->email) + foreach ($event->ticket->subscriptions as $sub) { + if ($sub->email !== Auth::user()->email) { Mail::to($sub->email)->send(new TicketUpdate($event->ticket, $event->corpsMaj)); + } + } } } diff --git a/app/Models/Message.php b/app/Models/Message.php index 471f7b1..dfbe163 100644 --- a/app/Models/Message.php +++ b/app/Models/Message.php @@ -2,18 +2,55 @@ namespace App\Models; +use App\Enums\TicketType; +use App\Events\NotifySubscriptions; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Http\UploadedFile; class Message extends Model { use HasFactory; - public function ticket() { - return $this->belongsTo('App\Models\Ticket', 'ticket_id'); + protected $fillable = [ + 'message' + ]; + + protected static function booted() + { + static::created(function (Message $message) { + event(new NotifySubscriptions( + $message->ticket, + trans('Nouvelle réponse de :name :
:message', [ + 'name' => $message->user->name, + 'message' => $message->message + ]) + )); + }); } - public function user() { - return $this->belongsTo('App\Models\User', 'user_id'); + public function ticket() + { + return $this->belongsTo(Ticket::class, 'ticket_id'); + } + + public function user() + { + return $this->belongsTo(User::class, 'user_id'); + } + + public function saveFile(Project $project, Ticket $ticket, UploadedFile $file) + { + $projectAssetFullPath = Project::ticketAssetsFullPath($project->slug_name); + $fileAssetPath = Project::ticketAssetsPath($project->slug_name); + $fileName = sprintf( + '%s-%s-%d-%d.%s', + $project->slug_name, + $ticket->id, + rand(), + $file->extension() + ); + $file->move(sprintf('%s/messages/%d', $projectAssetFullPath, $ticket->id), $fileName); + return sprintf('%s/%d/%s', $fileAssetPath, $ticket->id, $fileName); } } diff --git a/app/Models/Project.php b/app/Models/Project.php index 5e7aea4..f1498f8 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -16,15 +16,15 @@ class Project extends Model protected static function booted() { static::created(function (Project $project) { - $path = Project::ticketAssetsPath($project->slug_name); + $path = Project::ticketAssetsFullPath($project->slug_name); if (!File::exists($path)) { File::makeDirectory($path); } }); static::updating(function (Project $project) { $oldName = Project::where('id', $project->id)->first()->name; - $oldPath = Project::ticketAssetsPath($oldName); - $newPath = Project::ticketAssetsPath($project->slug_name); + $oldPath = Project::ticketAssetsFullPath($oldName); + $newPath = Project::ticketAssetsFullPath($project->slug_name); if (File::exists($oldPath)) { File::move($oldPath, $newPath); } else { @@ -32,7 +32,7 @@ class Project extends Model } }); static::deleted(function (Project $project) { - $path = Project::ticketAssetsPath($project->slug_name); + $path = Project::ticketAssetsFullPath($project->slug_name); if (File::exists($path)) { File::deleteDirectory($path); } @@ -41,7 +41,7 @@ class Project extends Model public function tickets() { - return $this->hasMany('App\Models\Ticket', 'project_id'); + return $this->hasMany(Ticket::class, 'project_id'); } public function getSlugNameAttribute() @@ -49,8 +49,13 @@ class Project extends Model return Str::slug($this->name); } - protected static function ticketAssetsPath(string $str) + public static function ticketAssetsFullPath(string $str) { - return public_path(sprintf('fichiers/%s', $str)); + return public_path(self::ticketAssetsPath($str)); + } + + public static function ticketAssetsPath(string $str) + { + return sprintf('fichiers/%s', $str); } } diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index ff43a4d..45c8282 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -2,26 +2,79 @@ namespace App\Models; +use App\Enums\TicketStatus; +use App\Enums\TicketType; +use App\Events\NotifySubscriptions; +use App\Events\TicketStatusChanged; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Http\UploadedFile; +use Kwaadpepper\Enum\Traits\CastsEnums; class Ticket extends Model { use HasFactory; + use CastsEnums; - public function project() { - return $this->belongsTo('App\Models\Project', 'project_id'); + protected $enumCasts = [ + 'type' => TicketType::class, + 'status' => TicketStatus::class + ]; + + protected $fillable = [ + 'project_id', + 'type', + 'status', + 'text' + ]; + + protected static function booted() + { + static::updating(function (Ticket $ticket) { + $oldStatus = Ticket::where('id', $ticket->id)->first()->status; + if (!$ticket->status->equals($oldStatus)) { + event(new TicketStatusChanged($ticket, $oldStatus)); + event(new NotifySubscriptions($ticket, sprintf( + 'Le ticket à changé de status : %s', + $ticket->status->label + ))); + } + }); } - public function user() { - return $this->belongsTo('App\Models\User', 'created_by'); + public function project() + { + return $this->belongsTo(Project::class); } - public function messages() { - return $this->hasMany('App\Models\Message', 'ticket_id'); + public function user() + { + return $this->belongsTo(User::class, 'created_by'); } - public function subscriptions() { - return $this->belongsToMany('App\Models\User', 'subscriptions'); + public function messages() + { + return $this->hasMany(Message::class, 'ticket_id'); + } + + public function subscriptions() + { + return $this->belongsToMany(User::class, 'subscriptions'); + } + + public static function saveFile(Project $project, Ticket $ticket, TicketType $type, UploadedFile $file) + { + $projectAssetFullPath = Project::ticketAssetsFullPath($project->slug_name); + $fileAssetPath = Project::ticketAssetsPath($project->slug_name); + $fileName = sprintf( + '%s-%s-%d-%d.%s', + $project->slug_name, + $type->getDefinition(), + $ticket->id, + rand(), + $file->extension() + ); + $file->move(sprintf('%s/%d', $projectAssetFullPath, $ticket->id), $fileName); + return sprintf('%s/%d/%s', $fileAssetPath, $ticket->id, $fileName); } } diff --git a/app/Models/User.php b/app/Models/User.php index e1034d2..1bb0266 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,14 +2,14 @@ namespace App\Models; -use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { - use HasFactory, Notifiable; + use HasFactory; + use Notifiable; /** * The attributes that are mass assignable. @@ -41,15 +41,23 @@ class User extends Authenticatable 'email_verified_at' => 'datetime', ]; - public function tickets() { - return $this->hasMany("App\Models\Ticket", 'created_by'); + public function tickets() + { + return $this->hasMany(Ticket::class, 'created_by'); } - public function messages() { - return $this->hasMany('App\Models\Messages', 'user_id'); + public function messages() + { + return $this->hasMany(Messages::class, 'user_id'); } - public function subscriptions() { - return $this->belongsToMany('App\Models\Ticket', 'subscriptions'); + public function subscriptions() + { + return $this->belongsToMany(Ticket::class, 'subscriptions'); + } + + public function project() + { + return $this->belongsTo(Project::class); } } diff --git a/app/Policies/ProjectPolicy.php b/app/Policies/ProjectPolicy.php new file mode 100644 index 0000000..9513d25 --- /dev/null +++ b/app/Policies/ProjectPolicy.php @@ -0,0 +1,99 @@ +role === 1; + } + + /** + * Determine whether the user can view the model. + * + * @param \App\Models\User $user + * @param \App\Models\Project $project + * @return mixed + */ + //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function view(User $user, Project $project) + { + return $user->role === 1; + } + + /** + * Determine whether the user can create models. + * + * @param \App\Models\User $user + * @return mixed + */ + public function create(User $user) + { + return $user->role === 1; + } + + /** + * Determine whether the user can update the model. + * + * @param \App\Models\User $user + * @param \App\Models\Project $project + * @return mixed + */ + //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function update(User $user, Project $project) + { + return $user->role === 1; + } + + /** + * Determine whether the user can delete the model. + * + * @param \App\Models\User $user + * @param \App\Models\Project $project + * @return mixed + */ + //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function delete(User $user, Project $project) + { + return $user->role === 1; + } + + /** + * Determine whether the user can restore the model. + * + * @param \App\Models\User $user + * @param \App\Models\Project $project + * @return mixed + */ + //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function restore(User $user, Project $project) + { + return $user->role === 1; + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param \App\Models\User $user + * @param \App\Models\Project $project + * @return mixed + */ + //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function forceDelete(User $user, Project $project) + { + return $user->role === 1; + } +} diff --git a/app/Policies/TicketPolicy.php b/app/Policies/TicketPolicy.php new file mode 100644 index 0000000..de00932 --- /dev/null +++ b/app/Policies/TicketPolicy.php @@ -0,0 +1,123 @@ +role === 1 or $ticket->created_by == $user->id; + } + + /** + * Determine whether the user can delete the model. + * + * @param \App\Models\User $user + * @param \App\Models\Ticket $ticket + * @return mixed + */ + //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function delete(User $user, Ticket $ticket) + { + return false; + } + + /** + * Determine whether the user can restore the model. + * + * @param \App\Models\User $user + * @param \App\Models\Ticket $ticket + * @return mixed + */ + public function restore(User $user, Ticket $ticket) + { + return Auth::user()->role === 1 or $ticket->created_by == $user->id; + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param \App\Models\User $user + * @param \App\Models\Ticket $ticket + * @return mixed + */ + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function forceDelete(User $user, Ticket $ticket) + { + return false; + } + + public function changeStatus(User $user) + { + return $user->role === 1; + } + + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed + public function suscribeToTicket(User $user, Ticket $ticket) + { + return !$ticket->status->equals(TicketStatus::archived()); + } + + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed + public function createMessage(User $user, Ticket $ticket) + { + if ($user->role !== 1 and $ticket->created_by !== $user->id) { + return Response::deny(trans('Vous ne pouvez modifier un ticket qui ne vous appartient pas.')); + } + return $ticket->status->equals(TicketStatus::archived()) ? + Response::deny(trans('Vous ne pouvez ajouter un message à un ticket qui est archivé')) : Response::allow(); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index ce74491..3a82fd0 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,18 +2,22 @@ namespace App\Providers; +use App\Models\Ticket; +use App\Policies\ProjectPolicy; +use App\Policies\TicketPolicy; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; -use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider { + /** * The policy mappings for the application. * * @var array */ protected $policies = [ - // 'App\Models\Model' => 'App\Policies\ModelPolicy', + Project::class => ProjectPolicy::class, + Ticket::class => TicketPolicy::class ]; /** diff --git a/config/app.php b/config/app.php index 8bfe3b7..c697c9a 100644 --- a/config/app.php +++ b/config/app.php @@ -226,7 +226,8 @@ return [ 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, - + 'TicketType' => App\Enums\TicketType::class, + 'TicketStatus' => App\Enums\TicketStatus::class ], ]; diff --git a/resources/css/app.css b/resources/css/app.css deleted file mode 100644 index e69de29..0000000 diff --git a/resources/sass/app.scss b/resources/sass/app.scss index 3193ffa..fcf3e2d 100644 --- a/resources/sass/app.scss +++ b/resources/sass/app.scss @@ -6,3 +6,13 @@ // Bootstrap @import '~bootstrap/scss/bootstrap'; + +a.col_sort { + &:link,:visited,:hover,:active { + text-decoration: none; + } +} + +.container-xxl { + max-width: 1340px !important; +} diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index e4f00e3..65d5188 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -1,114 +1,8 @@ @extends('layout.app') -@section('tickets', 'active') - @section('content')
- @auth -

Gestion des ticketsCréer un ticket

- - - - - - - - - - - - @if (Auth::user()->role === 1) - - @endif - - - @foreach ($tickets as $ticket) - @if (Auth::user()->project_id === $ticket->project->id || Auth::user()->role === 1) - - - - - - - - - - - - - @endif - @endforeach - -
- #  - @include('modules.sorter', ['colname' => 'id']) - - Projet  - @include('modules.sorter', ['colname' => 'project']) - - Type  - @include('modules.sorter', ['colname' => 'type'])FichierFichier 2 - Date de création  - @include('modules.sorter', ['colname' => 'created_at']) - Créé par  - @include('modules.sorter', ['colname' => 'created_by']) - Status  - @include('modules.sorter', ['colname' => 'status']) - Contenu
- {{ $ticket->id }} - {{ $ticket->project->name }} - @if ($ticket->type === 0) - Bug - @else - Évolution - @endif - - @if ($ticket->file !== null) - Fichier - @endif - - @if ($ticket->file2 !== null) - Fichier 2 - @endif - {{ Carbon\Carbon::parse($ticket->created_at)->format('d/m/Y à H:i') }}{{ $ticket->user->name }}{{ $status[$ticket->status] }} - {{-- Modal du ticket --}} - - - - - - - @if (Auth::user()->role === 1) - Modifier - @endif -
- {{ $tickets->withQueryString()->links() }} - @else

Bienvenue sur le support LDC WEB.

Pour consulter vos tickets, merci de vous connecter : Connexion

- @endauth
@endsection diff --git a/resources/views/layout/app.blade.php b/resources/views/layout/app.blade.php index 4fa3e24..942580b 100644 --- a/resources/views/layout/app.blade.php +++ b/resources/views/layout/app.blade.php @@ -12,14 +12,18 @@