Updated the flow of customer message and customer service

This commit is contained in:
vishalsingh.qlo325
2025-02-24 11:08:44 +05:30
parent b13ef0a185
commit b4f03b3917
25 changed files with 553 additions and 289 deletions

View File

@@ -48,7 +48,22 @@
@include left(1px)
@include box-shadow(white 0 0 0 3px inset)
.submitReply
i[class^="icon-"]
display: block
width: 30px
height: 30px
margin: 0 auto
font-size: 28px
background: transparent
background-size: 26px
background-position: center
.table.customer_thread, #MeaningStatus
.text-success
color: #56CE56
.text-warning
color: #FCC94F
.message-item-initial
h2

View File

@@ -84,6 +84,11 @@
.panel-refund-request .refunded_amount
margin-top: 2px
.order-notes
.panel-content
max-height: 240px
overflow: auto
.panel-guest_address
.panel-heading
align-items: center

View File

@@ -51,7 +51,7 @@
<span class="message-mail">{l s='No new messages'}</span>
{else}
<a href="{$currentIndex|escape:'html':'UTF-8'}&amp;token={$token|escape:'html':'UTF-8'}&amp;id_customer_thread={$id_customer_thread}&amp;viewcustomer_thread" class="button">
{$total_thread}
{$total_thread}
{if $total_thread > 1}{l s='New messages'}{else}{l s='New message'}{/if}
</a>
{/if}
@@ -64,10 +64,10 @@
{l s='Meaning of status'}
</div>
<ul class="list-unstyled">
<li class="text-success"><i class="icon-circle"></i> {l s='Open'}</li>
<li class="text-danger"><i class="icon-circle"></i> {l s='Closed'}</li>
<li class="text-warning"><i class="icon-circle"></i> {l s='Pending 1'}</li>
<li class="text-warning"><i class="icon-circle"></i> {l s='Pending 2'}</li>
<li><i class="icon-circle text-success"></i> {l s='Open'}</li>
<li><i class="icon-circle text-danger"></i> {l s='Closed'}</li>
<li><i class="icon-circle text-warning"></i> {l s='Pending 1'}</li>
<li><i class="icon-circle text-warning"></i> {l s='Pending 2'}</li>
</ul>
</div>
</div>
@@ -78,12 +78,12 @@
</div>
<ul class="list-unstyled">
{assign var=count value=0}
{foreach $params as $key => $val}
{foreach $stats as $key => $stat}
{assign var=count value=$count+1}
<li>{$key} <span class="badge">{$val}</span></li>
<li>{$stat.title} <span class="badge">{$stat.val}</span></li>
{/foreach}
</ul>
</div>
</div>
</div>
{/block}
{/block}

View File

@@ -45,14 +45,15 @@
{if !$initial}
<h4 class="message-item-heading">
<i class="icon-mail-reply text-muted"></i>
{if $type == 'customer'}
{$message.customer_name|escape:'html':'UTF-8'}
{else}
{$message.employee_name|escape:'html':'UTF-8'}
{/if}
{if $type == 'customer'}
{$message.customer_name|escape:'html':'UTF-8'}
{else}
{$message.employee_name|escape:'html':'UTF-8'}
{/if}
</h4>
{/if}
<span class="message-date">&nbsp;<i class="icon-calendar"></i> - {dateFormat date=$message.date_add full=0} - <i class="icon-time"></i> {$message.date_add|substr:11:5}</span>
{if $message.private}<span class="badge badge-info">{l s='Private'}</span>{/if}
{if isset($message.file_name)} <span class="message-product">&nbsp;<i class="icon-link"></i> <a href="{$message.file_name|escape:'html':'UTF-8'}" class="_blank">{l s="Attachment"}</a></span>{/if}
{if isset($message.product_name)} <span class="message-attachment">&nbsp;<i class="icon-book"></i> <a href="{$message.product_link|escape:'html':'UTF-8'}" class="_blank">{l s="Room type:"} {$message.product_name|escape:'html':'UTF-8'} </a></span>{/if}
<p class="message-item-text">{$message.message|escape:'html':'UTF-8'|nl2br}</p>

View File

@@ -30,11 +30,6 @@
<div class="panel-heading">
<i class="icon-comments"></i>
{l s="Thread"}: <span class="badge">#{$id_customer_thread|intval}</span>
{if isset($next_thread) && $next_thread}
<a class="btn btn-default pull-right" href="{$next_thread.href|escape:'html':'UTF-8'}">
{$next_thread.name} <i class="icon-forward"></i>
</a>
{/if}
</div>
<div class="well">
<form action="{$link->getAdminLink('AdminCustomerThreads')|escape:'html':'UTF-8'}&amp;viewcustomer_thread&amp;id_customer_thread={$id_customer_thread|intval}" method="post" enctype="multipart/form-data" class="form-horizontal">
@@ -116,7 +111,9 @@
{l s="Choose a template"}
</button>
-->
<button class="btn btn-default pull-right" name="submitReply"><i class="process-icon-mail-reply"></i> {l s="Send"}</button>
<button class="submitReply btn btn-default pull-right" name="submitReply"><i class="icon-mail-reply"></i> {l s="Send"}</button>
<button class="submitReply btn btn-default pull-right" name="submitReplyAndClose"><i class="icon-reply-all"></i> {l s='Reply And Close'}</button>
<button class="submitReply btn btn-default pull-right" name="submitReplyAsPrivate"><i class="icon-lock"></i> {l s='Add Private Message'}</button>
<input type="hidden" name="id_customer_thread" value="{$thread->id|intval}" />
<input type="hidden" name="msg_email" value="{$thread->email}" />
</div>
@@ -180,5 +177,3 @@
</script>
{/block}

View File

@@ -282,6 +282,10 @@
})
$('#order_message').live('change', function(e) {
e.preventDefault();
let visibility = 0;
if ($('#visibility').prop('checked')) {
visibility = 1;
}
$.ajax({
type:"POST",
url: "{$link->getAdminLink('AdminCarts')|addslashes}",
@@ -294,8 +298,9 @@
action: "updateOrderMessage",
id_cart: id_cart,
id_customer: id_customer,
message: $(this).val()
},
message: $(this).val(),
visibility: visibility
},
success : function(res)
{
displaySummary(res);
@@ -2170,6 +2175,15 @@
<textarea name="order_message" id="order_message" rows="3" cols="45"></textarea>
</div>
</div>
<div class="form-group col-lg-12">
<span class="col-lg-3"></span>
<p class="checkbox col-lg-6">
<label class="control-label" for="visibility">
<input type="checkbox" name="visibility" id="visibility" value="1" />
{l s='Display Message to Customer?'}
</label>
</p>
</div>
<div class="form-group" {if $order_total <= 0}style="display: none;"{/if}>
{if !$PS_CATALOG_MODE}
<div class="col-lg-9 col-lg-offset-3">

View File

@@ -704,7 +704,7 @@
{* Order Internal notes *}
{if (sizeof($messages))}
<div class="panel">
<div class="panel order-notes">
<div class="panel-heading">
<i class="icon-undo"></i> &nbsp;{l s='Order Notes'}
</div>
@@ -1153,7 +1153,11 @@
<div class="panel">
<div class="panel-heading">
<i class="icon-envelope"></i> &nbsp;{l s='Messages'} <span class="badge">{sizeof($customer_thread_message)}</span>
<a href="{$link->getAdminLink('AdminCustomerThreads')|escape:'html':'UTF-8'}&amp;id_order={$order->id|intval}" class="pull-right">{l s='Show all messages'}</a>
{if $id_customer_thread}
<a href="{$link->getAdminLink('AdminCustomerThreads')|escape:'html':'UTF-8'}&amp;viewcustomer_thread&id_customer_thread={$id_customer_thread|intval}" class="pull-right ">{l s='Show all messages'}</a>
{else}
<a href="{$link->getAdminLink('AdminCustomerThreads')|escape:'html':'UTF-8'}" class="pull-right">{l s='Show all messages'}</a>
{/if}
</div>
<div id="messages">
{if $can_edit}

View File

@@ -32,6 +32,7 @@ class CustomerMessageCore extends ObjectModel
public $message;
public $file_name;
public $ip_address;
public $id_product;
public $user_agent;
public $private;
public $date_add;
@@ -51,6 +52,7 @@ class CustomerMessageCore extends ObjectModel
'id_employee' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_customer_thread' => array('type' => self::TYPE_INT),
'ip_address' => array('type' => self::TYPE_STRING, 'validate' => 'isIp2Long', 'size' => 15),
'id_product' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'message' => array('type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'required' => true, 'size' => 16777216),
'file_name' => array('type' => self::TYPE_STRING),
'user_agent' => array('type' => self::TYPE_STRING),
@@ -72,7 +74,7 @@ class CustomerMessageCore extends ObjectModel
),
);
public static function getMessagesByOrderId($id_order, $private = true)
public static function getMessagesByOrderId($id_order, $private = true, $limit = false)
{
return Db::getInstance()->executeS('
SELECT cm.*,
@@ -91,8 +93,9 @@ class CustomerMessageCore extends ObjectModel
WHERE ct.id_order = '.(int)$id_order.'
'.(!$private ? 'AND cm.`private` = 0' : '').'
GROUP BY cm.id_customer_message
ORDER BY cm.date_add DESC
');
ORDER BY cm.date_add DESC'.
(($limit) ? ' LIMIT '.(int) $limit: ' ')
);
}
public static function getTotalCustomerMessages($where = null)

View File

@@ -32,13 +32,20 @@ class CustomerThreadCore extends ObjectModel
public $id_contact;
public $id_customer;
public $id_order;
public $id_product;
public $user_name;
public $phone;
public $subject;
public $status;
public $email;
public $token;
public $date_add;
public $date_upd;
const QLO_CUSTOMER_THREAD_STATUS_OPEN = 1;
const QLO_CUSTOMER_THREAD_STATUS_PENDING1 = 2;
const QLO_CUSTOMER_THREAD_STATUS_PENDING2 = 3;
const QLO_CUSTOMER_THREAD_STATUS_CLOSED = 4;
/**
* @see ObjectModel::$definition
*/
@@ -48,10 +55,12 @@ class CustomerThreadCore extends ObjectModel
'fields' => array(
'id_lang' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_contact' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'user_name' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName'),
'phone' => array('type' => self::TYPE_STRING, 'validate' => 'isPhoneNumber', 'size' => 32),
'subject' => array('type' => self::TYPE_STRING, 'validate' => 'isCleanHtml'),
'id_shop' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_customer' =>array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_product' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'email' => array('type' => self::TYPE_STRING, 'validate' => 'isEmail', 'size' => 254),
'token' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true),
'status' => array('type' => self::TYPE_STRING),
@@ -158,15 +167,38 @@ class CustomerThreadCore extends ObjectModel
return Db::getInstance()->executeS($sql);
}
public static function getIdCustomerThreadByEmailAndIdOrder($email, $id_order, $id_contact = false)
public static function getIdCustomerThreadByEmailAndIdOrder($email, $id_order, $id_contact = false, $query = false)
{
return Db::getInstance()->getValue('
SELECT a.`id_customer_thread`
FROM `'._DB_PREFIX_.'customer_thread` a
WHERE a.`email` = \''.pSQL($email).'\'
AND a.`id_shop` = '.(int)Context::getContext()->shop->id.'
AND a.`id_order` = '.(int)$id_order.' '.
(($id_contact) ? ' AND a.`id_contact`='.(int) $id_contact : ' ').
(($query) ? $query : ' ')
);
}
public static function getIdCustomerThreadByIdOrder($idOrder)
{
return Db::getInstance()->getValue('
SELECT cm.id_customer_thread
FROM '._DB_PREFIX_.'customer_thread cm
WHERE cm.email = \''.pSQL($email).'\'
AND cm.id_shop = '.(int)Context::getContext()->shop->id.'
AND cm.id_order = '.(int)$id_order.' '.
(($id_contact) ? ' AND cm.id_contact='.(int) $id_contact : ' ')
WHERE cm.id_shop = '.(int)Context::getContext()->shop->id.'
AND cm.id_order = '.(int)$idOrder
);
}
public static function getIdCustomerThreadByToken($token)
{
return Db::getInstance()->getValue('
SELECT cm.id_customer_thread
FROM '._DB_PREFIX_.'customer_thread cm
WHERE cm.id_shop = '.(int)Context::getContext()->shop->id.'
AND token = \''.pSQL($token).'\''
);
}
@@ -234,7 +266,7 @@ class CustomerThreadCore extends ObjectModel
return Db::getInstance()->getValue('
SELECT id_customer_thread
FROM '._DB_PREFIX_.'customer_thread ct
WHERE ct.status = "open"
WHERE ct.status = '.CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN.'
AND ct.date_upd = (
SELECT date_add FROM '._DB_PREFIX_.'customer_message
WHERE (id_employee IS NULL OR id_employee = 0)

View File

@@ -699,7 +699,7 @@ abstract class PaymentModuleCore extends Module
$customer_thread->id_order = (int)$order->id;
$customer_thread->id_lang = (int)$this->context->language->id;
$customer_thread->email = $this->context->customer->email;
$customer_thread->status = 'open';
$customer_thread->status = CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN;
$customer_thread->token = Tools::passwdGen(12);
$customer_thread->add();
@@ -707,7 +707,7 @@ abstract class PaymentModuleCore extends Module
$customer_message->id_customer_thread = $customer_thread->id;
$customer_message->id_employee = 0;
$customer_message->message = $update_message->message;
$customer_message->private = 0;
$customer_message->private = !Tools::getValue('visibility');
if (!$customer_message->add()) {
$this->errors[] = Tools::displayError('An error occurred while saving message');

View File

@@ -587,6 +587,7 @@ class AdminCartsControllerCore extends AdminController
$message->message = $message_content;
$message->id_cart = (int)$this->context->cart->id;
$message->id_customer = (int)$this->context->cart->id_customer;
$message->private = !Tools::getValue('visibility');
$message->save();
}
} elseif (Validate::isLoadedObject($message)) {
@@ -598,7 +599,7 @@ class AdminCartsControllerCore extends AdminController
public function ajaxProcessUpdateCurrency()
{
if ($this->tabAccess['edit'] === '1') {
if ($this->tabAccess['edit'] === '1' || $this->tabAccess['add'] === '1') {
$currency = new Currency((int)Tools::getValue('id_currency'));
if (Validate::isLoadedObject($currency) && !$currency->deleted && $currency->active) {
$this->context->cart->id_currency = (int)$currency->id;

View File

@@ -51,10 +51,22 @@ class AdminCustomerThreadsControllerCore extends AdminController
}
$icon_array = array(
'open' => array('class' => 'icon-circle text-success', 'alt' => $this->l('Open')),
'closed' => array('class' => 'icon-circle text-danger', 'alt' => $this->l('Closed')),
'pending1' => array('class' => 'icon-circle text-warning', 'alt' => $this->l('Pending 1')),
'pending2' => array('class' => 'icon-circle text-warning', 'alt' => $this->l('Pending 2')),
CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN => array(
'class' => 'icon-circle text-success',
'alt' => $this->l('Open')
),
CustomerThread::QLO_CUSTOMER_THREAD_STATUS_CLOSED => array(
'class' => 'icon-circle text-danger',
'alt' => $this->l('Closed')
),
CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING1 => array(
'class' => 'icon-circle text-warning',
'alt' => $this->l('Pending 1')
),
CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING2 => array(
'class' => 'icon-circle text-warning',
'alt' => $this->l('Pending 2')
),
);
$status_array = array();
@@ -67,7 +79,9 @@ class AdminCustomerThreadsControllerCore extends AdminController
$this->access_join = ' LEFT JOIN '._DB_PREFIX_.'orders ord ON (a.id_order = ord.id_order)';
$this->access_join .= ' LEFT JOIN '._DB_PREFIX_.'htl_booking_detail hbd ON (hbd.id_order = ord.id_order)';
if ($acsHtls = HotelBranchInformation::getProfileAccessedHotels($this->context->employee->id_profile, 1, 1)) {
$this->access_where = ' AND IF(a.`id_order`, hbd.`id_hotel` IN ('.implode(',', $acsHtls).'), 1)';
$this->access_where = ' WHERE IF(a.`id_order`, hbd.`id_hotel` IN ('.implode(',', $acsHtls).'), 1)';
} else {
$this->access_where = ' WHERE a.`id_order` = 0';
}
$this->fields_list = array(
@@ -79,12 +93,19 @@ class AdminCustomerThreadsControllerCore extends AdminController
'customer' => array(
'title' => $this->l('Customer'),
'filter_key' => 'customer',
'callback' => 'getCustomerLink',
'tmpTableFilter' => true,
),
'email' => array(
'title' => $this->l('Email'),
'filter_key' => 'a!email',
),
'phone' => array(
'title' => $this->l('Phone'),
'filter_key' => 'a!phone',
'optional' => true,
'visible_default' => true,
),
'contact' => array(
'title' => $this->l('Type'),
'type' => 'select',
@@ -92,12 +113,21 @@ class AdminCustomerThreadsControllerCore extends AdminController
'filter_key' => 'cl!id_contact',
'filter_type' => 'int',
),
'subject' => array(
'title' => $this->l('Subject'),
'filter_key' => 'a!subject',
'tmpTableFilter' => true,
'maxlength' => 40,
'optional' => true,
'visible_default' => true,
),
'language' => array(
'title' => $this->l('Language'),
'type' => 'select',
'list' => $language_array,
'filter_key' => 'l!id_lang',
'filter_type' => 'int',
'optional' => true,
),
'status' => array(
'title' => $this->l('Status'),
@@ -118,9 +148,10 @@ class AdminCustomerThreadsControllerCore extends AdminController
'filter_key' => 'messages',
'tmpTableFilter' => true,
'maxlength' => 40,
'optional' => true,
),
'private' => array(
'title' => $this->l('Private'),
'title' => $this->l('Has Private'),
'type' => 'select',
'filter_key' => 'private',
'align' => 'center',
@@ -135,6 +166,7 @@ class AdminCustomerThreadsControllerCore extends AdminController
'title' => $this->l('Last message'),
'havingFilter' => true,
'type' => 'datetime',
'optional' => true,
),
);
@@ -147,22 +179,61 @@ class AdminCustomerThreadsControllerCore extends AdminController
);
$this->shopLinkType = 'shop';
$arr = array();
if ($contacts = Contact::getContacts($this->context->language->id)) {
foreach ($contacts as $contact) {
$arr[] = array('email_message' => $contact['id_contact'], 'name' => $contact['name']);
}
}
$this->fields_options = array(
'contact' => array(
'title' => $this->l('Contact options'),
'fields' => array(
'PS_CUSTOMER_SERVICE_FILE_UPLOAD' => array(
'title' => $this->l('Allow file uploading'),
'hint' => $this->l('Allow customers to upload files using the contact page.'),
'type' => 'bool'
),
'title' => $this->l('Allow file uploading'),
'hint' => $this->l('Allow customers to upload files using the contact page.'),
'type' => 'bool'
),
'PS_MAIL_EMAIL_MESSAGE' => array(
'title' => $this->l('Send email to'),
'desc' => $this->l('Where customers send messages from the order page.'),
'validation' => 'isUnsignedId',
'type' => 'select',
'cast' => 'intval',
'identifier' => 'email_message',
'list' => $arr
),
'PS_CUSTOMER_SERVICE_CONTACT_ALLOW' => array(
'title' => $this->l('Allow guest to select contact'),
'hint' => $this->l('Allow guest to select the contact while submitting message from the contact page.'),
'type' => 'bool'
),
'PS_CUSTOMER_SERVICE_EMAIL_MESSAGE' => array(
'title' => $this->l('Send email to'),
'desc' => $this->l('Where customers send messages from the Contact page.'),
'validation' => 'isUnsignedId',
'type' => 'select',
'cast' => 'intval',
'identifier' => 'email_message',
'list' => $arr
),
'PS_CUSTOMER_SERVICE_NAME_REQUIRED' => array(
'title' => $this->l('Set Name as required'),
'hint' => $this->l('Set Name as required while submitting message from the contact page.'),
'type' => 'bool'
),
'PS_CUSTOMER_SERVICE_PHONE_REQUIRED' => array(
'title' => $this->l('Set Phone as required'),
'hint' => $this->l('Set Phone as required while submitting message from the contact page.'),
'type' => 'bool'
),
'PS_CUSTOMER_SERVICE_SIGNATURE' => array(
'title' => $this->l('Default message'),
'hint' => $this->l('Please fill out the message fields that appear by default when you answer a thread on the customer service page.'),
'type' => 'textareaLang',
'lang' => true
)
'title' => $this->l('Default message'),
'hint' => $this->l('Please fill out the message fields that appear by default when you answer a thread on the customer service page.'),
'type' => 'textareaLang',
'lang' => true
)
),
'submit' => array(
'title' => $this->l('Save'),
@@ -242,6 +313,25 @@ class AdminCustomerThreadsControllerCore extends AdminController
);
parent::__construct();
$this->_new_list_header_design = true;
$this->list_no_link = true;
}
public function getCustomerLink($customer, $tr)
{
if ($tr['id_customer']) {
return '<a href="'.$this->context->link->getAdminLink('AdminCustomers').'&viewcustomer&id_customer='.$tr['id_customer'].'">'.$customer.' (#'.$tr['id_customer'].')</a>';
}
return $customer;
}
public function setMedia()
{
parent::setMedia();
if (!$this->display || $this->display == 'list') {
$this->addJS(_PS_JS_DIR_.'admin/customer_thread.js');
}
}
public function renderList()
@@ -253,7 +343,7 @@ class AdminCustomerThreadsControllerCore extends AdminController
$this->addRowAction('delete');
$this->_select = '
CONCAT(c.`firstname`," ",c.`lastname`) as customer, cl.`name` as contact, l.`name` as language, group_concat(message) as messages, cm.private,
IF(a.`user_name`="", CONCAT(c.`firstname`," ",c.`lastname`), a.`user_name`) AS customer, cl.`name` as contact, l.`name` as language, group_concat(message) as messages, cm.private,
(
SELECT IFNULL(CONCAT(LEFT(e.`firstname`, 1),". ",e.`lastname`), "--")
FROM `'._DB_PREFIX_.'customer_message` cm2
@@ -274,10 +364,6 @@ class AdminCustomerThreadsControllerCore extends AdminController
LEFT JOIN `'._DB_PREFIX_.'contact_lang` cl
ON (cl.`id_contact` = a.`id_contact` AND cl.`id_lang` = '.(int)$this->context->language->id.')';
if ($id_order = Tools::getValue('id_order')) {
$this->_where .= ' AND id_order = '.(int)$id_order;
}
$this->_group = 'GROUP BY cm.id_customer_thread';
$this->_orderBy = 'id_customer_thread';
$this->_orderWay = 'DESC';
@@ -286,19 +372,42 @@ class AdminCustomerThreadsControllerCore extends AdminController
$categories = Contact::getCategoriesContacts();
$params = array(
$this->l('Total threads') => $all = CustomerThread::getTotalCustomerThreads(),
$this->l('Threads pending') => $pending = CustomerThread::getTotalCustomerThreads('status LIKE "%pending%"'),
$this->l('Total number of customer messages') => CustomerMessage::getTotalCustomerMessages('id_employee = 0'),
$this->l('Total number of employee messages') => CustomerMessage::getTotalCustomerMessages('id_employee != 0'),
$this->l('Unread threads') => $unread = CustomerThread::getTotalCustomerThreads('status = "open"'),
$this->l('Closed threads') => $all - ($unread + $pending)
$stats = array(
array(
'title' => $this->l('Total threads'),
'val' => $all = CustomerThread::getTotalCustomerThreads(),
),
array(
'title' => $this->l('Threads pending'),
'val' => $pending = CustomerThread::getTotalCustomerThreads(
'status='.(int) CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING1.'
OR status='.(int) CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING2
),
),
array(
'title' => $this->l('Total number of customer messages'),
'val' => CustomerMessage::getTotalCustomerMessages('id_employee = 0'),
),
array(
'title' => $this->l('Total number of employee messages'),
'val' => CustomerMessage::getTotalCustomerMessages('id_employee != 0'),
),
array(
'title' => $this->l('Unread threads'),
'val' => $unread = CustomerThread::getTotalCustomerThreads(
'status='.CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN
),
),
array(
'title' => $this->l('Closed threads'),
'val' => $all - ($unread + $pending),
),
);
$this->tpl_list_vars = array(
'contacts' => $contacts,
'categories' => $categories,
'params' => $params
'stats' => $stats
);
return parent::renderList();
@@ -342,10 +451,9 @@ class AdminCustomerThreadsControllerCore extends AdminController
);
}
if ($id_status = (int)Tools::getValue('setstatus')) {
$status_array = array(1 => 'open', 2 => 'closed', 3 => 'pending1', 4 => 'pending2');
Db::getInstance()->execute('
UPDATE '._DB_PREFIX_.'customer_thread
SET status = "'.$status_array[$id_status].'"
SET status = "'.(int) $id_status.'"
WHERE id_customer_thread = '.(int)$id_customer_thread.' LIMIT 1
');
}
@@ -363,7 +471,7 @@ class AdminCustomerThreadsControllerCore extends AdminController
LEFT OUTER JOIN '._DB_PREFIX_.'customer c
ON (c.email = ct.email)
WHERE ct.id_customer_thread = '.(int)Tools::getValue('id_customer_thread').'
ORDER BY cm.date_add DESC
ORDER BY cm.date_add, cm.id_employee, cm.private DESC
');
$output = $this->displayMessage($messages, true, (int)Tools::getValue('id_employee_forward'));
$cm = new CustomerMessage();
@@ -395,7 +503,8 @@ class AdminCustomerThreadsControllerCore extends AdminController
$employee->firstname.' '.$employee->lastname,
$current_employee->email,
$current_employee->firstname.' '.$current_employee->lastname,
null, null, _PS_MAIL_DIR_, true)) {
null, null, _PS_MAIL_DIR_, true)
) {
$cm->id_employee = (int)$employee->id;
$cm->private = 1;
$cm->message = $this->l('Message forwarded to').' '.$employee->firstname.' '.$employee->lastname."\n".$this->l('Comment:').' '.$message;
@@ -424,7 +533,7 @@ class AdminCustomerThreadsControllerCore extends AdminController
$this->errors[] = '<div class="alert error">'.Tools::displayError('The email address is invalid.').'</div>';
}
}
if (Tools::isSubmit('submitReply')) {
if (Tools::isSubmit('submitReply') || Tools::isSubmit('submitReplyAndClose')) {
$ct = new CustomerThread($id_customer_thread);
ShopUrl::cacheMainDomainForShop((int)$ct->id_shop);
@@ -446,14 +555,26 @@ class AdminCustomerThreadsControllerCore extends AdminController
$file_attachment['mime'] = $_FILES['joinFile']['type'];
}
$customer = new Customer($ct->id_customer);
if ($ct->id_order) {
$link = Tools::url(
$this->context->link->getPageLink('order-detail', true, (int)$ct->id_lang, null, false, $ct->id_shop),
'id_order='.$ct->id_order
);
} else {
$link = Tools::url(
$this->context->link->getPageLink('contact', true, (int)$ct->id_lang, null, false, $ct->id_shop),
'token='.$ct->token
);
}
$headingText = $this->l('Hi');
if ($customer->id) {
$headingText .= ' '.$customer->firstname.' '.$customer->lastname;
}
$params = array(
'{reply}' => Tools::nl2br(Tools::getValue('reply_message')),
'{link}' => Tools::url(
$this->context->link->getPageLink('contact', true, (int)$ct->id_lang, null, false, $ct->id_shop),
'id_customer_thread='.(int)$ct->id.'&token='.$ct->token
),
'{firstname}' => $customer->firstname,
'{lastname}' => $customer->lastname
'{link}' => $link,
'{headingtext}' => $headingText
);
//#ct == id_customer_thread #tc == token of thread <== used in the synchronization imap
$contact = new Contact((int)$ct->id_contact, (int)$ct->id_lang);
@@ -471,8 +592,12 @@ class AdminCustomerThreadsControllerCore extends AdminController
'reply_msg',
sprintf(Mail::l('An answer to your message is available #ct%1$s #tc%2$s', $ct->id_lang), $ct->id, $ct->token),
$params, Tools::getValue('msg_email'), null, $from_email, $from_name, $file_attachment, null,
_PS_MAIL_DIR_, true, $ct->id_shop)) {
$ct->status = 'closed';
_PS_MAIL_DIR_, true, $ct->id_shop)
) {
if (Tools::isSubmit('submitReplyAndClose')) {
$ct->status = CustomerThread::QLO_CUSTOMER_THREAD_STATUS_CLOSED;
}
$ct->update();
}
Tools::redirectAdmin(
@@ -481,7 +606,24 @@ class AdminCustomerThreadsControllerCore extends AdminController
} else {
$this->errors[] = Tools::displayError('An error occurred. Your message was not sent. Please contact your system administrator.');
}
} else if (Tools::isSubmit('submitReplyAsPrivate')) {
$ct = new CustomerThread($id_customer_thread);
ShopUrl::cacheMainDomainForShop((int)$ct->id_shop);
$cm = new CustomerMessage();
$cm->id_employee = (int)$this->context->employee->id;
$cm->id_customer_thread = $ct->id;
$cm->ip_address = (int)ip2long(Tools::getRemoteAddr());
$cm->message = Tools::getValue('reply_message');
$cm->private = 1;
$cm->read = 1;
if (($error = $cm->validateField('message', $cm->message, null, array(), true)) !== true) {
$this->errors[] = $error;
} elseif (!$cm->add()) {
$this->errors[] = Tools::displayError('Some error occured while saving the message');
}
}
}
return parent::postProcess();
@@ -603,64 +745,54 @@ class AdminCustomerThreadsControllerCore extends AdminController
}
}
$next_thread = CustomerThread::getNextThread((int)$thread->id);
$contacts = Contact::getContacts($this->context->language->id);
$actions = array();
if ($next_thread) {
$next_thread = array(
'href' => self::$currentIndex.'&id_customer_thread='.(int)$next_thread.'&viewcustomer_thread&token='.$this->token,
'name' => $this->l('Reply to the next unanswered message in this thread')
);
}
if ($thread->status != 'closed') {
$actions['closed'] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=2&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
if ($thread->status != CustomerThread::QLO_CUSTOMER_THREAD_STATUS_CLOSED) {
$actions[CustomerThread::QLO_CUSTOMER_THREAD_STATUS_CLOSED] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus='.CustomerThread::QLO_CUSTOMER_THREAD_STATUS_CLOSED.'&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
'label' => $this->l('Mark as "handled"'),
'name' => 'setstatus',
'value' => 2
'value' => CustomerThread::QLO_CUSTOMER_THREAD_STATUS_CLOSED
);
} else {
$actions['open'] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
$actions[CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus='.CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN.'&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
'label' => $this->l('Re-open'),
'name' => 'setstatus',
'value' => 1
'value' => CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN
);
}
if ($thread->status != 'pending1') {
$actions['pending1'] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=3&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
if ($thread->status != CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING1) {
$actions[CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING1] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus='.CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING1.'&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
'label' => $this->l('Mark as "pending 1" (will be answered later)'),
'name' => 'setstatus',
'value' => 3
'value' => CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING1
);
} else {
$actions['pending1'] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
$actions[CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING1] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus='.CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN.'&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
'label' => $this->l('Disable pending status'),
'name' => 'setstatus',
'value' => 1
'value' => CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN
);
}
if ($thread->status != 'pending2') {
$actions['pending2'] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=4&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
if ($thread->status != CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING2) {
$actions[CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING2] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus='.CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING2.'&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
'label' => $this->l('Mark as "pending 2" (will be answered later)'),
'name' => 'setstatus',
'value' => 4
'value' => CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING2
);
} else {
$actions['pending2'] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
$actions[CustomerThread::QLO_CUSTOMER_THREAD_STATUS_PENDING2] = array(
'href' => self::$currentIndex.'&viewcustomer_thread&setstatus='.CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN.'&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
'label' => $this->l('Disable pending status'),
'name' => 'setstatus',
'value' => 1
'value' => CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN
);
}
@@ -710,7 +842,6 @@ class AdminCustomerThreadsControllerCore extends AdminController
'messages' => $messages,
'first_message' => $first_message,
'contact' => $contact,
'next_thread' => $next_thread,
'orders' => isset($orders) ? $orders : false,
'customer' => isset($customer) ? $customer : false,
'products' => isset($products) ? $products : false,
@@ -721,10 +852,6 @@ class AdminCustomerThreadsControllerCore extends AdminController
'timeline_items' => $timeline_items,
);
if ($next_thread) {
$this->tpl_view_vars['next_thread'] = $next_thread;
}
return parent::renderView();
}
@@ -737,10 +864,15 @@ class AdminCustomerThreadsControllerCore extends AdminController
$content = '';
if (!$message['private']) {
$content .= $this->l('Message to: ').' <span class="badge">'.(!$message['id_employee'] ? $message['subject'] : $message['customer_name']).'</span><br/>';
if ($message['id_employee']) {
$content .= $this->l('Message to: ').' <span class="badge">'.($message['customer_name']).'</span><br/><br/>';
}
} else {
$content .= '<span class="label label-info">'.$this->l('Private').'</span><br/><br/>';
}
if (Validate::isLoadedObject($product)) {
$content .= '<br/>'.$this->l('Room type: ').'<span class="label label-info">'.$product->name.'</span><br/><br/>';
$content .= $this->l('Room type: ').'<span class="label label-info">'.$product->name.'</span><br/><br/>';
}
$content .= Tools::safeOutput($message['message']);
@@ -1079,7 +1211,7 @@ class AdminCustomerThreadsControllerCore extends AdminController
$ct->id_contact = $id_contact;
$ct->id_lang = (int)Configuration::get('PS_LANG_DEFAULT');
$ct->id_shop = $this->context->shop->id; //new customer threads for unrecognized mails are not shown without shop id
$ct->status = 'open';
$ct->status = CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN;
$ct->token = Tools::passwdGen(12);
$ct->add();
} else {

View File

@@ -80,25 +80,11 @@ class AdminEmailsControllerCore extends AdminController
}
parent::__construct();
foreach (Contact::getContacts($this->context->language->id) as $contact) {
$arr[] = array('email_message' => $contact['id_contact'], 'name' => $contact['name']);
}
$this->fields_options = array(
'email' => array(
'title' => $this->l('Email'),
'icon' => 'icon-envelope',
'fields' => array(
'PS_MAIL_EMAIL_MESSAGE' => array(
'title' => $this->l('Send email to'),
'desc' => $this->l('Where customers send messages from the order page.'),
'validation' => 'isUnsignedId',
'type' => 'select',
'cast' => 'intval',
'identifier' => 'email_message',
'list' => $arr
),
'fields' => array(
'PS_MAIL_METHOD' => array(
'title' => '',
'validation' => 'isGenericName',

View File

@@ -1424,7 +1424,7 @@ class AdminOrdersControllerCore extends AdminController
$customer_thread->id_order = (int)$order->id;
$customer_thread->id_lang = (int)$this->context->language->id;
$customer_thread->email = $customer->email;
$customer_thread->status = 'open';
$customer_thread->status = CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN;
$customer_thread->token = Tools::passwdGen(12);
$customer_thread->add();
} else {
@@ -3036,6 +3036,13 @@ class AdminOrdersControllerCore extends AdminController
}
}
}
$objCustomerThread = new CustomerThread();
$messages = Message::getMessagesByOrderId($order->id, true);
if ($customerMessages = CustomerMessage::getMessagesByOrderId($order->id, true)) {
$messages = array_merge($messages, $customerMessages);
}
$this->tpl_view_vars = array(
'guestFormattedAddress' => $guestFormattedAddress,
'idOrderAddressInvoice' => $idOrderAddressInvoice,
@@ -3080,8 +3087,9 @@ class AdminOrdersControllerCore extends AdminController
'discounts' => $order->getCartRules(),
'total_paid' => $order->getTotalPaid(),
'customer_thread_message' => CustomerThread::getCustomerMessages($order->id_customer, null, $order->id),
'id_customer_thread' => $objCustomerThread->getIdCustomerThreadByIdOrder($order->id),
'orderMessages' => OrderMessage::getOrderMessages($order->id_lang),
'messages' => Message::getMessagesByOrderId($order->id, true),
'messages' => $messages,
'carrier' => new Carrier($order->id_carrier),
'history' => $history,
'order_payment_detail' => $order_payment_detail,

View File

@@ -39,16 +39,40 @@ class ContactControllerCore extends FrontController
$saveContactKey = $this->context->cookie->contactFormKey;
$extension = array('.txt', '.rtf', '.doc', '.docx', '.pdf', '.zip', '.png', '.jpeg', '.gif', '.jpg');
$file_attachment = Tools::fileAttachment('fileUpload');
$message = Tools::getValue('message'); // Html entities is not usefull, iscleanHtml check there is no bad html tags.
$message = trim(Tools::getValue('message')); // Html entities is not usefull, iscleanHtml check there is no bad html tags.
$url = Tools::getValue('url');
$phone = Tools::getValue('phone');
$subject = trim(Tools::getValue('subject'));
$userName = Tools::getValue('user_name');
$id_contact = (int)Tools::getValue('id_contact');
$objCustomerThread = new CustomerThread();
$id_customer_thread = (int)$objCustomerThread->getIdCustomerThreadByToken(Tools::getValue('token'));
$nameRequired = Configuration::get('PS_CUSTOMER_SERVICE_NAME_REQUIRED');
$phoneRequired = Configuration::get('PS_CUSTOMER_SERVICE_PHONE_REQUIRED');
if (!Configuration::get('PS_CUSTOMER_SERVICE_CONTACT_ALLOW')) {
$id_contact = (int) Configuration::get('PS_CUSTOMER_SERVICE_EMAIL_MESSAGE');
}
if (!($from = trim(Tools::getValue('from'))) || !Validate::isEmail($from)) {
$this->errors[] = Tools::displayError('Invalid email address.');
} elseif (!$message) {
$this->errors[] = Tools::displayError('The message cannot be blank.');
} elseif (!Validate::isCleanHtml($message)) {
$this->errors[] = Tools::displayError('Invalid message');
} elseif (!($id_contact = (int)Tools::getValue('id_contact')) || !(Validate::isLoadedObject($contact = new Contact($id_contact, $this->context->language->id)))) {
$this->errors[] = Tools::displayError('Please select a subject from the list provided. ');
} elseif (!$id_customer_thread && !$subject) {
$this->errors[] = Tools::displayError('The subject cannot be blank.');
} elseif (!$id_customer_thread && !Validate::isCleanHtml($subject)) {
$this->errors[] = Tools::displayError('Invalid subject$subject');
} else if (!$id_customer_thread && $nameRequired && !trim($userName)) {
$this->errors[] = Tools::displayError('Name is required.');
} else if (!$id_customer_thread && trim($nameRequired) && !Validate::isGenericName($userName)) {
$this->errors[] = Tools::displayError('Invalid name.');
} else if (!$id_customer_thread && $phoneRequired && !trim($phone)) {
$this->errors[] = Tools::displayError('Phone is required.');
} else if (!$id_customer_thread && trim($phone) && !Validate::isPhoneNumber($phone)) {
$this->errors[] = Tools::displayError('Invalid Phone number.');
} elseif (!Validate::isLoadedObject($contact = new Contact($id_contact, $this->context->language->id))) {
$this->errors[] = Tools::displayError('Please select a subject from the list provided.');
} elseif (!empty($file_attachment['name']) && $file_attachment['error'] != 0) {
$this->errors[] = Tools::displayError('An error occurred during the file-upload process.');
} elseif (!empty($file_attachment['name']) && !in_array(Tools::strtolower(substr($file_attachment['name'], -4)), $extension) && !in_array(Tools::strtolower(substr($file_attachment['name'], -5)), $extension)) {
@@ -71,44 +95,15 @@ class ContactControllerCore extends FrontController
$id_order = (int) $order->id_customer === (int) $customer->id ? $id_order : 0;
}
if (!((
($id_customer_thread = (int)Tools::getValue('id_customer_thread'))
&& (int)Db::getInstance()->getValue('
SELECT cm.id_customer_thread FROM '._DB_PREFIX_.'customer_thread cm
WHERE cm.id_customer_thread = '.(int)$id_customer_thread.' AND cm.id_shop = '.(int)$this->context->shop->id.' AND token = \''.pSQL(Tools::getValue('token')).'\'')
) || (
$id_customer_thread = CustomerThread::getIdCustomerThreadByEmailAndIdOrder($from, $id_order, $id_contact)
))) {
$fields = Db::getInstance()->executeS('
SELECT cm.id_customer_thread, cm.id_contact, cm.id_customer, cm.id_order, cm.id_product, cm.email
FROM '._DB_PREFIX_.'customer_thread cm
WHERE email = \''.pSQL($from).'\' AND cm.id_shop = '.(int)$this->context->shop->id.' AND ('.
($customer->id ? 'id_customer = '.(int)$customer->id.' OR ' : '').'
id_order = '.(int)$id_order.')');
$score = 0;
foreach ($fields as $key => $row) {
$tmp = 0;
if ((int)$row['id_customer'] && $row['id_customer'] != $customer->id && $row['email'] != $from) {
continue;
}
if ($row['id_order'] != 0 && $id_order != $row['id_order']) {
continue;
}
if ($row['email'] == $from) {
$tmp += 4;
}
if ($row['id_contact'] == $id_contact) {
$tmp++;
}
if (Tools::getValue('id_product') != 0 && $row['id_product'] == Tools::getValue('id_product')) {
$tmp += 2;
}
if ($tmp >= 5 && $tmp >= $score) {
$score = $tmp;
$id_customer_thread = $row['id_customer_thread'];
}
}
if (!$id_customer_thread) {
$id_customer_thread = CustomerThread::getIdCustomerThreadByEmailAndIdOrder(
$from,
$id_order,
$id_contact,
' AND a.`status` !='. CustomerThread::QLO_CUSTOMER_THREAD_STATUS_CLOSED
);
}
$old_message = Db::getInstance()->getValue('
SELECT cm.message FROM '._DB_PREFIX_.'customer_message cm
LEFT JOIN '._DB_PREFIX_.'customer_thread cc on (cm.id_customer_thread = cc.id_customer_thread)
@@ -123,13 +118,10 @@ class ContactControllerCore extends FrontController
if ($contact->customer_service) {
if ((int)$id_customer_thread) {
$ct = new CustomerThread($id_customer_thread);
$ct->status = 'open';
$ct->status = CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN;
$ct->id_lang = (int)$this->context->language->id;
$ct->id_contact = (int)$id_contact;
$ct->id_order = (int)$id_order;
if ($id_product = (int)Tools::getValue('id_product')) {
$ct->id_product = $id_product;
}
$ct->update();
} else {
$ct = new CustomerThread();
@@ -137,14 +129,13 @@ class ContactControllerCore extends FrontController
$ct->id_customer = (int)$customer->id;
}
$ct->id_shop = (int)$this->context->shop->id;
$ct->id_order = (int)$id_order;
if ($id_product = (int)Tools::getValue('id_product')) {
$ct->id_product = $id_product;
}
$ct->phone = $phone;
$ct->user_name = $userName;
$ct->subject = $subject;
$ct->id_contact = (int)$id_contact;
$ct->id_lang = (int)$this->context->language->id;
$ct->email = $from;
$ct->status = 'open';
$ct->status = CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN;
$ct->token = Tools::passwdGen(12);
$ct->add();
}
@@ -153,6 +144,9 @@ class ContactControllerCore extends FrontController
$cm = new CustomerMessage();
$cm->id_customer_thread = $ct->id;
$cm->message = $message;
if ($id_product = (int)Tools::getValue('id_product')) {
$cm->id_product = $id_product;
}
if (isset($file_attachment['rename']) && !empty($file_attachment['rename']) && rename($file_attachment['tmp_name'], _PS_UPLOAD_DIR_.basename($file_attachment['rename']))) {
$cm->file_name = $file_attachment['rename'];
@chmod(_PS_UPLOAD_DIR_.basename($file_attachment['rename']), 0664);
@@ -266,21 +260,21 @@ class ContactControllerCore extends FrontController
'max_upload_size' => (int)Tools::getMaxUploadSize()
));
if (($id_customer_thread = (int)Tools::getValue('id_customer_thread')) && $token = Tools::getValue('token')) {
$customer_thread = Db::getInstance()->getRow('
if ($token = Tools::getValue('token')) {
if ($customer_thread = Db::getInstance()->getRow('
SELECT cm.*
FROM '._DB_PREFIX_.'customer_thread cm
WHERE cm.id_customer_thread = '.(int)$id_customer_thread.'
AND cm.id_shop = '.(int)$this->context->shop->id.'
WHERE cm.id_shop = '.(int)$this->context->shop->id.'
AND token = \''.pSQL($token).'\'
');
$order = new Order((int)$customer_thread['id_order']);
if (Validate::isLoadedObject($order)) {
$customer_thread['reference'] = $order->getUniqReference();
')) {
$order = new Order((int)$customer_thread['id_order']);
if (Validate::isLoadedObject($order)) {
$customer_thread['reference'] = $order->getUniqReference();
}
$this->context->smarty->assign('customerThread', $customer_thread);
}
$this->context->smarty->assign('customerThread', $customer_thread);
}
// Send Hotel location information
$gblHtlAddress = Configuration::get('WK_HOTEL_GLOBAL_ADDRESS');
$gblHtlPhone = Configuration::get('WK_HOTEL_GLOBAL_CONTACT_NUMBER');
@@ -320,6 +314,9 @@ class ContactControllerCore extends FrontController
'contacts' => Contact::getContacts($this->context->language->id),
'message' => html_entity_decode(Tools::getValue('message')),
'contactKey' => $contactKey,
'contactNameRequired' => Configuration::get('PS_CUSTOMER_SERVICE_NAME_REQUIRED'),
'contactPhoneRequired' => Configuration::get('PS_CUSTOMER_SERVICE_PHONE_REQUIRED'),
'allowContactSelection' => Configuration::get('PS_CUSTOMER_SERVICE_CONTACT_ALLOW')
)
);

View File

@@ -695,21 +695,23 @@ class OrderDetailControllerCore extends FrontController
$ct->id_contact = 0;
$ct->id_customer = (int)$order->id_customer;
$ct->id_shop = (int)$this->context->shop->id;
if ($id_product && $order->orderContainProduct($id_product)) {
$ct->id_product = $id_product;
}
$ct->id_order = (int)$order->id;
$ct->id_lang = (int)$this->context->language->id;
$ct->email = $this->context->customer->email;
$ct->status = 'open';
$ct->status = CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN;
$ct->token = Tools::passwdGen(12);
$ct->add();
} else {
$ct = new CustomerThread((int)$id_customer_thread);
$ct->status = 'open';
$ct->status = CustomerThread::QLO_CUSTOMER_THREAD_STATUS_OPEN;
$ct->update();
}
if ($id_product && $order->orderContainProduct($id_product)) {
$cm->id_product = $id_product;
}
$cm->user_agent = $_SERVER['HTTP_USER_AGENT'];
$cm->id_customer_thread = $ct->id;
$cm->message = $msgText;
$cm->ip_address = (int)ip2long($_SERVER['REMOTE_ADDR']);
@@ -819,4 +821,4 @@ class OrderDetailControllerCore extends FrontController
}
}
}
}
}

View File

@@ -633,6 +633,7 @@ CREATE TABLE `PREFIX_customer_message` (
`id_customer_thread` int(11) DEFAULT NULL,
`id_employee` int(10) unsigned DEFAULT NULL,
`message` MEDIUMTEXT NOT NULL,
`id_product` int(10) unsigned DEFAULT NULL,
`file_name` varchar(18) DEFAULT NULL,
`ip_address` varchar(16) DEFAULT NULL,
`user_agent` varchar(128) DEFAULT NULL,
@@ -658,8 +659,10 @@ CREATE TABLE `PREFIX_customer_thread` (
`id_contact` int(10) unsigned NOT NULL,
`id_customer` int(10) unsigned DEFAULT NULL,
`id_order` int(10) unsigned DEFAULT NULL,
`id_product` int(10) unsigned DEFAULT NULL,
`status` enum('open','closed','pending1','pending2') NOT NULL DEFAULT 'open',
`user_name` VARCHAR(128) DEFAULT NULL,
`phone` VARCHAR(32) DEFAULT NULL,
`subject` TEXT DEFAULT NULL,
`status` INT(10) NOT NULL DEFAULT 1,
`email` varchar(128) NOT NULL,
`token` varchar(12) DEFAULT NULL,
`date_add` datetime NOT NULL,

View File

@@ -946,6 +946,15 @@
</configuration>
<configuration id="PS_BACKDATE_ORDER_EMPLOYEES" name="PS_BACKDATE_ORDER_EMPLOYEES">
<value>0</value>
</configuration>
<configuration id="PS_CUSTOMER_SERVICE_NAME_REQUIRED" name="PS_CUSTOMER_SERVICE_NAME_REQUIRED">
<value>1</value>
</configuration>
<configuration id="PS_CUSTOMER_SERVICE_PHONE_REQUIRED" name="PS_CUSTOMER_SERVICE_PHONE_REQUIRED">
<value>1</value>
</configuration>
<configuration id="PS_CUSTOMER_SERVICE_CONTACT_ALLOW" name="PS_CUSTOMER_SERVICE_CONTACT_ALLOW">
<value>1</value>
</configuration>
</entities>
</entity_configuration>

View File

@@ -0,0 +1,32 @@
/*
* Copyright since 2010 Webkul.
*
* NOTICE OF LICENSE
*
* All right is reserved,
* Please go through this link for complete license : https://store.webkul.com/license.html
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to newer
* versions in the future. If you wish to customize this module for your
* needs please refer to https://store.webkul.com/customisation-guidelines/ for more information.
*
* @author Webkul IN <support@webkul.com>
* @copyright Since 2010 Webkul IN
* @license https://store.webkul.com/license.html
*/
$(document).ready(function() {
if ($('#PS_CUSTOMER_SERVICE_CONTACT_ALLOW_on').prop('checked')) {
$('[name="PS_CUSTOMER_SERVICE_EMAIL_MESSAGE"]').closest('.form-group').hide();
}
$(document).on('change', '[name="PS_CUSTOMER_SERVICE_CONTACT_ALLOW"]', function() {
if ($('#PS_CUSTOMER_SERVICE_CONTACT_ALLOW_on').prop('checked')) {
$('[name="PS_CUSTOMER_SERVICE_EMAIL_MESSAGE"]').closest('.form-group').hide();
} else {
$('[name="PS_CUSTOMER_SERVICE_EMAIL_MESSAGE"]').closest('.form-group').show();
}
});
});

View File

@@ -4,51 +4,51 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<title>Message from {shop_name}</title>
<style> @media only screen and (max-width: 300px){
<style> @media only screen and (max-width: 300px){
body {
width:218px !important;
margin:auto !important;
}
.table {width:195px !important;margin:auto !important;}
.logo, .titleblock, .linkbelow, .box, .footer, .space_footer{width:auto !important;display: block !important;}
.logo, .titleblock, .linkbelow, .box, .footer, .space_footer{width:auto !important;display: block !important;}
span.title{font-size:20px !important;line-height: 23px !important}
span.subtitle{font-size: 14px !important;line-height: 18px !important;padding-top:10px !important;display:block !important;}
span.subtitle{font-size: 14px !important;line-height: 18px !important;padding-top:10px !important;display:block !important;}
td.box p{font-size: 12px !important;font-weight: bold !important;}
.table-recap table, .table-recap thead, .table-recap tbody, .table-recap th, .table-recap td, .table-recap tr {
display: block !important;
.table-recap table, .table-recap thead, .table-recap tbody, .table-recap th, .table-recap td, .table-recap tr {
display: block !important;
}
.table-recap{width: 200px!important;}
.table-recap tr td, .conf_body td{text-align:center !important;}
.table-recap tr td, .conf_body td{text-align:center !important;}
.address{display: block !important;margin-bottom: 10px !important;}
.space_address{display: none !important;}
.space_address{display: none !important;}
}
@media only screen and (min-width: 301px) and (max-width: 500px) {
@media only screen and (min-width: 301px) and (max-width: 500px) {
body {width:308px!important;margin:auto!important;}
.table {width:285px!important;margin:auto!important;}
.logo, .titleblock, .linkbelow, .box, .footer, .space_footer{width:auto!important;display: block!important;}
.table-recap table, .table-recap thead, .table-recap tbody, .table-recap th, .table-recap td, .table-recap tr {
display: block !important;
.table {width:285px!important;margin:auto!important;}
.logo, .titleblock, .linkbelow, .box, .footer, .space_footer{width:auto!important;display: block!important;}
.table-recap table, .table-recap thead, .table-recap tbody, .table-recap th, .table-recap td, .table-recap tr {
display: block !important;
}
.table-recap{width: 295px !important;}
.table-recap tr td, .conf_body td{text-align:center !important;}
}
@media only screen and (min-width: 501px) and (max-width: 768px) {
body {width:478px!important;margin:auto!important;}
.table {width:450px!important;margin:auto!important;}
.logo, .titleblock, .linkbelow, .box, .footer, .space_footer{width:auto!important;display: block!important;}
}
@media only screen and (max-device-width: 480px) {
body {width:308px!important;margin:auto!important;}
.table {width:285px;margin:auto!important;}
.table {width:450px!important;margin:auto!important;}
.logo, .titleblock, .linkbelow, .box, .footer, .space_footer{width:auto!important;display: block!important;}
}
@media only screen and (max-device-width: 480px) {
body {width:308px!important;margin:auto!important;}
.table {width:285px;margin:auto!important;}
.logo, .titleblock, .linkbelow, .box, .footer, .space_footer{width:auto!important;display: block!important;}
.table-recap{width: 295px!important;}
.table-recap tr td, .conf_body td{text-align:center!important;}
.table-recap tr td, .conf_body td{text-align:center!important;}
.address{display: block !important;margin-bottom: 10px !important;}
.space_address{display: none !important;}
.space_address{display: none !important;}
}
</style>
@@ -66,31 +66,29 @@
</a>
</td>
</tr>
<tr>
<td align="center" class="titleblock" style="padding:7px 0">
<span class="title" style="font-weight:500;font-size:28px;text-transform:uppercase;line-height:33px">Hi {firstname} {lastname},</span>
</td>
</tr>
<tr>
<td class="space_footer" style="padding:0!important">&nbsp;</td>
</tr>
<tr>
<td class="linkbelow" style="padding:7px 0">
<span>{reply}</span>
</td>
</tr>
<tr>
<td class="space_footer" style="padding:0!important">&nbsp;</td>
</tr>
<tr>
<td class="linkbelow" style="padding:7px 0">
<span>
Please do not reply directly to this email, we will not receive it. <br>
In order to reply, please use the following link: <a href="{link}" target="_blank" style="color:#337ff1">{link}</a> </span>
</td>
</tr>
<tr>
<td align="center" class="titleblock" style="padding:7px 0">
<span class="title" style="font-weight:500;font-size:28px;text-transform:uppercase;line-height:33px">{headingtext}</span>
</td>
</tr>
<tr>
<td class="space_footer" style="padding:0!important">&nbsp;</td>
</tr>
<tr>
<td class="linkbelow" style="padding:7px 0">
<span>{reply}</span>
</td>
</tr>
<tr>
<td class="space_footer" style="padding:0!important">&nbsp;</td>
</tr>
<tr>
<td class="linkbelow" style="padding:7px 0">
<span>
Please do not reply directly to this email, we will not receive it. <br>
In order to reply, you can <a href="{link}" target="_blank" style="color:#337ff1">click here</a></span>
</td>
</tr>
<tr>
<td class="space_footer" style="padding:0!important">&nbsp;</td>
</tr>

View File

@@ -1,14 +1,14 @@
[/{shop_url}]
[/{shop_url}]
Hi {firstname} {lastname},
{headingtext},
{reply}
{reply}
Please do not reply directly to this email, we will not receive it.
Please do not reply directly to this email, we will not receive it.
In order to reply, please use the following link: {link} [/{link}]
In order to reply, you can click here [/{link}].
{shop_name} [/{shop_url}] powered by Webkul(tm)
[https://webkul.com]
[https://webkul.com]

View File

@@ -70,25 +70,56 @@
{/if}
<div class="col-sm-6 {if !(isset($gblHtlAddress) && $gblHtlAddress) && !(isset($gblHtlPhone) && $gblHtlPhone) && !(isset($gblHtlEmail) && $gblHtlEmail)} col-sm-offset-3 {/if}">
{block name='contact_form_content'}
{if isset($customerThread.token)}
<form action="{$link->getPageLink('contact', null, null, array('token' => $customerThread.token))}" method="post" class="contact-form-box" enctype="multipart/form-data">
{else}
<form action="{$link->getPageLink('contact')}" method="post" class="contact-form-box" enctype="multipart/form-data">
{if isset($customerThread.id_contact) && $customerThread.id_contact && $contacts|count}
{assign var=flag value=true}
{foreach from=$contacts item=contact}
{if $contact.id_contact == $customerThread.id_contact}
<input type="text" class="form-control" id="contact_name" name="contact_name" value="{$contact.name|escape:'html':'UTF-8'}" readonly="readonly" />
<input type="hidden" name="id_contact" value="{$contact.id_contact|intval}" />
{$flag=false}
{/if}
{if !isset($customerThread.id_contact)}
<div class="form-group row">
<div class="col-sm-12">
<label for="user_name" class="control-label">
{l s='Name'}{if isset($contactNameRequired) && $contactNameRequired}*{/if}
</label>
<input class="form-control contact_input" type="text" id="user_name" name="user_name" value="{if isset($customerThread.user_name)}{$customerThread.user_name|escape:'html':'UTF-8'}{/if}"/>
</div>
</div>
{/if}
<div class="form-group row">
<div class="col-sm-12">
<label for="Email" class="control-label">
{l s='Email'}*
</label>
{if isset($customerThread.email)}
<input class="form-control contact_input" type="email" id="email" name="from" value="{if isset($customerThread.email)}{$customerThread.email|escape:'html':'UTF-8'}" readonly="readonly"{/if} />
{else}
<input class="form-control contact_input validate" type="email" id="email" name="from" data-validate="isEmail" value="{$email|escape:'html':'UTF-8'}" />
{/if}
{/foreach}
{if $flag && isset($contacts.0.id_contact)}
<input type="text" class="form-control" id="contact_name" name="contact_name" value="{$contacts.0.name|escape:'html':'UTF-8'}" readonly="readonly" />
<input type="hidden" name="id_contact" value="{$contacts.0.id_contact|intval}" />
{/if}
{else}
</div>
</div>
{if !isset($customerThread.id_contact)}
<div class="form-group row">
<div class="col-sm-12">
<label for="phone" class="control-label">
{l s='Phone'}{if isset($contactPhoneRequired) && $contactPhoneRequired}*{/if}
</label>
<input class="form-control contact_input" type="text" id="phone" name="phone" value="{if isset($customerThread.phone)}{$customerThread.phone|escape:'html':'UTF-8'}{/if}"/>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<label for="subject" class="control-label">
{l s='Subject'}*
</label>
<input class="form-control contact_input" type="text" id="subject" name="subject" value="{if isset($customerThread.name)}{$customerThread.name|escape:'html':'UTF-8'}{/if}"/>
</div>
</div>
{/if}
{if !isset($customerThread.id_contact) && isset($allowContactSelection) && $allowContactSelection}
<div class="form-group row">
<div class="col-sm-12">
<label for="message" class="control-label">
{l s='Subject'}
{l s='Send To'}*
</label>
<div class="dropdown">
<button class="form-control contact_type_input" type="button" data-toggle="dropdown">
@@ -112,27 +143,20 @@
</div>
</div>
</div>
{elseif isset($customerThread.id_contact) && isset($allowContactSelection) && $allowContactSelection}
<input type="hidden" id="id_contact" name="id_contact" value="{$customerThread.id_contact|escape:'html':'UTF-8'}"/>
{/if}
<div class="form-group row">
<div class="col-sm-12">
<label for="price" class="control-label">
{l s='Email'}
</label>
{if isset($customerThread.email)}
<input class="form-control contact_input" type="email" id="email" name="from" value="{$customerThread.email|escape:'html':'UTF-8'}" readonly="readonly" />
{else}
<input class="form-control contact_input validate" type="email" id="email" name="from" data-validate="isEmail" value="{$email|escape:'html':'UTF-8'}" />
{/if}
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<label for="message" class="control-label">
{l s='Message/Query'}
{l s='Message/Query'}*
</label>
<textarea class="form-control contact_textarea" id="message" name="message">{if isset($message)}{$message|escape:'html':'UTF-8'|stripslashes}{/if}</textarea>
</div>
</div>
<div class="form-group">
{l s='* Required fields'}
</div>
{if $fileupload == 1}
<div class="form-group row">
<div class="col-sm-12">

View File

@@ -200,13 +200,13 @@
font-size: 16px;
font-weight: 600;
height: 47px;
margin-bottom: 20px;
margin-bottom: 5px;
padding: 10px;
}
.contact_textarea {
color: #9a9a9a;
height: 150px !important;
margin-bottom: 20px;
margin-bottom: 5px;
padding: 10px;
font-size: 16px;
font-weight: 600;

View File

@@ -606,7 +606,7 @@
color: #55c65e;
}
#sendOrderMessage {
#sendOrderMessage, .select-room-type .card-subheader {
margin-bottom: 10px;
}
@@ -1121,4 +1121,4 @@
text-decoration: underline;
text-underline-offset: 3px;
}
/* Guest Tracking page styles - end */
/* Guest Tracking page styles - end */

View File

@@ -528,6 +528,9 @@
<form action="{$link->getPageLink('order-detail', true)|escape:'html':'UTF-8'}" method="post" class="std" id="sendOrderMessage">
<div class="form-group select-room-type">
<label for="id_product">{l s='Room Type'}</label>
<p class="card-subheader text-muted">
{l s='To add a comment about a room type, please select one first.'}
</p>
<select name="id_product" class="form-control">
<option value="0">{l s='-- Choose --'}</option>