Ticketing systems needed by every corporation these days so in this tutorial we will implement a simple ticketing system using the laravel framework.
Requirements:
- Laravel 5 preferable version 5.5
- Mysql
First of all create a new laravel project using composer as follows:
$ composer create-project laravel/laravel support-ticket "5.5.*" --prefer-dist
After installation complete make sure you have .env file otherwise copy file .env.example and rename it to .env
set your application key:
$ php artisan key:generate
Navigate to http://localhost/support-ticket/public/. Well the application is running with the default landing page.
Sometimes when run the website for the first time you might see a blank page. To solve such problem give writable permissions to directories such as storage/ and bootstrap/.
$ sudo chmod -R 777 storage/ bootstrap/
Now let’s go to phpmyadmin and create a new database called “ticketing”.
Then open your .env file and set your database credentials as shown:
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=ticketing DB_USERNAME=root DB_PASSWORD=
Now let’s list the database tables that we will need in our application.
- Tickets: This is the main table for storing tickets.
- Comments: This table responsible for storing user comments and replies.
- Categories: This table responsible for storing ticket categories so user will select category when creating ticket.
We will use laravel migrations to create our tables so right in the terminal type the following:
$ php artisan make:model Ticket -m
$ php artisan make:model Comment -m
$ php artisan make:model Category -m
The above commands will create three models along with their migrations for tables tickets, comments, and categories.
Now let’s populate the migration files.
Go to the ticket migration file located at database/migrations/ and modify as follows:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateTicketsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('tickets', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned(); $table->integer('category_id')->unsigned(); $table->string('ticket_id')->unique(); $table->string('title'); $table->string('priority'); $table->text('message'); $table->string('status'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('tickets'); } }
In the same way open the comment migration file and modify:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateCommentsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('comments', function (Blueprint $table) { $table->increments('id'); $table->integer('ticket_id')->unsigned(); $table->integer('user_id')->unsigned(); $table->text('comment'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('comments'); } }
Next open the category migration file and modify as follows:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateCategoriesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('categories', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('categories'); } }
Also we need to modify the create_users migration as follows:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->integer('is_admin')->unsigned()->default(0); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } }
Now run the migration to create the tables.
$ php artisan migrate
Setting the relationships:
Now we need to setup the relationships between our models. So if we imagine the scenario we we end up with this structure:
- Relationship between tickets and comments: Ticket has many comments and comment belong to specific ticket.
- Relationship between tickets and user: Ticket belong to one user and user can create many tickets.
- Relationship between tickets and categories: Ticket belong to one category and category has many tickets.
- Relationship between comments and users: Comment belong to one user and user has many comments.
app/Ticket.php
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Ticket extends Model { protected $fillable = [ 'user_id', 'category_id', 'ticket_id', 'title', 'priority', 'message', 'status' ]; public function category() { return $this->belongsTo(Category::class); } public function comments() { return $this->hasMany(Comment::class); } public function user() { return $this->belongsTo(User::class); } }
app/Comment.php
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Comment extends Model { protected $fillable = [ 'ticket_id', 'user_id', 'comment' ]; public function ticket() { return $this->belongsTo(Ticket::class); } public function user() { return $this->belongsTo(User::class); } }
app/Category.php
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Category extends Model { protected $fillable = ['name']; public function tickets() { return $this->hasMany(Ticket::class); } }
app/User.php
<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; public function comments() { return $this->hasMany(Comment::class); } public function tickets() { return $this->hasMany(Ticket::class); } }
As shown in the code above we established the relationships using laravel’s helper functions for relations such as hasMany, belongsTo.
Now its time to setup authentication.
Setting Up Authentication
Let’s generate authentication routes as laravel helps us with this with single command and you have ready to use authentication system:
$ php artisan make:auth
Now if you visit your application there are two links appearing at the top for login form and register form like this:
Now we have to implement the ticket controller
Preparing Application Controllers:
Let’s create a new controller that will manipulate tickets display and creation.
$ php artisan make:controller TicketsController --resource
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class TicketsController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { // } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit($id) { // } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { // } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { // } }
In the above command we created a resource controller with the basic functions. Now remove actions edit, update, and destroy as it will not be needed.
Let’s add this line in the constructor:
$this->middleware('auth');
This line tells laravel to allow only authenticated users to view and create tickets.
In the way create the comments controller:
php artisan make:controller CommentsController
app/Http/Controllers/CommentsController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class CommentsController extends Controller { public function postComment(Request $request) { } }
Admin Middleware
At this point we need a way for each user to manage his tickets so we can add admin page for this but with laravel’s middleware this is pretty easy. We can do this by adding a middleware so for example our tickets page can be accessed by http://localhost/projectname/admin/tickets.
Create a new class in this path app/Http/Middleware called AdminMiddleware.php with this contents:
app/Http/Middleware/AdminMiddleware.php
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Auth; class AdminMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if(!Auth::check() || (Auth::check() && Auth::user()->is_admin !== 1)) { return redirect('home'); } return $next($request); } }
In the above code i checked if the user not login or he is login but not is_admin then we redirect him to the landing page else we proceed.
app/Http/Kernel.php
protected $routeMiddleware = [ 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'admin' => \App\Http\Middleware\AdminMiddleware::class ];
To apply this middleware we will add it to our routes as shown below:
Preparing Application Routes:
Now we will add our required routes so open routes/web.php and modify it like this:
routes/web.php
<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', 'HomeController@index'); Auth::routes(); Route::get('/home', 'HomeController@index')->name('home'); Route::get('new-ticket', 'TicketsController@create'); Route::post('new-ticket', 'TicketsController@store'); Route::get('my_tickets', 'TicketsController@userTickets'); Route::get('tickets/{ticket_id}', 'TicketsController@show'); Route::post('comment', 'CommentsController@postComment'); Route::group(['prefix' => 'admin', 'middleware' => 'admin'], function (){ Route::get('tickets', 'TicketsController@index'); Route::post('close_ticket/{ticket_id}', 'TicketsController@close'); });
Tickets Controller:
Now the first thing we need to do is to manage and create user tickets. This will be done in the index and user_tickets function so let’s modify our ticket controller like this:
app/Http/Controllers/TicketsController.php
<?php namespace App\Http\Controllers; use App\Category; use App\Ticket; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class TicketsController extends Controller { public function __construct() { $this->middleware('auth'); } /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $tickets = Ticket::paginate(10); return view('tickets.index', compact('tickets')); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } public function userTickets() { $tickets = Ticket::where('user_id', Auth::user()->id)->paginate(10); return view('tickets.user_tickets', compact('tickets')); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } }
For the view create a new folder in the views directory called tickets and inside it create file index.blade.php with the following contents:
resources/views/tickets/index.blade.php (Admin tickets)
@extends('layouts.app') @section('title', 'All Tickets') @section('content') <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading"> <i class="fa fa-ticket"> Tickets</i> </div> <div class="panel-body"> @if ($tickets->isEmpty()) <p>There are currently no tickets.</p> @else <table class="table"> <thead> <tr> <th>Category</th> <th>Title</th> <th>Status</th> <th>Last Updated</th> <th style="text-align:center" colspan="2">Actions</th> </tr> </thead> <tbody> @foreach ($tickets as $ticket) <tr> <td> {{ $ticket->category->name }} </td> <td> <a href="{{ url('tickets/'. $ticket->ticket_id) }}"> #{{ $ticket->ticket_id }} - {{ $ticket->title }} </a> </td> <td> @if ($ticket->status === 'Open') <span class="label label-success">{{ $ticket->status }}</span> @else <span class="label label-danger">{{ $ticket->status }}</span> @endif </td> <td>{{ $ticket->updated_at }}</td> <td> @if($ticket->status === 'Open') <a href="{{ url('tickets/' . $ticket->ticket_id) }}" class="btn btn-primary">Comment</a> <form action="{{ url('admin/close_ticket/' . $ticket->ticket_id) }}" method="POST"> {!! csrf_field() !!} <button type="submit" class="btn btn-danger">Close</button> </form> @endif </td> </tr> @endforeach </tbody> </table> {{ $tickets->render() }} @endif </div> </div> </div> </div> @endsection
resources/views/tickets/user_tickets.blade.php (User tickets)
@extends('layouts.app') @section('title', 'My Tickets') @section('content') <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading"> <i class="fa fa-ticket"> My Tickets</i> </div> <div class="panel-body"> @if($tickets->isEmpty()) <p>You have not created any tickets.</p> @else <table class="table"> <thead> <tr> <th>Category</th> <th>Title</th> <th>Status</th> <th>Last Updated</th> </tr> </thead> <tbody> @foreach($tickets as $ticket) <tr> <td> {{ $ticket->category->name }} </td> <td> <a href="{{ url('tickets/' . $ticket->ticket_id) }}"> #{{ $ticket->ticket_id }} - {{ $ticket->title }} </a> </td> <td> @if($ticket->status == "Open") <span class="label label-success">{{ $ticket->status }}</span> @else <span class="label label-danger">{{ $ticket->status }}</span> @endif </td> <td> {{ $ticket->updated_at }} </td> </tr> @endforeach </tbody> </table> {{ $tickets->render() }} @endif </div> </div> </div> </div> @endsection
In the above code we created two views the first view will display all tickets so that the system admin can view them and reply or close for each one. The other view will display user related tickets.
First we check if the tickets not empty else we displayed a message to the user that no tickets exist. If there are tickets then we display tickets by looping over them and display them with ticket title, category and status. We also add buttons for commenting and closing the ticket.
To test this scenario we will need two accounts. One admin account which has is_admin=1 and the other is user account which has is_admin=0 as shown:
Now login with admin account and navigate to http://localhost/support-ticket/public/admin/tickets and you will see “There are currently no tickets.“.
Logout and login with user account and navigate to http://localhost/support-ticket/public/my_tickets and you will see “You have not created any tickets yet.“.
Creating Tickets Form
Let’s create our tickets form to enable users to create new ticket. Go to app/Http/Controllers/TicketsController.php and modify it like this:
app/Http/Controllers/TicketsController.php
<?php namespace App\Http\Controllers; use App\Category; use App\Ticket; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use App\Mailers\AppMailer; class TicketsController extends Controller { public function __construct() { $this->middleware('auth'); } /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $tickets = Ticket::paginate(10); return view('tickets.index', compact('tickets')); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { $categories = Category::all(); return view('tickets.create', compact('categories')); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request, AppMailer $mailer) { $this->validate($request, [ 'title' => 'required', 'category' => 'required', 'priority' => 'required', 'message' => 'required' ]); $ticket = new Ticket([ 'title' => $request->input('title'), 'user_id' => Auth::user()->id, 'ticket_id' => strtoupper(str_random(10)), 'category_id' => $request->input('category'), 'priority' => $request->input('priority'), 'message' => $request->input('message'), 'status' => "Open" ]); $ticket->save(); $mailer->sendTicketInformation(Auth::user(), $ticket); return redirect()->back()->with("status", "A ticket with ID: #$ticket->ticket_id has been opened."); } public function userTickets() { $tickets = Ticket::where('user_id', Auth::user()->id)->paginate(10); return view('tickets.user_tickets', compact('tickets')); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } }
app/Mailers/AppMailer.php
<?php namespace App\Mailers; use App\Ticket; use Illuminate\Contracts\Mail\Mailer; class AppMailer { protected $mailer; protected $fromAddress = 'support@supportticket.dev'; protected $fromName = 'Support Ticket'; protected $to; protected $subject; protected $view; protected $data = []; /** * AppMailer constructor. * @param $mailer */ public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function sendTicketInformation($user, Ticket $ticket) { $this->to = $user->email; $this->subject = "[Ticket ID: $ticket->ticket_id] $ticket->title"; $this->view = 'emails.ticket_info'; $this->data = compact('user', 'ticket'); return $this->deliver(); } public function sendTicketComments($ticketOwner, $user, Ticket $ticket, $comment) { } public function sendTicketStatusNotification($ticketOwner, Ticket $ticket) { } public function deliver() { $this->mailer->send($this->view, $this->data, function($message){ $message->from($this->fromAddress, $this->fromName) ->to($this->to)->subject($this->subject); }); } }
resources/views/tickets/create.blade.php
@extends('layouts.app') @section('title', 'Open Ticket') @section('content') <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading">Open New Ticket</div> <div class="panel-body"> @if (session('status')) <div class="alert alert-success"> {{ session('status') }} </div> @endif <form class="form-horizontal" role="form" method="POST"> {!! csrf_field() !!} <div class="form-group{{ $errors->has('title') ? ' has-error' : '' }}"> <label for="title" class="col-md-4 control-label">Title</label> <div class="col-md-6"> <input id="title" type="text" class="form-control" name="title" value="{{ old('title') }}"> @if ($errors->has('title')) <span class="help-block"> <strong>{{ $errors->first('title') }}</strong> </span> @endif </div> </div> <div class="form-group{{ $errors->has('category') ? ' has-error' : '' }}"> <label for="category" class="col-md-4 control-label">Category</label> <div class="col-md-6"> <select id="category" type="category" class="form-control" name="category"> <option value="">Select Category</option> @foreach ($categories as $category) <option value="{{ $category->id }}">{{ $category->name }}</option> @endforeach </select> @if ($errors->has('category')) <span class="help-block"> <strong>{{ $errors->first('category') }}</strong> </span> @endif </div> </div> <div class="form-group{{ $errors->has('priority') ? ' has-error' : '' }}"> <label for="priority" class="col-md-4 control-label">Priority</label> <div class="col-md-6"> <select id="priority" type="" class="form-control" name="priority"> <option value="">Select Priority</option> <option value="low">Low</option> <option value="medium">Medium</option> <option value="high">High</option> </select> @if ($errors->has('priority')) <span class="help-block"> <strong>{{ $errors->first('priority') }}</strong> </span> @endif </div> </div> <div class="form-group{{ $errors->has('message') ? ' has-error' : '' }}"> <label for="message" class="col-md-4 control-label">Message</label> <div class="col-md-6"> <textarea rows="10" id="message" class="form-control" name="message"></textarea> @if ($errors->has('message')) <span class="help-block"> <strong>{{ $errors->first('message') }}</strong> </span> @endif </div> </div> <div class="form-group"> <div class="col-md-6 col-md-offset-4"> <button type="submit" class="btn btn-primary"> <i class="fa fa-btn fa-ticket"></i> Open Ticket </button> </div> </div> </form> </div> </div> </div> </div> @endsection
resources/views/emails/ticket_info.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Suppor Ticket Information</title> </head> <body> <p> Thank you {{ ucfirst($user->name) }} for contacting our support team. A support ticket has been opened for you. You will be notified when a response is made by email. The details of your ticket are shown below: </p> <p>Title: {{ $ticket->title }}</p> <p>Priority: {{ $ticket->priority }}</p> <p>Status: {{ $ticket->status }}</p> <p> You can view the ticket at any time at {{ url('tickets/'. $ticket->ticket_id) }} </p> </body> </html>
In the above code first in the TicketsController we added the code that displays the create form and in the store method we validate and submit the form.
$mailer->sendTicketInformation(Auth::user(), $ticket);
This line send mail to the user with ticket information to notify him that a new ticket created. For this we added simple class app/Mailers/AppMailer.php to send enable users to send mails.
Note that to test this scenario you can use dummy mail service like Mailtrap. Go to https://mailtrap.io and create a new account it will give you some credentials take them and update your .env file like this:
MAIL_DRIVER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=<add your username> MAIL_PASSWORD=<add your password> MAIL_ENCRYPTION=null
After that now you ready to test creating tickets just login with the user account and navigate to http://localhost/support-ticket/public/new-ticket and fill the form and click submit as shown:
Now go to http://localhost/support-ticket/public/my_tickets.
Displaying A Single Ticket
To display single ticket and enable user to reply and comment in the ticket we will update the show method in the TicketsController as shown below:
<?php namespace App\Http\Controllers; use App\Category; use App\Ticket; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use App\Mailers\AppMailer; class TicketsController extends Controller { public function __construct() { $this->middleware('auth'); } /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $tickets = Ticket::paginate(10); return view('tickets.index', compact('tickets')); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { $categories = Category::all(); return view('tickets.create', compact('categories')); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request, AppMailer $mailer) { $this->validate($request, [ 'title' => 'required', 'category' => 'required', 'priority' => 'required', 'message' => 'required' ]); $ticket = new Ticket([ 'title' => $request->input('title'), 'user_id' => Auth::user()->id, 'ticket_id' => strtoupper(str_random(10)), 'category_id' => $request->input('category'), 'priority' => $request->input('priority'), 'message' => $request->input('message'), 'status' => "Open" ]); $ticket->save(); $mailer->sendTicketInformation(Auth::user(), $ticket); return redirect()->back()->with("status", "A ticket with ID: #$ticket->ticket_id has been opened."); } public function userTickets() { $tickets = Ticket::where('user_id', Auth::user()->id)->paginate(10); return view('tickets.user_tickets', compact('tickets')); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($ticket_id) { $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail(); return view('tickets.show', compact('ticket')); } }
resources/views/tickets/show.blade.php
@extends('layouts.app') @section('title', $ticket->title) @section('content') <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading"> #{{ $ticket->ticket_id }} - {{ $ticket->title }} </div> <div class="panel-body"> @if (session('status')) <div class="alert alert-success"> {{ session('status') }} </div> @endif <div class="ticket-info"> <p>{{ $ticket->message }}</p> <p>Category: {{ $ticket->category->name }}</p> <p> @if ($ticket->status === 'Open') Status: <span class="label label-success">{{ $ticket->status }}</span> @else Status: <span class="label label-danger">{{ $ticket->status }}</span> @endif </p> <p>Created on: {{ $ticket->created_at->diffForHumans() }}</p> </div> </div> </div> <hr> @include('tickets.comments') <hr> @include('tickets.reply') </div> </div> @endsection
resources/views/tickets/comments.blade.php
<div class="comments"> @foreach($ticket->comments as $comment) <div class="panel panel-@if($ticket->user->id === $comment->user_id){{"default"}}@else{{"success"}}@endif"> <div class="panel panel-heading"> {{ $comment->user->name }} <span class="pull-right">{{ $comment->created_at->format('Y-m-d') }}</span> </div> <div class="panel panel-body"> {{ $comment->comment }} </div> </div> @endforeach </div>
resources/views/tickets/reply.blade.php
<div class="panel panel-default"> <div class="panel-heading">Add reply</div> <div class="panel-body"> <div class="comment-form"> <form action="{{ url('comment') }}" method="POST" class="form"> {!! csrf_field() !!} <input type="hidden" name="ticket_id" value="{{ $ticket->id }}"> <div class="form-group{{ $errors->has('comment') ? ' has-error' : '' }}"> <textarea rows="10" id="comment" class="form-control" name="comment"></textarea> @if ($errors->has('comment')) <span class="help-block"> <strong>{{ $errors->first('comment') }}</strong> </span> @endif </div> <div class="form-group"> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> </div> </div> </div>
In the above code First we display the ticket info like category, status then we display the comments and replies located in comments.blade.php finally the reply form.
Replying The Tickets
To add reply on a ticket this will be done in the CommentsController so modify it as shown:
app/Http/Controllers/CommentsController.php
<?php namespace App\Http\Controllers; use App\Comment; use App\Mailers\AppMailer; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class CommentsController extends Controller { public function postComment(Request $request, AppMailer $mailer) { $this->validate($request, [ 'comment' => 'required' ]); $comment = Comment::create([ 'ticket_id' => $request->input('ticket_id'), 'user_id' => Auth::user()->id, 'comment' => $request->input('comment') ]); // send mail if the user commenting is not the ticket owner if($comment->ticket->user->id !== Auth::user()->id) { $mailer->sendTicketComments($comment->ticket->user, Auth::user(), $comment->ticket, $comment); } return redirect()->back()->with("status", "Your comment has be submitted."); } }
app/Mailers/AppMailer.php
<?php namespace App\Mailers; use App\Ticket; use Illuminate\Contracts\Mail\Mailer; class AppMailer { protected $mailer; protected $fromAddress = 'support@supportticket.dev'; protected $fromName = 'Support Ticket'; protected $to; protected $subject; protected $view; protected $data = []; /** * AppMailer constructor. * @param $mailer */ public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function sendTicketInformation($user, Ticket $ticket) { $this->to = $user->email; $this->subject = "[Ticket ID: $ticket->ticket_id] $ticket->title"; $this->view = 'emails.ticket_info'; $this->data = compact('user', 'ticket'); return $this->deliver(); } public function sendTicketComments($ticketOwner, $user, Ticket $ticket, $comment) { $this->to = $ticketOwner->email; $this->subject = "RE: $ticket->title (Ticket ID: $ticket->ticket_id)"; $this->view = 'emails.ticket_comments'; $this->data = compact('ticketOwner', 'user', 'ticket', 'comment'); return $this->deliver(); } public function sendTicketStatusNotification($ticketOwner, Ticket $ticket) { } public function deliver() { $this->mailer->send($this->view, $this->data, function($message){ $message->from($this->fromAddress, $this->fromName) ->to($this->to)->subject($this->subject); }); } }
resources/views/emails/ticket_comments.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Support Ticket</title> </head> <body> <p> {{ $comment->comment }} </p> --- <p>Replied by: {{ $user->name }}</p> <p>Title: {{ $ticket->title }}</p> <p>Ticket ID: {{ $ticket->ticket_id }}</p> <p>Status: {{ $ticket->status }}</p> <p> You can view the ticket at any time at {{ url('tickets/'. $ticket->ticket_id) }} </p> </body> </html>
Now in the ticket page try to add a reply and click submit you will see that the reply appear in the comments list with user and time like this:
Closing The Ticket
Its important for the system admin to close the ticket once resolved. To do this we will update the TicketsController and add the close method like this:
app/Http/Controllers/TicketsController.php
<?php namespace App\Http\Controllers; use App\Category; use App\Ticket; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use App\Mailers\AppMailer; class TicketsController extends Controller { public function __construct() { $this->middleware('auth'); } /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $tickets = Ticket::paginate(10); return view('tickets.index', compact('tickets')); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { $categories = Category::all(); return view('tickets.create', compact('categories')); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request, AppMailer $mailer) { $this->validate($request, [ 'title' => 'required', 'category' => 'required', 'priority' => 'required', 'message' => 'required' ]); $ticket = new Ticket([ 'title' => $request->input('title'), 'user_id' => Auth::user()->id, 'ticket_id' => strtoupper(str_random(10)), 'category_id' => $request->input('category'), 'priority' => $request->input('priority'), 'message' => $request->input('message'), 'status' => "Open" ]); $ticket->save(); $mailer->sendTicketInformation(Auth::user(), $ticket); return redirect()->back()->with("status", "A ticket with ID: #$ticket->ticket_id has been opened."); } public function userTickets() { $tickets = Ticket::where('user_id', Auth::user()->id)->paginate(10); return view('tickets.user_tickets', compact('tickets')); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($ticket_id) { $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail(); return view('tickets.show', compact('ticket')); } public function close($ticket_id, AppMailer $mailer) { $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail(); $ticket->status = "Closed"; $ticket->save(); $ticketOwner = $ticket->user; $mailer->sendTicketStatusNotification($ticketOwner, $ticket); return redirect()->back()->with("status", "The ticket has been closed."); } }
app/Mailers/AppMailer.php
<?php namespace App\Mailers; use App\Ticket; use Illuminate\Contracts\Mail\Mailer; class AppMailer { protected $mailer; protected $fromAddress = 'support@supportticket.dev'; protected $fromName = 'Support Ticket'; protected $to; protected $subject; protected $view; protected $data = []; /** * AppMailer constructor. * @param $mailer */ public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function sendTicketInformation($user, Ticket $ticket) { $this->to = $user->email; $this->subject = "[Ticket ID: $ticket->ticket_id] $ticket->title"; $this->view = 'emails.ticket_info'; $this->data = compact('user', 'ticket'); return $this->deliver(); } public function sendTicketComments($ticketOwner, $user, Ticket $ticket, $comment) { $this->to = $ticketOwner->email; $this->subject = "RE: $ticket->title (Ticket ID: $ticket->ticket_id)"; $this->view = 'emails.ticket_comments'; $this->data = compact('ticketOwner', 'user', 'ticket', 'comment'); return $this->deliver(); } public function sendTicketStatusNotification($ticketOwner, Ticket $ticket) { $this->to = $ticketOwner->email; $this->subject = "RE: $ticket->title (Ticket ID: $ticket->ticket_id)"; $this->view = 'emails.ticket_status'; $this->data = compact('ticketOwner', 'ticket'); return $this->deliver(); } public function deliver() { $this->mailer->send($this->view, $this->data, function($message){ $message->from($this->fromAddress, $this->fromName) ->to($this->to)->subject($this->subject); }); } }
resources/views/emails/ticket_status.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Suppor Ticket Status</title> </head> <body> <p> Hello {{ ucfirst($ticketOwner->name) }}, </p> <p> Your support ticket with ID #{{ $ticket->ticket_id }} has been marked has resolved and closed. </p> </body> </html>
In the above code first we update the status to closed then we sent the user a notification email that his ticket closed then redirect to the tickets page.
Updating Navigation
To enable users and admins for easy navigation to the tickets page we will update the HomeController and home view as follows:
app/Http/Controllers/HomeController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class HomeController extends Controller { return view('home'); }
resources/views/home.blade.php
@extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="panel panel-default"> <div class="panel-heading">Dashboard</div> <div class="panel-body"> <p>You are logged in!</p> @if(Auth::user()->is_admin) <p> See all <a href="{{ url('admin/tickets') }}">tickets</a> </p> @else <p> See all your <a href="{{ url('my_tickets') }}">tickets</a> or <a href="{{ url('new-ticket') }}">open new ticket</a> </p> @endif </div> </div> </div> </div> </div> @endsection
you lost me at “Preparing Controllers” 🙁
Good job!
error in home.blade.php
before
See all your tickets or open new ticket
after
See all your tickets or open new ticket
I will check that
yes you are right it should be
See all your tickets or open new ticket
There seems to be a problem with displaying the ticket. I get an error saying that there is an unknown column ‘comments.tickets_id’ in ‘where clause’.
It’s ticket_id not tickets_id
How would one go about deleting a ticket?
You can add a method to delete a ticket but the admin only who can delete a ticket
hello, i would like to implement this, and i need some help. do you think you can email me?
of course
Please email me at dynarome@protonmail.com
Hi I don’t know if you guys still here, but I’ve got a question. I want to add markdown to my mailing, but I don’t get it working. How would you implement that? The markdown function.
What you mean with markdown?
Do you have a repository?
No bro i didn’t add this tut to a repository
You have to follow the tutorial step by step in order to get the idea
Great tutorial, I learned a lot today!
Thank you!
Laravel is now at 8.x. Since v6 the
is no more there.
So instead of it you should run:
and then:
Also the helpers for models are now in app/Models (so app/Models/Ticket.php instead of app/Ticket.php and so on)
Hope it helps
Yes you should make the necessary updates when working with laravel 8
When you say
Where is the constructor located as it is not very clear.
public function __construct()
{
$this->middleware(‘auth’);
}
You have to add this in app/HTTP/Controllers/TIcketsController.php file like this:
class TicketsController extends Controller
{
…
public function __construct()
{
$this->middleware(‘auth’);
}
…
}
how to upload a picture to a ticket
It’s simple, you can add another field to the tickets table migration say “picture” and update the controller and view codes to allow uploading
can you explain step by step? please
when the admin leaves comments on the application, an error appears. (View [emails.ticket_comments] not found)
But when the user leaves comments, there is no error. Can you tell me where to fix what?
Yes be sure you have this view file exist
views/emails/ticket_comments.blade.php
look at the tutorial and search for ticket_comments.blade.php
Please tell me if updating laravel to version 8 of the application will work? or something needs to be changed?
I think so, to make the project in laravel 8:
– Create a new fresh laravel 8 project
– Then copy all the code from the old project to the new project
– Remember that the models now located in the Models/ directory in laravel 8
– Test it after updating.
How to make it so that when a user creates a ticket, a letter is sent to the administrator’s mail. That such and such a user created a ticket.
Someone asks that question before, so what is the problem with that?
I didn’t find. Please explain me
Great Job. Thank you
How to make it so that when a user creates a ticket, a letter is sent to the administrator’s mail.
Create a new function in the AppMailer class and call it in the store() method
great. but you did not say about categories controller and how to create a category , so i write it and then i can create and edit a category.
however thank you so much.
A detailed video tutorial on this would be helpful. =)
Sorry dear, just this post
Thank you so much. It still work with laravel 9.
where is this route ‘Route::get(‘/’, ‘HomeController@index’);‘
how its working i don’t know because HomeController page is missing
The HomeController is already come with laravel. I have already added it
With some minor changes, it works on Laravel 10.
How did you solve the problem with the HomeController not existing error whenever I try to open the website for checking?
Read the full article it’s at the bottom of the article
Thanks for replying. How did you solve the error on the HomeController.php line 6 that says “syntax error, unexpected token “return”, expecting “function” or “const””?
Also, there is another error saying “Attempt to read property “is_admin” on null”, sorry for the multiple questions btw, I am completely new to Laravel.
Just install a fresh version of laravel Choose laravel 5.5 because this tutorial written using that version and then follow the tutorial again! it will work.
Sorry, but you can ignore all my other comments, how do I create categories instead? I’m really sorry to bother you this much.
This is a very informative article. Thanks for sharing these helpful articles.
How do you modify the Navbar?
Thank you for the G8 tutorial bro