Compare commits
No commits in common. "8d0a45471370d3be5b3b352966874f7d696fe0e2" and "d61183c0a822aecd12574239a63d8f9061ed9178" have entirely different histories.
8d0a454713
...
d61183c0a8
6 changed files with 6 additions and 791 deletions
|
@ -1,8 +1,3 @@
|
||||||
1.1.2
|
|
||||||
|
|
||||||
* Fix creating a new Conversation with a new Customer.
|
|
||||||
* Fix the error shown when trying to create a Customer with no linked Mailbox.
|
|
||||||
|
|
||||||
1.1.1
|
1.1.1
|
||||||
|
|
||||||
* Fix the ability to start new Conversations.
|
* Fix the ability to start new Conversations.
|
||||||
|
|
|
@ -1,742 +0,0 @@
|
||||||
<?php
|
|
||||||
/*
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
SPDX-FileCopyrightText: © 2024 Millions Missing FRANCE <info@millionsmissing.fr>
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Modules\MMFRestrictedCustomers\Http\Controllers;
|
|
||||||
|
|
||||||
use Validator;
|
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
use App\Attachment;
|
|
||||||
use App\Conversation;
|
|
||||||
use App\Events\ConversationStatusChanged;
|
|
||||||
use App\Events\ConversationUserChanged;
|
|
||||||
use App\Events\UserAddedNote;
|
|
||||||
use App\Events\UserCreatedConversation;
|
|
||||||
use App\Events\UserReplied;
|
|
||||||
use App\Mailbox;
|
|
||||||
use App\MailboxUser;
|
|
||||||
use App\Thread;
|
|
||||||
|
|
||||||
use Modules\MMFRestrictedCustomers\Entities\Customer;
|
|
||||||
|
|
||||||
use App\Http\Controllers\ConversationsController as BaseConversationsController;
|
|
||||||
|
|
||||||
class ConversationsController extends BaseConversationsController {
|
|
||||||
/**
|
|
||||||
* Conversations ajax controller.
|
|
||||||
*/
|
|
||||||
public function ajax(Request $request) {
|
|
||||||
// This override is only required for "send_reply" requests.
|
|
||||||
if ( $request->action != 'send_reply' )
|
|
||||||
return parent::ajax($request);
|
|
||||||
|
|
||||||
$response = [
|
|
||||||
'status' => 'error',
|
|
||||||
'msg' => '', // this is error message
|
|
||||||
];
|
|
||||||
|
|
||||||
$user = auth()->user();
|
|
||||||
|
|
||||||
$mailbox = Mailbox::findOrFail($request->mailbox_id);
|
|
||||||
|
|
||||||
if (!$response['msg'] && !$user->can('view', $mailbox)) {
|
|
||||||
$response['msg'] = __('Not enough permissions');
|
|
||||||
}
|
|
||||||
|
|
||||||
$conversation = null;
|
|
||||||
if (!$response['msg'] && !empty($request->conversation_id)) {
|
|
||||||
$conversation = Conversation::find($request->conversation_id);
|
|
||||||
if ($conversation && !$user->can('view', $conversation)) {
|
|
||||||
$response['msg'] = __('Not enough permissions');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$new = false;
|
|
||||||
if (empty($request->conversation_id)) {
|
|
||||||
$new = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$is_note = false;
|
|
||||||
if (!empty($request->is_note)) {
|
|
||||||
$is_note = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conversation type.
|
|
||||||
$type = Conversation::TYPE_EMAIL;
|
|
||||||
if (!empty($request->type)) {
|
|
||||||
$type = (int)$request->type;
|
|
||||||
} elseif ($conversation) {
|
|
||||||
$type = $conversation->type;
|
|
||||||
}
|
|
||||||
|
|
||||||
$is_phone = false;
|
|
||||||
if ($type == Conversation::TYPE_PHONE) {
|
|
||||||
$is_phone = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$is_custom = false;
|
|
||||||
if ($type == Conversation::TYPE_CUSTOM) {
|
|
||||||
$is_custom = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$is_create = false;
|
|
||||||
if (!empty($request->is_create)) {
|
|
||||||
//if ($new || ($from_draft && $conversation->threads_count == 1)) {
|
|
||||||
$is_create = $request->is_create;
|
|
||||||
}
|
|
||||||
|
|
||||||
$is_forward = false;
|
|
||||||
if (!empty($request->subtype) && (int)$request->subtype == Thread::SUBTYPE_FORWARD) {
|
|
||||||
$is_forward = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$is_multiple = false;
|
|
||||||
if (!empty($request->multiple_conversations)) {
|
|
||||||
$is_multiple = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If reply is being created from draft, there is already thread created
|
|
||||||
$thread = null;
|
|
||||||
$from_draft = false;
|
|
||||||
if (( ! $is_note || $is_phone || $is_custom ) && ! $response['msg'] && ! empty($request->thread_id)) {
|
|
||||||
$thread = Thread::find($request->thread_id);
|
|
||||||
if ($thread && (!$conversation || $thread->conversation_id != $conversation->id)) {
|
|
||||||
$response['msg'] = __('Incorrect thread');
|
|
||||||
} else {
|
|
||||||
$from_draft = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$response['msg']) {
|
|
||||||
if ($thread && $from_draft && $thread->state == Thread::STATE_PUBLISHED) {
|
|
||||||
$response['msg'] = __('Message has been already sent. Please discard this draft.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate form
|
|
||||||
if (!$response['msg']) {
|
|
||||||
if ($new) {
|
|
||||||
if ($type == Conversation::TYPE_EMAIL) {
|
|
||||||
$validator = Validator::make($request->all(), [
|
|
||||||
'to' => 'required|array',
|
|
||||||
'subject' => 'required|string|max:998',
|
|
||||||
'body' => 'required|string',
|
|
||||||
'cc' => 'nullable|array',
|
|
||||||
'bcc' => 'nullable|array',
|
|
||||||
]);
|
|
||||||
} elseif ($type === Conversation::TYPE_PHONE) {
|
|
||||||
// Phone conversation.
|
|
||||||
$validator = Validator::make($request->all(), [
|
|
||||||
'name' => 'required|string',
|
|
||||||
'subject' => 'required|string|max:998',
|
|
||||||
'body' => 'required|string',
|
|
||||||
'phone' => 'nullable|string',
|
|
||||||
'to_email' => 'nullable|string',
|
|
||||||
]);
|
|
||||||
} elseif ($type === Conversation::TYPE_CUSTOM) {
|
|
||||||
$validation_rules = \Eventy::filter('conversation.custom.validation_rules', [
|
|
||||||
'body' => 'required|string',
|
|
||||||
'cc' => 'nullable|array',
|
|
||||||
'bcc' => 'nullable|array',
|
|
||||||
], $request);
|
|
||||||
$validator = Validator::make($request->all(), $validation_rules);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$validator = Validator::make($request->all(), [
|
|
||||||
'body' => 'required|string',
|
|
||||||
'cc' => 'nullable|array',
|
|
||||||
'bcc' => 'nullable|array',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($validator->fails()) {
|
|
||||||
foreach ($validator->errors()->getMessages()as $errors) {
|
|
||||||
foreach ($errors as $field => $message) {
|
|
||||||
$response['msg'] .= $message.' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = $request->body;
|
|
||||||
|
|
||||||
// Replace base64 images with attachment URLs in case text
|
|
||||||
// was copy and pasted into the editor.
|
|
||||||
// https://github.com/freescout-helpdesk/freescout/issues/3057
|
|
||||||
$body = Thread::replaceBase64ImagesWithAttachments($body);
|
|
||||||
|
|
||||||
// List of emails.
|
|
||||||
$to_array = [];
|
|
||||||
if ($is_forward) {
|
|
||||||
$to_array = Conversation::sanitizeEmails($request->to_email);
|
|
||||||
} else {
|
|
||||||
$to_array = Conversation::sanitizeEmails($request->to);
|
|
||||||
}
|
|
||||||
// Check To
|
|
||||||
if (! $response['msg'] && $new && ! $is_phone && ! $is_custom) {
|
|
||||||
if (!$to_array) {
|
|
||||||
$response['msg'] .= __('Incorrect recipients');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check max. message size.
|
|
||||||
if (!$response['msg']) {
|
|
||||||
|
|
||||||
$max_message_size = (int)config('app.max_message_size');
|
|
||||||
if ($max_message_size) {
|
|
||||||
// Todo: take into account conversation history.
|
|
||||||
$message_size = mb_strlen($body, '8bit');
|
|
||||||
|
|
||||||
// Calculate attachments size.
|
|
||||||
$attachments_ids = array_merge($request->attachments ?? [], $request->embeds ?? []);
|
|
||||||
$attachments_ids = $this->decodeAttachmentsIds($attachments_ids);
|
|
||||||
|
|
||||||
if (count($attachments_ids)) {
|
|
||||||
$attachments_to_check = Attachment::select('size')->whereIn('id', $attachments_ids)->get();
|
|
||||||
foreach ($attachments_to_check as $attachment) {
|
|
||||||
$message_size += (int)$attachment->size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($message_size > $max_message_size*1024*1024) {
|
|
||||||
$response['msg'] = __('Message is too large — :info. Please shorten your message or remove some attachments.', ['info' => __('Max. Message Size').': '.$max_message_size.' MB']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$response['msg']) {
|
|
||||||
|
|
||||||
// Get attachments info
|
|
||||||
// Delete removed attachments.
|
|
||||||
$attachments_info = $this->processReplyAttachments($request);
|
|
||||||
|
|
||||||
// Determine redirect.
|
|
||||||
// Must be done before updating current conversation's status or assignee.
|
|
||||||
// Redirect URL for new no saved yet conversation is determined below.
|
|
||||||
if (!$new) {
|
|
||||||
$response['redirect_url'] = $this->getRedirectUrl($request, $conversation, $user);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conversation
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
$status_changed = false;
|
|
||||||
$user_changed = false;
|
|
||||||
// Chat conversations in chat mode can not be undone.
|
|
||||||
$can_undo = true;
|
|
||||||
|
|
||||||
$request_status = (int)$request->status;
|
|
||||||
|
|
||||||
if ($new) {
|
|
||||||
// New conversation
|
|
||||||
$conversation = new Conversation();
|
|
||||||
$conversation->type = $type;
|
|
||||||
$conversation->subject = $request->subject;
|
|
||||||
$conversation->setPreview($body);
|
|
||||||
$conversation->mailbox_id = $request->mailbox_id;
|
|
||||||
$conversation->created_by_user_id = auth()->user()->id;
|
|
||||||
$conversation->source_via = Conversation::PERSON_USER;
|
|
||||||
$conversation->source_type = Conversation::SOURCE_TYPE_WEB;
|
|
||||||
} else {
|
|
||||||
// Reply or note
|
|
||||||
if ($request_status && $request_status != (int)$conversation->status) {
|
|
||||||
$status_changed = true;
|
|
||||||
}
|
|
||||||
if (!empty($request->subject)) {
|
|
||||||
$conversation->subject = $request->subject;
|
|
||||||
}
|
|
||||||
// When switching from regular message to phone and message sent
|
|
||||||
// without saving a draft type need to be saved here.
|
|
||||||
// Or vise versa.
|
|
||||||
if (($conversation->type == Conversation::TYPE_EMAIL && $type == Conversation::TYPE_PHONE)
|
|
||||||
|| ($conversation->type == Conversation::TYPE_PHONE && $type == Conversation::TYPE_EMAIL)
|
|
||||||
) {
|
|
||||||
$conversation->type = $type;
|
|
||||||
}
|
|
||||||
// Allow to convert phone conversations into email conversations.
|
|
||||||
if ($conversation->isPhone() && !$is_note && $conversation->customer
|
|
||||||
&& $customer_email = $conversation->customer->getMainEmail()
|
|
||||||
) {
|
|
||||||
$conversation->type = Conversation::TYPE_EMAIL;
|
|
||||||
$conversation->customer_email = $customer_email;
|
|
||||||
$is_phone = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($attachments_info['has_attachments']) {
|
|
||||||
$conversation->has_attachments = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Customer can be empty in existing conversation if this is a draft.
|
|
||||||
$customer_email = '';
|
|
||||||
$customer = null;
|
|
||||||
|
|
||||||
if ($is_phone && $is_create) {
|
|
||||||
// Phone.
|
|
||||||
$phone_customer_data = $this->processPhoneCustomer($request);
|
|
||||||
|
|
||||||
$customer_email = $phone_customer_data['customer_email'];
|
|
||||||
$customer = $phone_customer_data['customer'];
|
|
||||||
if (! $conversation->customer_id) {
|
|
||||||
$conversation->customer_id = $customer->id;
|
|
||||||
}
|
|
||||||
} elseif ($is_custom) {
|
|
||||||
// No customer for custom conversations.
|
|
||||||
} else {
|
|
||||||
// Email or reply to a phone conversation.
|
|
||||||
if (!empty($to_array)) {
|
|
||||||
$customer_email = $to_array[0];
|
|
||||||
} elseif (!$conversation->customer_email
|
|
||||||
&& ($conversation->isEmail() || $conversation->isPhone())
|
|
||||||
&& $conversation->customer_id
|
|
||||||
&& $conversation->customer
|
|
||||||
) {
|
|
||||||
// When replying to a phone conversation, we need to
|
|
||||||
// set 'customer_email' for the conversation.
|
|
||||||
$customer_email = $conversation->customer->getMainEmail();
|
|
||||||
}
|
|
||||||
if (!$conversation->customer_id) {
|
|
||||||
// The new Customer must be linked to a specific Mailbox.
|
|
||||||
$data = [];
|
|
||||||
$data['mailbox_id'] = $conversation->mailbox_id;
|
|
||||||
$customer = Customer::create($customer_email, $data);
|
|
||||||
$conversation->customer_id = $customer->id;
|
|
||||||
} else {
|
|
||||||
$customer = $conversation->customer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($customer_email && !$is_note && !$is_forward) {
|
|
||||||
$conversation->customer_email = $customer_email;
|
|
||||||
}
|
|
||||||
|
|
||||||
$prev_status = $conversation->status;
|
|
||||||
|
|
||||||
$conversation->status = $request_status ?: $conversation->status;
|
|
||||||
|
|
||||||
if (($prev_status != $conversation->status || $is_create)
|
|
||||||
&& $conversation->status == Conversation::STATUS_CLOSED
|
|
||||||
) {
|
|
||||||
$conversation->closed_by_user_id = $user->id;
|
|
||||||
$conversation->closed_at = date('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to set state, as it may have been a draft.
|
|
||||||
$prev_state = $conversation->state;
|
|
||||||
$conversation->state = Conversation::STATE_PUBLISHED;
|
|
||||||
|
|
||||||
// Set assignee
|
|
||||||
$prev_user_id = $conversation->user_id;
|
|
||||||
if ((int) $request->user_id != -1) {
|
|
||||||
// Check if user has access to the current mailbox
|
|
||||||
if ((int) $conversation->user_id != (int) $request->user_id && $mailbox->userHasAccess($request->user_id)) {
|
|
||||||
$conversation->user_id = $request->user_id;
|
|
||||||
$user_changed = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$conversation->user_id = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// To is a single email string.
|
|
||||||
$to = '';
|
|
||||||
// List of emails.
|
|
||||||
$to_list = [];
|
|
||||||
if ($is_forward) {
|
|
||||||
if (empty($request->to_email[0])) {
|
|
||||||
$response['msg'] = __('Please specify a recipient.');
|
|
||||||
return \Response::json($response);
|
|
||||||
}
|
|
||||||
$to = $request->to_email[0];
|
|
||||||
} else {
|
|
||||||
if (!empty($request->to)) {
|
|
||||||
// When creating a new conversation, to is a list of emails.
|
|
||||||
if (is_array($request->to)) {
|
|
||||||
$to = $request->to[0];
|
|
||||||
} else {
|
|
||||||
$to = $request->to;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$to = $conversation->customer_email;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$is_note && !$is_forward) {
|
|
||||||
// Save extra recipients to CC
|
|
||||||
if ($is_create && !$is_multiple && count($to_array) > 1) {
|
|
||||||
$conversation->setCc(array_merge(Conversation::sanitizeEmails($request->cc), $to_array));
|
|
||||||
} else {
|
|
||||||
if (!$is_multiple) {
|
|
||||||
$conversation->setCc(array_merge(Conversation::sanitizeEmails($request->cc), [$to]));
|
|
||||||
} else {
|
|
||||||
$conversation->setCc(Conversation::sanitizeEmails($request->cc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$conversation->setBcc($request->bcc);
|
|
||||||
$conversation->last_reply_at = $now;
|
|
||||||
$conversation->last_reply_from = Conversation::PERSON_USER;
|
|
||||||
$conversation->user_updated_at = $now;
|
|
||||||
}
|
|
||||||
if ($conversation->isPhone() && $is_note) {
|
|
||||||
$conversation->last_reply_at = $now;
|
|
||||||
$conversation->last_reply_from = Conversation::PERSON_USER;
|
|
||||||
}
|
|
||||||
$conversation->updateFolder();
|
|
||||||
if ($from_draft) {
|
|
||||||
// Increment number of replies in conversation
|
|
||||||
$conversation->threads_count++;
|
|
||||||
// We need to set preview here as when conversation is created from draft,
|
|
||||||
// ThreadObserver::created() method is not called.
|
|
||||||
$conversation->setPreview($body);
|
|
||||||
}
|
|
||||||
$conversation->save();
|
|
||||||
|
|
||||||
// Redirect URL for new not saved yet conversation must be determined here.
|
|
||||||
if ($new) {
|
|
||||||
$response['redirect_url'] = $this->getRedirectUrl($request, $conversation, $user);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire events
|
|
||||||
\Eventy::action('conversation.send_reply_save', $conversation, $request);
|
|
||||||
|
|
||||||
if (!$new) {
|
|
||||||
if ($status_changed) {
|
|
||||||
event(new ConversationStatusChanged($conversation));
|
|
||||||
\Eventy::action('conversation.status_changed', $conversation, $user, $changed_on_reply = true, $prev_status);
|
|
||||||
}
|
|
||||||
if ($user_changed) {
|
|
||||||
event(new ConversationUserChanged($conversation, $user));
|
|
||||||
\Eventy::action('conversation.user_changed', $conversation, $user, $prev_user_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($conversation->state != $prev_state) {
|
|
||||||
\Eventy::action('conversation.state_changed', $conversation, $user, $prev_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create thread
|
|
||||||
if (!$thread) {
|
|
||||||
$thread = new Thread();
|
|
||||||
$thread->conversation_id = $conversation->id;
|
|
||||||
if ($is_note || $is_forward) {
|
|
||||||
$thread->type = Thread::TYPE_NOTE;
|
|
||||||
} else {
|
|
||||||
$thread->type = Thread::TYPE_MESSAGE;
|
|
||||||
}
|
|
||||||
$thread->source_via = Thread::PERSON_USER;
|
|
||||||
$thread->source_type = Thread::SOURCE_TYPE_WEB;
|
|
||||||
} else {
|
|
||||||
if ($is_forward || $is_phone) {
|
|
||||||
$thread->type = Thread::TYPE_NOTE;
|
|
||||||
} else {
|
|
||||||
$thread->type = Thread::TYPE_MESSAGE;
|
|
||||||
}
|
|
||||||
$thread->created_at = $now;
|
|
||||||
}
|
|
||||||
if ($new) {
|
|
||||||
$thread->first = true;
|
|
||||||
}
|
|
||||||
$thread->user_id = $conversation->user_id;
|
|
||||||
$thread->status = $request_status ?? $conversation->status;
|
|
||||||
$thread->state = Thread::STATE_PUBLISHED;
|
|
||||||
if (!$is_custom) {
|
|
||||||
$thread->customer_id = $customer->id;
|
|
||||||
}
|
|
||||||
$thread->created_by_user_id = auth()->user()->id;
|
|
||||||
$thread->edited_by_user_id = null;
|
|
||||||
$thread->edited_at = null;
|
|
||||||
$thread->body = $body;
|
|
||||||
if ($is_create && !$is_multiple && count($to_array) > 1) {
|
|
||||||
$thread->setTo($to_array);
|
|
||||||
} else {
|
|
||||||
$thread->setTo($to);
|
|
||||||
}
|
|
||||||
// We save CC and BCC as is and filter emails when sending replies
|
|
||||||
$thread->setCc($request->cc);
|
|
||||||
$thread->setBcc($request->bcc);
|
|
||||||
if ($attachments_info['has_attachments'] && !$is_forward) {
|
|
||||||
$thread->has_attachments = true;
|
|
||||||
}
|
|
||||||
if (!empty($request->saved_reply_id)) {
|
|
||||||
$thread->saved_reply_id = $request->saved_reply_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
$forwarded_conversations = [];
|
|
||||||
$forwarded_threads = [];
|
|
||||||
|
|
||||||
if ($is_forward) {
|
|
||||||
// Create forwarded conversations.
|
|
||||||
foreach ($to_array as $recipient_email) {
|
|
||||||
$forwarded_conversation = $conversation->replicate();
|
|
||||||
$forwarded_conversation->type = Conversation::TYPE_EMAIL;
|
|
||||||
$forwarded_conversation->setPreview($thread->body);
|
|
||||||
$forwarded_conversation->created_by_user_id = auth()->user()->id;
|
|
||||||
$forwarded_conversation->source_via = Conversation::PERSON_USER;
|
|
||||||
$forwarded_conversation->source_type = Conversation::SOURCE_TYPE_WEB;
|
|
||||||
$forwarded_conversation->threads_count = 0; // Counter will be incremented in ThreadObserver.
|
|
||||||
$forwarded_customer = Customer::create($recipient_email);
|
|
||||||
$forwarded_conversation->customer_id = $forwarded_customer->id;
|
|
||||||
// Reload customer object, otherwise it stores previous customer.
|
|
||||||
$forwarded_conversation->load('customer');
|
|
||||||
$forwarded_conversation->customer_email = $recipient_email;
|
|
||||||
$forwarded_conversation->subject = 'Fwd: '.$forwarded_conversation->subject;
|
|
||||||
//$forwarded_conversation->setCc(array_merge(Conversation::sanitizeEmails($request->cc), [$to]));
|
|
||||||
$forwarded_conversation->setCc(Conversation::sanitizeEmails($request->cc));
|
|
||||||
$forwarded_conversation->setBcc($request->bcc);
|
|
||||||
$forwarded_conversation->last_reply_at = $now;
|
|
||||||
$forwarded_conversation->last_reply_from = Conversation::PERSON_USER;
|
|
||||||
$forwarded_conversation->user_updated_at = $now;
|
|
||||||
if ($attachments_info['has_attachments']) {
|
|
||||||
$forwarded_conversation->has_attachments = true;
|
|
||||||
}
|
|
||||||
$forwarded_conversation->updateFolder();
|
|
||||||
$forwarded_conversation->save();
|
|
||||||
|
|
||||||
$forwarded_thread = $thread->replicate();
|
|
||||||
|
|
||||||
$forwarded_conversations[] = $forwarded_conversation;
|
|
||||||
$forwarded_threads[] = $forwarded_thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set forwarding meta data.
|
|
||||||
// todo: store array of numbers and IDs.
|
|
||||||
$thread->subtype = Thread::SUBTYPE_FORWARD;
|
|
||||||
$thread->setMeta(Thread::META_FORWARD_CHILD_CONVERSATION_NUMBER, $forwarded_conversation->number);
|
|
||||||
$thread->setMeta(Thread::META_FORWARD_CHILD_CONVERSATION_ID, $forwarded_conversation->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conversation history.
|
|
||||||
if (!empty($request->conv_history)) {
|
|
||||||
if ($request->conv_history != 'global') {
|
|
||||||
if ($is_forward && !empty($forwarded_threads)) {
|
|
||||||
foreach ($forwarded_threads as $forwarded_thread) {
|
|
||||||
$forwarded_thread->setMeta(Thread::META_CONVERSATION_HISTORY, $request->conv_history);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$thread->setMeta(Thread::META_CONVERSATION_HISTORY, $request->conv_history);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// From (mailbox alias).
|
|
||||||
if (!empty($request->from_alias)) {
|
|
||||||
$thread->from = $request->from_alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
\Eventy::action('thread.before_save_from_request', $thread, $request);
|
|
||||||
$thread->save();
|
|
||||||
|
|
||||||
// Save forwarded thread.
|
|
||||||
if ($is_forward) {
|
|
||||||
foreach ($forwarded_conversations as $i => $forwarded_conversation) {
|
|
||||||
$forwarded_thread = $forwarded_threads[$i];
|
|
||||||
|
|
||||||
$forwarded_thread->conversation_id = $forwarded_conversation->id;
|
|
||||||
$forwarded_thread->type = Thread::TYPE_MESSAGE;
|
|
||||||
$forwarded_thread->subtype = null;
|
|
||||||
if ($attachments_info['has_attachments']) {
|
|
||||||
$forwarded_thread->has_attachments = true;
|
|
||||||
}
|
|
||||||
$forwarded_thread->setMeta(Thread::META_FORWARD_PARENT_CONVERSATION_NUMBER, $conversation->number);
|
|
||||||
$forwarded_thread->setMeta(Thread::META_FORWARD_PARENT_CONVERSATION_ID, $conversation->id);
|
|
||||||
$forwarded_thread->setMeta(Thread::META_FORWARD_PARENT_THREAD_ID, $thread->id);
|
|
||||||
\Eventy::action('send_reply.before_save_forwarded_thread', $forwarded_thread, $request);
|
|
||||||
$forwarded_thread->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If thread has been created from draft, remove the draft
|
|
||||||
// if ($request->thread_id) {
|
|
||||||
// $draft_thread = Thread::find($request->thread_id);
|
|
||||||
// if ($draft_thread) {
|
|
||||||
// $draft_thread->delete();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if ($from_draft) {
|
|
||||||
// Remove conversation from drafts folder if needed
|
|
||||||
$conversation->maybeRemoveFromDrafts();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update folders counters
|
|
||||||
$conversation->mailbox->updateFoldersCounters();
|
|
||||||
|
|
||||||
$response['status'] = 'success';
|
|
||||||
|
|
||||||
// Set thread_id for uploaded attachments
|
|
||||||
if ($attachments_info['attachments']) {
|
|
||||||
if ($is_forward) {
|
|
||||||
// Copy attachments for each thread.
|
|
||||||
if (count($forwarded_threads) > 1) {
|
|
||||||
$attachments = Attachment::whereIn('id', $attachments_info['attachments'])->get();
|
|
||||||
}
|
|
||||||
foreach ($forwarded_threads as $i => $forwarded_thread) {
|
|
||||||
if ($i == 0) {
|
|
||||||
Attachment::whereIn('id', $attachments_info['attachments'])->update(['thread_id' => $forwarded_thread->id]);
|
|
||||||
} else {
|
|
||||||
foreach ($attachments as $attachment) {
|
|
||||||
$attachment->duplicate($forwarded_thread->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Attachment::whereIn('id', $attachments_info['attachments'])
|
|
||||||
->where('thread_id', null)
|
|
||||||
->update(['thread_id' => $thread->id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Follow conversation if it's assigned to someone else.
|
|
||||||
if (!$is_create && !$new && !$is_forward && !$is_note
|
|
||||||
&& $conversation->user_id != $user->id
|
|
||||||
) {
|
|
||||||
$user->followConversation($conversation->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($conversation->isChat() && \Helper::isChatMode()) {
|
|
||||||
$can_undo = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When user creates a new conversation it may be saved as draft first.
|
|
||||||
if ($is_create) {
|
|
||||||
// New conversation.
|
|
||||||
event(new UserCreatedConversation($conversation, $thread));
|
|
||||||
\Eventy::action('conversation.created_by_user_can_undo', $conversation, $thread);
|
|
||||||
// After Conversation::UNDO_TIMOUT period trigger final event.
|
|
||||||
\Helper::backgroundAction('conversation.created_by_user', [$conversation, $thread], now()->addSeconds($this->getUndoTimeout($can_undo)));
|
|
||||||
} elseif ($is_forward) {
|
|
||||||
// Forward.
|
|
||||||
// Notifications to users not sent.
|
|
||||||
event(new UserAddedNote($conversation, $thread));
|
|
||||||
foreach ($forwarded_conversations as $i => $forwarded_conversation) {
|
|
||||||
$forwarded_thread = $forwarded_threads[$i];
|
|
||||||
|
|
||||||
// To send email with forwarded conversation.
|
|
||||||
event(new UserReplied($forwarded_conversation, $forwarded_thread));
|
|
||||||
\Eventy::action('conversation.user_forwarded_can_undo', $conversation, $thread, $forwarded_conversation, $forwarded_thread);
|
|
||||||
// After Conversation::UNDO_TIMOUT period trigger final event.
|
|
||||||
\Helper::backgroundAction('conversation.user_forwarded', [$conversation, $thread, $forwarded_conversation, $forwarded_thread], now()->addSeconds(Conversation::UNDO_TIMOUT));
|
|
||||||
}
|
|
||||||
} elseif ($is_note) {
|
|
||||||
// Note.
|
|
||||||
event(new UserAddedNote($conversation, $thread));
|
|
||||||
\Eventy::action('conversation.note_added', $conversation, $thread);
|
|
||||||
} else {
|
|
||||||
// Reply.
|
|
||||||
event(new UserReplied($conversation, $thread));
|
|
||||||
\Eventy::action('conversation.user_replied_can_undo', $conversation, $thread);
|
|
||||||
// After Conversation::UNDO_TIMOUT period trigger final event.
|
|
||||||
\Helper::backgroundAction('conversation.user_replied', [$conversation, $thread], now()->addSeconds($this->getUndoTimeout($can_undo)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send new conversation separately to each customer.
|
|
||||||
if ($is_create && count($to_array) > 1 && $is_multiple) {
|
|
||||||
$prev_customers_ids = [];
|
|
||||||
foreach ($to_array as $i => $customer_email) {
|
|
||||||
// Skip first email, as conversation has already been created for it.
|
|
||||||
if ($i == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Get customer by email.
|
|
||||||
$customer_tmp = Customer::getByEmail($customer_email);
|
|
||||||
// Skip same customers.
|
|
||||||
if ($customer_tmp && in_array($customer_tmp->id, $prev_customers_ids)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$customer_tmp) {
|
|
||||||
$customer_tmp = Customer::create($customer_email);
|
|
||||||
}
|
|
||||||
|
|
||||||
$prev_customers_ids[] = $customer_tmp->id;
|
|
||||||
|
|
||||||
// Copy conversation and thread.
|
|
||||||
$conversation_copy = $conversation->replicate();
|
|
||||||
$thread_copy = $thread->replicate();
|
|
||||||
|
|
||||||
// Save conversation.
|
|
||||||
$conversation_copy->threads_count = 0;
|
|
||||||
$conversation_copy->customer_id = $customer_tmp->id;
|
|
||||||
// Reload customer, otherwise all recipients will have the same name.
|
|
||||||
$conversation_copy->load('customer');
|
|
||||||
$conversation_copy->customer_email = $customer_email;
|
|
||||||
$conversation_copy->has_attachments = $conversation->has_attachments;
|
|
||||||
$conversation_copy->push();
|
|
||||||
|
|
||||||
$thread_copy->conversation_id = $conversation_copy->id;
|
|
||||||
$thread_copy->customer_id = $customer_tmp->id;
|
|
||||||
$thread_copy->has_attachments = $conversation->has_attachments;
|
|
||||||
$thread_copy->setTo($customer_email);
|
|
||||||
// Reload the conversation, otherwise Thread observer will be
|
|
||||||
// increasing threads_count for the first conversation.
|
|
||||||
$thread_copy->load('conversation');
|
|
||||||
$thread_copy->push();
|
|
||||||
|
|
||||||
// Copy attachments.
|
|
||||||
if (!empty($attachments_info['attachments'])) {
|
|
||||||
$attachments = Attachment::whereIn('id', $attachments_info['attachments'])->get();
|
|
||||||
foreach ($attachments as $attachment) {
|
|
||||||
$attachment->duplicate($thread_copy->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Events.
|
|
||||||
// todo: allow to undo all emails
|
|
||||||
event(new UserCreatedConversation($conversation_copy, $thread_copy));
|
|
||||||
\Eventy::action('conversation.created_by_user_can_undo', $conversation_copy, $thread_copy);
|
|
||||||
// After Conversation::UNDO_TIMOUT period trigger final event.
|
|
||||||
\Helper::backgroundAction('conversation.created_by_user', [$conversation_copy, $thread_copy], now()->addSeconds($this->getUndoTimeout($can_undo)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compose flash message.
|
|
||||||
$show_view_link = true;
|
|
||||||
if (!empty($request->after_send) && $request->after_send == MailboxUser::AFTER_SEND_STAY) {
|
|
||||||
$show_view_link = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$flash_vars = ['%tag_start%' => '<strong>', '%tag_end%' => '</strong>', '%view_start%' => ' <a href="'.$conversation->url().'">', '%a_end%' => '</a> ', '%undo_start%' => ' <a href="'.route('conversations.undo', ['thread_id' => $thread->id]).'" class="text-danger">'];
|
|
||||||
|
|
||||||
if ($is_phone) {
|
|
||||||
$flash_type = 'warning';
|
|
||||||
if ($show_view_link) {
|
|
||||||
$flash_text = __(':%tag_start%Conversation created:%tag_end% :%view_start%View:%a_end% or :%undo_start%Undo:%a_end%', $flash_vars);
|
|
||||||
} else {
|
|
||||||
$flash_text = '<strong>'.__('Conversation created').'</strong>';
|
|
||||||
}
|
|
||||||
} elseif ($is_custom) {
|
|
||||||
$flash_type = 'warning';
|
|
||||||
$identifier = \Eventy::filter('conversation.custom.identifier', __('Custom conversation'), $request);
|
|
||||||
if ($show_view_link) {
|
|
||||||
$flash_text = __(':%tag_start%' . $identifier . ' added:%tag_end% :%view_start%View:%a_end%', $flash_vars);
|
|
||||||
} else {
|
|
||||||
$flash_text = '<strong>'.__('%identifier% added',['%identifier%'=>$identifier]).'</strong>';
|
|
||||||
}
|
|
||||||
} elseif ($is_note) {
|
|
||||||
$flash_type = 'warning';
|
|
||||||
if ($show_view_link) {
|
|
||||||
$flash_text = __(':%tag_start%Note added:%tag_end% :%view_start%View:%a_end%', $flash_vars);
|
|
||||||
} else {
|
|
||||||
$flash_text = '<strong>'.__('Note added').'</strong>';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$flash_type = 'success';
|
|
||||||
if ($show_view_link) {
|
|
||||||
$flash_text = __(':%tag_start%Email Sent:%tag_end% :%view_start%View:%a_end% or :%undo_start%Undo:%a_end%', $flash_vars);
|
|
||||||
} else {
|
|
||||||
$flash_text = __(':%tag_start%Email Sent:%tag_end% :%undo_start%Undo:%a_end%', $flash_vars);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($can_undo) {
|
|
||||||
\Session::flash('flash_'.$flash_type.'_floating', $flash_text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($response['status'] == 'error' && empty($response['msg'])) {
|
|
||||||
$response['msg'] = 'Unknown error occurred';
|
|
||||||
}
|
|
||||||
|
|
||||||
return \Response::json($response);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -70,13 +70,7 @@ class MMFRestrictedCustomersServiceProvider extends ServiceProvider {
|
||||||
if ( array_key_exists('mailbox_id', $data) ) {
|
if ( array_key_exists('mailbox_id', $data) ) {
|
||||||
$customer->mailbox_id = $data['mailbox_id'];
|
$customer->mailbox_id = $data['mailbox_id'];
|
||||||
} else {
|
} else {
|
||||||
$customer_id = $customer->id;
|
$customer = Customer::find($customer->id);
|
||||||
if ( $customer_id == null ) {
|
|
||||||
Log::error('The creation of a Customer with no link to a Mailbox is not allowed, and has been aborted.');
|
|
||||||
// TODO: Error out more gracefully.
|
|
||||||
abort(500, 'The creation of a Customer with no link to a Mailbox is not allowed, and has been aborted.');
|
|
||||||
}
|
|
||||||
$customer = Customer::find($customer_id);
|
|
||||||
if ( $customer == null ) {
|
if ( $customer == null ) {
|
||||||
Log::error('Trying to use a Customer in another Mailbox than the one it is linked to is not allowed, and has been aborted.');
|
Log::error('Trying to use a Customer in another Mailbox than the one it is linked to is not allowed, and has been aborted.');
|
||||||
// TODO: Error out more gracefully.
|
// TODO: Error out more gracefully.
|
||||||
|
|
38
README.md
38
README.md
|
@ -33,11 +33,11 @@ So it is highly recommended to backup your customers database:
|
||||||
|
|
||||||
Download the [release tarball] and extract its content into `Modules/MMFRestrictedCustomers`.
|
Download the [release tarball] and extract its content into `Modules/MMFRestrictedCustomers`.
|
||||||
|
|
||||||
[release tarball]: https://port.numenaute.org/MMF/freescout-restricted-customers/archive/1.1.2.tar.gz
|
[release tarball]: https://port.numenaute.org/MMF/freescout-restricted-customers/archive/1.1.1.tar.gz
|
||||||
|
|
||||||
```
|
```
|
||||||
wget https://port.numenaute.org/MMF/freescout-restricted-customers/archive/1.1.2.tar.gz -O freescout-restricted-customers-1.1.2.tar.gz
|
wget https://port.numenaute.org/MMF/freescout-restricted-customers/archive/1.1.1.tar.gz -O freescout-restricted-customers-1.1.1.tar.gz
|
||||||
tar xf freescout-restricted-customers-1.1.2.tar.gz -C Modules
|
tar xf freescout-restricted-customers-1.1.1.tar.gz -C Modules
|
||||||
mv Modules/freescout-restricted-customers Modules/MMFRestrictedCustomers
|
mv Modules/freescout-restricted-customers Modules/MMFRestrictedCustomers
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -76,38 +76,6 @@ Route::get('/customers/ajax-search', ['uses' => '\Modules\MMFRestrictedCustomers
|
||||||
Route::post('/customers/ajax', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@ajax', 'laroute' => true])->name('customers.ajax');
|
Route::post('/customers/ajax', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@ajax', 'laroute' => true])->name('customers.ajax');
|
||||||
```
|
```
|
||||||
|
|
||||||
This other section:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Conversations
|
|
||||||
Route::get('/conversation/{id}', ['uses' => 'ConversationsController@view', 'laroute' => true])->name('conversations.view');
|
|
||||||
Route::post('/conversation/ajax', ['uses' => 'ConversationsController@ajax', 'laroute' => true])->name('conversations.ajax');
|
|
||||||
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('/mailbox/{mailbox_id}/clone-ticket/{from_thread_id}', 'ConversationsController@cloneConversation')->name('conversations.clone_conversation');
|
|
||||||
//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');
|
|
||||||
Route::get('/search', 'ConversationsController@search')->name('conversations.search');
|
|
||||||
Route::get('/conversation/undo-reply/{thread_id}', 'ConversationsController@undoReply')->name('conversations.undo');
|
|
||||||
Route::get('/mailbox/{mailbox_id}/chats', 'ConversationsController@chats')->name('conversations.chats');
|
|
||||||
```
|
|
||||||
|
|
||||||
should be replaced with:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Conversations
|
|
||||||
Route::get('/conversation/{id}', ['uses' => 'ConversationsController@view', 'laroute' => true])->name('conversations.view');
|
|
||||||
Route::post('/conversation/ajax', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\ConversationsController@ajax', 'laroute' => true])->name('conversations.ajax');
|
|
||||||
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('/mailbox/{mailbox_id}/clone-ticket/{from_thread_id}', 'ConversationsController@cloneConversation')->name('conversations.clone_conversation');
|
|
||||||
//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');
|
|
||||||
Route::get('/search', 'ConversationsController@search')->name('conversations.search');
|
|
||||||
Route::get('/conversation/undo-reply/{thread_id}', 'ConversationsController@undoReply')->name('conversations.undo');
|
|
||||||
Route::get('/mailbox/{mailbox_id}/chats', 'ConversationsController@chats')->name('conversations.chats');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Edit the artisan commands
|
### Edit the artisan commands
|
||||||
|
|
||||||
Console commands set in other modules or in Freescout itself can not be automatically overridden.
|
Console commands set in other modules or in Freescout itself can not be automatically overridden.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "millions-missing-france/freescout-restricted-customers",
|
"name": "millions-missing-france/freescout-restricted-customers",
|
||||||
"description": "Freescout restricted customers - Restrict access to Freescout customers to specific mailboxes",
|
"description": "Freescout restricted customers - Restrict access to Freescout customers to specific mailboxes",
|
||||||
"version": "1.1.2",
|
"version": "1.1.1",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"license": ["AGPL-3.0-only"],
|
"license": ["AGPL-3.0-only"],
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "MMFRestrictedCustomers",
|
"name": "MMFRestrictedCustomers",
|
||||||
"alias": "mmfrestrictedcustomers",
|
"alias": "mmfrestrictedcustomers",
|
||||||
"description": "Freescout restricted customers - Restrict access to Freescout customers to specific mailboxes",
|
"description": "Freescout restricted customers - Restrict access to Freescout customers to specific mailboxes",
|
||||||
"version": "1.1.2",
|
"version": "1.1.1",
|
||||||
"detailsUrl": "",
|
"detailsUrl": "",
|
||||||
"author": "Millions Missing FRANCE",
|
"author": "Millions Missing FRANCE",
|
||||||
"authorUrl": "info@millionsmissing.fr",
|
"authorUrl": "info@millionsmissing.fr",
|
||||||
|
|
Loading…
Reference in a new issue