Backend Developmentcrm

Implementing CRM System With Laravel Part 10: Mailbox Module

implementing crm with laravel mailbox module

In this part of this series i will go on by implementing an important module in CRM systems which is a Mailbox.

 

 

Series Topics:

 

Some CRM systems contain dedicated modules for emails and messaging to enable users to send messages to each other, In this part i will implement a simple module for sending emails. At first we need to create some migrations and models.

 

Let’s install special package used to migrate specific migration file:

composer require sayeed/custom-migrate

 

Creating Migrations & Models

Run those commands in terminal

php artisan make:model Models/MailboxFolder -m
php artisan make:model Models/Mailbox -m
php artisan make:model Models/MailboxUserFolder -m
php artisan make:model Models/MailboxReceiver -m
php artisan make:model Models/MailboxAttachment -m
php artisan make:model Models/MailboxFlags -m
php artisan make:model Models/MailboxTmpReceiver -m

Those command will create migrations and models in one step. Here we have multiple tables that represent the mailbox:

  • MailboxFolder: Represent the mailbox folder like in Gmail Inbox, Send, Trash, etc.
  • Mailbox: The main table for storing message subject and body.
  • MailboxUserFolder: This table Links the mailbox message with the user_id and folder_id.
  • MailboxReceiver: Stores the message receiver ids.
  • MailboxAttachment: If there are an attachment for the message, this table stores them.
  • MailboxFlags: This table stores specific flags according to the current user for example is_important marks the message as important and shown with a star beside it and is_unread to mark the message with bold.
  • MailboxTmpReceiver: This table like the receiver table but used only in case we save the message as a “Draft”.

 

Modifying Migrations

Open XXXX_XX_XX_XXXXX_create_mailbox_folder_table.php and modify it as shown:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMailboxFolderTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mailbox_folder', function (Blueprint $table) {
            $table->increments('id');
            $table->string("title");
            $table->string("icon");
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mailbox_folder');
    }
}

XXXX_XX_XX_XXXXX_create_mailbox_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMailboxTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mailbox', function (Blueprint $table) {
            $table->increments('id');
            $table->string("subject");
            $table->longText("body")->nullable();
            $table->integer("sender_id")->unsigned();
            $table->string("time_sent");
            $table->integer("parent_id")->default(0);
            $table->timestamps();

            $table->foreign('sender_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mailbox');
    }
}

XXXX_XX_XX_XXXXX_create_mailbox_user_folder_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMailboxUserFolderTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mailbox_user_folder', function (Blueprint $table) {
            $table->increments('id');
            $table->integer("user_id")->unsigned();
            $table->integer("mailbox_id")->unsigned();
            $table->integer("folder_id")->unsigned();
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users');
            $table->foreign('mailbox_id')->references('id')->on('mailbox')->onDelete('cascade');
            $table->foreign('folder_id')->references('id')->on('mailbox_folder');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mailbox_user_folder');
    }
}

XXXX_XX_XX_XXXXX_create_mailbox_receiver_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMailboxReceiverTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mailbox_receiver', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('mailbox_id')->unsigned();
            $table->integer('receiver_id')->unsigned();
            $table->timestamps();

            $table->foreign('mailbox_id')->references('id')->on('mailbox')->onDelete('cascade');
            $table->foreign('receiver_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mailbox_receiver');
    }
}

XXXX_XX_XX_XXXXX_create_mailbox_attachment_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMailboxAttachmentTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mailbox_attachment', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('mailbox_id')->unsigned();
            $table->string('attachment');
            $table->timestamps();

            $table->foreign('mailbox_id')->references('id')->on('mailbox')->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mailbox_attachment');
    }
}

XXXX_XX_XX_XXXXX_create_mailbox_flags_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMailboxFlagsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mailbox_flags', function (Blueprint $table) {
            $table->increments('id');
            $table->tinyInteger("is_unread")->default(0);
            $table->tinyInteger("is_important")->default(0);
            $table->integer('mailbox_id')->unsigned();
            $table->integer('user_id')->unsigned();
            $table->timestamps();

            $table->foreign('mailbox_id')->references('id')->on('mailbox')->onDelete('cascade');
            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mailbox_flags');
    }
}

XXXX_XX_XX_XXXXX_create_mailbox_tmp_receiver_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMailboxTmpReceiverTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mailbox_tmp_receiver', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('mailbox_id')->unsigned();
            $table->integer('receiver_id')->unsigned();
            $table->timestamps();

            $table->foreign('mailbox_id')->references('id')->on('mailbox')->onDelete('cascade');
            $table->foreign('receiver_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mailbox_tmp_receiver');
    }
}

 

Now using the custom migration package to migrate:

php artisan migrate:custom -f XXXX_XX_XX_XXXXX_create_mailbox_folder_table
php artisan migrate:custom -f XXXX_XX_XX_XXXXX_create_mailbox_table
php artisan migrate:custom -f XXXX_XX_XX_XXXXX_create_mailbox_user_folder_table
php artisan migrate:custom -f XXXX_XX_XX_XXXXX_create_mailbox_receiver_table
php artisan migrate:custom -f XXXX_XX_XX_XXXXX_create_mailbox_attachment_table
php artisan migrate:custom -f XXXX_XX_XX_XXXXX_create_mailbox_flags_table
php artisan migrate:custom -f XXXX_XX_XX_XXXXX_create_mailbox_tmp_receiver_table

As shown in the above code in the migration files every email message will have subject, body, sender, time sent. The parent id marks the message as if this is a reply or not. The mailbox user folder table holds the user_id, mailbox id or message id and the folder id. The other tables is self explanatory.

Default Mailbox Folders

Let’s insert the default mailbox folders, create a new seeder file using this command:

php artisan make:seed MailboxFolderSeeder

Open database/seeds/MailboxFolderSeeder.php

<?php

use Illuminate\Database\Seeder;

class MailboxFolderSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        foreach (config('seed_data.mailbox_folders') as $value) {
            \Illuminate\Support\Facades\DB::table('mailbox_folder')->insert([
                'title' => $value["title"],
                'icon' => $value["icon"]
            ]);
        }
    }
}

Now run this command to seed this file:

php artisan db:seed --class=MailboxFolderSeeder

The folders are inserted successfully, next we will update the models.

 

 

Modifying Mailbox Models

Open app/Models/Mailbox.php and modify as below:

<?php

namespace App\Models;

use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;

class Mailbox extends Model
{
    protected $table = "mailbox";

    protected $fillable = ["subject", "body", "sender_id", "time_sent", "parent_id"];


    public function sender()
    {
        return $this->belongsTo(User::class, "sender_id");
    }

    public function receivers()
    {
        return $this->hasMany(MailboxReceiver::class);
    }

    public function tmpReceivers()
    {
        return $this->hasMany(MailboxTmpReceiver::class);
    }

    public function attachments()
    {
        return $this->hasMany(MailboxAttachment::class, "mailbox_id");
    }

    public function replies()
    {
        return $this->hasMany(self::class, "parent_id")->where("parent_id", "<>", 0);
    }

    public function userFolders()
    {
        return $this->hasMany(MailboxUserFolder::class);
    }

    public function userFolder()
    {
        return $this->hasMany(MailboxUserFolder::class)->where('user_id', Auth::user()->id)->first();
    }

    public function flags()
    {
        return $this->hasMany(MailboxFlags::class);
    }

    public function flag()
    {
        return $this->hasMany(MailboxFlags::class)->where('user_id', Auth::user()->id)->first();
    }
}

app/Models/MailboxAttachment.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class MailboxAttachment extends Model
{
    protected $table = "mailbox_attachment";

    protected $fillable = ["mailbox_id", "attachment"];
}

app/Models/MailboxFlags.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class MailboxFlags extends Model
{
    protected $table = "mailbox_flags";

    protected $fillable = ["is_unread", "is_important", "mailbox_id", "user_id"];
}

app/Models/MailboxFolder.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class MailboxFolder extends Model
{
    protected $table = "mailbox_folder";

    protected $fillable = ["title", "icon"];
}

app/Models/MailboxReceiver.php

<?php

namespace App\Models;

use App\User;
use Illuminate\Database\Eloquent\Model;

class MailboxReceiver extends Model
{
    protected $table = "mailbox_receiver";

    protected $fillable = ["mailbox_id", "receiver_id"];


    public function mailbox()
    {
        return $this->belongsTo(Mailbox::class, "mailbox_id");
    }

    public function user()
    {
        return $this->belongsTo(User::class, "receiver_id");
    }
}

app/Models/MailboxTmpReceiver.php

<?php

namespace App\Models;

use App\User;
use Illuminate\Database\Eloquent\Model;

class MailboxTmpReceiver extends Model
{
    protected $table = "mailbox_tmp_receiver";

    protected $fillable = ["mailbox_id", "receiver_id"];


    public function mailbox()
    {
        return $this->belongsTo(Mailbox::class, "mailbox_id");
    }

    public function user()
    {
        return $this->belongsTo(User::class, "receiver_id");
    }
}

app/Models/MailboxUserFolder.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class MailboxUserFolder extends Model
{
    protected $table = "mailbox_user_folder";

    protected $fillable = ["user_id", "mailbox_id", "folder_id"];


    public function folder()
    {
        return $this->belongsTo(MailboxFolder::class, "folder_id");
    }
}

I have updated the models, and added some relations that will be used later when displaying the mailbox data.

update app/Helpers/General.php add this function

/**
 * get Unread Messages
 *
 *
 * @return mixed
 */
function getUnreadMessages()
{
    $folder = \App\Models\MailboxFolder::where('title', "Inbox")->first();

    $messages = \App\Models\Mailbox::join('mailbox_receiver', 'mailbox_receiver.mailbox_id', '=', 'mailbox.id')
        ->join('mailbox_user_folder', 'mailbox_user_folder.user_id', '=', 'mailbox_receiver.receiver_id')
        ->join('mailbox_flags', 'mailbox_flags.user_id', '=', 'mailbox_user_folder.user_id')
        ->where('mailbox_receiver.receiver_id', \Auth::user()->id)
//                          ->where('parent_id', 0)
        ->where('mailbox_flags.is_unread', 1)
        ->where('mailbox_user_folder.folder_id', $folder->id)
        ->where('sender_id', '!=', \Auth::user()->id)
        ->whereRaw('mailbox.id=mailbox_receiver.mailbox_id')
        ->whereRaw('mailbox.id=mailbox_flags.mailbox_id')
        ->whereRaw('mailbox.id=mailbox_user_folder.mailbox_id')
        ->select(["*", "mailbox.id as id"])
        ->get();

    return $messages;
}

This function retrieves the unread messages for the current logged in user, here we make a join with mailbox related tables like the mailbox receiver and mailbox flags and fetch all the data where the receiver id is “Auth::user()->id” and is_unread flag equal to 1. I will use this function below.

 

Mailbox Controller

Run this command in terminal to create a new controller

php artisan make:controller MailboxController

Modify app/Http/Controllers/MailboxController.php add the below code

<?php

namespace App\Http\Controllers;

use App\Helpers\MailerFactory;
use App\Models\MailboxFolder;
use App\Models\Mailbox;
use App\Models\MailboxAttachment;
use App\Models\MailboxFlags;
use App\Models\MailboxReceiver;
use App\Models\MailboxTmpReceiver;
use App\Models\MailboxUserFolder;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class MailboxController extends Controller
{
    protected $mailer;

    protected $folders = array();

    public function __construct(MailerFactory $mailer)
    {
        $this->mailer = $mailer;

        $this->getFolders();
    }

    /**
     * index
     *
     * list all messages
     *
     * @return \Illuminate\View\View
     */
    public function index(Request $request, $folder = "")
    {
       
    }

 
    /**
     * create
     *
     * show compose form
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function create()
    {
        
    }


    /**
     * store
     *
     * store and send the message
     *
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function store(Request $request)
    {
         
    }


    /**
     * show email
     *
     * @param $id
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function show($id)
    {
        
    }


    /**
     * toggle important
     *
     *
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function toggleImportant(Request $request)
    {
        
    }


    /**
     * trash email
     *
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function trash(Request $request)
    {
        
    }


    /**
     * getReply
     *
     * show reply form
     *
     * @param $id
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function getReply($id)
    {
        
    }


    /**
     * postReply
     *
     *
     * send the reply
     *
     * @param Request $request
     * @param $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function postReply(Request $request, $id)
    {
        
    }

    /**
     * getForward
     *
     * show forward form
     *
     * @param $id
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function getForward($id)
    {
       
    }
     

     /**
     * postForward
     *
     * forward the message
     *
     * @param Request $request
     * @param $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function postForward(Request $request, $id)
    {
        
    }


    /**
     * send
     *
     * used to send a Draft message
     *
     * @param $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function send($id)
    {
        
    }

    /**
     * get Folders
     */
    private function getFolders(): void
    {
        $this->folders = MailboxFolder::all();
    }

}

I have added method signatures without implementations that will represent the mailbox actions, those methods will be updated in the next sections.

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::group(['prefix' => 'admin', 'middleware' => 'auth'], function () {

    Route::get('/', function () {
        return view('pages.home.index');
    });

    Route::resource('/users', 'UsersController');

    Route::get('/my-profile', 'UsersController@getProfile');

    Route::get('/my-profile/edit', 'UsersController@getEditProfile');

    Route::patch('/my-profile/edit', 'UsersController@postEditProfile');

    Route::resource('/permissions', 'PermissionsController');

    Route::resource('/roles', 'RolesController');

    Route::get('/users/role/{id}', 'UsersController@getRole');

    Route::put('/users/role/{id}', 'UsersController@updateRole');

    Route::resource('/documents', 'DocumentsController');

    Route::get('/documents/{id}/assign', 'DocumentsController@getAssignDocument');

    Route::put('/documents/{id}/assign', 'DocumentsController@postAssignDocument');

    Route::resource('/contacts', 'ContactsController');

    Route::get('/contacts/{id}/assign', 'ContactsController@getAssignContact');

    Route::put('/contacts/{id}/assign', 'ContactsController@postAssignContact');

    Route::get('/api/contacts/get-contacts-by-status', 'ContactsController@getContactsByStatus');

    Route::resource('/tasks', 'TasksController');

    Route::get('/tasks/{id}/assign', 'TasksController@getAssignTask');

    Route::put('/tasks/{id}/assign', 'TasksController@postAssignTask');

    Route::get('/tasks/{id}/update-status', 'TasksController@getUpdateStatus');

    Route::put('/tasks/{id}/update-status', 'TasksController@postUpdateStatus');

    Route::get('/mailbox/{folder?}', 'MailboxController@index');

    Route::get('/mailbox-create', 'MailboxController@create');

    Route::post('/mailbox-create', 'MailboxController@store');

    Route::get('/mailbox-show/{id}', 'MailboxController@show');

    Route::put('/mailbox-toggle-important', 'MailboxController@toggleImportant');

    Route::delete('/mailbox-trash', 'MailboxController@trash');

    Route::get('/mailbox-reply/{id}', 'MailboxController@getReply');

    Route::post('/mailbox-reply/{id}', 'MailboxController@postReply');

    Route::get('/mailbox-forward/{id}', 'MailboxController@getForward');

    Route::post('/mailbox-forward/{id}', 'MailboxController@postForward');

    Route::get('/mailbox-send/{id}', 'MailboxController@send');

    Route::get('/forbidden', function () {
        return view('pages.forbidden.forbidden_area');
    });
});

Route::get('/', function () {
   return redirect()->to('/admin');
});

Auth::routes();

Listing All Messages

The first step in the mailbox is to display tall the messages categorized by folder, this will be done in the index() method below:

class MailboxController extends Controller
{
   

  .....
  .....
  .....
   
   /**
     * index
     *
     * @return \Illuminate\View\View
     */
    public function index(Request $request, $folder = "")
    {
        $keyword = $request->get('search');
        $perPage = 15;

        $folders = $this->folders;

        if(empty($folder)) {
            $folder = "Inbox";
        }

        $messages = $this->getData($keyword, $perPage, $folder);

        $unreadMessages = count(getUnreadMessages());

        return view('pages.mailbox.index', compact('folders', 'messages', 'unreadMessages'));
    }

    /**
     * getData
     *
     *
     * @param $keyword
     * @param $perPage
     * @param $foldername
     * @return array
     */
    private function getData($keyword, $perPage, $foldername)
    {
        $folder = MailboxFolder::where('title', $foldername)->first();

        if($foldername == "Inbox") {

            $query = Mailbox::join('mailbox_receiver', 'mailbox_receiver.mailbox_id', '=', 'mailbox.id')
                    ->join('mailbox_user_folder', 'mailbox_user_folder.user_id', '=', 'mailbox_receiver.receiver_id')
                    ->join('mailbox_flags', 'mailbox_flags.user_id', '=', 'mailbox_user_folder.user_id')
                    ->where('mailbox_receiver.receiver_id', Auth::user()->id)
                    ->where('mailbox_user_folder.folder_id', $folder->id)
                    ->where('sender_id', '!=', Auth::user()->id)
//                    ->where('parent_id', 0)
                    ->whereRaw('mailbox.id=mailbox_receiver.mailbox_id')
                    ->whereRaw('mailbox.id=mailbox_flags.mailbox_id')
                    ->whereRaw('mailbox.id=mailbox_user_folder.mailbox_id')
                    ->select(["*", "mailbox.id as id", "mailbox_flags.id as mailbox_flag_id", "mailbox_user_folder.id as mailbox_folder_id"]);
        } else if ($foldername == "Sent" || $foldername == "Drafts") {
            $query = Mailbox::join('mailbox_user_folder', 'mailbox_user_folder.mailbox_id', '=', 'mailbox.id')
                ->join('mailbox_flags', 'mailbox_flags.user_id', '=', 'mailbox_user_folder.user_id')
                ->where('mailbox_user_folder.folder_id', $folder->id)
                ->where('mailbox_user_folder.user_id', Auth::user()->id)
//                ->where('parent_id', 0)
                ->whereRaw('mailbox.id=mailbox_flags.mailbox_id')
                ->whereRaw('mailbox.id=mailbox_user_folder.mailbox_id')
                ->select(["*", "mailbox.id as id", "mailbox_flags.id as mailbox_flag_id", "mailbox_user_folder.id as mailbox_folder_id"]);
        } else {
            $query = Mailbox::join('mailbox_user_folder', 'mailbox_user_folder.mailbox_id', '=', 'mailbox.id')
                ->join('mailbox_flags', 'mailbox_flags.user_id', '=', 'mailbox_user_folder.user_id')
                ->leftJoin('mailbox_receiver', 'mailbox_receiver.mailbox_id', '=', 'mailbox.id')
                ->where(function ($query) {
                    $query->where('mailbox_user_folder.user_id', Auth::user()->id)
                          ->orWhere('mailbox_receiver.receiver_id', Auth::user()->id);
                })
                ->where('mailbox_user_folder.folder_id', $folder->id)
//                ->where('parent_id', 0)
                ->whereRaw('mailbox.id=mailbox_flags.mailbox_id')
                ->whereRaw('mailbox.id=mailbox_user_folder.mailbox_id')
                ->whereRaw('mailbox_user_folder.user_id!=mailbox_receiver.receiver_id')
                ->select(["*", "mailbox.id as id", "mailbox_flags.id as mailbox_flag_id", "mailbox_user_folder.id as mailbox_folder_id"]);
        }


        if (!empty($keyword)) {
            $query->where('subject', 'like', "%$keyword%");
        }

        $query->orderBy('mailbox.id', 'DESC');

        $messages = $query->paginate($perPage);

        return $messages;
    }
}

The index() method lists the mailbox messages according to the folder, Don’t be shocked with the the query in the getData() method it just retrieves the messages according to the current folder and the current user. Retrieving the messages is a matter of joining the Mailbox table with the other related tables.

For the Inbox folder we need to retrieve the messages that was sent to me so in other words where receiver_id equal to “Auth::user()->id” and folder is Inbox like this:

->where('mailbox_receiver.receiver_id', Auth::user()->id)
                    ->where('mailbox_user_folder.folder_id', $folder->id)
                    ->where('sender_id', '!=', Auth::user()->id)

For the Sent or Drafts folder i need to fetch the messages that i sent shown in this condition:

->where('mailbox_user_folder.folder_id', $folder->id)
                ->where('mailbox_user_folder.user_id', Auth::user()->id)

For the Trash folder i need to retrieve the messages that have been added to the trash folder, note that i can trash a message that sent to me or a message that i received.

create resources/views/pages/mailbox/index.blade.php and add the below code:

@extends('layout.app')

@section('title', ' | Mailbox')

@section('content')

    <section class="content-header">
        <h1>
            Mailbox

            @if($unreadMessages)
                <small>{{$unreadMessages}} new messages</small>
            @endif
        </h1>
        <ol class="breadcrumb">
            <li><a href="{{ url('/admin') }}"><i class="fa fa-dashboard"></i> Dashboard</a></li>
            <li class="active">Mailbox</li>
        </ol>
    </section>

    <section class="content">
        <div class="row">

            @include('includes.flash_message')

            <div class="col-md-3">

                    <a href="{{ url('admin/mailbox-create') }}" class="btn btn-primary btn-block margin-bottom">Compose</a>

                @include('pages.mailbox.includes.folders_panel')
            </div>
            <!-- /.col -->
            <div class="col-md-9">
                <div class="box box-primary">
                    <div class="box-header with-border">
                        <h3 class="box-title">{{ Request::segment(3)==""?"Inbox":Request::segment(3) }}</h3>

                        <div class="box-tools pull-right">
                            <div class="has-feedback">
                                <form method="GET" action="{{ url('/admin/mailbox/' . Request::segment(3)) }}" accept-charset="UTF-8" class="form-inline my-2 my-lg-0" role="search">
                                    <input type="text" class="form-control input-sm" name="search" value="{{ request('search') }}" placeholder="Search Mail">
                                    <span class="glyphicon glyphicon-search form-control-feedback"></span>
                                </form>
                            </div>
                        </div>
                        <!-- /.box-tools -->
                    </div>

                    @if(!$messages->isEmpty())
                        <!-- /.box-header -->
                        <div class="box-body no-padding">

                            @include('pages.mailbox.includes.mailbox_controls')

                            <div class="table-responsive mailbox-messages">
                                <table class="table table-hover table-striped">
                                    <tbody>

                                    @foreach($messages as $message)
                                        <tr data-mailbox-id="{{ $message->id }}" data-mailbox-flag-id="{{ $message->mailbox_flag_id }}" data-user-folder-id="{{ $message->mailbox_folder_id }}">
                                            <td>
                                                @if(Request::segment(3) != 'Trash')
                                                    <input type="checkbox" value="1" data-mailbox-id="{{ $message->id }}" data-mailbox-flag-id="{{ $message->mailbox_flag_id }}" class="check-message">
                                                @endif
                                            </td>
                                            @if(Request::segment(3) != 'Trash')
                                                <td class="mailbox-star">
                                                    <a href="#"><i class="fa {{ $message->is_important==1?'fa-star':'fa-star-o' }} text-yellow"></i></a>
                                                </td>
                                            @endif
                                            <td class="mailbox-name"><a href="{{ url('admin/mailbox-show/' . $message->id) }}">{{ $message->sender->name }}</a></td>
                                            <td class="mailbox-subject">
                                                @if($message->is_unread == 1)
                                                    <b>{{ $message->subject }}</b>
                                                @else
                                                    {{ $message->subject }}
                                                @endif
                                            </td>
                                            <td class="mailbox-attachment">
                                                @if($message->attachments->count() > 0)
                                                    <i class="fa fa-paperclip"></i>
                                                @endif
                                            </td>
                                            <td class="mailbox-date">@if($message->time_sent) {{ Carbon\Carbon::parse($message->time_sent)->diffForHumans()}} @else {{ "not sent yet" }}  @endif</td>
                                        </tr>
                                    @endforeach
                                    </tbody>
                                </table>
                                <!-- /.table -->
                            </div>
                            <!-- /.mail-box-messages -->
                        </div>
                        <!-- /.box-body -->
                        <div class="box-footer no-padding">

                            @include('pages.mailbox.includes.mailbox_controls')

                        </div>
                    @else
                            <div class="box-body">
                                <p>No messages found</p>
                            </div>
                    @endif
                </div>
                <!-- /. box -->
            </div>
            <!-- /.col -->
        </div>
        <!-- /.row -->
    </section>
@endsection

@section('scripts')

    <script src="{{ asset('theme/views/mailbox/functions.js') }}" type="text/javascript"></script>

    <script src="{{ asset('theme/views/mailbox/index.js') }}" type="text/javascript"></script>

@endsection

resources/views/pages/mailbox/includes/folders_panel.blade.php

<div class="box box-solid">
    <div class="box-header with-border">
        <h3 class="box-title">Folders</h3>

        <div class="box-tools">
            <button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i>
            </button>
        </div>
    </div>
    <div class="box-body no-padding">
        <ul class="nav nav-pills nav-stacked">
            @foreach($folders as $folder)
                <li class="{{ Request::segment(3)=='' && $folder->title=='Inbox'?'active':(Request::segment(3) == $folder->title?'active':'') }}"><a href="{{ url('admin/mailbox/' . $folder->title) }}"><i class="{{ $folder->icon }}"></i> {{ $folder->title }}
                        @if($folder->title=='Inbox' && $unreadMessages)<span class="label label-primary pull-right">{{$unreadMessages}}</span> @endif
                     </a></li>
            @endforeach
        </ul>
    </div>
    <!-- /.box-body -->
</div>

resources/views/pages/mailbox/includes/mailbox_controls.blade.php

<div class="mailbox-controls">

    <!-- Check all button -->
    @if(Request::segment(3) != 'Trash')
        <button type="button" class="btn btn-default btn-sm checkbox-toggle"><i class="fa fa-square-o"></i>
        </button>
    @endif
    <div class="btn-group">

        @if(Request::segment(3)==''||Request::segment(3)=='Inbox')
            <button type="button" class="btn btn-default btn-sm mailbox-star-all" title="toggle important state" style="display: {{user_can("toggle_important_email")?'inline':'none'}}"><i class="fa fa-star"></i></button>
            <button type="button" class="btn btn-default btn-sm mailbox-trash-all" title="add to trash" style="display: {{user_can("trash_email")?'inline':'none'}}"><i class="fa fa-trash-o"></i></button>
            <button type="button" class="btn btn-default btn-sm mailbox-reply" title="reply" style="display: {{user_can("reply_email")?'inline':'none'}}"><i class="fa fa-reply"></i></button>
            <button type="button" class="btn btn-default btn-sm mailbox-forward" title="forward" style="display: {{user_can("forward_email")?'inline':'none'}}"><i class="fa fa-mail-forward"></i></button>
        @elseif(Request::segment(3) == 'Sent')
            <button type="button" class="btn btn-default btn-sm mailbox-star-all" title="toggle important state" style="display: {{user_can("toggle_important_email")?'inline':'none'}}"><i class="fa fa-star"></i></button>
            <button type="button" class="btn btn-default btn-sm mailbox-trash-all" title="add to trash" style="display: {{user_can("trash_email")?'inline':'none'}}"><i class="fa fa-trash-o"></i></button>
            <button type="button" class="btn btn-default btn-sm mailbox-forward" title="forward" style="display: {{user_can("forward_email")?'inline':'none'}}"><i class="fa fa-mail-forward"></i></button>
        @elseif(Request::segment(3) == 'Drafts')
            <button type="button" class="btn btn-default btn-sm mailbox-star-all" title="toggle important state" style="display: {{user_can("toggle_important_email")?'inline':'none'}}"><i class="fa fa-star"></i></button>
            <button type="button" class="btn btn-default btn-sm mailbox-trash-all" title="add to trash" style="display: {{user_can("trash_email")?'inline':'none'}}"><i class="fa fa-trash-o"></i></button>
            <button type="button" class="btn btn-default btn-sm mailbox-send" title="send" style="display: {{user_can("send_email")?'inline':'none'}}"><i class="fa fa-mail-forward"></i></button>
        @endif
    </div>
    <div class="pull-right">

        {{$messages->currentPage()}}-{{$messages->perPage()}}/{{$messages->total()}}

        <div class="btn-group">
            {!! $messages->appends(['search' => Request::get('search')])->render('vendor.pagination.mailbox') !!}
        </div>

        <!-- /.btn-group -->
    </div>
    <!-- /.pull-right -->
</div>

public/theme/views/mailbox/functions.js

/**
 * Mailbox main helper functions
 */

var Mailbox = {
    toggleImportant: function toggleImportant(ids, cb) {

        
    },
    trash: function trash(ids, cb) {                    // move to the trash folder

        
    },
    send: function send(mailbox_id) {
       window.location.replace(BASE_URL + "/admin/mailbox-send/" + mailbox_id);
    },
    reply: function reply(mailbox_id) {
       window.location.replace(BASE_URL + "/admin/mailbox-reply/" + mailbox_id);
    },
    forward: function forward(mailbox_id) {
       window.location.replace(BASE_URL + "/admin/mailbox-forward/" + mailbox_id);
    }
};

public/theme/views/mailbox/index.js

$(function () {
    //Enable iCheck plugin for checkboxes
    //iCheck for checkbox and radio inputs
    $('.mailbox-messages input[type="checkbox"]').iCheck({
        checkboxClass: 'icheckbox_flat-blue',
        radioClass: 'iradio_flat-blue'
    });

    //Enable check and uncheck all functionality
    $(".checkbox-toggle").click(function () {
        var clicks = $(this).data('clicks');
        if (clicks) {
            //Uncheck all checkboxes
            $(".mailbox-messages input[type='checkbox']").iCheck("uncheck");
            $(".fa", this).removeClass("fa-check-square-o").addClass('fa-square-o');
        } else {
            //Check all checkboxes
            $(".mailbox-messages input[type='checkbox']").iCheck("check");
            $(".fa", this).removeClass("fa-square-o").addClass('fa-check-square-o');
        }
        $(this).data("clicks", !clicks);
    });

     // handle reply
    $(".mailbox-reply").on("click", function (e) {
        if($(".check-message:checked").length != 1) {
            alert("Please select one message only to reply");

            return false;
        }

        Mailbox.reply($(".check-message:checked").parents("tr").attr("data-mailbox-id"));
    });

    // handle forward
    $(".mailbox-forward").on("click", function (e) {
        if($(".check-message:checked").length != 1) {
            alert("Please select one message only to forward");

            return false;
        }

        Mailbox.forward($(".check-message:checked").parents("tr").attr("data-mailbox-id"));
    });

    // handle send
    $(".mailbox-send").on("click", function (e) {
        if($(".check-message:checked").length != 1) {
            alert("Please select one message only to send");

            return false;
        }

        Mailbox.send($(".check-message:checked").parents("tr").attr("data-mailbox-id"));
    });
});

function checkboxCheck()
{
    if($(".check-message:checked").length == 0) {
        alert("Please select at least one row to process!");

        return false;
    }

    return true;
}

In the above files i have added the code for displaying messages, note that i have created other partials views for the mailbox controls and mailbox folders.

The mailbox listing screen divided into two parts the first is a sidebar which display the folders and the other display the messages for each folder.

The mailbox controls also located in a separated partial view. Note here the controls will be shown according to the Folder which mean that some controls will be shown in a folder and some are not.

I have added some javascript files the first will contain some helper functions that we will use later located in functions.js we will update it later. The other file index.js contain contain code for initializing the grid checkboxes, and calling the mailbox functions defined in functions.js

 

Customizing Pagination

If you checked the mailbox controls view you will see this code:

{!! $messages->appends(['search' => Request::get('search')])->render('vendor.pagination.mailbox') !!}

This code generates a pagination links using custom pagination file

To create custom pagination first run this command:

php artisan vendor:publish --tag=laravel-pagination

This command will create a vendor/pagination directory under views/ directory because we will need a custom pagination view for our mailbox listing page so create resources/views/vendor/pagination/mailbox.blade.php with the below code:

 

resources/views/vendor/pagination/mailbox.blade.php

@if ($paginator->hasPages())

    @if ($paginator->onFirstPage())
        <a class="btn btn-default btn-sm pagination-previous" disabled><i class="fa fa-chevron-left"></i></a>
    @else
        <a href="{{ $paginator->previousPageUrl() }}" rel="prev" class="btn btn-default btn-sm pagination-previous"><i class="fa fa-chevron-left"></i></a>
    @endif

    @if ($paginator->hasMorePages())
        <a class="btn btn-default btn-sm pagination-next" href="{{ $paginator->nextPageUrl() }}" rel="next"><i class="fa fa-chevron-right"></i></a>
    @else
        <a class="btn btn-default btn-sm pagination-next" disabled><i class="fa fa-chevron-right"></i></a>
    @endif
@endif

That’s it the custom pagination is working now.

 

Composing Email

Let’s see how to compose an email, the process is to show a form to enter the message subject, body, recipients, and attachments if exist, then the user can send the message or save it as a draft.

Update Mailbox controller by updating the create() and store() methods:

/**
     * create
     *
     * show compose form
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function create()
    {
        $folders = $this->folders;

        $unreadMessages = count(getUnreadMessages());

        $users = User::where('is_active', 1)->where('id', '!=', Auth::user()->id)->get();

        return view('pages.mailbox.compose', compact('folders', 'unreadMessages', 'users'));
    }


    /**
     * store
     *
     * store and send the message
     *
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'subject' => 'required',
            'receiver_id' => 'required',
            'body' => 'required'
        ]);

        try {
            $this->validateAttachments($request);
        } catch (\Exception $ex) {
            return redirect('admin/mailbox-create')->with('flash_error', $ex->getMessage());
        }

        $receiver_ids = $request->receiver_id;

        $subject = $request->subject;

        $body = $request->body;

        $submit = $request->submit;


        // save message
        $mailbox = new Mailbox();

        $mailbox->subject = $subject;
        $mailbox->body = $body;
        $mailbox->sender_id = Auth::user()->id;
        $mailbox->time_sent = date("Y-m-d H:i:s");
        $mailbox->parent_id = 0;

        $mailbox->save();


        // save receivers and flags
        $this->save($submit, $receiver_ids, $mailbox);


        // save attachments if found
        $this->uploadAttachments($request, $mailbox);


        // check for the submit button and whether to send or save as draft
        if($request->submit == 1) {

            $this->mailer->sendMailboxEmail($mailbox);

            return redirect('admin/mailbox/Sent')->with('flash_message', 'Message sent');
        }

        return redirect('admin/mailbox/Drafts')->with('flash_message', 'Message saved as draft');
    }

Add those private helper methods to the end of the controller:

/**
     * validateAttachments
     *
     *
     * @param $request
     * @throws \Exception
     */
    private function validateAttachments($request)
    {
        $check = [];

        if($request->hasFile('attachments')) {

            $allowedfileExtension = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'txt', 'xls', 'xlsx', 'odt', 'dot', 'html', 'htm', 'rtf', 'ods', 'xlt', 'csv', 'bmp', 'odp', 'pptx', 'ppsx', 'ppt', 'potm'];

            $files = $request->file('attachments');

            foreach ($files as $file) {
                $filename = $file->getClientOriginalName();
                $extension = $file->getClientOriginalExtension();

                if(!in_array($extension, $allowedfileExtension)) {
                    $check[] = $extension;
                }
            }
        }

        if(count($check) > 0) {
            throw new \Exception("One or more files contain invalid extensions: ". implode(",", $check));
        }
    }

    /**
     * save
     *
     *
     * @param $submit
     * @param $receiver_ids
     * @param $mailbox
     */
    private function save($submit, $receiver_ids, $mailbox)
    {

        // We will save two records in tables mailbox_user_folder and mailbox_flags
        // for both the sender and the receivers
        // For the sender perspective the message will be in the "Sent" folder
        // For the receiver perspective the message will be in the "Inbox" folder


        // 1. The sender
        // save folder as "Sent" or "Drafts" depending on button
        $mailbox_user_folder = new MailboxUserFolder();

        $mailbox_user_folder->mailbox_id = $mailbox->id;

        $mailbox_user_folder->user_id = $mailbox->sender_id;

        // if click "Draft" button save into "Drafts" folder
        if($submit == 2) {
            $mailbox_user_folder->folder_id = MailboxFolder::where("title", "Drafts")->first()->id;
        } else {
            $mailbox_user_folder->folder_id = MailboxFolder::where("title", "Sent")->first()->id;
        }

        $mailbox_user_folder->save();

        // save flags "is_unread=0"
        $mailbox_flag = new MailboxFlags();

        $mailbox_flag->mailbox_id = $mailbox->id;

        $mailbox_flag->user_id = $mailbox->sender_id;;

        $mailbox_flag->is_unread = 0;

        $mailbox_flag->is_important = 0;

        $mailbox_flag->save();


        // 2. The receivers
        // if there are receivers and sent button clicked then save into flags, folders and receivers
        if($submit == 1) {

            foreach ($receiver_ids as $receiver_id) {

                // save receiver
                $mailbox_receiver = new MailboxReceiver();

                $mailbox_receiver->mailbox_id = $mailbox->id;

                $mailbox_receiver->receiver_id = $receiver_id;

                $mailbox_receiver->save();


                // save folder as "Inbox"
                $mailbox_user_folder = new MailboxUserFolder();

                $mailbox_user_folder->mailbox_id = $mailbox->id;

                $mailbox_user_folder->user_id = $receiver_id;

                $mailbox_user_folder->folder_id = MailboxFolder::where("title", "Inbox")->first()->id;

                $mailbox_user_folder->save();


                // save flags "is_unread=1"
                $mailbox_flag = new MailboxFlags();

                $mailbox_flag->mailbox_id = $mailbox->id;

                $mailbox_flag->user_id = $receiver_id;

                $mailbox_flag->is_unread = 1;

                $mailbox_flag->save();
            }
        } else {

            // save into tmp receivers
            foreach ($receiver_ids as $receiver_id) {

                // save receiver
                $mailbox_receiver = new MailboxTmpReceiver();

                $mailbox_receiver->mailbox_id = $mailbox->id;

                $mailbox_receiver->receiver_id = $receiver_id;

                $mailbox_receiver->save();
            }
        }
    }

    /**
     * uploadAttachments
     *
     *
     * @param $request
     * @param $mailbox
     */
    private function uploadAttachments($request, $mailbox)
    {

        checkDirectory("documents");

        $destination = public_path('uploads/mailbox/');

        if($request->hasFile('attachments')) {
            $files = $request->file('attachments');

            foreach ($files as $file) {
                $filename = $file->getClientOriginalName();
                $extension = $file->getClientOriginalExtension();

                $new_name = pathinfo($filename, PATHINFO_FILENAME) . '-' . time().'.'.$extension;

                $file->move($destination, $new_name);

                $attachment = new MailboxAttachment();
                $attachment->mailbox_id = $mailbox->id;
                $attachment->attachment = $new_name;
                $attachment->save();
            }
        }
    }

The above code save and send the message to the recipients. First we show the compose form which we will show below. Next on submitting the form, the store() method triggered which simply do some simple validations then we save the message.

Then we save the receiver ids, folders, and flags. This is described in the save() private method which do a lot of steps. First from the sender perspective we save a record in the Mailbox user folder table with folder “Sent” or “Draft“. Also we save a record on the Mailbox Flags table.

Second From the receivers perspective i checked if i clicked the send button then we looped over the receivers, for each receiver we save a record in the Mailbox receiver table, and Mailbox user folder table with folder “Inbox” and Mailbox Flags with flag “is_unread=1”.

If we click the draft button on the form we save the data on the MailboxTmpReceiver table.

Finally we save the attachments if exist and we send the message.

 

Open app/Helpers/MailerFactory.php and add this method

/**
     * send mailbox email
     *
     *
     * @param $mailbox
     * @param $receivers
     */
    public function sendMailboxEmail($mailbox)
    {
        try {

            foreach ($mailbox->receivers as $receiver) {

                $user = User::find($receiver->receiver_id);

                $this->mailer->send("emails.mailbox_send", ['user' => $user, 'mailbox' => $mailbox], function ($message) use ($user, $mailbox) {

                    $message->from($this->fromAddress, $this->fromName)
                        ->to($user->email)->subject($mailbox->subject);

                    if($mailbox->attachments->count() > 0) {
                        foreach($mailbox->attachments as $attachment) {
                            $message->attach(public_path('uploads/mailbox/' . $attachment->attachment));
                        }
                    }

                });
            }
        } catch (\Exception $ex) {
            die("Mailer Factory error: " . $ex->getMessage());
        }
    }

resources/views/pages/mailbox/compose.blade.php

@extends('layout.app')

@section('title', ' | Mailbox | Compose message')

@section('content')

    <section class="content-header">
        <h1>
            Mailbox
            @if($unreadMessages)
                <small>{{$unreadMessages}} new messages</small>
            @endif
        </h1>
        <ol class="breadcrumb">
            <li><a href="{{ url('/admin') }}"><i class="fa fa-dashboard"></i> Dashboard</a></li>
            <li><a href="{{ url('/admin/mailbox') }}"> Mailbox</a></li>
            <li class="active">Compose</li>
        </ol>
    </section>

    <section class="content">
        <div class="row">
            <div class="col-md-3">
                <a href="{{ url('admin/mailbox') }}" class="btn btn-primary btn-block margin-bottom">Back to inbox</a>

                @include('pages.mailbox.includes.folders_panel')
            </div>
            <div class="col-md-9">
                <form method="post" action="{{ url('admin/mailbox-create') }}" enctype="multipart/form-data">
                    {{ csrf_field() }}
                    <div class="box box-primary">
                        <div class="box-header with-border">
                            <h3 class="box-title">Compose New Message</h3>
                        </div>

                        @if ($errors->any())
                            <ul class="alert alert-danger">
                                @foreach ($errors->all() as $error)
                                    <li>{{ $error }}</li>
                                @endforeach
                            </ul>
                    @endif

                        <!-- /.box-header -->
                        <div class="box-body">
                            <div class="form-group">
                                <?php $selected_receivers = old('receiver_id') ?>
                                <select name="receiver_id[]" id="receiver_id" multiple class="form-control">
                                    @foreach($users as $user)
                                        <option value="{{ $user->id }}" {{ $selected_receivers!=null && in_array($user->id, $selected_receivers)?"selected":"" }}>{{ $user->name }}</option>
                                    @endforeach
                                </select>
                            </div>
                            <div class="form-group">
                                <input class="form-control" name="subject" placeholder="Subject:" value="{{ old("subject")!=null?old("subject"):"" }}">
                            </div>
                            <div class="form-group">
                                <textarea id="compose-textarea" class="form-control" name="body" style="height: 300px">
                                    {{ old("body")!=null?old("body"):"" }}
                                </textarea>
                            </div>
                            <div class="form-group">
                                <div class="btn btn-default btn-file">
                                    <i class="fa fa-paperclip"></i> Attachments
                                    <input type="file" name="attachments[]" multiple>
                                </div>
                                <p class="help-block">Max. {{ (int)(ini_get('upload_max_filesize')) }}M</p>
                            </div>
                        </div>
                        <!-- /.box-body -->
                        <div class="box-footer">
                            <div class="pull-right">
                                <button type="submit" name="submit" value="2" class="btn btn-default"><i class="fa fa-pencil"></i> Draft</button>
                                <button type="submit" name="submit" value="1" class="btn btn-primary"><i class="fa fa-envelope-o"></i> Send</button>
                            </div>
                            <button type="reset" class="btn btn-default"><i class="fa fa-times"></i> Discard</button>
                        </div>
                        <!-- /.box-footer -->
                    </div>
                    <!-- /. box -->
                </form>
            </div>
            <!-- /.col -->
        </div>
        <!-- /.row -->
    </section>
@endsection

@section('scripts')

    <script>
        $(function () {
            //Add text editor
            $("#compose-textarea").wysihtml5();

            $("#receiver_id").select2({placeholder: "To:"});
        });
    </script>

@endsection

resources/views/emails/mailbox_send.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ $mailbox->subject }}</title>
</head>
<body>
<p>
    Hello {{ $user->name }},
</p>

<p>
    {!! $mailbox->body !!}
</p>
</body>
</html>

 

Continue to Part 11: Mailbox Complete>>>

 

4.3 4 votes
Article Rating

What's your reaction?

Excited
3
Happy
-1
Not Sure
0
Confused
4

You may also like

Subscribe
Notify of
guest
2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
abdelfattah
abdelfattah
3 years ago

when i try to make this composer require sayeed/custom-migrate
it give me error i’m using laravel 6