mirror of
https://github.com/freescout-helpdesk/freescout.git
synced 2024-11-24 03:12:46 +01:00
View send log for threads
This commit is contained in:
parent
2016ef2a53
commit
a6cd30caa1
@ -13,8 +13,10 @@ use App\Events\UserReplied;
|
||||
use App\Folder;
|
||||
use App\Mailbox;
|
||||
use App\MailboxUser;
|
||||
use App\SendLog;
|
||||
use App\Thread;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Validator;
|
||||
|
||||
class ConversationsController extends Controller
|
||||
@ -512,6 +514,63 @@ class ConversationsController extends Controller
|
||||
return \Response::json($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversations ajax controller.
|
||||
*/
|
||||
public function ajaxHtml(Request $request)
|
||||
{
|
||||
switch ($request->action) {
|
||||
|
||||
case 'send_log':
|
||||
return $this->ajaxHtmlSendLog();
|
||||
}
|
||||
|
||||
abort(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send log
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function ajaxHtmlSendLog()
|
||||
{
|
||||
$thread_id = Input::get('thread_id');
|
||||
if (!$thread_id) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$thread = Thread::find($thread_id);
|
||||
if (!$thread) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
if (!$user->can('view', $thread->conversation)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
// Get send log
|
||||
$log_records = SendLog::where('thread_id', $thread_id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
$customers_log = [];
|
||||
$users_log = [];
|
||||
foreach ($log_records as $log_record) {
|
||||
if ($log_record->user_id) {
|
||||
$users_log[$log_record->email][] = $log_record;
|
||||
} else {
|
||||
$customers_log[$log_record->email][] = $log_record;
|
||||
}
|
||||
}
|
||||
|
||||
return view('conversations/ajax_html/send_log', [
|
||||
'customers_log' => $customers_log,
|
||||
'users_log' => $users_log,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redirect URL after performing an action.
|
||||
*/
|
||||
|
@ -22,6 +22,11 @@ class SendReplyToCustomer implements ShouldQueue
|
||||
|
||||
public $customer;
|
||||
|
||||
private $failures = [];
|
||||
private $recipients = [];
|
||||
private $last_thread = null;
|
||||
private $message_id = '';
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -50,11 +55,11 @@ class SendReplyToCustomer implements ShouldQueue
|
||||
|
||||
$new = false;
|
||||
$headers = [];
|
||||
$last_thread = $this->threads->first();
|
||||
$this->last_thread = $this->threads->first();
|
||||
$prev_thread = null;
|
||||
|
||||
// Configure mail driver according to Mailbox settings
|
||||
\App\Mail\Mail::setMailDriver($mailbox, $last_thread->created_by_user);
|
||||
\App\Mail\Mail::setMailDriver($mailbox, $this->last_thread->created_by_user);
|
||||
|
||||
if (count($this->threads) == 1) {
|
||||
$new = true;
|
||||
@ -73,15 +78,16 @@ class SendReplyToCustomer implements ShouldQueue
|
||||
$headers['In-Reply-To'] = '<'.$prev_thread->message_id.'>';
|
||||
$headers['References'] = '<'.$prev_thread->message_id.'>';
|
||||
}
|
||||
$message_id = \App\Mail\Mail::MESSAGE_ID_PREFIX_REPLY_TO_CUSTOMER.'-'.$last_thread->id.'-'.md5($last_thread->id).'@'.$mailbox->getEmailDomain();
|
||||
$headers['Message-ID'] = $message_id;
|
||||
$this->message_id = \App\Mail\Mail::MESSAGE_ID_PREFIX_REPLY_TO_CUSTOMER.'-'.$this->last_thread->id.'-'.md5($this->last_thread->id).'@'.$mailbox->getEmailDomain();
|
||||
$headers['Message-ID'] = $this->message_id;
|
||||
|
||||
$customer_email = $this->customer->getMainEmail();
|
||||
$cc_array = $mailbox->removeMailboxEmailsFromList($last_thread->getCcArray());
|
||||
$bcc_array = $mailbox->removeMailboxEmailsFromList($last_thread->getBccArray());
|
||||
|
||||
$cc_array = $mailbox->removeMailboxEmailsFromList($this->last_thread->getCcArray());
|
||||
$bcc_array = $mailbox->removeMailboxEmailsFromList($this->last_thread->getBccArray());
|
||||
$this->recipients = array_merge([$customer_email], $cc_array, $bcc_array);
|
||||
|
||||
try {
|
||||
$mail = Mail::to([['name' => $this->customer->getFullName(), 'email' => $customer_email]])
|
||||
Mail::to([['name' => $this->customer->getFullName(), 'email' => $customer_email]])
|
||||
->cc($cc_array)
|
||||
->bcc($bcc_array)
|
||||
->send(new ReplyToCustomer($this->conversation, $this->threads, $headers));
|
||||
@ -93,31 +99,23 @@ class SendReplyToCustomer implements ShouldQueue
|
||||
])
|
||||
->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
|
||||
->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR);
|
||||
|
||||
// Failures will be save to send log when retry attempts will finish
|
||||
$this->failures = $this->recipients;
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// In message_id we are storing Message-ID of the incoming email which created the thread
|
||||
// Outcoming message_id can be generated for each thread by thread->id
|
||||
// $last_thread->message_id = $message_id;
|
||||
// $last_thread->save();
|
||||
// $this->last_thread->message_id = $message_id;
|
||||
// $this->last_thread->save();
|
||||
|
||||
// Laravel tells us exactly what email addresses failed
|
||||
$failures = Mail::failures();
|
||||
$this->failures = Mail::failures();
|
||||
|
||||
// Save to send log
|
||||
$recipients = array_merge([$customer_email], $cc_array, $bcc_array);
|
||||
foreach ($recipients as $recipient) {
|
||||
if (in_array($recipient, $failures)) {
|
||||
$status = SendLog::STATUS_SEND_ERROR;
|
||||
} else {
|
||||
$status = SendLog::STATUS_ACCEPTED;
|
||||
}
|
||||
if ($customer_email == $recipient) {
|
||||
$customer_id = $this->customer->id;
|
||||
} else {
|
||||
$customer_id = null;
|
||||
}
|
||||
SendLog::log($last_thread->id, $message_id, $recipient, $status, $customer_id);
|
||||
}
|
||||
$this->saveToSendLog();
|
||||
|
||||
if (!empty($failures)) {
|
||||
throw new \Exception('Could not send email to: '.implode(', ', $failures));
|
||||
@ -142,5 +140,27 @@ class SendReplyToCustomer implements ShouldQueue
|
||||
])
|
||||
->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
|
||||
->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR);
|
||||
|
||||
$this->saveToSendLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save failed email to send log.
|
||||
*/
|
||||
public function saveToSendLog()
|
||||
{
|
||||
foreach ($this->recipients as $recipient) {
|
||||
if (in_array($recipient, $this->failures)) {
|
||||
$status = SendLog::STATUS_SEND_ERROR;
|
||||
} else {
|
||||
$status = SendLog::STATUS_ACCEPTED;
|
||||
}
|
||||
if ($customer_email == $recipient) {
|
||||
$customer_id = $this->customer->id;
|
||||
} else {
|
||||
$customer_id = null;
|
||||
}
|
||||
SendLog::log($this->last_thread->id, $this->message_id, $recipient, $status, $customer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
app/Observers/SendLogObserver.php
Normal file
20
app/Observers/SendLogObserver.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
class SendLogObserver
|
||||
{
|
||||
/**
|
||||
* Send log created.
|
||||
*
|
||||
* @param SendLog $send_log
|
||||
*/
|
||||
public function created(SendLog $send_log)
|
||||
{
|
||||
// Update status for thread if any
|
||||
if ($send_log->thread_id && ($send_log->customer_id || ($send_log->user_id && $send_log->user_id == $send_log->thread->user_id)) {
|
||||
$send_log->thread->send_status = $send_log->status;
|
||||
$send_log->thread->save();
|
||||
}
|
||||
}
|
||||
}
|
@ -56,7 +56,7 @@ class SendLog extends Model
|
||||
/**
|
||||
* Save log record.
|
||||
*/
|
||||
public static function log($thread_id, $message_id, $email, $status, $customer_id = null, $user_id = null, $message = null)
|
||||
public static function log($thread_id, $message_id, $email, $status, $customer_id = null, $user_id = null, $status_message = null)
|
||||
{
|
||||
$send_log = new self();
|
||||
$send_log->thread_id = $thread_id;
|
||||
@ -65,9 +65,54 @@ class SendLog extends Model
|
||||
$send_log->status = $status;
|
||||
$send_log->customer_id = $customer_id;
|
||||
$send_log->user_id = $user_id;
|
||||
$send_log->message = $message;
|
||||
$send_log->status_message = $status_message;
|
||||
$send_log->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name of the status.
|
||||
*/
|
||||
public function getStatusName()
|
||||
{
|
||||
switch ($this->status) {
|
||||
case self::STATUS_ACCEPTED:
|
||||
return __('Accepted for delivery');
|
||||
case self::STATUS_SEND_ERROR:
|
||||
return __('Send error');
|
||||
case self::STATUS_DELIVERY_SUCCESS:
|
||||
return __('Successfully delivered');
|
||||
case self::STATUS_DELIVERY_ERROR:
|
||||
return __('Delivery error');
|
||||
case self::STATUS_OPENED:
|
||||
return __('Recipient opened the message');
|
||||
case self::STATUS_CLICKED:
|
||||
return __('Recipient clicked a link in the message');
|
||||
case self::STATUS_UNSUBSCRIBED:
|
||||
return __('Recipient unsubscribed');
|
||||
case self::STATUS_COMPLAINED:
|
||||
return __('Recipient complained');
|
||||
default:
|
||||
return __('Unknown');
|
||||
}
|
||||
}
|
||||
|
||||
public function isErrorStatus()
|
||||
{
|
||||
if (in_array($this->status, [self::STATUS_SEND_ERROR, self::STATUS_DELIVERY_ERROR])) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function isSuccessStatus()
|
||||
{
|
||||
if (in_array($this->status, [self::STATUS_DELIVERY_SUCCESS])) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,8 +51,9 @@ class CreateThreadsTable extends Migration
|
||||
$table->integer('created_by_customer_id')->nullable();
|
||||
// ID of Saved reply that was used to create this Thread (savedReplyId)
|
||||
$table->integer('saved_reply_id')->nullable();
|
||||
// Status of the email sent to customer or user, to whom the thread is assigned
|
||||
//$table->unsignedTinyInteger('send_status')->default(Thread::SEND_STATUS_TOSEND);
|
||||
// Status of the email sent to the customer or user, to whom the thread is assigned
|
||||
// Values are in SendLog
|
||||
$table->unsignedTinyInteger('send_status')->nullable();
|
||||
// Text describing the sending status
|
||||
//$table->string('send_status_text', 255)->nullable();
|
||||
// Email opened by customer
|
||||
|
@ -2,9 +2,9 @@
|
||||
/**
|
||||
* Outgoing emails.
|
||||
*/
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateSendLogsTable extends Migration
|
||||
{
|
||||
@ -18,6 +18,7 @@ class CreateSendLogsTable extends Migration
|
||||
Schema::create('send_logs', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('thread_id')->index();
|
||||
// Customer ID is set only if email sent to the main conversation customer
|
||||
$table->integer('customer_id')->nullable();
|
||||
$table->integer('user_id')->nullable();
|
||||
// Message-ID header of the outgoing email
|
||||
@ -25,7 +26,7 @@ class CreateSendLogsTable extends Migration
|
||||
// We have to keep email as customer's or user's email may change
|
||||
$table->string('email', 191);
|
||||
$table->unsignedTinyInteger('status');
|
||||
$table->string('message', 255)->nullable();
|
||||
$table->string('status_message', 255)->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes
|
||||
|
18
public/js/main.js
vendored
18
public/js/main.js
vendored
@ -132,6 +132,12 @@ $(document).ready(function(){
|
||||
$.summernote.lang['en-US'].image.dropImage = Lang.get("messages.drag_image_file");
|
||||
}
|
||||
})(jQuery);
|
||||
|
||||
// Modal windows
|
||||
$('a[data-trigger="modal"]').click(function(e) {
|
||||
showModal($(this));
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
|
||||
function mailboxUpdateInit(from_name_custom)
|
||||
@ -422,6 +428,15 @@ function conversationInit()
|
||||
}
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// View Send Log
|
||||
jQuery(".thread-send-log-trigger").click(function(e){
|
||||
var thread_id = $(this).parents('.thread:first').attr('data-thread_id');
|
||||
if (!thread_id) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -707,6 +722,9 @@ function showModal(a, onshow)
|
||||
title = a.text();
|
||||
}
|
||||
var remote = a.attr('data-remote');
|
||||
if (!remote) {
|
||||
remote = a.attr('href');
|
||||
}
|
||||
var body = a.attr('data-modal-body');
|
||||
var footer = a.attr('data-modal-footer');
|
||||
var no_close_btn = a.attr('data-no-close-btn');
|
||||
|
59
resources/views/conversations/ajax_html/send_log.blade.php
Normal file
59
resources/views/conversations/ajax_html/send_log.blade.php
Normal file
@ -0,0 +1,59 @@
|
||||
@if (!$customers_log && !$users_log)
|
||||
<div class="alert alert-warning">{{ __("Log is empty") }}</div>
|
||||
@else
|
||||
@if (!empty($customers_log))
|
||||
<h5>{{ __("Emails to customers") }}</h5>
|
||||
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>{{ __("Customer") }}</th>
|
||||
<th>{{ __("Date") }}</th>
|
||||
<th>{{ __("Status") }}</th>
|
||||
</tr>
|
||||
@foreach ($customers_log as $email => $logs)
|
||||
@foreach ($logs as $log)
|
||||
<tr>
|
||||
@if ($loop->index == 0)
|
||||
<td rowspan="{{ count($logs) }}">{{ $email }}</td>
|
||||
@endif
|
||||
<td class="small">{{ App\User::dateFormat($log->created_at) }}</td>
|
||||
<td>
|
||||
<span class="@if ($log->isErrorStatus())text-danger @elseif ($log->isSuccessStatus()) text-success @endif">{{ $log->getStatusName() }}</span>
|
||||
@if ($log->status_message)
|
||||
<div class="text-help">{{ $log->status_message }}</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</table>
|
||||
@endif
|
||||
|
||||
@if (!empty($users_log))
|
||||
<h5>{{ __("Emails to users") }}</h5>
|
||||
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>{{ __("User") }}</th>
|
||||
<th>{{ __("Date") }}</th>
|
||||
<th>{{ __("Status") }}</th>
|
||||
</tr>
|
||||
@foreach ($users_log as $email => $logs)
|
||||
@foreach ($logs as $log)
|
||||
<tr>
|
||||
@if ($loop->index == 0)
|
||||
<td rowspan="{{ count($logs) }}">{{ $email }}</td>
|
||||
@endif
|
||||
<td class="small">{{ App\User::dateFormat($log->created_at) }}</td>
|
||||
<td>
|
||||
<span class="@if ($log->isErrorStatus())text-danger @elseif ($log->isSuccessStatus()) text-success @endif">{{ $log->getStatusName() }}</span>
|
||||
@if ($log->status_message)
|
||||
<div class="text-help">{{ $log->status_message }}</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</table>
|
||||
@endif
|
||||
@endif
|
@ -284,7 +284,11 @@
|
||||
<li><a href="#" title="" class="thread-edit-trigger">{{ __("Edit") }} (todo)</a></li>
|
||||
<li><a href="javascript:alert('todo: implement hiding threads');void(0);" title="" class="thread-hide-trigger">{{ __("Hide") }} (todo)</a></li>
|
||||
<li><a href="javascript:alert('todo: implement new conversation from thread');void(0);" title="{{ __("Start a conversation with this thread") }}" class="new-conv">{{ __("New Conversation") }}</a></li>
|
||||
<li><a href="#" title="{{ __("Show original email") }}" class="thread-orig-trigger">{{ __("Show Original") }} (todo)</a></li>
|
||||
@if (Auth::user()->isAdmin())
|
||||
<li><a href="#" title="{{ __("Show original email headers") }}" class="thread-orig-trigger">{{ __("Show Headers") }} (todo)</a></li>
|
||||
<li><a href="{{ route('conversations.ajax_html', ['action' =>
|
||||
'send_log']) }}?thread_id={{ $thread->id }}" title="{{ __("View email sending log") }}" class="thread-send-log-trigger" data-trigger="modal">{{ __("View Send Log") }}</a></li>
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -43,6 +43,7 @@ Route::post('/conversation/ajax', ['uses' => 'ConversationsController@ajax', 'la
|
||||
Route::post('/conversation/upload', ['uses' => 'ConversationsController@upload', 'laroute' => true])->name('conversations.upload');
|
||||
Route::get('/mailbox/{mailbox_id}/new-ticket', 'ConversationsController@create')->name('conversations.create');
|
||||
Route::get('/conversation/draft/{id}', 'ConversationsController@draft')->name('conversations.draft');
|
||||
Route::get('/conversation/ajax_html/{action}', ['uses' => 'ConversationsController@ajaxHtml', 'laroute' => true])->name('conversations.ajax_html');
|
||||
|
||||
// Mailboxes
|
||||
Route::get('/settings/mailboxes', 'MailboxesController@mailboxes')->name('mailboxes');
|
||||
|
Loading…
Reference in New Issue
Block a user