mirror of
https://github.com/HDInnovations/UNIT3D-Community-Edition.git
synced 2026-05-03 08:50:22 -05:00
refactor: split tickets, use route model binding, form requests and mass assignment
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author HDVinnie <hdinnovations@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TicketAssigneeController extends Controller
|
||||
{
|
||||
final public function store(Request $request, Ticket $ticket): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->group->is_modo, 403);
|
||||
|
||||
$ticket->update([
|
||||
'staff_id' => $request->user()->id,
|
||||
'staff_read' => 0,
|
||||
]);
|
||||
|
||||
return to_route('tickets.show', ['ticket' => $ticket])
|
||||
->withSuccess(trans('ticket.assigned-success'));
|
||||
}
|
||||
|
||||
final public function destroy(Request $request, Ticket $ticket): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->group->is_modo, 403);
|
||||
|
||||
$ticket->update([
|
||||
'staff_id' => null,
|
||||
]);
|
||||
|
||||
return to_route('tickets.show', ['ticket' => $ticket])
|
||||
->withSuccess(trans('ticket.unassigned-success'));
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,19 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Ticket;
|
||||
use App\Models\TicketAttachment;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TicketAttachmentController extends Controller
|
||||
{
|
||||
/**
|
||||
* Download a ticket attachment from storage.
|
||||
*/
|
||||
final public function download(TicketAttachment $attachment): \Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
final public function download(Request $request, Ticket $ticket, TicketAttachment $attachment): \Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
{
|
||||
abort_unless($request->user()->group->is_modo || $request->user()->id === $ticket->user_id, 403);
|
||||
|
||||
return response()->download(getcwd().'/files/attachments/attachments/'.$attachment->file_name)->deleteFileAfterSend(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\StoreTicketRequest;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\TicketAttachment;
|
||||
use App\Models\TicketCategory;
|
||||
use App\Models\TicketPriority;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -43,159 +43,60 @@ class TicketController extends Controller
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
final public function store(Request $request): \Illuminate\Http\RedirectResponse
|
||||
final public function store(StoreTicketRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$ticket = Ticket::create(['user_id' => $request->user()->id] + $request->validated());
|
||||
|
||||
$ticket = new Ticket();
|
||||
$ticket->user_id = $user->id;
|
||||
$ticket->category_id = $request->input('category');
|
||||
$ticket->priority_id = $request->input('priority');
|
||||
$ticket->subject = $request->input('subject');
|
||||
$ticket->body = $request->input('body');
|
||||
|
||||
$v = validator($ticket->toArray(), [
|
||||
'user_id' => 'required|exists:users,id',
|
||||
'category_id' => 'required|exists:ticket_categories,id',
|
||||
'priority_id' => 'required|exists:ticket_priorities,id',
|
||||
'subject' => 'required',
|
||||
'body' => 'required',
|
||||
]);
|
||||
|
||||
if ($v->fails()) {
|
||||
return to_route('tickets.create')
|
||||
->withInput()
|
||||
->withErrors($v->errors());
|
||||
}
|
||||
|
||||
$ticket->save();
|
||||
|
||||
return to_route('tickets.show', ['id' => $ticket->id])
|
||||
return to_route('tickets.show', ['ticket' => $ticket])
|
||||
->withSuccess(trans('ticket.created-success'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
final public function show(Request $request, int $id): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
final public function show(Request $request, Ticket $ticket): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
{
|
||||
$user = $request->user();
|
||||
$ticket = Ticket::with(['comments'])->findOrFail($id);
|
||||
abort_unless($user->group->is_modo || $user->id == $ticket->user_id, 403);
|
||||
abort_unless($request->user()->group->is_modo || $request->user()->id === $ticket->user_id, 403);
|
||||
|
||||
if ($user->id == $ticket->user_id) {
|
||||
if ($request->user()->id === $ticket->user_id) {
|
||||
$ticket->user_read = 1;
|
||||
$ticket->save();
|
||||
}
|
||||
|
||||
if ($user->id == $ticket->staff_id) {
|
||||
if ($request->user()->id === $ticket->staff_id) {
|
||||
$ticket->staff_read = 1;
|
||||
$ticket->save();
|
||||
}
|
||||
|
||||
return view('ticket.show', [
|
||||
'user' => $user,
|
||||
'ticket' => $ticket,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
final public function update(Request $request, int $id): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$ticket = Ticket::findOrFail($id);
|
||||
$user = $request->user();
|
||||
abort_unless($user->group->is_modo || $user->id == $ticket->user_id, 403);
|
||||
|
||||
$ticket->category_id = $request->input('category');
|
||||
$ticket->priority_id = $request->input('priority');
|
||||
$ticket->subject = $request->input('subject');
|
||||
$ticket->body = $request->input('body');
|
||||
|
||||
$v = validator($ticket->toArray(), [
|
||||
'user_id' => 'required|exists:users,id',
|
||||
'category_id' => 'required|exists:ticket_categories,id',
|
||||
'priority_id' => 'required|exists:ticket_priorities,id',
|
||||
'subject' => 'required',
|
||||
'body' => 'required',
|
||||
]);
|
||||
|
||||
if ($v->fails()) {
|
||||
return to_route('tickets.create')
|
||||
->withInput()
|
||||
->withErrors($v->errors());
|
||||
}
|
||||
|
||||
$ticket->save();
|
||||
|
||||
return to_route('tickets.show', ['id' => $ticket->id])
|
||||
->withSuccess(trans('ticket.updated-success'));
|
||||
return view('ticket.show', [
|
||||
'user' => $request->user(),
|
||||
'ticket' => $ticket->load('comments'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
final public function destroy(Request $request, int $id): \Illuminate\Http\RedirectResponse
|
||||
final public function destroy(Request $request, Ticket $ticket): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$ticket = Ticket::findOrFail($id);
|
||||
$user = $request->user();
|
||||
abort_unless($user->group->is_modo, 403);
|
||||
abort_unless($request->user()->group->is_modo, 403);
|
||||
|
||||
$ticket->comments()->delete();
|
||||
TicketAttachment::where('ticket_id', '=', $id)->delete();
|
||||
$ticket->attachments()->delete();
|
||||
$ticket->delete();
|
||||
|
||||
return to_route('tickets.index')
|
||||
->withSuccess(trans('ticket.deleted-success'));
|
||||
}
|
||||
|
||||
final public function assign(Request $request, int $id): \Illuminate\Http\RedirectResponse
|
||||
final public function close(Request $request, Ticket $ticket): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$ticket = Ticket::findOrFail($id);
|
||||
$user = $request->user();
|
||||
abort_unless($user->group->is_modo, 403);
|
||||
abort_unless($request->user()->group->is_modo || $request->user()->id === $ticket->user_id, 403);
|
||||
|
||||
$ticket->staff_id = $request->input('user_id');
|
||||
$ticket->staff_read = 0;
|
||||
|
||||
$v = validator($ticket->toArray(), [
|
||||
'user_id' => 'required|exists:users,id',
|
||||
$ticket->update([
|
||||
'closed_at' => now(),
|
||||
]);
|
||||
|
||||
if ($v->fails()) {
|
||||
return to_route('tickets.show', ['id' => $ticket->id])
|
||||
->withErrors($v->errors());
|
||||
}
|
||||
|
||||
$ticket->save();
|
||||
|
||||
return to_route('tickets.show', ['id' => $ticket->id])
|
||||
->withSuccess(trans('ticket.assigned-success'));
|
||||
}
|
||||
|
||||
final public function unassign(Request $request, int $id): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$ticket = Ticket::findOrFail($id);
|
||||
$user = $request->user();
|
||||
abort_unless($user->group->is_modo, 403);
|
||||
|
||||
$ticket->staff_id = null;
|
||||
$ticket->save();
|
||||
|
||||
return to_route('tickets.show', ['id' => $ticket->id])
|
||||
->withSuccess(trans('ticket.unassigned-success'));
|
||||
}
|
||||
|
||||
final public function close(Request $request, int $id): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$ticket = Ticket::findOrFail($id);
|
||||
$user = $request->user();
|
||||
abort_unless($user->group->is_modo || $user->id == $ticket->user_id, 403);
|
||||
|
||||
$ticket->closed_at = now();
|
||||
$ticket->save();
|
||||
|
||||
return to_route('tickets.index')
|
||||
->withSuccess(trans('ticket.closed-success'));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author Roardom <roardom@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreTicketRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(Request $request): array
|
||||
{
|
||||
return [
|
||||
'category_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists('ticket_categories', 'id'),
|
||||
],
|
||||
'priority_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists('ticket_priorities', 'id'),
|
||||
],
|
||||
'subject' => [
|
||||
'required',
|
||||
'max:255',
|
||||
],
|
||||
'body' => [
|
||||
'required',
|
||||
'max:65535',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ class Ticket extends Model
|
||||
'reminded_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function scopeStatus($query, $status)
|
||||
{
|
||||
if ($status === 'all') {
|
||||
|
||||
@@ -45,7 +45,7 @@ class StaffCommentCreated extends Notification
|
||||
return (new MailMessage())
|
||||
->subject('A comment was added (Staff)')
|
||||
->line('A comment was added')
|
||||
->action('View Ticket', route('tickets.show', ['id' => $this->comment->ticket->id]));
|
||||
->action('View Ticket', route('tickets.show', ['ticket' => $this->comment->ticket]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ class StaffTicketAssigned extends Notification
|
||||
return (new MailMessage())
|
||||
->subject('A ticket was assigned (Ticket # '.$this->ticket->id.')')
|
||||
->line('A ticket was assigned to '.$this->ticket->staff->username)
|
||||
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
|
||||
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ class StaffTicketClosed extends Notification
|
||||
return (new MailMessage())
|
||||
->subject('A ticket was closed (Staff)')
|
||||
->line('A ticket was closed')
|
||||
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
|
||||
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ class StaffTicketCreated extends Notification
|
||||
return (new MailMessage())
|
||||
->subject('A ticket was created (Ticket # '.$this->ticket->id.')')
|
||||
->line('A ticket was created.')
|
||||
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
|
||||
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ class UserCommentCreated extends Notification
|
||||
return (new MailMessage())
|
||||
->subject('A comment was added (User)')
|
||||
->line('A comment was added')
|
||||
->action('View Ticket', route('tickets.show', ['id' => $this->comment->ticket->id]));
|
||||
->action('View Ticket', route('tickets.show', ['ticket' => $this->comment->ticket]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ class UserTicketAssigned extends Notification
|
||||
return (new MailMessage())
|
||||
->subject('Your ticket was assigned (Ticket # '.$this->ticket->id.')')
|
||||
->line('Your ticket was assigned to '.$this->ticket->user->username)
|
||||
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
|
||||
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ class UserTicketClosed extends Notification
|
||||
return (new MailMessage())
|
||||
->subject('Your ticket was closed (User)')
|
||||
->line('Your ticket was closed')
|
||||
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
|
||||
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ class UserTicketCreated extends Notification
|
||||
return (new MailMessage())
|
||||
->subject('Your ticket was created (Ticket # '.$this->ticket->id.')')
|
||||
->line('Your ticket was created.')
|
||||
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
|
||||
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,7 @@ class UserTicketStale extends Notification
|
||||
->cc($this->ticket->staff->email)
|
||||
->subject('Your ticket is still open')
|
||||
->line('This is a reminder that your ticket is still open')
|
||||
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
|
||||
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<menu class="data-table__actions">
|
||||
<li class="data-table__action">
|
||||
<form
|
||||
action="{{ route('tickets.attachment.download', $attachment) }}"
|
||||
action="{{ route('tickets.attachment.download', ['ticket' => $ticket, 'attachment' => $attachment]) }}"
|
||||
method="POST"
|
||||
>
|
||||
@csrf
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
{{ $ticket->id }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ route('tickets.show', ['id' => $ticket->id]) }}">{{ $ticket->subject }}</a>
|
||||
<a href="{{ route('tickets.show', ['ticket' => $ticket]) }}">{{ $ticket->subject }}</a>
|
||||
@if (auth()->user()->group->is_modo)
|
||||
@php
|
||||
$myTicketUnread = DB::table('tickets')
|
||||
@@ -164,7 +164,7 @@
|
||||
<td>
|
||||
<menu class="data-table__actions">
|
||||
<li class="data-table__action">
|
||||
<form method="POST" action="{{ route('tickets.close', ['id' => $ticket->id]) }}">
|
||||
<form method="POST" action="{{ route('tickets.close', ['ticket' => $ticket]) }}">
|
||||
@csrf
|
||||
<button class="form__button form__button--text">
|
||||
{{ __('ticket.close') }}
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
@if($user->group->is_modo)
|
||||
<form
|
||||
class="form form--horizontal"
|
||||
action="{{ route('tickets.assign', ['id' => $ticket->id]) }}"
|
||||
action="{{ route('tickets.assignee.store', ['ticket' => $ticket]) }}"
|
||||
method="POST"
|
||||
x-data
|
||||
>
|
||||
@@ -85,10 +85,11 @@
|
||||
</form>
|
||||
@if(! empty($ticket->staff_id))
|
||||
<form
|
||||
action="{{ route('tickets.unassign', ['id' => $ticket->id]) }}"
|
||||
action="{{ route('tickets.assignee.destroy', ['ticket' => $ticket]) }}"
|
||||
method="POST"
|
||||
>
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<p class="form__group form__group--horizontal">
|
||||
<button class="form__button form__button--filled form__button--centered">
|
||||
{{ __('ticket.unassign') }}
|
||||
@@ -97,7 +98,7 @@
|
||||
</form>
|
||||
@endif
|
||||
<form
|
||||
action="{{ route('tickets.destroy', ['id' => $ticket->id]) }}"
|
||||
action="{{ route('tickets.destroy', ['ticket' => $ticket]) }}"
|
||||
method="POST"
|
||||
>
|
||||
@csrf
|
||||
@@ -111,7 +112,7 @@
|
||||
@endif
|
||||
@if(empty($ticket->closed_at))
|
||||
<form
|
||||
action="{{ route('tickets.close', ['id' => $ticket->id]) }}"
|
||||
action="{{ route('tickets.close', ['ticket' => $ticket]) }}"
|
||||
method="POST"
|
||||
>
|
||||
<p class="form__group form__group--horizontal">
|
||||
|
||||
+8
-10
@@ -272,16 +272,14 @@ Route::middleware('language')->group(function (): void {
|
||||
Route::name('tickets.')->group(function (): void {
|
||||
Route::get('/', [App\Http\Controllers\TicketController::class, 'index'])->name('index');
|
||||
Route::get('/create', [App\Http\Controllers\TicketController::class, 'create'])->name('create');
|
||||
Route::post('/store', [App\Http\Controllers\TicketController::class, 'store'])->name('store');
|
||||
Route::get('/{id}', [App\Http\Controllers\TicketController::class, 'show'])->where('id', '[0-9]+')->name('show');
|
||||
Route::get('/{id}/edit', [App\Http\Controllers\TicketController::class, 'edit'])->name('edit');
|
||||
Route::patch('/{id}/update', [App\Http\Controllers\TicketController::class, 'update'])->name('update');
|
||||
Route::delete('/{id}/destroy', [App\Http\Controllers\TicketController::class, 'destroy'])->name('destroy');
|
||||
Route::post('/{id}/assign', [App\Http\Controllers\TicketController::class, 'assign'])->name('assign');
|
||||
Route::post('/{id}/unassign', [App\Http\Controllers\TicketController::class, 'unassign'])->name('unassign');
|
||||
Route::post('/{id}/close', [App\Http\Controllers\TicketController::class, 'close'])->name('close');
|
||||
Route::post('/attachments/{attachment}/download', [App\Http\Controllers\TicketAttachmentController::class, 'download'])->name('attachment.download');
|
||||
});
|
||||
Route::post('/', [App\Http\Controllers\TicketController::class, 'store'])->name('store');
|
||||
Route::get('/{ticket}', [App\Http\Controllers\TicketController::class, 'show'])->name('show');
|
||||
Route::delete('/{ticket}', [App\Http\Controllers\TicketController::class, 'destroy'])->name('destroy');
|
||||
Route::post('/{ticket}/assignee', [App\Http\Controllers\TicketAssigneeController::class, 'store'])->name('assignee.store');
|
||||
Route::delete('/{ticket}/assignee', [App\Http\Controllers\TicketAssigneeController::class, 'destroy'])->name('assignee.destroy');
|
||||
Route::post('/{ticket}/close', [App\Http\Controllers\TicketController::class, 'close'])->name('close');
|
||||
Route::post('/{ticket}/attachments/{attachment}/download', [App\Http\Controllers\TicketAttachmentController::class, 'download'])->name('attachment.download');
|
||||
})->scopeBindings();
|
||||
});
|
||||
|
||||
// Missing System
|
||||
|
||||
Reference in New Issue
Block a user