added cpanel api

This commit is contained in:
abdul-wahab12345 2024-06-28 06:58:04 +00:00
parent 804e880331
commit 1760c7e0aa
16 changed files with 383 additions and 56 deletions

View File

@ -6,6 +6,50 @@
use App\Models\Response;
use App\Models\TicketNote;
use Illuminate\Support\Facades\Session;
use Mailgun\Mailgun;
use App\Models\Company;
function get_company($key,$value){
return Company::where($key,$value)->first();
}
function get_current_company_tickets($args = []){
$companyId = getSelectedCompany();
if(!$companyId){
return false;
}
$company = get_company('id',$companyId);
if(!$company){
return false;
}
$tickets = Ticket::where('to_email', $company->email);
if(isset($args['type'])){
$tickets->where('type',$args['type']);
}
if(isset($args['status'])){
$tickets->where('status',$args['status']);
}
if(isset($args['orderby'])){
$tickets->orderBy($args['orderby'],$args['order']??'asc');
}
if(isset($args['with'])){
$tickets->with($args['with']);
}
$tickets = $tickets->get();
return $tickets;
}
function getResponse($company_id, $key, $type) {
$meta = CompanyMeta::where('company_id', $company_id)->where('key', $key)->where('type', $type)->first();
@ -70,7 +114,7 @@ function insertTicket($from_email, $to_email, $subject, $content, $type, $sender
function getSelectedCompany() {
$company = Session::get('selected_company');
if (!$company) {
return response()->json(['message' => 'Company not found'], 404);
return false;
}
return $company;
}
@ -110,3 +154,44 @@ function containsHtml($string) {
return $string != strip_tags($string);
}
}
if (!function_exists('sendEmailViaMailgun')) {
function sendEmailViaMailgun( $domain, $from, $to, $subject, $html) {
$apiKey = env('MAILGUN_SECRET');
// Create a new Mailgun instance with API credentials
$mg = Mailgun::create($apiKey);
// Prepare the message parameters
$params = [
'from' => $from,
'to' => $to,
'subject' => $subject,
'html' => $html
];
// Send the email
$response = $mg->messages()->send($domain, $params);
// Return the response from Mailgun
return $response;
}
function createResponse($ticket_id, $message, $user_id = 0)
{
// Create a new Response instance
$response = new Response;
// Set the properties of the Response
$response->message = $message;
$response->ticket_id = $ticket_id;
$response->user_id = $user_id; // You may want to dynamically set the user_id based on the authenticated user
// Save the response to the database
$response->save();
// Return the created response
return $response;
}
}

View File

@ -13,19 +13,15 @@ class DashboardController extends Controller
{
public function dashboard()
{
$companyId = Session::get('selected_company');
$company = Company::find($companyId);
$tickets = Ticket::where('type', 'inbox')
->where('to_email', 'LIKE', '%' . $company->domain)
->get();
$tickets = get_current_company_tickets(['type' => 'inbox']);
return view('index', ['tickets' => $tickets]);
}
public function waiting()
{
$companyId = Session::get('selected_company');
$company = Company::find($companyId);
$tickets = Ticket::where('status', 'waiting')->where('type', 'chat')->where('to_email', 'LIKE', '%' . $company->domain)->get();
$tickets = get_current_company_tickets(['status' => 'waiting']);
return view('waiting', ['tickets' => $tickets]);
}

View File

@ -309,8 +309,18 @@ public function deleteSpamHandling($index)
public function inbox()
{
$tickets = Ticket::orderBy('id', 'desc')->with('lastResponse')->get();
$tickets = get_current_company_tickets([
'type' => 'inbox',
'orderby' => 'id',
'order' => 'desc',
'with' => 'lastResponse'
]);
$messages = [];
return view('inbox', ['tickets' => $tickets, 'messages' => $messages]);
}

View File

@ -4,32 +4,56 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class EmailController extends Controller
{
public function saveEmail(Request $request){
$token = $request->input('token');
$timestamp = $request->input('timestamp');
$signature = $request->input('signature');
DB::beginTransaction();
// if (!verifyMailgunSignature($token, $timestamp, $signature)) {
// abort(403, 'Invalid signature.');
// }
try {
$token = $request->input('token');
$timestamp = $request->input('timestamp');
$signature = $request->input('signature');
$data = $this->extractMailgunData($request->all());
insertTicket($data['from_email'], $data['to_email'], $data['subject'], $data['message'] );
update_setting('aw_test',json_encode($request->all()));
if (!verifyMailgunSignature($token, $timestamp, $signature)) {
update_setting('aw_test','Invalid signature.');
abort(403, 'Invalid signature.');
}
$data = $this->extractMailgunData($request->all());
$to_email = $data['to_email'];
$message = $data['message'];
update_setting('aw_test',$to_email);
$company = get_company('email',$to_email);
if($company){
$ticket = insertTicket($data['from_email'], $data['to_email'], $data['subject'], $message,'inbox',$data['from_name'] );
if($ticket)
$response = createResponse($ticket->id,$message);
}else{}
// update_setting('aw_test',json_encode($request->all()));
DB::commit();
} catch (\Exception $e) {
update_setting('aw_test',$e->getMessage());
DB::rollBack();
// Handle the exception
}
}
public function extractMailgunData($data) {
// Decode the JSON payload into an associative array
// $data = json_decode($jsonPayload, true);
// Prepare an array to hold the extracted data
$extractedData = [
'from_email' => $data['from'],

View File

@ -5,6 +5,8 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\MailgunService;
use Illuminate\Support\Str;
use App\Services\CPanelApiService;
class MailgunController extends Controller
{
@ -69,6 +71,18 @@ public function verifyDomain(Request $request)
}elseif($state == 'active'){
$this->createRoute($request);
$email = "kundesone.$domain@kundesone.no";
$this->createEmail($domain,$email);
$company = get_company('domain',$domain);
if($company){
$company->internal_email = $email;
$company->save();
}
return $email;
return redirect('/dashboard');
}
@ -81,6 +95,23 @@ public function createRoute(Request $request)
$response = $this->mailgunService->createRoute($domain, $forwardUrl);
return $response;
}
public function createEmail($domain,$email){
$password = Str::random(12);
$quota = 0;
$response = $this->cpanelApiService->createEmailAccount($domain, $email, $password, $quota);
if (isset($response['error'])) {
return false;
}
return $email;
}
}

View File

@ -6,22 +6,27 @@
use App\Models\Ticket;
use App\Models\Comment;
use App\Models\Response;
use App\Models\Company;
use Carbon\Carbon;
use Illuminate\Support\Facades\Session;
class TicketController extends Controller
{
public function allTickets()
{
$tickets = Ticket::all();
$tickets = get_current_company_tickets();
return view('all-tickets', ['tickets' => $tickets]);
}
public function storeResponse(Request $request)
{
$this->validate($request, [
'message' => 'required'
'message' => 'required' ,
'ticket_id' => 'required' ,
]);
$ticket_id = $request->ticket_id;
// Load the HTML content into DOMDocument
$dom = new \DOMDocument();
libxml_use_internal_errors(true); // Prevents HTML errors from being thrown as exceptions
@ -40,11 +45,21 @@ public function storeResponse(Request $request)
// Save the modified HTML
$messageWithClasses = $dom->saveHTML($dom->documentElement);
$response = new Response;
$response->message = $messageWithClasses;
$response->ticket_id = $request->ticket_id;
$response->user_id = 1;
$response->save();
// create response
$response = createResponse($ticket_id,$messageWithClasses,1);
$ticket = Ticket::find($ticket_id);
$companyId = Session::get('selected_company');
$company = get_company('id',$companyId);
//Send mail to mailgun
$domain = $company->domain;
$from = $company->email;
$to = $ticket->from_email;
$subject = $ticket->subject;
$html = $request->message;
sendEmailViaMailgun($domain, $from, $to, $subject, $html);
// Return the updated response and time
return response()->json([
@ -85,4 +100,12 @@ public function storeComment(Request $request)
return $comment;
}
public function deleteComment($commentId)
{
$comment = Comment::findOrFail($commentId);
$comment->delete();
return response()->json(['message' => 'Comment Deleted Successfully']);
}
}

View File

@ -0,0 +1,50 @@
<?php
// app/Services/CPanelApiService.php
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
class CPanelApiService
{
protected $client;
protected $cpanelUrl;
protected $cpanelUsername;
protected $cpanelToken;
public function __construct()
{
$this->cpanelUrl = env('CPANEL_URL');
$this->cpanelUsername = env('CPANEL_USERNAME');
$this->cpanelToken = env('CPANEL_TOKEN');
$this->client = new Client([
'base_uri' => $this->cpanelUrl,
'headers' => [
'Authorization' => 'cpanel ' . $this->cpanelUsername . ':' . $this->cpanelToken
]
]);
}
public function createEmailAccount($domain, $email, $password, $quota)
{
$query = [
'cpanel_jsonapi_user' => $this->cpanelUsername,
'cpanel_jsonapi_apiversion' => '2',
'cpanel_jsonapi_module' => 'Email',
'cpanel_jsonapi_func' => 'addpop',
'domain' => $domain,
'email' => $email,
'password' => $password,
'quota' => $quota
];
try {
$response = $this->client->request('GET', '/json-api/cpanel', ['query' => $query]);
return json_decode($response->getBody(), true);
} catch (GuzzleException $e) {
return ['error' => $e->getMessage()];
}
}
}

View File

@ -6,7 +6,7 @@
"license": "MIT",
"require": {
"php": "^8.1",
"guzzlehttp/guzzle": "^7.2",
"guzzlehttp/guzzle": "^7.8",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8",

2
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e3f9f69a248021a08a4b420d64573db7",
"content-hash": "3427d1f055fffcf2c0867d5d8137c3f3",
"packages": [
{
"name": "brick/math",

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('companies', function (Blueprint $table) {
$table->string('internal_email')->default('');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('companies', function (Blueprint $table) {
//
});
}
};

View File

@ -30,3 +30,8 @@
[26-Jun-2024 04:05:49 UTC] PHP Warning: Module "fileinfo" is already loaded in Unknown on line 0
[26-Jun-2024 11:15:17 UTC] PHP Warning: Module "fileinfo" is already loaded in Unknown on line 0
[26-Jun-2024 11:31:01 UTC] PHP Warning: Module "fileinfo" is already loaded in Unknown on line 0
[28-Jun-2024 06:04:29 UTC] PHP Warning: Module "fileinfo" is already loaded in Unknown on line 0
[28-Jun-2024 06:04:33 UTC] PHP Warning: Module "fileinfo" is already loaded in Unknown on line 0
[28-Jun-2024 06:04:34 UTC] PHP Warning: Module "fileinfo" is already loaded in Unknown on line 0
[28-Jun-2024 06:18:54 UTC] PHP Warning: Module "fileinfo" is already loaded in Unknown on line 0
[28-Jun-2024 06:20:52 UTC] PHP Warning: Module "fileinfo" is already loaded in Unknown on line 0

View File

@ -4,7 +4,7 @@
<div class="dev-toggle-sidebar">
<img src="{{ asset('images/icons/blocks-icon.svg') }}">
</div>
<h2 class="d-flex align-items-center">Hello Maxwell <span>👋🏼</span>,</h2>
<h2 class="d-flex align-items-center">Hello {{auth()->user()->name}} <span>👋🏼</span>,</h2>
</div>
<div class="col-sm-8 d-flex justify-content-end align-items-center">
<div class="search-box d-flex align-items-center">

View File

@ -186,9 +186,7 @@ class="form-control input-reply-textarea" required>{!! $email_signature->value ?
title.</span>
</div>
</div>
@if(Auth::user()->role_id == 2)
<button type="submit" class="dev-form-submit-btn">Save</button>
@endif
</form>
</div>
</div>
@ -420,9 +418,7 @@ class="form-control input-reply-textarea" required>{!! $email_signature->value ?
</div>
</div>
</form>
@if(Auth::user()->role_id == 2)
<button type="submit" class="dev-form-submit-btn">Save</button>
@endif
</div>
</form>
</div>
@ -471,15 +467,13 @@ function toggleClosed(day) {
@csrf
<div class="dev-input-group dev-input-group-input-info">
<label>Name</label>
<input name="name" type="text" placeholder="Type here" value="{{ $canned_response[0]['value'] ?? '' }}" required>
<input name="name" type="text" placeholder="Type here" required>
</div>
<div class="dev-input-group dev-input-group-input-info">
<label>Text</label>
<textarea name="text" required rows="6">{{ $canned_response[1]['value'] ?? '' }}</textarea>
<textarea name="text" required rows="6"></textarea>
</div>
@if(Auth::user()->role_id == 2)
<button type="submit" class="dev-form-submit-btn">Save</button>
@endif
</form>
</div>
</div>
@ -487,6 +481,7 @@ function toggleClosed(day) {
<div class="dev-title-row">
<h2>Canned responses</h2>
<div class="dev-users-boxs">
@if(!is_null($canned_response))
@foreach(json_decode($canned_response->value) as $index => $values)
<div class="dev-users-box">
<div class="dev-box">
@ -497,10 +492,11 @@ function toggleClosed(day) {
<img src="{{ asset('images/settingss.svg') }}" alt="">
</div>
<div class="dev-icon">
<a href="{{ route('delete.canned.response', $index) }}" class="delete-user"><img src="{{ asset('images/binn.svg') }}" alt=""></a>
<a style="cursor:pointer;" href="{{ route('delete.canned.response', $index) }}" class="delete-user"><img src="{{ asset('images/binn.svg') }}" alt=""></a>
</div>
</div>
@endforeach
@endif
</div>
</div>
</div>
@ -865,6 +861,7 @@ class="form-control input-reply-textarea">{!! $automatic_reply_text->value ?? ''
<h2>Email addresses that never should be marked as spam<br>
Blocked email addresses</h2>
<div class="dev-users-boxs">
@if(!is_null($spam_handling))
@foreach(json_decode($spam_handling->value) as $index => $values)
<div class="dev-users-box">
<div class="dev-box">
@ -875,14 +872,46 @@ class="form-control input-reply-textarea">{!! $automatic_reply_text->value ?? ''
<img src="{{ asset('images/settingss.svg') }}" alt="">
</div>
<div class="dev-icon">
<a href="{{ route('delete.spam.handling', $index) }}" class="delete-user"><img src="{{ asset('images/binn.svg') }}" alt=""></a>
<a href="{{ route('delete.spam.handling', $index) }}" style="cursor:pointer;" class="delete-user"><img src="{{ asset('images/binn.svg') }}" alt=""></a>
</div>
</div>
@endforeach
@endif
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
<script defer>
document.addEventListener('DOMContentLoaded', function () {
const deleteLinks = document.querySelectorAll('.delete-user');
deleteLinks.forEach(function (link) {
link.addEventListener('click', function (event) {
event.preventDefault(); // Prevent the default link click behavior
const url = this.href;
Swal.fire({
title: 'Are you sure?',
text: "You won't be able to revert this!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, delete it!'
}).then((result) => {
if (result.isConfirmed) {
// If the user confirms, redirect to the delete URL
window.location.href = url;
}
});
});
});
});
</script>
<!-- -->
<div class="dev-tabcontent dev-tabcontent-tags">
<div class="dev-tabcontent-outers">

View File

@ -103,7 +103,7 @@ class="side-bar-link bg-light-color d-flex align-items-center justify-content-be
<div class="dev-toggle-sidebar">
<img src="{{ asset('images/icons/blocks-icon.svg') }}">
</div>
<h2 class="d-flex align-items-center">Hello Maxwell <span>👋🏼</span>,</h2>
<h2 class="d-flex align-items-center">Hello {{auth()->user()->name}} <span>👋🏼</span>,</h2>
</div>
<div class="col-sm-8 d-flex justify-content-end align-items-center">
<div class="search-box d-flex align-items-center">

View File

@ -61,7 +61,7 @@ class="ui button comment--btn bg-dark-green-color color-light comment-edit-btn "
<i class="edit outline icon"></i>
</button>
<button
class="ui button comment--btn bg-light-green-color color-light comment-delete-btn">
class="ui button comment--btn bg-light-green-color color-light comment-delete-btn" data-comment-id="{{ $comment->id }}">
<i class="trash alternate outline icon"></i>
</button>
</div>
@ -80,6 +80,50 @@ class="ui button comment--btn bg-light-green-color color-light comment-delete-bt
</ul>
</div>
<!--delete Comment-->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
$(document).ready(function() {
$('.comment-delete-btn').on('click', function() {
var commentId = $(this).data('comment-id');
var commentElement = $(this).closest('.response-comment');
// SweetAlert confirmation dialog
Swal.fire({
title: 'Are you sure?',
text: "You won't be able to revert this!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, delete it!'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: '/delete-comment/' + commentId,
method: 'GET',
data: {
_token: '{{ csrf_token() }}'
},
success: function(response) {
toastr.success("Comment Deleted Successfully");
commentElement.remove();
},
error: function(error) {
console.error('Error deleting comment:', error);
Swal.fire(
'Error!',
'An error occurred while deleting the comment.',
'error'
);
}
});
}
});
});
});
</script>
<!--store comment-->
<script>
$(document).ready(function() {
@ -101,9 +145,18 @@ class="ui button comment--btn bg-light-green-color color-light comment-delete-bt
_token: '{{ csrf_token() }}'
},
success: function(response) {
$('.accordion-body--custom').prepend(response);
toastr.success("Comment Added Successfully");
$('.input-comment-textarea').val('');
$.ajax({
url: '/fetch-action-box/' + ticketId,
method: 'GET',
success: function(response) {
$('.action-box').html(response);
},
error: function(error) {
console.log('Error fetching action box content:', error);
}
});
},
error: function(error) {
console.error('Error adding comment:', error);
@ -116,35 +169,27 @@ class="ui button comment--btn bg-light-green-color color-light comment-delete-bt
<!--change ticket status-->
<script>
$(document).ready(function() {
// Click event for status buttons
$('.status-btn').on('click', function() {
var newStatus = $(this).data('status');
// Update UI immediately
$('.status-btn').removeClass('active');
$(this).addClass('active');
// Optionally, update backend via AJAX
updateTicketStatus(newStatus);
});
// Function to update ticket status via AJAX
function updateTicketStatus(newStatus) {
// Example AJAX request
$.ajax({
url: 'update-ticket-status/{{ $ticket->id }}', // Replace with your route
url: 'update-ticket-status/{{ $ticket->id }}',
method: 'POST',
data: {
status: newStatus,
_token: '{{ csrf_token() }}'
},
success: function(response) {
// Handle success response if needed
toastr.success(response.message);
},
error: function(error) {
console.error('Error updating ticket status:', error);
// Optionally handle error response
}
});
}

View File

@ -48,6 +48,7 @@
Route::get('/inbox-setting', [InboxController::class, 'inboxSetting'])->name('inbox.setting');
Route::post('update-ticket-status/{ticketId}', [TicketController::class, 'updateStatus']);
Route::post('store-comment', [TicketController::class, 'storeComment']);
Route::get('delete-comment/{commentId}', [TicketController::class, 'deleteComment']);
Route::post('store/response', [TicketController::class, 'storeResponse'])->name('store.response');
//Basic Setting Route
Route::post('inbox/basic-setting', [InboxController::class, 'basicSetting'])->name('inbox.basic.setting');