add: helpdesk system beta

- multilingual needed
- events need to be completed
- creating ticket categories from staff dashboard needs to be done
This commit is contained in:
HDVinnie
2021-02-28 18:39:51 -05:00
parent e6fac5b9d3
commit 85b31e81e4
55 changed files with 2996 additions and 45 deletions
@@ -0,0 +1,44 @@
<?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\Console\Commands;
use App\Models\Ticket;
use Illuminate\Console\Command;
class CheckForStaleTickets extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tickets:stale';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Checks for tickets open longer than 3 days';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Ticket::checkForStaleTickets();
}
}
+55
View File
@@ -0,0 +1,55 @@
<?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\Events;
use App\Models\User;
use App\Models\Comment;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CommentCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $comment;
public $user;
/**
* Create a new event instance.
*
* @param Comment $comment
* @param User $user
* @return mixed
*/
public function __construct(Comment $comment, User $user)
{
$this->comment = $comment;
$this->user = $user;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
+51
View File
@@ -0,0 +1,51 @@
<?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\Events;
use App\Models\Ticket;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TicketAssigned
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $ticket;
/**
* Create a new event instance.
*
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
+51
View File
@@ -0,0 +1,51 @@
<?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\Events;
use App\Models\Ticket;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TicketClosed
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $ticket;
/**
* Create a new event instance.
*
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
+55
View File
@@ -0,0 +1,55 @@
<?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\Events;
use App\Models\User;
use App\Models\Ticket;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TicketCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public $ticket;
/**
* Create a new event instance.
*
* @param Ticket $ticket
* @param User $user
* @return mixed
*/
public function __construct(Ticket $ticket, User $user)
{
$this->ticket = $ticket;
$this->user = $user;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
+51
View File
@@ -0,0 +1,51 @@
<?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\Events;
use App\Models\Ticket;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TicketWentStale
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $ticket;
/**
* Create a new event instance.
*
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
+39 -2
View File
@@ -13,6 +13,7 @@
namespace App\Http\Controllers;
use App\Models\Ticket;
use App\Achievements\UserMade100Comments;
use App\Achievements\UserMade200Comments;
use App\Achievements\UserMade300Comments;
@@ -60,7 +61,7 @@ class CommentController extends Controller
* @param \Illuminate\Http\Request $request
* @param $id
*
* @return Illuminate\Http\RedirectResponse
* @return \Illuminate\Http\RedirectResponse
*/
public function collection(Request $request, $id)
{
@@ -69,7 +70,7 @@ class CommentController extends Controller
if ($user->can_comment == 0) {
return \redirect()->route('collection.show', ['id' => $collection->id])
->withErros('Your Comment Rights Have Been Revoked!');
->withErrors('Your Comment Rights Have Been Revoked!');
}
$comment = new Comment();
@@ -521,6 +522,42 @@ class CommentController extends Controller
->withSuccess('Your Comment Has Been Added!');
}
/**
* Store A New Comment To A Request.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\TorrentRequest $id
*
* @return \Illuminate\Http\RedirectResponse
*/
public function ticket(Request $request, $id)
{
$ticket = Ticket::findOrFail($id);
$user = $request->user();
$comment = new Comment();
$comment->content = $request->input('content');
$comment->anon = 0;
$comment->user_id = $user->id;
$comment->ticket_id = $ticket->id;
$v = \validator($comment->toArray(), [
'content' => 'required',
'user_id' => 'required',
'ticket_id' => 'required',
'anon' => 'required',
]);
if ($v->fails()) {
return \redirect()->route('request', ['id' => $tr->id])
->withErrors($v->errors());
}
$comment->save();
return \redirect()->route('tickets.show', ['id' => $ticket->id])
->withSuccess('Your Comment Has Been Added!');
}
/**
* Store A New Comment To A Torrent Via Quick Thanks.
*
@@ -0,0 +1,31 @@
<?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\TicketAttachment;
class TicketAttachmentController extends Controller
{
/**
* Download a ticket attachment from storage.
*
* @param \App\Models\TicketAttachment $attachment
*
* @return mixed
*/
final public function download(TicketAttachment $attachment)
{
return \response()->download(\getcwd().'/files/attachments/attachments/'.$attachment->file_name)->deleteFileAfterSend(false);
}
}
+252
View File
@@ -0,0 +1,252 @@
<?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\Comment;
use App\Events\TicketAssigned;
use App\Events\TicketClosed;
use App\Events\TicketCreated;
use App\Models\Ticket;
use App\Models\TicketCategory;
use App\Models\TicketPriority;
use Illuminate\Http\Request;
use App\Models\TicketAttachment;
class TicketController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
final public function index(Request $request): \Illuminate\Contracts\View\Factory|\Illuminate\View\View|\Illuminate\Contracts\Foundation\Application
{
return \view('ticket.index');
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
final public function create(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View|\Illuminate\Contracts\Foundation\Application
{
$categories = TicketCategory::all()->sortBy('position');
$priorities = TicketPriority::all()->sortBy('position');
return view('ticket.create', [
'categories' => $categories,
'priorities' => $priorities
]);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\RedirectResponse
*/
final public function store(Request $request): \Illuminate\Http\RedirectResponse
{
$user = $request->user();
$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 \redirect()->route('tickets.create')
->withInput()
->withErrors($v->errors());
}
$ticket->save();
return \redirect()->route('tickets.show', ['id' => $ticket->id])
->withSuccess('Your Helpdesk Ticket Was Created Successfully!');
}
/**
* Display the specified resource.
*
* @param \Illuminate\Http\Request $request
* @param int $id
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
final public function show(Request $request, int $id): \Illuminate\Contracts\View\Factory|\Illuminate\View\View|\Illuminate\Contracts\Foundation\Application
{
$user = $request->user();
$ticket = Ticket::with(['comments'])->findOrFail($id);
return view('ticket.show', [
'user' => $user,
'ticket' => $ticket,
]);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
*
* @return \Illuminate\Http\Response
*/
final public function edit(int $id): \Illuminate\Http\Response
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
*/
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 \redirect()->route('tickets.create')
->withInput()
->withErrors($v->errors());
}
$ticket->save();
return \redirect()->route('tickets.show', ['id' => $ticket->id])
->withSuccess('Your Helpdesk Ticket Was Updated Successfully!');
}
/**
* Remove the specified resource from storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
*/
final public function destroy(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);
Comment::where('ticket_id', '=', $id)->delete();
TicketAttachment::where('ticket_id', '=', $id)->delete();
$ticket->delete();
return \redirect()->route('tickets.index')
->withSuccess('Your Helpdesk Ticket Was Deleted Successfully!');
}
/**
* @param \Illuminate\Http\Request $request
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
*/
final public function assign(Request $request, int $id): \Illuminate\Http\RedirectResponse
{
$ticket = Ticket::findOrFail($id);
$user = $request->user();
\abort_unless($user->group->is_modo, 403);
$ticket->staff_id = $request->input('user_id');
$v = \validator($ticket->toArray(), [
'user_id' => 'required|exists:users,id',
]);
if ($v->fails()) {
return \redirect()->route('tickets.show', ['id' => $ticket->id])
->withErrors($v->errors());
}
$ticket->save();
return \redirect()->route('tickets.show', ['id' => $ticket->id])
->withSuccess('Helpdesk Ticket Was Assigned Successfully!');
}
/**
* @param \Illuminate\Http\Request $request
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
*/
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 \redirect()->route('tickets.show', ['id' => $ticket->id])
->withSuccess('Helpdesk Ticket Was Unassigned Successfully!');
}
/**
* @param \Illuminate\Http\Request $request
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
*/
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 \redirect()->route('tickets.show', ['id' => $ticket->id])
->withSuccess('Helpdesk Ticket Was Closed Successfully!');
}
}
+60
View File
@@ -0,0 +1,60 @@
<?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\Livewire;
use Livewire\WithFileUploads;
use App\Models\TicketAttachment;
use Livewire\Component;
class AttachmentUpload extends Component
{
use WithFileUploads;
public $user;
public $ticket;
public $attachment;
public $storedImage;
public function mount(int $id)
{
$this->user = \auth()->user();
$this->ticket = $id;
}
public function upload()
{
$this->validate([
'attachment' => 'image|max:1024', // 1MB Max
]);
$fileName = \uniqid('', true).'.'.$this->attachment->getClientOriginalExtension();
$this->attachment->storeAs('attachments', $fileName, 'attachments');
$attachment = new TicketAttachment();
$attachment->user_id = $this->user->id;
$attachment->ticket_id = $this->ticket;
$attachment->file_name = $fileName;
$attachment->file_size = $this->attachment->getSize();
$attachment->file_extension = $this->attachment->getMimeType();
$attachment->save();
$this->dispatchBrowserEvent('success', ['type' => 'success', 'message' => 'Ticket Attachment Uploaded Successfully!']);
}
public function render()
{
return \view('livewire.attachment-upload');
}
}
+83
View File
@@ -0,0 +1,83 @@
<?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\Livewire;
use App\Models\Ticket;
use Livewire\Component;
use Livewire\WithPagination;
class TicketSearch extends Component
{
use WithPagination;
public $user;
public $perPage = 25;
public $searchTerm = '';
public $sortField = 'created_at';
public $sortDirection = 'desc';
public function paginationView()
{
return 'vendor.pagination.livewire-pagination';
}
public function updatingSearchTerm()
{
$this->resetPage();
}
public function mount()
{
$this->user = \auth()->user();
}
public function getTicketsProperty()
{
if ($this->user->group->is_modo) {
return Ticket::query()
->with(['user', 'category', 'priority'])
->when($this->searchTerm, function ($query) {
return $query->where('subject', 'LIKE', '%'.$this->searchTerm.'%');
})
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage);
} else {
return Ticket::query()
->with(['user', 'category', 'priority'])
->where('user_id', '=', $this->user->id)
->when($this->searchTerm, function ($query) {
return $query->where('subject', 'LIKE', '%'.$this->searchTerm.'%');
})
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage);
}
}
public function sortBy($field)
{
if ($this->sortField === $field) {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->sortDirection = 'asc';
}
$this->sortField = $field;
}
public function render()
{
return \view('livewire.ticket-search', [
'tickets' => $this->tickets
]);
}
}
@@ -0,0 +1,46 @@
<?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\Listeners;
use App\Models\User;
use App\Events\CommentCreated;
use App\Notifications\StaffCommentCreated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Notification;
class NotifyStaffCommentWasCreated
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param CommentCreated $event
* @return void
*/
public function handle(CommentCreated $event)
{
$staff = User::where(['is_modo' => 1])->limit(1)->get();
Notification::send($staff, new StaffCommentCreated($event->comment));
}
}
@@ -0,0 +1,47 @@
<?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\Listeners;
use App\Models\User;
use App\Events\TicketAssigned;
use App\Notifications\StaffTicketAssigned;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Notification;
class NotifyStaffTicketWasAssigned
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param TicketAssigned $event
*
* @return void
*/
public function handle(TicketAssigned $event)
{
$staff = User::where(['is_modo' => 1])->limit(1)->get();
Notification::send($staff, new StaffTicketAssigned($event->ticket));
}
}
@@ -0,0 +1,47 @@
<?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\Listeners;
use App\Models\User;
use App\Events\TicketClosed;
use App\Notifications\StaffTicketClosed;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Notification;
class NotifyStaffTicketWasClosed
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param TicketClosed $event
*
* @return void
*/
public function handle(TicketClosed $event)
{
$staff = User::where(['is_modo' => 1])->limit(1)->get();
Notification::send($staff, new StaffTicketClosed($event->ticket));
}
}
@@ -0,0 +1,42 @@
<?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\Listeners;
use App\Providers\TicketCreated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class NotifyStaffTicketWasCreated
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param TicketCreated $event
* @return void
*/
public function handle(TicketCreated $event)
{
//
}
}
@@ -0,0 +1,42 @@
<?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\Listeners;
use App\Providers\CommentCreated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class NotifyUserCommentWasCreated
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param CommentCreated $event
* @return void
*/
public function handle(CommentCreated $event)
{
//
}
}
+44
View File
@@ -0,0 +1,44 @@
<?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\Listeners;
use App\Events\TicketWentStale;
use App\Notifications\UserTicketStale;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class NotifyUserTicketIsStale
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param TicketWentStale $event
* @return void
*/
public function handle(TicketWentStale $event)
{
$event->ticket->user->notify(new UserTicketStale($event->ticket));
$event->ticket->update(['reminded_at' => time()]);
}
}
@@ -0,0 +1,42 @@
<?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\Listeners;
use App\Providers\TicketAssigned;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class NotifyUserTicketWasAssigned
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param TicketAssigned $event
* @return void
*/
public function handle(TicketAssigned $event)
{
//
}
}
@@ -0,0 +1,42 @@
<?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\Listeners;
use App\Providers\TicketClosed;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class NotifyUserTicketWasClosed
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param TicketClosed $event
* @return void
*/
public function handle(TicketClosed $event)
{
//
}
}
@@ -0,0 +1,42 @@
<?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\Listeners;
use App\Providers\TicketCreated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class NotifyUserTicketWasCreated
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param TicketCreated $event
* @return void
*/
public function handle(TicketCreated $event)
{
//
}
}
+35 -9
View File
@@ -14,7 +14,6 @@
namespace App\Models;
use App\Helpers\Bbcode;
use App\Helpers\Linkify;
use App\Traits\Auditable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -59,6 +58,19 @@ class Comment extends Model
use HasFactory;
use Auditable;
/**
* Belongs To A User.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class)->withDefault([
'username' => 'System',
'id' => '1',
]);
}
/**
* Belongs To A Torrent.
*
@@ -100,16 +112,13 @@ class Comment extends Model
}
/**
* Belongs To A User.
* Belongs To A Ticket.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
public function ticket()
{
return $this->belongsTo(User::class)->withDefault([
'username' => 'System',
'id' => '1',
]);
return $this->belongsTo(Ticket::class);
}
/**
@@ -132,8 +141,25 @@ class Comment extends Model
public function getContentHtml()
{
$bbcode = new Bbcode();
$linkify = new Linkify();
return $bbcode->parse($linkify->linky($this->content), true);
return $bbcode->parse($this->content, true);
}
/**
* Nootify Staff There Is Stale Tickets.
*
* @param \App\Models\Ticket $ticket
*/
public static function checkForStale(Ticket $ticket)
{
if (empty($ticket->reminded_at) || strtotime($ticket->reminded_at) < strtotime('+ 3 days'))
{
$last_comment = $ticket->comments()->orderBy('id', 'desc')->first();
if (isset($last_comment->id) && ! $last_comment->user->is_modo && strtotime($last_comment->created_at) < strtotime('- 3 days'))
{
event(new TicketWentStale($last_comment->ticket));
}
}
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Ticket extends Model
{
use HasFactory;
protected $dates = [
'closed_at',
'reminded_at',
];
public function scopeStatus($query, $status)
{
if($status === 'all')
{
return $query;
}
else if($status === 'closed')
{
return $query->whereNotNull('closed_at');
}
else if($status === 'open')
{
return $query->whereNull('closed_at');
}
}
public function scopeStale($query)
{
return $query->with(['comments' => function ($query) {
$query->orderBy('id', 'desc');
}, 'comments.user'])
->has('comments')
->where('reminded_at', '<', strtotime('+ 3 days'))
->orWhereNull('reminded_at');
}
public static function checkForStaleTickets()
{
$open_tickets = self::status('open')
->whereNotNull('staff_id')
->get();
foreach($open_tickets as $open_ticket)
{
Comment::checkForStale($open_ticket);
}
}
/**
* Belongs To A User (Created).
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class)->withDefault([
'username' => 'System',
'id' => '1',
]);
}
/**
* Belongs To A Staff User (Assigned).
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function staff()
{
return $this->belongsTo(User::class, 'staff_id');
}
/**
* Belongs To A Ticket Priority.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function priority()
{
return $this->belongsTo(TicketPriority::class);
}
/**
* Belongs To A Ticket Category.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function category()
{
return $this->belongsTo(TicketCategory::class);
}
/**
* Has Many Ticket Attachments.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function attachments()
{
return $this->hasMany(TicketAttachment::class);
}
/**
* Has Many Comments.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function comments()
{
return $this->hasMany(Comment::class);
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TicketAttachment extends Model
{
use HasFactory;
protected $appends = [
'full_disk_path'
];
public function getFullDiskPathAttribute()
{
return $this->disk_path . '' . $this->file_name;
}
/**
* Belongs To A User.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Belongs To A Ticket.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function ticket()
{
return $this->belongsTo(Ticket::class);
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TicketCategory extends Model
{
use HasFactory;
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TicketPriority extends Model
{
use HasFactory;
}
+10
View File
@@ -564,6 +564,16 @@ class User extends Authenticatable
return $this->hasMany(Warning::class);
}
/**
* Has Many Tickets.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function tickets()
{
return $this->hasMany(Ticket::class, 'user_id');
}
/**
* Get the Users username as slug.
*
+79
View File
@@ -0,0 +1,79 @@
<?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\Notifications;
use App\Models\Comment;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class StaffCommentCreated extends Notification
{
use Queueable;
private $comment;
/**
* Create a new notification instance.
*
* @param Comment $comment
*
* @return mixed
*/
public function __construct(Comment $comment)
{
$this->comment = $comment;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
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]));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+76
View File
@@ -0,0 +1,76 @@
<?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\Notifications;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class StaffTicketAssigned extends Notification
{
use Queueable;
private $ticket;
/**
* Create a new notification instance.
*
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
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]));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+75
View File
@@ -0,0 +1,75 @@
<?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\Notifications;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class StaffTicketClosed extends Notification
{
use Queueable;
private $ticket;
/**
* Create a new notification instance.
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->subject('A ticket was closed (Staff)')
->line('A ticket was closed')
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+76
View File
@@ -0,0 +1,76 @@
<?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\Notifications;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class StaffTicketCreated extends Notification
{
use Queueable;
private $ticket;
/**
* Create a new notification instance.
*
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
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]));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+75
View File
@@ -0,0 +1,75 @@
<?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\Notifications;
use App\Models\Comment;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class UserCommentCreated extends Notification
{
use Queueable;
private $comment;
/**
* Create a new notification instance.
* @param Comment $comment
* @return mixed
*/
public function __construct(Comment $comment)
{
$this->comment = $comment;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
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]));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+76
View File
@@ -0,0 +1,76 @@
<?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\Notifications;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class UserTicketAssigned extends Notification
{
use Queueable;
private $ticket;
/**
* Create a new notification instance.
*
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
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]));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+75
View File
@@ -0,0 +1,75 @@
<?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\Notifications;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class UserTicketClosed extends Notification
{
use Queueable;
private $ticket;
/**
* Create a new notification instance.
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->subject('Your ticket was closed (User)')
->line('Your ticket was closed')
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+76
View File
@@ -0,0 +1,76 @@
<?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\Notifications;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class UserTicketCreated extends Notification
{
use Queueable;
private $ticket;
/**
* Create a new notification instance.
*
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
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]));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+77
View File
@@ -0,0 +1,77 @@
<?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\Notifications;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class UserTicketStale extends Notification
{
use Queueable;
private $ticket;
/**
* Create a new notification instance.
*
* @param Ticket $ticket
* @return mixed
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->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]));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+52 -7
View File
@@ -13,11 +13,31 @@
namespace App\Providers;
use App\Listeners\LoginListener;
use App\Listeners\LogoutListener;
use Illuminate\Auth\Events\Failed;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use App\Listeners\AchievementUnlocked;
use App\Listeners\FailedLoginListener;
use Assada\Achievements\Event\Unlocked;
use App\Listeners\PasswordProtectBackup;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Spatie\Backup\Events\BackupZipWasCreated;
use App\Events\TicketCreated;
use App\Events\CommentCreated;
use App\Listeners\NotifyUserTicketWasCreated;
use App\Listeners\NotifyStaffTicketWasCreated;
use App\Listeners\NotifyUserCommentWasCreated;
use App\Listeners\NotifyStaffCommentWasCreated;
use App\Events\TicketClosed;
use App\Listeners\NotifyUserTicketWasClosed;
use App\Listeners\NotifyStaffTicketWasClosed;
use App\Events\TicketAssigned;
use App\Listeners\NotifyUserTicketWasAssigned;
use App\Listeners\NotifyStaffTicketWasAssigned;
use App\Events\TicketWentStale;
use App\Listeners\NotifyUserTicketIsStale;
class EventServiceProvider extends ServiceProvider
{
@@ -27,21 +47,47 @@ class EventServiceProvider extends ServiceProvider
* @var array
*/
protected $listen = [
// Auth System
Logout::class => [
\App\Listeners\LogoutListener::class,
LogoutListener::class,
],
Login::class => [
\App\Listeners\LoginListener::class,
LoginListener::class,
],
Failed::class => [
\App\Listeners\FailedLoginListener::class,
FailedLoginListener::class,
],
'Assada\Achievements\Event\Unlocked' => [
\App\Listeners\AchievementUnlocked::class,
// Achievements System
Unlocked::class => [
AchievementUnlocked::class,
],
// Backups System
BackupZipWasCreated::class => [
\App\Listeners\PasswordProtectBackup::class,
PasswordProtectBackup::class,
],
// Ticket System
TicketCreated::class => [
NotifyUserTicketWasCreated::class,
NotifyStaffTicketWasCreated::class,
],
CommentCreated::class => [
NotifyUserCommentWasCreated::class,
NotifyStaffCommentWasCreated::class,
],
TicketClosed::class => [
NotifyUserTicketWasClosed::class,
NotifyStaffTicketWasClosed::class,
],
TicketAssigned::class => [
NotifyUserTicketWasAssigned::class,
NotifyStaffTicketWasAssigned::class,
],
TicketWentStale::class => [
NotifyUserTicketIsStale::class
]
];
/**
@@ -51,7 +97,6 @@ class EventServiceProvider extends ServiceProvider
*/
public function boot()
{
//
}
}
+5
View File
@@ -80,6 +80,11 @@ return [
'root' => public_path('files/subtitles'),
],
'attachments' => [
'driver' => 'local',
'root' => public_path('files/attachments'),
],
],
/*
@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTicketsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tickets', function (Blueprint $table) {
$table->id();
$table->integer('user_id')->index();
$table->integer('category_id')->index();
$table->integer('priority_id')->index();
$table->integer('staff_id')->nullable()->index();
$table->string('subject');
$table->longText('body');
$table->timestamp('closed_at')->nullable();
$table->timestamp('reminded_at')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tickets');
}
}
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTicketCategoriesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('ticket_categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('position');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('ticket_categories');
}
}
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTicketPrioritiesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('ticket_priorities', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('position');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('ticket_priorities');
}
}
@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTicketAttachmentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('ticket_attachments', function (Blueprint $table) {
$table->id();
$table->integer('user_id')->index();
$table->integer('ticket_id')->index();
$table->string('file_name')->nullable();
$table->string('file_size')->nullable();
$table->string('file_extension')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('ticket_attachments');
}
}
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddTicketIdToCommentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('comments', function (Blueprint $table) {
$table->integer('ticket_id')->nullable()->index()->after('playlist_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('comments', function (Blueprint $table) {
$table->dropColumn('ticket_id');
});
}
}
+2
View File
@@ -40,6 +40,8 @@ class DatabaseSeeder extends Seeder
BotsTableSeeder::class,
MediaLanguagesSeeder::class,
ResolutionsTableSeeder::class,
TicketCategoriesTableSeeder::class,
TicketPrioritiesTableSeeder::class
]);
}
}
@@ -0,0 +1,92 @@
<?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 Database\Seeders;
use App\Models\TicketCategory;
use Illuminate\Database\Seeder;
class TicketCategoriesTableSeeder extends Seeder
{
private $categories;
public function __construct()
{
$this->categories = $this->getTicketCategories();
}
/**
* Auto generated seed file.
*
* @return voids
*/
final public function run(): void
{
foreach ($this->categories as $category) {
TicketCategory::updateOrCreate($category);
}
}
/**
* @return array[]
*/
private function getTicketCategories(): array
{
return [
[
'name' => 'Accounts',
'position' => 0
],
[
'name' => 'Appeals',
'position' => 1
],
[
'name' => 'Forums',
'position' => 2
],
[
'name' => 'Requests',
'position' => 3
],
[
'name' => 'Subtitles',
'position' => 4
],
[
'name' => 'Torrents',
'position' => 5
],
[
'name' => 'MediaHub',
'position' => 6
],
[
'name' => 'Technical',
'position' => 7
],
[
'name' => 'Playlists',
'position' => 8
],
[
'name' => 'Bugs',
'position' => 9
],
[
'name' => 'Other',
'position' => 10
],
];
}
}
@@ -0,0 +1,60 @@
<?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 Database\Seeders;
use App\Models\TicketPriority;
use Illuminate\Database\Seeder;
class TicketPrioritiesTableSeeder extends Seeder
{
private $priorities;
public function __construct()
{
$this->priorities = $this->getTicketPriorities();
}
/**
* Auto generated seed file.
*
* @return voids
*/
final public function run(): void
{
foreach ($this->priorities as $priority) {
TicketPriority::updateOrCreate($priority);
}
}
/**
* @return array[]
*/
private function getTicketPriorities(): array
{
return [
[
'name' => 'Low',
'position' => 0
],
[
'name' => 'Medium',
'position' => 1
],
[
'name' => 'High',
'position' => 2
],
];
}
}
+3 -3
View File
@@ -377,10 +377,10 @@ a:focus {
}
hr {
margin-top: 24px;
margin-bottom: 24px;
margin-top: 10px;
margin-bottom: 10px;
border: 0;
border-top: 1px solid #ddd;
border-top: .5px solid #797979;
}
[role='button'] {
@@ -0,0 +1,7 @@
<div>
<input type="file" wire:model="attachment">
@error('attachment') <span class="error">{{ $message }}</span> @enderror
<button wire:click="upload">Save Attachment</button>
</div>
@@ -275,16 +275,6 @@
<a role="menuitem" tabindex="-1"
href="{{ route('download', ['id' => $bookmark->id]) }}">@lang('common.download') @lang('torrent.torrent')</a>
</li>
<li role="presentation">
<form role="menuitem" tabindex="-1"action="{{ route('bookmarks.destroy', ['id' => $bookmark->id]) }}" method="POST"
style="display: inline;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-xxs btn-danger">
@lang('torrent.delete-bookmark')
</button>
</form>
</li>
</ul>
</div>
</td>
@@ -0,0 +1,150 @@
<div>
<div class="mb-10 form-inline pull-left">
<a href="{{ route('tickets.create') }}" class="btn btn-success"><i class="fas fa-plus"></i> New Ticket</a>
</div>
<div class="mb-10 form-inline pull-right">
<div class="form-group">
@lang('common.quantity')
<select wire:model="perPage" class="form-control">
<option>25</option>
<option>50</option>
<option>100</option>
</select>
</div>
<div class="form-group">
<input type="text" wire:model="searchTerm" class="form-control" style="width: 275px;" placeholder="Subject"/>
</div>
</div>
<div class="box-body no-padding">
<table class="table vertical-align table-hover">
<tbody>
<tr>
<th>
<div sortable wire:click="sortBy('id')" :direction="$sortField === 'id' ? $sortDirection : null" role="button">
#
@include('livewire.includes._sort-icon', ['field' => 'id'])
</div>
</th>
<th>
<div sortable wire:click="sortBy('subject')" :direction="$sortField === 'subject' ? $sortDirection : null" role="button">
Subject
@include('livewire.includes._sort-icon', ['field' => 'subject'])
</div>
</th>
<th>
<div sortable wire:click="sortBy('priority_id')" :direction="$sortField === 'priority_id' ? $sortDirection : null" role="button">
Priority
@include('livewire.includes._sort-icon', ['field' => 'priority_id'])
</div>
</th>
<th>
<div sortable wire:click="sortBy('user_id')" :direction="$sortField === 'user_id' ? $sortDirection : null" role="button">
@lang('common.username')
@include('livewire.includes._sort-icon', ['field' => 'user_id'])
</div>
</th>
<th class="hidden-sm hidden-xs">
<div sortable wire:click="sortBy('closed_at')" :direction="$sortField === 'closed_at' ? $sortDirection : null"
role="button">
Status
@include('livewire.includes._sort-icon', ['field' => 'closed_at'])
</div>
</th>
<th class="hidden-sm hidden-xs">
<div sortable wire:click="sortBy('staff_id')" :direction="$sortField === 'staff_id' ? $sortDirection : null"
role="button">
Assigned
@include('livewire.includes._sort-icon', ['field' => 'staff_id'])
</div>
</th>
<th class="hidden-sm hidden-xs">
<div sortable wire:click="sortBy('created_at')" :direction="$sortField === 'created_at' ? $sortDirection : null"
role="button">
Created
@include('livewire.includes._sort-icon', ['field' => 'created_at'])
</div>
</th>
<th>@lang('common.action')</th>
</tr>
@foreach ($tickets as $ticket)
<tr>
<td>
<span class="badge-user text-bold">
{{ $ticket->id }}
</span>
</td>
<td>
<span class="badge-user text-bold">
<a href="{{ route('tickets.show', ['id' => $ticket->id]) }}">{{ $ticket->subject }}</a>
</span>
</td>
<td>
<span class="badge-user text-bold">
@if($ticket->priority->name === 'Low')
<i class="fas fa-circle text-yellow"></i>
@elseif ($ticket->priority->name === 'Medium')
<i class="fas fa-circle text-orange"></i>
@elseif ($ticket->priority->name === 'High')
<i class="fas fa-circle text-red"></i>
@endif
{{ $ticket->priority->name }}
</span>
</td>
<td>
<span class="badge-user text-bold">
{{ $ticket->user->username }}
</span>
</td>
<td>
<span class="badge-user text-bold">
@if($ticket->closed_at)
<i class="fas fa-circle text-danger"></i> Closed
@else
<i class="fas fa-circle text-success"></i> Open
@endif
</span>
</td>
<td>
<span class="badge-user text-bold">
{{ $ticket->staff->username ?? 'Unassigned' }}
</span>
</td>
<td>
<span class="badge-user text-bold">
{{ $ticket->created_at->diffForHumans() }}
</span>
</td>
<td>
<div class="dropdown">
<a class="dropdown-toggle btn btn-default btn-xs" data-toggle="dropdown" href="#" aria-expanded="true">
@lang('common.actions')
<i class="fas fa-caret-circle-right"></i>
</a>
<ul class="dropdown-menu">
<li role="presentation">
<a role="menuitem" tabindex="-1" target="_blank"
href="{{ route('tickets.show', ['id' => $ticket->id]) }}">View</a>
</li>
<li role="presentation">
<a role="menuitem" tabindex="-1"
href="{{ route('tickets.close', ['id' => $ticket->id]) }}">Close</a>
</li>
</ul>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
@if (! $tickets->count())
<div class="margin-10">
@lang('common.no-result')
</div>
@endif
<br>
<div class="text-center">
{{ $tickets->links() }}
</div>
</div>
</div>
+23 -8
View File
@@ -11,10 +11,9 @@
<span class="hoe-sidebar-toggle"><a href="#"></a></span>
<ul class="left-navbar">
<li class="dropdown hoe-rheader-submenu message-notification left-min-30">
@php $pm = DB::table('private_messages')->where('receiver_id', '=', auth()->user()->id)->where('read',
'=', '0')->count(); @endphp
@php $pm = DB::table('private_messages')->where('receiver_id', '=', auth()->user()->id)->where('read', '=', '0')->count(); @endphp
<a href="{{ route('inbox') }}" class="dropdown-toggle icon-circle">
<i class="{{ config('other.font-awesome') }} fa-envelope text-blue"></i>
<i class="{{ config('other.font-awesome') }} fa-envelope"></i>
@if ($pm > 0)
<div class="notify"><span class="heartbit"></span><span class="point fa-beat"></span></div>
@endif
@@ -31,15 +30,27 @@
</li>
<li class="dropdown hoe-rheader-submenu message-notification left-min-30">
<a href="{{ route('achievements.index') }}" class="icon-circle">
<i class="{{ config('other.font-awesome') }} fa-trophy text-gold"></i>
<a href="{{ route('articles.index') }}" class="icon-circle">
<i class="{{ config('other.font-awesome') }} fa-newspaper"></i>
</a>
</li>
<li class="dropdown hoe-rheader-submenu message-notification left-min-65">
<a href="{{ route('tickets.index') }}" class="icon-circle">
<i class="{{ config('other.font-awesome') }} fa-life-ring"></i>
@if (auth()->user()->group->is_modo)
@php $tickets = DB::table('tickets')->whereNull('staff_id')->whereNull('closed_at')->count(); @endphp
@if ($tickets > 0)
<div class="notify"><span class="heartbit"></span><span class="point fa-beat"></span></div>
@endif
@endif
</a>
</li>
@if (auth()->user()->group->is_modo)
<li class="dropdown hoe-rheader-submenu message-notification left-min-65">
<a href="{{ route('staff.moderation.index') }}" class="icon-circle">
<i class="{{ config('other.font-awesome') }} fa-tasks text-red"></i>
<i class="{{ config('other.font-awesome') }} fa-tasks"></i>
@php $modder = DB::table('torrents')->where('status', '=', '0')->count(); @endphp
@if ($modder > 0)
<div class="notify"><span class="heartbit"></span><span class="point fa-beat"></span></div>
@@ -83,6 +94,11 @@
<i class="{{ config('other.font-awesome') }} fa-shield-alt"></i> @lang('user.my-security')
</a>
</li>
<li>
<a href="{{ route('achievements.index') }}">
<i class="{{ config('other.font-awesome') }} fa-trophy-alt"></i> My @lang('user.achievements')
</a>
</li>
<li>
<a href="{{ route('user_uploads', ['username' => auth()->user()->username]) }}">
<i class="{{ config('other.font-awesome') }} fa-upload"></i> @lang('user.my-uploads')
@@ -112,6 +128,5 @@
</ul>
</li>
</ul>
</li>
</div>
</header>
</header>
+1 -3
View File
@@ -1,6 +1,4 @@
@php
echo '<?xml version="1.0" encoding="UTF-8" ?>'
@endphp
{!! '<?xml version="1.0" encoding="UTF-8" ?>' !!}
<rss version="2.0"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
+78
View File
@@ -0,0 +1,78 @@
@extends('layout.default')
@section('title')
<title>Helpdesk - {{ config('other.title') }}</title>
@endsection
@section('breadcrumb')
<li>
<a href="{{ route('tickets.index') }}" itemprop="url" class="l-breadcrumb-item-link">
<span itemprop="title" class="l-breadcrumb-item-link-title">Helpdesk</span>
</a>
</li>
<li>
<a href="{{ route('tickets.create') }}" itemprop="url" class="l-breadcrumb-item-link">
<span itemprop="title" class="l-breadcrumb-item-link-title">Create Ticket</span>
</a>
</li>
@endsection
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10 col-sm-10 col-md-offset-1">
<div class="panel panel-chat shoutbox">
<div class="panel-heading"><i class="fas fa-plus"></i> Create Ticket</div>
<div class="panel-body">
@if(session('errors'))
<div class="alert alert-danger">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h6><b>Please fix the following error(s) and try again:</b></h6>
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('tickets.store') }}" method="POST">
@csrf
<div class="form-row">
<div class="form-group col-6">
<label for="category">Category <span class="text-danger small">*</span></label>
<select name="category" id="category" class="form-control">
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
</div>
<div class="form-group col-6">
<label for="priority">Priority <span class="text-danger small">*</span></label>
<select name="priority" id="priority" class="form-control">
@foreach($priorities as $priority)
<option value="{{ $priority->id }}">{{ $priority->name }}</option>
@endforeach
</select>
</div>
<div class="form-group col-12">
<label for="subject">Subject <span class="text-danger small">*</span></label>
<input type="text" class="form-control" name="subject" id="subject" placeholder="Enter subject here..." autocomplete="off">
</div>
<div class="form-group col-12">
<label for="body">Body <span class="text-danger small">*</span></label>
<textarea name="body" id="body" cols="30" rows="3" class="form-control" placeholder="Enter body here..."></textarea>
</div>
<div class="text-center">
<button type="submit" class="btn btn-success btn-md"><i class="fas fa-save"></i> Submit Ticket</button>
<button type="reset" class="btn btn-danger btn-md"><i class="fas fa-eraser"></i> Reset</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
+35
View File
@@ -0,0 +1,35 @@
@extends('layout.default')
@section('title')
<title>Helpdesk - {{ config('other.title') }}</title>
@endsection
@section('breadcrumb')
<li>
<a href="{{ route('tickets.index') }}" itemprop="url" class="l-breadcrumb-item-link">
<span itemprop="title" class="l-breadcrumb-item-link-title">Helpdesk</span>
</a>
</li>
@endsection
@section('content')
<style>
td {
vertical-align: middle !important;
}
</style>
<div class="container">
<div class="block">
<div class="header gradient silver">
<div class="inner_content">
<div class="page-title">
<h1 style="margin: 0;">Helpdesk</h1>
</div>
</div>
</div>
</div>
</div>
<div class="box container">
@livewire('ticket-search')
</div>
@endsection
+218
View File
@@ -0,0 +1,218 @@
@extends('layout.default')
@section('title')
<title>Helpdesk - {{ config('other.title') }}</title>
@endsection
@section('breadcrumb')
<li>
<a href="{{ route('tickets.index') }}" itemprop="url" class="l-breadcrumb-item-link">
<span itemprop="title" class="l-breadcrumb-item-link-title">Helpdesk</span>
</a>
</li>
<li>
<a href="{{ route('tickets.show', ['id' => $ticket->id]) }}" itemprop="url" class="l-breadcrumb-item-link">
<span itemprop="title" class="l-breadcrumb-item-link-title">Ticket #{{ $ticket->id }}</span>
</a>
</li>
@endsection
@section('content')
<div class="container well">
<div class="row justify-content-center">
<div class="col-12">
@if(session('errors'))
<div class="alert alert-danger fade show">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h6><b>Please fix the following error(s) and try again:</b></h6>
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="col-md-8">
<div class="panel panel-chat shoutbox">
<div class="panel-heading">Ticket <i class="fas fa-hashtag"></i> {{ $ticket->id }}</div>
<div class="panel-body">
<span class="float-right small text-right">
<i class="far fa-user"></i> Opened By: {{ $ticket->user->username }}
<i class="far fa-clock"></i> {{ $ticket->created_at->format('m/d/Y') }}
<div class="form-inline">
<div class="form-group">
@if(empty($ticket->closed_at))
<form style="display: inline;" role="form" method="POST" action="{{ route('tickets.close', ['id' => $ticket->id]) }}">
@csrf
<button type="submit" class="btn btn-xs btn-warning"><i class="fas fa-times"></i> Close</button>
</form>
@endif
@if(!empty($ticket->closed_at))
<span style="display: inline;" class="text-danger">Closed {{ $ticket->closed_at->format('m/d/Y') }}</span>
@endif
<form style="display: inline;" role="form" method="POST" action="{{ route('tickets.destroy', ['id' => $ticket->id]) }}">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-xs btn-danger"><i class="fas fa-times"></i> Delete</button>
</form>
</div>
</div>
</span>
@if($user->group->is_modo)
<div class="btn-group" role="group">
<div class="mb-10 form-inline pull-right">
<div class="form-group">
@if(empty($ticket->staff_id))
<form role="form" method="POST" action="{{ route('tickets.assign', ['id' => $ticket->id]) }}">
@csrf
<select name="user_id" class="form-control">
@foreach(App\Models\User::select(['id', 'username'])->whereIn('group_id', [10, 6, 4])->get() as $user)
<option value="{{ $user->id }}">{{ $user->username }}</option>
@endforeach
</select>
<button type="submit" class="btn btn-sm btn-warning">Assign</button>
</form>
@else
<form role="form" method="POST" action="{{ route('tickets.unassign', ['id' => $ticket->id]) }}">
@csrf
<button type="submit" class="btn btn-sm btn-warning">Unassign</button>
</form>
@endif
</div>
</div>
</div>
@endif
<hr style="margin: 8px">
<div class="row">
@if(isset($ticket->staff_id))
<div class="col-md-3">
<label for=""><b>Assigned Staff</b></label><br>
<i class="far fa-user"></i> {{ $ticket->staff->username }}
</div>
@endif
<div class="col-md-4">
<label for=""><b>Category</b></label><br>
{{ $ticket->category->name }}
</div>
<div class="col-md-2">
<label for=""><b>Priority</b></label><br>
@if($ticket->priority->name === 'Low')
<i class="fas fa-circle text-yellow"></i>
@elseif ($ticket->priority->name === 'Medium')
<i class="fas fa-circle text-orange"></i>
@elseif ($ticket->priority->name === 'High')
<i class="fas fa-circle text-red"></i>
@endif
{{ $ticket->priority->name }}
</div>
<div class="col-md-3">
<label for=""><b>Subject</b></label><br>
{{ $ticket->subject }}
</div>
</div>
<hr style="margin: 8px">
<div class="row">
<div class="col-md-12">
<label for=""><b>Description</b></label><br>
{{ $ticket->body }}
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-chat shoutbox">
<div class="panel-heading"><i class="fas fa-file-pdf"></i> Attachments</div>
<div class="panel-body">
@livewire('attachment-upload', ['id' => $ticket->id])
@if(count($ticket->attachments))
<div class="table-responsive">
<table class="table" style="margin-bottom:0">
<tbody>
@foreach($ticket->attachments as $attachment)
<tr>
<td style="width:100px">
<form action="{{ route('tickets.attachment.download', $attachment) }}" method="POST">
@csrf
<button class="btn btn-success btn-sm">Download</button>
</form>
</td>
<td>{{ $attachment->file_name }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
No attachments found
@endif
</div>
</div>
</div>
<div class="col-md-12 col-sm-12">
<div class="panel panel-chat shoutbox">
<div class="panel-heading">
<h4>
<i class="{{ config('other.font-awesome') }} fa-comment"></i> @lang('common.comments')
</h4>
</div>
<div class="panel-body no-padding">
<ul class="media-list comments-list">
@if (count($ticket->comments) == 0)
<div class="text-center">
<h4 class="text-bold text-danger">
<i class="{{ config('other.font-awesome') }} fa-frown"></i> @lang('common.no-comments')!
</h4>
</div>
@else
@foreach ($ticket->comments as $comment)
<li class="media" style="border-left: 5px solid rgb(1,188,140);">
<div class="media-body">
<a href="{{ route('users.show', ['username' => $comment->user->username]) }}"
class="pull-left" style="padding-right: 10px;">
@if ($comment->user->image != null)
<img src="{{ url('files/img/' . $comment->user->image) }}"
alt="{{ $comment->user->username }}" class="img-avatar-48"></a>
@else
<img src="{{ url('img/profile.png') }}" alt="{{ $comment->user->username }}"
class="img-avatar-48"></a>
@endif
<strong>
<a href="{{ route('users.show', ['username' => $comment->user->username]) }}"
style="color:{{ $comment->user->group->color }};">
<span><i class="{{ $comment->user->group->icon }}"></i> {{ $comment->user->username }}</span>
</a>
</strong>
<span class="text-muted"><small><em>{{ $comment->created_at->toDayDateTimeString() }} ({{ $comment->created_at->diffForHumans() }})</em></small></span>
<div class="pt-5">
@joypixels($comment->getContentHtml())
</div>
</div>
</li>
@endforeach
@endif
</ul>
</div>
</div>
</div>
<div class="col-md-12">
<form role="form" method="POST" action="{{ route('comment_ticket', ['id' => $ticket->id]) }}">
@csrf
<div class="form-group">
<label for="content">@lang('common.your-comment'):</label>
<textarea id="content" name="content" cols="30" rows="5" class="form-control"></textarea>
</div>
<button type="submit" class="btn btn-success">@lang('common.submit')</button>
</form>
</div>
</div>
</div>
</div>
@endsection
+18 -3
View File
@@ -178,6 +178,7 @@ Route::group(['middleware' => 'language'], function () {
Route::post('/request/{id}', 'CommentController@request')->name('comment_request');
Route::post('/playlist/{id}', 'CommentController@playlist')->name('comment_playlist');
Route::post('/collection/{id}', 'CommentController@collection')->name('comment_collection');
Route::post('/ticket/{id}', 'CommentController@ticket')->name('comment_ticket');
Route::post('/edit/{comment_id}', 'CommentController@editComment')->name('comment_edit');
Route::get('/delete/{comment_id}', 'CommentController@deleteComment')->name('comment_delete');
});
@@ -245,7 +246,6 @@ Route::group(['middleware' => 'language'], function () {
});
Route::group(['prefix' => 'torrents'], function () {
Route::get('/feedizeTorrents/{type}', 'TorrentController@feedize')->name('feedizeTorrents')->middleware('modo');
Route::get('/filter', 'TorrentController@faceted');
Route::get('/filterSettings', 'TorrentController@filtered');
Route::get('/', 'TorrentController@torrents')->name('torrents');
@@ -437,7 +437,23 @@ Route::group(['middleware' => 'language'], function () {
Route::post('/{id}/update', 'SubtitleController@update')->name('update');
Route::delete('/{id}/delete', 'SubtitleController@destroy')->name('destroy');
Route::get('/{id}/download', 'SubtitleController@download')->name('download');
Route::get('/filter', 'SubtitleController@faceted');
});
});
// Tickets System
Route::group(['prefix' => 'tickets'], function () {
Route::name('tickets.')->group(function () {
Route::get('/', 'TicketController@index')->name('index');
Route::get('/create', 'TicketController@create')->name('create');
Route::post('/store', 'TicketController@store')->name('store');
Route::get('/{id}', 'TicketController@show')->where('id', '[0-9]+')->name('show');
Route::get('/{id}/edit', 'TicketController@edit')->name('edit');
Route::patch('/{id}/update', 'TicketController@update')->name('update');
Route::delete('/{id}/destroy', 'TicketController@destroy')->name('destroy');
Route::post('/{id}/assign', 'TicketController@assign')->name('assign');
Route::post('/{id}/unassign', 'TicketController@unassign')->name('unassign');
Route::post('/{id}/close', 'TicketController@close')->name('close');
Route::post('/attachments/{attachment}/download', 'TicketAttachmentController@download')->name('attachment.download');
});
});
});
@@ -897,7 +913,6 @@ Route::group(['middleware' => 'language'], function () {
// User Tools TODO: Leaving since we will be refactoring users and roles
Route::group(['prefix' => 'users'], function () {
Route::get('/', 'UserController@index')->name('user_search');
Route::get('/search', 'UserController@search')->name('user_results');
Route::post('/{username}/edit', 'UserController@edit')->name('user_edit');
Route::get('/{username}/settings', 'UserController@settings')->name('user_setting');
Route::post('/{username}/permissions', 'UserController@permissions')->name('user_permissions');