PageRenderTime 56ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/controllers/admin/AdminCustomerThreadsController.php

https://gitlab.com/mtellezgalindo/PrestaShop
PHP | 1000 lines | 857 code | 106 blank | 37 comment | 120 complexity | 87b3b6c836e90d83afcf481078861039 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-3.0
  1. <?php
  2. /*
  3. * 2007-2014 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2014 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. class AdminCustomerThreadsControllerCore extends AdminController
  27. {
  28. public function __construct()
  29. {
  30. $this->bootstrap = true;
  31. $this->context = Context::getContext();
  32. $this->table = 'customer_thread';
  33. $this->className = 'CustomerThread';
  34. $this->lang = false;
  35. $contact_array = array();
  36. $contacts = Contact::getContacts($this->context->language->id);
  37. foreach ($contacts as $contact)
  38. $contact_array[$contact['id_contact']] = $contact['name'];
  39. $language_array = array();
  40. $languages = Language::getLanguages();
  41. foreach ($languages as $language)
  42. $language_array[$language['id_lang']] = $language['name'];
  43. $icon_array = array(
  44. 'open' => array('class' => 'icon-circle text-success', 'alt' => $this->l('Open')),
  45. 'closed' => array('class' => 'icon-circle text-danger', 'alt' => $this->l('Closed')),
  46. 'pending1' => array('class' => 'icon-circle text-warning', 'alt' => $this->l('Pending 1')),
  47. 'pending2' => array('class' => 'icon-circle text-warning', 'alt' => $this->l('Pending 2')),
  48. );
  49. $status_array = array();
  50. foreach ($icon_array as $k => $v)
  51. $status_array[$k] = $v['alt'];
  52. $this->fields_list = array(
  53. 'id_customer_thread' => array(
  54. 'title' => $this->l('ID'),
  55. 'align' => 'center',
  56. 'class' => 'fixed-width-xs'
  57. ),
  58. 'customer' => array(
  59. 'title' => $this->l('Customer'),
  60. 'filter_key' => 'customer',
  61. 'tmpTableFilter' => true,
  62. ),
  63. 'email' => array(
  64. 'title' => $this->l('Email'),
  65. 'filter_key' => 'a!email',
  66. ),
  67. 'contact' => array(
  68. 'title' => $this->l('Type'),
  69. 'type' => 'select',
  70. 'list' => $contact_array,
  71. 'filter_key' => 'cl!id_contact',
  72. 'filter_type' => 'int',
  73. ),
  74. 'language' => array(
  75. 'title' => $this->l('Language'),
  76. 'type' => 'select',
  77. 'list' => $language_array,
  78. 'filter_key' => 'l!id_lang',
  79. 'filter_type' => 'int',
  80. ),
  81. 'status' => array(
  82. 'title' => $this->l('Status'),
  83. 'type' => 'select',
  84. 'list' => $status_array,
  85. 'icon' => $icon_array,
  86. 'align' => 'center',
  87. 'filter_key' => 'a!status',
  88. 'filter_type' => 'string',
  89. ),
  90. 'employee' => array(
  91. 'title' => $this->l('Employee'),
  92. 'filter_key' => 'employee',
  93. 'tmpTableFilter' => true,
  94. ),
  95. 'messages' => array(
  96. 'title' => $this->l('Messages'),
  97. 'filter_key' => 'messages',
  98. 'tmpTableFilter' => true,
  99. 'maxlength' => 40,
  100. ),
  101. 'date_upd' => array(
  102. 'title' => $this->l('Last message'),
  103. 'havingFilter' => true,
  104. 'type' => 'datetime',
  105. ),
  106. );
  107. $this->bulk_actions = array(
  108. 'delete' => array(
  109. 'text' => $this->l('Delete selected'),
  110. 'confirm' => $this->l('Delete selected items?'),
  111. 'icon' => 'icon-trash'
  112. ),
  113. );
  114. $this->shopLinkType = 'shop';
  115. $this->fields_options = array(
  116. 'contact' => array(
  117. 'title' => $this->l('Contact options'),
  118. 'fields' => array(
  119. 'PS_CUSTOMER_SERVICE_FILE_UPLOAD' => array(
  120. 'title' => $this->l('Allow file uploading'),
  121. 'hint' => $this->l('Allow customers to upload files using the contact page.'),
  122. 'type' => 'bool'
  123. ),
  124. 'PS_CUSTOMER_SERVICE_SIGNATURE' => array(
  125. 'title' => $this->l('Default message'),
  126. 'hint' => $this->l('Please fill out the message fields that appear by default when you answer a thread on the customer service page.'),
  127. 'type' => 'textareaLang',
  128. 'lang' => true
  129. )
  130. ),
  131. 'submit' => array('title' => $this->l('Save'))
  132. ),
  133. 'general' => array(
  134. 'title' => $this->l('Customer service options'),
  135. 'fields' => array(
  136. 'PS_SAV_IMAP_URL' => array(
  137. 'title' => $this->l('IMAP URL'),
  138. 'hint' => $this->l('URL for your IMAP server (ie.: mail.server.com).'),
  139. 'type' => 'text'
  140. ),
  141. 'PS_SAV_IMAP_PORT' => array(
  142. 'title' => $this->l('IMAP port'),
  143. 'hint' => $this->l('Port to use to connect to your IMAP server.'),
  144. 'type' => 'text',
  145. 'defaultValue' => 143,
  146. ),
  147. 'PS_SAV_IMAP_USER' => array(
  148. 'title' => $this->l('IMAP user'),
  149. 'hint' => $this->l('User to use to connect to your IMAP server.'),
  150. 'type' => 'text'
  151. ),
  152. 'PS_SAV_IMAP_PWD' => array(
  153. 'title' => $this->l('IMAP password'),
  154. 'hint' => $this->l('Password to use to connect your IMAP server.'),
  155. 'type' => 'text'
  156. ),
  157. 'PS_SAV_IMAP_DELETE_MSG' => array(
  158. 'title' => $this->l('Delete messages'),
  159. 'hint' => $this->l('Delete messages after synchronization. If you do not enable this option, the synchronization will take more time.'),
  160. 'type' => 'bool',
  161. ),
  162. 'PS_SAV_IMAP_CREATE_THREADS' => array(
  163. 'title' => $this->l('Create new threads'),
  164. 'hint' => $this->l('Create new threads for unrecognized emails.'),
  165. 'type' => 'bool',
  166. ),
  167. 'PS_SAV_IMAP_OPT_NORSH' => array(
  168. 'title' => $this->l('IMAP options').' (/norsh)',
  169. 'type' => 'bool',
  170. 'hint' => $this->l('Do not use RSH or SSH to establish a preauthenticated IMAP sessions.'),
  171. ),
  172. 'PS_SAV_IMAP_OPT_SSL' => array(
  173. 'title' => $this->l('IMAP options').' (/ssl)',
  174. 'type' => 'bool',
  175. 'hint' => $this->l('Use the Secure Socket Layer (TLS/SSL) to encrypt the session.'),
  176. ),
  177. 'PS_SAV_IMAP_OPT_VALIDATE-CERT' => array(
  178. 'title' => $this->l('IMAP options').' (/validate-cert)',
  179. 'type' => 'bool',
  180. 'hint' => $this->l('Validate certificates from the TLS/SSL server.'),
  181. ),
  182. 'PS_SAV_IMAP_OPT_NOVALIDATE-CERT' => array(
  183. 'title' => $this->l('IMAP options').' (/novalidate-cert)',
  184. 'type' => 'bool',
  185. 'hint' => $this->l('Do not validate certificates from the TLS/SSL server. This is only needed if a server uses self-signed certificates.'),
  186. ),
  187. 'PS_SAV_IMAP_OPT_TLS' => array(
  188. 'title' => $this->l('IMAP options').' (/tls)',
  189. 'type' => 'bool',
  190. 'hint' => $this->l('Force use of start-TLS to encrypt the session, and reject connection to servers that do not support it.'),
  191. ),
  192. 'PS_SAV_IMAP_OPT_NOTLS' => array(
  193. 'title' => $this->l('IMAP options').' (/notls)',
  194. 'type' => 'bool',
  195. 'hint' => $this->l('Do not use start-TLS to encrypt the session, even with servers that support it.'),
  196. ),
  197. ),
  198. 'submit' => array('title' => $this->l('Save')),
  199. ),
  200. );
  201. parent::__construct();
  202. }
  203. public function renderList()
  204. {
  205. $this->addRowAction('view');
  206. $this->addRowAction('delete');
  207. $this->_select = '
  208. CONCAT(c.`firstname`," ",c.`lastname`) as customer, cl.`name` as contact, l.`name` as language, group_concat(message) as messages,
  209. (
  210. SELECT IFNULL(CONCAT(LEFT(e.`firstname`, 1),". ",e.`lastname`), "--")
  211. FROM `'._DB_PREFIX_.'customer_message` cm2
  212. INNER JOIN '._DB_PREFIX_.'employee e
  213. ON e.`id_employee` = cm2.`id_employee`
  214. WHERE cm2.id_employee > 0
  215. AND cm2.`id_customer_thread` = a.`id_customer_thread`
  216. ORDER BY cm2.`date_add` DESC LIMIT 1
  217. ) as employee';
  218. $this->_join = '
  219. LEFT JOIN `'._DB_PREFIX_.'customer` c
  220. ON c.`id_customer` = a.`id_customer`
  221. LEFT JOIN `'._DB_PREFIX_.'customer_message` cm
  222. ON cm.`id_customer_thread` = a.`id_customer_thread`
  223. LEFT JOIN `'._DB_PREFIX_.'lang` l
  224. ON l.`id_lang` = a.`id_lang`
  225. LEFT JOIN `'._DB_PREFIX_.'contact_lang` cl
  226. ON (cl.`id_contact` = a.`id_contact` AND cl.`id_lang` = '.(int)$this->context->language->id.')';
  227. if ($id_order = Tools::getValue('id_order'))
  228. $this->_where .= ' AND id_order = '.(int)$id_order;
  229. $this->_group = 'GROUP BY cm.id_customer_thread';
  230. $this->_orderBy = 'id_customer_thread';
  231. $this->_orderWay = 'DESC';
  232. $contacts = CustomerThread::getContacts();
  233. $categories = Contact::getCategoriesContacts();
  234. $params = array(
  235. $this->l('Total threads') => $all = CustomerThread::getTotalCustomerThreads(),
  236. $this->l('Threads pending') => $pending = CustomerThread::getTotalCustomerThreads('status LIKE "%pending%"'),
  237. $this->l('Total number of customer messages') => CustomerMessage::getTotalCustomerMessages('id_employee = 0'),
  238. $this->l('Total number of employee messages') => CustomerMessage::getTotalCustomerMessages('id_employee != 0'),
  239. $this->l('Unread threads') => $unread = CustomerThread::getTotalCustomerThreads('status = "open"'),
  240. $this->l('Closed threads') => $all - ($unread + $pending)
  241. );
  242. $this->tpl_list_vars = array(
  243. 'contacts' => $contacts,
  244. 'categories' => $categories,
  245. 'params' => $params
  246. );
  247. return parent::renderList();
  248. }
  249. public function initToolbar()
  250. {
  251. parent::initToolbar();
  252. unset($this->toolbar_btn['new']);
  253. }
  254. public function postProcess()
  255. {
  256. if ($id_customer_thread = (int)Tools::getValue('id_customer_thread'))
  257. {
  258. if (($id_contact = (int)Tools::getValue('id_contact')))
  259. Db::getInstance()->execute('
  260. UPDATE '._DB_PREFIX_.'customer_thread
  261. SET id_contact = '.(int)$id_contact.'
  262. WHERE id_customer_thread = '.(int)$id_customer_thread
  263. );
  264. if ($id_status = (int)Tools::getValue('setstatus'))
  265. {
  266. $status_array = array(1 => 'open', 2 => 'closed', 3 => 'pending1', 4 => 'pending2');
  267. Db::getInstance()->execute('
  268. UPDATE '._DB_PREFIX_.'customer_thread
  269. SET status = "'.$status_array[$id_status].'"
  270. WHERE id_customer_thread = '.(int)$id_customer_thread.' LIMIT 1
  271. ');
  272. }
  273. if (isset($_POST['id_employee_forward']))
  274. {
  275. $messages = Db::getInstance()->getRow('
  276. SELECT ct.*, cm.*, cl.name subject, CONCAT(e.firstname, \' \', e.lastname) employee_name,
  277. CONCAT(c.firstname, \' \', c.lastname) customer_name, c.firstname
  278. FROM '._DB_PREFIX_.'customer_thread ct
  279. LEFT JOIN '._DB_PREFIX_.'customer_message cm
  280. ON (ct.id_customer_thread = cm.id_customer_thread)
  281. LEFT JOIN '._DB_PREFIX_.'contact_lang cl
  282. ON (cl.id_contact = ct.id_contact AND cl.id_lang = '.(int)$this->context->language->id.')
  283. LEFT OUTER JOIN '._DB_PREFIX_.'employee e
  284. ON e.id_employee = cm.id_employee
  285. LEFT OUTER JOIN '._DB_PREFIX_.'customer c
  286. ON (c.email = ct.email)
  287. WHERE ct.id_customer_thread = '.(int)Tools::getValue('id_customer_thread').'
  288. ORDER BY cm.date_add DESC
  289. ');
  290. $output = $this->displayMessage($messages, true, (int)Tools::getValue('id_employee_forward'));
  291. $cm = new CustomerMessage();
  292. $cm->id_employee = (int)$this->context->employee->id;
  293. $cm->id_customer_thread = (int)Tools::getValue('id_customer_thread');
  294. $cm->ip_address = (int)ip2long(Tools::getRemoteAddr());
  295. $current_employee = $this->context->employee;
  296. $id_employee = (int)Tools::getValue('id_employee_forward');
  297. $employee = new Employee($id_employee);
  298. $email = Tools::getValue('email');
  299. $message = Tools::getValue('message_forward');
  300. if (($error = $cm->validateField('message', $message, null, array(), true)) !== true)
  301. $this->errors[] = $error;
  302. elseif ($id_employee && $employee && Validate::isLoadedObject($employee))
  303. {
  304. $params = array(
  305. '{messages}' => stripslashes($output),
  306. '{employee}' => $current_employee->firstname.' '.$current_employee->lastname,
  307. '{comment}' => stripslashes(Tools::nl2br($_POST['message_forward'])),
  308. '{firstname}' => $employee->firstname,
  309. '{lastname}' => $employee->lastname,
  310. );
  311. if (Mail::Send(
  312. $this->context->language->id,
  313. 'forward_msg',
  314. Mail::l('Fwd: Customer message', $this->context->language->id),
  315. $params,
  316. $employee->email,
  317. $employee->firstname.' '.$employee->lastname,
  318. $current_employee->email,
  319. $current_employee->firstname.' '.$current_employee->lastname,
  320. null, null, _PS_MAIL_DIR_, true))
  321. {
  322. $cm->private = 1;
  323. $cm->message = $this->l('Message forwarded to').' '.$employee->firstname.' '.$employee->lastname."\n".$this->l('Comment:').' '.$message;
  324. $cm->add();
  325. }
  326. }
  327. elseif ($email && Validate::isEmail($email))
  328. {
  329. $params = array(
  330. '{messages}' => Tools::nl2br(stripslashes($output)),
  331. '{employee}' => $current_employee->firstname.' '.$current_employee->lastname,
  332. '{comment}' => stripslashes($_POST['message_forward'])
  333. );
  334. if (Mail::Send(
  335. $this->context->language->id,
  336. 'forward_msg',
  337. Mail::l('Fwd: Customer message', $this->context->language->id),
  338. $params, $email, null,
  339. $current_employee->email, $current_employee->firstname.' '.$current_employee->lastname,
  340. null, null, _PS_MAIL_DIR_, true))
  341. {
  342. $cm->message = $this->l('Message forwarded to').' '.$email."\n".$this->l('Comment:').' '.$message;
  343. $cm->add();
  344. }
  345. }
  346. else
  347. $this->errors[] = '<div class="alert error">'.Tools::displayError('The email address is invalid.').'</div>';
  348. }
  349. if (Tools::isSubmit('submitReply'))
  350. {
  351. $ct = new CustomerThread($id_customer_thread);
  352. ShopUrl::cacheMainDomainForShop((int)$ct->id_shop);
  353. $cm = new CustomerMessage();
  354. $cm->id_employee = (int)$this->context->employee->id;
  355. $cm->id_customer_thread = $ct->id;
  356. $cm->ip_address = (int)ip2long(Tools::getRemoteAddr());
  357. $cm->message = Tools::getValue('reply_message');
  358. if (($error = $cm->validateField('message', $cm->message, null, array(), true)) !== true)
  359. $this->errors[] = $error;
  360. elseif (isset($_FILES) && !empty($_FILES['joinFile']['name']) && $_FILES['joinFile']['error'] != 0)
  361. $this->errors[] = Tools::displayError('An error occurred during the file upload process.');
  362. elseif ($cm->add())
  363. {
  364. $file_attachment = null;
  365. if (!empty($_FILES['joinFile']['name']))
  366. {
  367. $file_attachment['content'] = file_get_contents($_FILES['joinFile']['tmp_name']);
  368. $file_attachment['name'] = $_FILES['joinFile']['name'];
  369. $file_attachment['mime'] = $_FILES['joinFile']['type'];
  370. }
  371. $customer = new Customer($ct->id_customer);
  372. $params = array(
  373. '{reply}' => Tools::nl2br(Tools::getValue('reply_message')),
  374. '{link}' => Tools::url(
  375. $this->context->link->getPageLink('contact', true),
  376. 'id_customer_thread='.(int)$ct->id.'&token='.$ct->token
  377. ),
  378. '{firstname}' => $customer->firstname,
  379. '{lastname}' => $customer->lastname
  380. );
  381. //#ct == id_customer_thread #tc == token of thread <== used in the synchronization imap
  382. $contact = new Contact((int)$ct->id_contact, (int)$ct->id_lang);
  383. if (Validate::isLoadedObject($contact))
  384. {
  385. $from_name = $contact->name;
  386. $from_email = $contact->email;
  387. }
  388. else
  389. {
  390. $from_name = null;
  391. $from_email = null;
  392. }
  393. if (Mail::Send(
  394. (int)$ct->id_lang,
  395. 'reply_msg',
  396. sprintf(Mail::l('An answer to your message is available #ct%1$s #tc%2$s', $ct->id_lang), $ct->id, $ct->token),
  397. $params, Tools::getValue('msg_email'), null, $from_email, $from_name, $file_attachment, null,
  398. _PS_MAIL_DIR_, true))
  399. {
  400. $ct->status = 'closed';
  401. $ct->update();
  402. }
  403. Tools::redirectAdmin(
  404. self::$currentIndex.'&id_customer_thread='.(int)$id_customer_thread.'&viewcustomer_thread&token='.Tools::getValue('token')
  405. );
  406. }
  407. else
  408. $this->errors[] = Tools::displayError('An error occurred. Your message was not sent. Please contact your system administrator.');
  409. }
  410. }
  411. return parent::postProcess();
  412. }
  413. public function initContent()
  414. {
  415. if (isset($_GET['filename']) && file_exists(_PS_UPLOAD_DIR_.$_GET['filename']) && Validate::isFileName($_GET['filename']))
  416. AdminCustomerThreadsController::openUploadedFile();
  417. return parent::initContent();
  418. }
  419. protected function openUploadedFile()
  420. {
  421. $filename = $_GET['filename'];
  422. $extensions = array(
  423. '.txt' => 'text/plain',
  424. '.rtf' => 'application/rtf',
  425. '.doc' => 'application/msword',
  426. '.docx'=> 'application/msword',
  427. '.pdf' => 'application/pdf',
  428. '.zip' => 'multipart/x-zip',
  429. '.png' => 'image/png',
  430. '.jpeg' => 'image/jpeg',
  431. '.gif' => 'image/gif',
  432. '.jpg' => 'image/jpeg',
  433. );
  434. $extension = false;
  435. foreach ($extensions as $key => $val)
  436. if (substr(Tools::strtolower($filename), -4) == $key || substr(Tools::strtolower($filename), -5) == $key)
  437. {
  438. $extension = $val;
  439. break;
  440. }
  441. if (!$extension || !Validate::isFileName($filename))
  442. die(Tools::displayError());
  443. if (ob_get_level() && ob_get_length() > 0)
  444. ob_end_clean();
  445. header('Content-Type: '.$extension);
  446. header('Content-Disposition:attachment;filename="'.$filename.'"');
  447. readfile(_PS_UPLOAD_DIR_.$filename);
  448. die;
  449. }
  450. public function renderKpis()
  451. {
  452. $time = time();
  453. $kpis = array();
  454. /* The data generation is located in AdminStatsControllerCore */
  455. $helper = new HelperKpi();
  456. $helper->id = 'box-pending-messages';
  457. $helper->icon = 'icon-envelope';
  458. $helper->color = 'color1';
  459. $helper->href = $this->context->link->getAdminLink('AdminCustomerThreads');
  460. $helper->title = $this->l('Pending Discussion Threads', null, null, false);
  461. if (ConfigurationKPI::get('PENDING_MESSAGES') !== false)
  462. $helper->value = ConfigurationKPI::get('PENDING_MESSAGES');
  463. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=pending_messages';
  464. $helper->refresh = (bool)(ConfigurationKPI::get('PENDING_MESSAGES_EXPIRE') < $time);
  465. $kpis[] = $helper->generate();
  466. $helper = new HelperKpi();
  467. $helper->id = 'box-age';
  468. $helper->icon = 'icon-time';
  469. $helper->color = 'color2';
  470. $helper->title = $this->l('Average Response Time', null, null, false);
  471. $helper->subtitle = $this->l('30 days', null, null, false);
  472. if (ConfigurationKPI::get('AVG_MSG_RESPONSE_TIME') !== false)
  473. $helper->value = ConfigurationKPI::get('AVG_MSG_RESPONSE_TIME');
  474. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=avg_msg_response_time';
  475. $helper->refresh = (bool)(ConfigurationKPI::get('AVG_MSG_RESPONSE_TIME_EXPIRE') < $time);
  476. $kpis[] = $helper->generate();
  477. $helper = new HelperKpi();
  478. $helper->id = 'box-messages-per-thread';
  479. $helper->icon = 'icon-copy';
  480. $helper->color = 'color3';
  481. $helper->title = $this->l('Messages per Thread', null, null, false);
  482. $helper->subtitle = $this->l('30 day', null, null, false);
  483. if (ConfigurationKPI::get('MESSAGES_PER_THREAD') !== false)
  484. $helper->value = ConfigurationKPI::get('MESSAGES_PER_THREAD');
  485. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=messages_per_thread';
  486. $helper->refresh = (bool)(ConfigurationKPI::get('MESSAGES_PER_THREAD_EXPIRE') < $time);
  487. $kpis[] = $helper->generate();
  488. $helper = new HelperKpiRow();
  489. $helper->kpis = $kpis;
  490. return $helper->generate();
  491. }
  492. public function renderView()
  493. {
  494. if (!$id_customer_thread = (int)Tools::getValue('id_customer_thread'))
  495. return;
  496. $this->context = Context::getContext();
  497. if (!($thread = $this->loadObject()))
  498. return;
  499. $this->context->cookie->{'customer_threadFilter_cl!id_contact'} = $thread->id_contact;
  500. $employees = Employee::getEmployees();
  501. $messages = CustomerThread::getMessageCustomerThreads($id_customer_thread);
  502. foreach ($messages as $key => $mess)
  503. {
  504. if ($mess['id_employee'])
  505. {
  506. $employee = new Employee($mess['id_employee']);
  507. $messages[$key]['employee_image'] = $employee->getImage();
  508. }
  509. if (isset($mess['file_name']) && $mess['file_name'] != '')
  510. $messages[$key]['file_name'] = _THEME_PROD_PIC_DIR_.$mess['file_name'];
  511. else
  512. unset($messages[$key]['file_name']);
  513. if ($mess['id_product'])
  514. {
  515. $product = new Product((int)$mess['id_product'], false, $this->context->language->id);
  516. if (Validate::isLoadedObject($product))
  517. {
  518. $messages[$key]['product_name'] = $product->name;
  519. $messages[$key]['product_link'] = $this->context->link->getAdminLink('AdminProducts').'&updateproduct&id_product='.(int)$product->id;
  520. }
  521. }
  522. }
  523. $next_thread = CustomerThread::getNextThread((int)$thread->id);
  524. $contacts = Contact::getContacts($this->context->language->id);
  525. $actions = array();
  526. if ($next_thread)
  527. $next_thread = array(
  528. 'href' => self::$currentIndex.'&id_customer_thread='.(int)$next_thread.'&viewcustomer_thread&token='.$this->token,
  529. 'name' => $this->l('Reply to the next unanswered message in this thread')
  530. );
  531. if ($thread->status != 'closed')
  532. $actions['closed'] = array(
  533. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=2&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  534. 'label' => $this->l('Mark as "handled"'),
  535. 'name' => 'setstatus',
  536. 'value' => 2
  537. );
  538. else
  539. $actions['open'] = array(
  540. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  541. 'label' => $this->l('Re-open'),
  542. 'name' => 'setstatus',
  543. 'value' => 1
  544. );
  545. if ($thread->status != 'pending1')
  546. $actions['pending1'] = array(
  547. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=3&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  548. 'label' => $this->l('Mark as "pending 1" (will be answered later)'),
  549. 'name' => 'setstatus',
  550. 'value' => 3
  551. );
  552. else
  553. $actions['pending1'] = array(
  554. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  555. 'label' => $this->l('Disable pending status'),
  556. 'name' => 'setstatus',
  557. 'value' => 1
  558. );
  559. if ($thread->status != 'pending2')
  560. $actions['pending2'] = array(
  561. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=4&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  562. 'label' => $this->l('Mark as "pending 2" (will be answered later)'),
  563. 'name' => 'setstatus',
  564. 'value' => 4
  565. );
  566. else
  567. $actions['pending2'] = array(
  568. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  569. 'label' => $this->l('Disable pending status'),
  570. 'name' => 'setstatus',
  571. 'value' => 1
  572. );
  573. if ($thread->id_customer)
  574. {
  575. $customer = new Customer($thread->id_customer);
  576. $orders = Order::getCustomerOrders($customer->id);
  577. if ($orders && count($orders))
  578. {
  579. $total_ok = 0;
  580. $orders_ok = array();
  581. foreach ($orders as $key => $order)
  582. {
  583. if ($order['valid'])
  584. {
  585. $orders_ok[] = $order;
  586. $total_ok += $order['total_paid_real'];
  587. }
  588. $orders[$key]['date_add'] = Tools::displayDate($order['date_add']);
  589. $orders[$key]['total_paid_real'] = Tools::displayPrice($order['total_paid_real'], new Currency((int)$order['id_currency']));
  590. }
  591. }
  592. $products = $customer->getBoughtProducts();
  593. if ($products && count($products))
  594. foreach ($products as $key => $product)
  595. $products[$key]['date_add'] = Tools::displayDate($product['date_add'], null, true);
  596. }
  597. $timeline_items = $this->getTimeline($messages, $thread->id_order);
  598. $first_message = $messages[0];
  599. if (!$messages[0]['id_employee'])
  600. unset($messages[0]);
  601. $contact = '';
  602. foreach ($contacts as $c)
  603. if ($c['id_contact'] == $thread->id_contact)
  604. $contact = $c['name'];
  605. $this->tpl_view_vars = array(
  606. 'id_customer_thread' => $id_customer_thread,
  607. 'thread' => $thread,
  608. 'actions' => $actions,
  609. 'employees' => $employees,
  610. 'current_employee' => $this->context->employee,
  611. 'messages' => $messages,
  612. 'first_message' => $first_message,
  613. 'contact' => $contact,
  614. 'next_thread' => $next_thread,
  615. 'orders' => isset($orders) ? $orders : false,
  616. 'customer' => isset($customer) ? $customer : false,
  617. 'products' => isset($products) ? $products : false,
  618. 'total_ok' => isset($total_ok) ? Tools::displayPrice($total_ok, $this->context->currency) : false,
  619. 'orders_ok' => isset($orders_ok) ? $orders_ok : false,
  620. 'count_ok' => isset($orders_ok) ? count($orders_ok) : false,
  621. 'PS_CUSTOMER_SERVICE_SIGNATURE' => str_replace('\r\n', "\n", Configuration::get('PS_CUSTOMER_SERVICE_SIGNATURE', (int)$thread->id_lang)),
  622. 'timeline_items' => $timeline_items,
  623. );
  624. if ($next_thread)
  625. $this->tpl_view_vars['next_thread'] = $next_thread;
  626. return parent::renderView();
  627. }
  628. public function getTimeline($messages, $id_order)
  629. {
  630. $timeline = array();
  631. foreach ($messages as $message)
  632. {
  633. $product = new Product((int)$message['id_product'], false, $this->context->language->id);
  634. $link_product = $this->context->link->getAdminLink('AdminOrders').'&vieworder&id_order='.(int)$product->id;
  635. $content = $this->l('Message to: ').' <span class="badge">'.(!$message['id_employee'] ? $message['subject'] : $message['customer_name']).'</span><br/>';
  636. if (Validate::isLoadedObject($product))
  637. $content .= '<br/>'.$this->l('Product: ').'<span class="label label-info">'.$product->name.'</span><br/><br/>';
  638. $content .= Tools::safeOutput($message['message']);
  639. $timeline[$message['date_add']][] = array(
  640. 'arrow' => 'left',
  641. 'background_color' => '',
  642. 'icon' => 'icon-envelope',
  643. 'content' => $content,
  644. 'date' => $message['date_add'],
  645. );
  646. }
  647. $order = new Order((int)$id_order);
  648. if (Validate::isLoadedObject($order))
  649. {
  650. $order_history = $order->getHistory($this->context->language->id);
  651. foreach ($order_history as $history)
  652. {
  653. $link_order = $this->context->link->getAdminLink('AdminOrders').'&vieworder&id_order='.(int)$order->id;
  654. $content = '<a class="badge" target="_blank" href="'.Tools::safeOutput($link_order).'">'.$this->l('Order').' #'.(int)$order->id.'</a><br/><br/>';
  655. $content .= '<span>'.$this->l('Status:').' '.$history['ostate_name'].'</span>';
  656. $timeline[$history['date_add']][] = array(
  657. 'arrow' => 'right',
  658. 'alt' => true,
  659. 'background_color' => $history['color'],
  660. 'icon' => 'icon-credit-card',
  661. 'content' => $content,
  662. 'date' => $history['date_add'],
  663. 'see_more_link' => $link_order,
  664. );
  665. }
  666. }
  667. krsort($timeline);
  668. return $timeline;
  669. }
  670. protected function displayMessage($message, $email = false, $id_employee = null)
  671. {
  672. $tpl = $this->createTemplate('message.tpl');
  673. $contacts = Contact::getContacts($this->context->language->id);
  674. foreach ($contacts as $contact)
  675. $contact_array[$contact['id_contact']] = array('id_contact' => $contact['id_contact'], 'name' => $contact['name']);
  676. $contacts = $contact_array;
  677. if (!$email)
  678. {
  679. if (!empty($message['id_product']) && empty($message['employee_name']))
  680. $id_order_product = Order::getIdOrderProduct((int)$message['id_customer'], (int)$message['id_product']);
  681. }
  682. $message['date_add'] = Tools::displayDate($message['date_add'], null, true);
  683. $message['user_agent'] = strip_tags($message['user_agent']);
  684. $message['message'] = preg_replace(
  685. '/(https?:\/\/[a-z0-9#%&_=\(\)\.\? \+\-@\/]{6,1000})([\s\n<])/Uui',
  686. '<a href="\1">\1</a>\2',
  687. html_entity_decode($message['message'],
  688. ENT_QUOTES, 'UTF-8')
  689. );
  690. $is_valid_order_id = true;
  691. $order = new Order((int)$message['id_order']);
  692. if (!Validate::isLoadedObject($order))
  693. $is_valid_order_id = false;
  694. $tpl->assign(array(
  695. 'thread_url' => Tools::getAdminUrl(basename(_PS_ADMIN_DIR_).'/'.
  696. $this->context->link->getAdminLink('AdminCustomerThreads').'&amp;id_customer_thread='
  697. .(int)$message['id_customer_thread'].'&amp;viewcustomer_thread=1'),
  698. 'link' => Context::getContext()->link,
  699. 'current' => self::$currentIndex,
  700. 'token' => $this->token,
  701. 'message' => $message,
  702. 'id_order_product' => isset($id_order_product) ? $id_order_product : null,
  703. 'email' => $email,
  704. 'id_employee' => $id_employee,
  705. 'PS_SHOP_NAME' => Configuration::get('PS_SHOP_NAME'),
  706. 'file_name' => file_exists(_PS_UPLOAD_DIR_.$message['file_name']),
  707. 'contacts' => $contacts,
  708. 'is_valid_order_id' => $is_valid_order_id
  709. ));
  710. return $tpl->fetch();
  711. }
  712. protected function displayButton($content)
  713. {
  714. return '<div><p>'.$content.'</p></div>';
  715. }
  716. public function renderOptions()
  717. {
  718. if (Configuration::get('PS_SAV_IMAP_URL')
  719. && Configuration::get('PS_SAV_IMAP_PORT')
  720. && Configuration::get('PS_SAV_IMAP_USER')
  721. && Configuration::get('PS_SAV_IMAP_PWD'))
  722. $this->tpl_option_vars['use_sync'] = true;
  723. else
  724. $this->tpl_option_vars['use_sync'] = false;
  725. return parent::renderOptions();
  726. }
  727. /**
  728. * AdminController::getList() override
  729. * @see AdminController::getList()
  730. */
  731. public function getList($id_lang, $order_by = null, $order_way = null, $start = 0, $limit = null, $id_lang_shop = false)
  732. {
  733. parent::getList($id_lang, $order_by, $order_way, $start, $limit, $id_lang_shop);
  734. $nb_items = count($this->_list);
  735. for ($i = 0; $i < $nb_items; ++$i)
  736. {
  737. if (isset($this->_list[$i]['messages']))
  738. $this->_list[$i]['messages'] = Tools::htmlentitiesDecodeUTF8($this->_list[$i]['messages']);
  739. }
  740. }
  741. public function updateOptionPsSavImapOpt($value)
  742. {
  743. if ($this->tabAccess['edit'] != '1')
  744. throw new PrestaShopException(Tools::displayError('You do not have permission to edit this.'));
  745. if (!$this->errors && $value)
  746. Configuration::updateValue('PS_SAV_IMAP_OPT', implode('', $value));
  747. }
  748. public function ajaxProcessMarkAsRead()
  749. {
  750. if ($this->tabAccess['edit'] != '1')
  751. throw new PrestaShopException(Tools::displayError('You do not have permission to edit this.'));
  752. $id_thread = Tools::getValue('id_thread');
  753. $messages = CustomerThread::getMessageCustomerThreads($id_thread);
  754. if (count($messages))
  755. Db::getInstance()->execute('UPDATE `'._DB_PREFIX_.'customer_message` set `read` = 1 WHERE `id_employee` = '.(int)$this->context->employee->id.' AND `id_customer_thread` = '.(int)$id_thread);
  756. }
  757. public function ajaxProcessSyncImap()
  758. {
  759. if ($this->tabAccess['edit'] != '1')
  760. throw new PrestaShopException(Tools::displayError('You do not have permission to edit this.'));
  761. if (Tools::isSubmit('syncImapMail'))
  762. {
  763. if (!($url = Configuration::get('PS_SAV_IMAP_URL'))
  764. || !($port = Configuration::get('PS_SAV_IMAP_PORT'))
  765. || !($user = Configuration::get('PS_SAV_IMAP_USER'))
  766. || !($password = Configuration::get('PS_SAV_IMAP_PWD')))
  767. die('{"hasError" : true, "errors" : ["Configuration is not correct"]}');
  768. $conf = Configuration::getMultiple(array(
  769. 'PS_SAV_IMAP_OPT_NORSH', 'PS_SAV_IMAP_OPT_SSL',
  770. 'PS_SAV_IMAP_OPT_VALIDATE-CERT', 'PS_SAV_IMAP_OPT_NOVALIDATE-CERT',
  771. 'PS_SAV_IMAP_OPT_TLS', 'PS_SAV_IMAP_OPT_NOTLS'));
  772. $conf_str = '';
  773. if ($conf['PS_SAV_IMAP_OPT_NORSH'])
  774. $conf_str .= '/norsh';
  775. if ($conf['PS_SAV_IMAP_OPT_SSL'])
  776. $conf_str .= '/ssl';
  777. if ($conf['PS_SAV_IMAP_OPT_VALIDATE-CERT'])
  778. $conf_str .= '/validate-cert';
  779. if ($conf['PS_SAV_IMAP_OPT_NOVALIDATE-CERT'])
  780. $conf_str .= '/novalidate-cert';
  781. if ($conf['PS_SAV_IMAP_OPT_TLS'])
  782. $conf_str .= '/tls';
  783. if ($conf['PS_SAV_IMAP_OPT_NOTLS'])
  784. $conf_str .= '/notls';
  785. if (!function_exists('imap_open'))
  786. die('{"hasError" : true, "errors" : ["imap is not installed on this server"]}');
  787. $mbox = @imap_open('{'.$url.':'.$port.$conf_str.'}', $user, $password);
  788. //checks if there is no error when connecting imap server
  789. $errors = array_unique(imap_errors());
  790. $str_errors = '';
  791. $str_error_delete = '';
  792. if (sizeof($errors) && is_array($errors))
  793. {
  794. $str_errors = '';
  795. foreach($errors as $error)
  796. $str_errors .= $error.', ';
  797. $str_errors = rtrim(trim($str_errors), ',');
  798. }
  799. //checks if imap connexion is active
  800. if (!$mbox)
  801. {
  802. $array = array('hasError' => true, 'errors' => array('Cannot connect to the mailbox :<br />'.($str_errors)));
  803. die(Tools::jsonEncode($array));
  804. }
  805. //Returns information about the current mailbox. Returns FALSE on failure.
  806. $check = imap_check($mbox);
  807. if (!$check)
  808. die('{"hasError" : true, "errors" : ["Fail to get information about the current mailbox"]}');
  809. if ($check->Nmsgs == 0)
  810. die('{"hasError" : true, "errors" : ["NO message to sync"]}');
  811. $result = imap_fetch_overview($mbox,"1:{$check->Nmsgs}",0);
  812. foreach ($result as $overview)
  813. {
  814. //check if message exist in database
  815. if (isset($overview->subject))
  816. $subject = $overview->subject;
  817. else
  818. $subject = '';
  819. //Creating an md5 to check if message has been allready processed
  820. $md5 = md5($overview->date.$overview->from.$subject.$overview->msgno);
  821. $exist = Db::getInstance()->getValue(
  822. 'SELECT `md5_header`
  823. FROM `'._DB_PREFIX_.'customer_message_sync_imap`
  824. WHERE `md5_header` = \''.pSQL($md5).'\'');
  825. if ($exist)
  826. {
  827. if (Configuration::get('PS_SAV_IMAP_DELETE_MSG'))
  828. if (!imap_delete($mbox, $overview->msgno))
  829. $str_error_delete = ', Fail to delete message';
  830. }
  831. else
  832. {
  833. //check if subject has id_order
  834. preg_match('/\#ct([0-9]*)/', $subject, $matches1);
  835. preg_match('/\#tc([0-9-a-z-A-Z]*)/', $subject, $matches2);
  836. $matchFound = false;
  837. if (isset($matches1[1]) && isset($matches2[1]))
  838. $matchFound = true;
  839. $new_ct = ( Configuration::get('PS_SAV_IMAP_CREATE_THREADS') && !$matchFound && (strpos($subject, '[no_sync]') == false));
  840. if ( $matchFound || $new_ct)
  841. {
  842. if ($new_ct)
  843. {
  844. if (!preg_match('/<('.Tools::cleanNonUnicodeSupport('[a-z\p{L}0-9!#$%&\'*+\/=?^`{}|~_-]+[.a-z\p{L}0-9!#$%&\'*+\/=?^`{}|~_-]*@[a-z\p{L}0-9]+[._a-z\p{L}0-9-]*\.[a-z0-9]+').')>/', $overview->from, $result)
  845. || !Validate::isEmail($from = $result[1]))
  846. continue;
  847. // we want to assign unrecognized mails to the right contact category
  848. $contacts = Contact::getContacts($this->context->language->id);
  849. if (!$contacts)
  850. continue;
  851. foreach ($contacts as $contact) {
  852. if (strpos($overview->to , $contact['email']) !== false)
  853. $id_contact = $contact['id_contact'];
  854. }
  855. if (!isset($id_contact)) // if not use the default contact category
  856. $id_contact = $contacts[0]['id_contact'];
  857. $customer = new Customer;
  858. $client = $customer->getByEmail($from); //check if we already have a customer with this email
  859. $ct = new CustomerThread();
  860. if (isset($client->id)) //if mail is owned by a customer assign to him
  861. $ct->id_customer = $client->id;
  862. $ct->email = $from;
  863. $ct->id_contact = $id_contact;
  864. $ct->id_lang = (int)Configuration::get('PS_LANG_DEFAULT');
  865. $ct->id_shop = $this->context->shop->id; //new customer threads for unrecognized mails are not shown without shop id
  866. $ct->status = 'open';
  867. $ct->token = Tools::passwdGen(12);
  868. $ct->add();
  869. }
  870. else
  871. $ct = new CustomerThread((int)$matches1[1]); //check if order exist in database
  872. if (Validate::isLoadedObject($ct) && ((isset($matches2[1]) && $ct->token == $matches2[1]) || $new_ct))
  873. {
  874. $message = imap_fetchbody($mbox, $overview->msgno, 1);
  875. $message = quoted_printable_decode($message);
  876. $message = utf8_encode($message);
  877. $message = quoted_printable_decode($message);
  878. $message = nl2br($message);
  879. $cm = new CustomerMessage();
  880. $cm->id_customer_thread = $ct->id;
  881. $cm->message = $message;
  882. $cm->add();
  883. }
  884. }
  885. Db::getInstance()->execute('INSERT INTO `'._DB_PREFIX_.'customer_message_sync_imap` (`md5_header`) VALUES (\''.pSQL($md5).'\')');
  886. }
  887. }
  888. imap_expunge($mbox);
  889. imap_close($mbox);
  890. $array = array('hasError' => false, 'errors' => array($str_errors.$str_error_delete));
  891. die(Tools::jsonEncode($array));
  892. }
  893. }
  894. }