PageRenderTime 54ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/controllers/admin/AdminCustomerThreadsController.php

https://github.com/netplayer/PrestaShop
PHP | 966 lines | 830 code | 99 blank | 37 comment | 118 complexity | d3d400d2a7a9e4b4cbf5f9b468c2d448 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, 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. $this->_group = 'GROUP BY cm.id_customer_thread';
  228. $this->_orderBy = 'id_customer_thread';
  229. $this->_orderWay = 'DESC';
  230. $contacts = CustomerThread::getContacts();
  231. $categories = Contact::getCategoriesContacts();
  232. $params = array(
  233. $this->l('Total threads') => $all = CustomerThread::getTotalCustomerThreads(),
  234. $this->l('Threads pending') => $pending = CustomerThread::getTotalCustomerThreads('status LIKE "%pending%"'),
  235. $this->l('Total number of customer messages') => CustomerMessage::getTotalCustomerMessages('id_employee = 0'),
  236. $this->l('Total number of employee messages') => CustomerMessage::getTotalCustomerMessages('id_employee != 0'),
  237. $this->l('Unread threads') => $unread = CustomerThread::getTotalCustomerThreads('status = "open"'),
  238. $this->l('Closed threads') => $all - ($unread + $pending)
  239. );
  240. $this->tpl_list_vars = array(
  241. 'contacts' => $contacts,
  242. 'categories' => $categories,
  243. 'params' => $params
  244. );
  245. return parent::renderList();
  246. }
  247. public function initToolbar()
  248. {
  249. parent::initToolbar();
  250. unset($this->toolbar_btn['new']);
  251. }
  252. public function postProcess()
  253. {
  254. if ($id_customer_thread = (int)Tools::getValue('id_customer_thread'))
  255. {
  256. if (($id_contact = (int)Tools::getValue('id_contact')))
  257. Db::getInstance()->execute('
  258. UPDATE '._DB_PREFIX_.'customer_thread
  259. SET id_contact = '.(int)$id_contact.'
  260. WHERE id_customer_thread = '.(int)$id_customer_thread
  261. );
  262. if ($id_status = (int)Tools::getValue('setstatus'))
  263. {
  264. $status_array = array(1 => 'open', 2 => 'closed', 3 => 'pending1', 4 => 'pending2');
  265. Db::getInstance()->execute('
  266. UPDATE '._DB_PREFIX_.'customer_thread
  267. SET status = "'.$status_array[$id_status].'"
  268. WHERE id_customer_thread = '.(int)$id_customer_thread.' LIMIT 1
  269. ');
  270. }
  271. if (isset($_POST['id_employee_forward']))
  272. {
  273. $messages = Db::getInstance()->getRow('
  274. SELECT ct.*, cm.*, cl.name subject, CONCAT(e.firstname, \' \', e.lastname) employee_name,
  275. CONCAT(c.firstname, \' \', c.lastname) customer_name, c.firstname
  276. FROM '._DB_PREFIX_.'customer_thread ct
  277. LEFT JOIN '._DB_PREFIX_.'customer_message cm
  278. ON (ct.id_customer_thread = cm.id_customer_thread)
  279. LEFT JOIN '._DB_PREFIX_.'contact_lang cl
  280. ON (cl.id_contact = ct.id_contact AND cl.id_lang = '.(int)$this->context->language->id.')
  281. LEFT OUTER JOIN '._DB_PREFIX_.'employee e
  282. ON e.id_employee = cm.id_employee
  283. LEFT OUTER JOIN '._DB_PREFIX_.'customer c
  284. ON (c.email = ct.email)
  285. WHERE ct.id_customer_thread = '.(int)Tools::getValue('id_customer_thread').'
  286. ORDER BY cm.date_add DESC
  287. ');
  288. $output = $this->displayMessage($messages, true, (int)Tools::getValue('id_employee_forward'));
  289. $cm = new CustomerMessage();
  290. $cm->id_employee = (int)$this->context->employee->id;
  291. $cm->id_customer_thread = (int)Tools::getValue('id_customer_thread');
  292. $cm->ip_address = ip2long(Tools::getRemoteAddr());
  293. $current_employee = $this->context->employee;
  294. $id_employee = (int)Tools::getValue('id_employee_forward');
  295. $employee = new Employee($id_employee);
  296. $email = Tools::getValue('email');
  297. $message = Tools::getValue('message_forward');
  298. if (($error = $cm->validateField('message', $message, null, array(), true)) !== true)
  299. $this->errors[] = $error;
  300. elseif ($id_employee && $employee && Validate::isLoadedObject($employee))
  301. {
  302. $params = array(
  303. '{messages}' => stripslashes($output),
  304. '{employee}' => $current_employee->firstname.' '.$current_employee->lastname,
  305. '{comment}' => stripslashes(Tools::nl2br($_POST['message_forward'])),
  306. '{firstname}' => $employee->firstname,
  307. '{lastname}' => $employee->lastname,
  308. );
  309. if (Mail::Send(
  310. $this->context->language->id,
  311. 'forward_msg',
  312. Mail::l('Fwd: Customer message', $this->context->language->id),
  313. $params,
  314. $employee->email,
  315. $employee->firstname.' '.$employee->lastname,
  316. $current_employee->email,
  317. $current_employee->firstname.' '.$current_employee->lastname,
  318. null, null, _PS_MAIL_DIR_, true))
  319. {
  320. $cm->private = 1;
  321. $cm->message = $this->l('Message forwarded to').' '.$employee->firstname.' '.$employee->lastname."\n".$this->l('Comment:').' '.$message;
  322. $cm->add();
  323. }
  324. }
  325. elseif ($email && Validate::isEmail($email))
  326. {
  327. $params = array(
  328. '{messages}' => Tools::nl2br(stripslashes($output)),
  329. '{employee}' => $current_employee->firstname.' '.$current_employee->lastname,
  330. '{comment}' => stripslashes($_POST['message_forward'])
  331. );
  332. if (Mail::Send(
  333. $this->context->language->id,
  334. 'forward_msg',
  335. Mail::l('Fwd: Customer message', $this->context->language->id),
  336. $params, $email, null,
  337. $current_employee->email, $current_employee->firstname.' '.$current_employee->lastname,
  338. null, null, _PS_MAIL_DIR_, true))
  339. {
  340. $cm->message = $this->l('Message forwarded to').' '.$email."\n".$this->l('Comment:').' '.$message;
  341. $cm->add();
  342. }
  343. }
  344. else
  345. $this->errors[] = '<div class="alert error">'.Tools::displayError('The email address is invalid.').'</div>';
  346. }
  347. if (Tools::isSubmit('submitReply'))
  348. {
  349. $ct = new CustomerThread($id_customer_thread);
  350. ShopUrl::cacheMainDomainForShop((int)$ct->id_shop);
  351. $cm = new CustomerMessage();
  352. $cm->id_employee = (int)$this->context->employee->id;
  353. $cm->id_customer_thread = $ct->id;
  354. $cm->ip_address = ip2long(Tools::getRemoteAddr());
  355. $cm->message = Tools::getValue('reply_message');
  356. if (($error = $cm->validateField('message', $cm->message, null, array(), true)) !== true)
  357. $this->errors[] = $error;
  358. elseif (isset($_FILES) && !empty($_FILES['joinFile']['name']) && $_FILES['joinFile']['error'] != 0)
  359. $this->errors[] = Tools::displayError('An error occurred during the file upload process.');
  360. elseif ($cm->add())
  361. {
  362. $file_attachment = null;
  363. if (!empty($_FILES['joinFile']['name']))
  364. {
  365. $file_attachment['content'] = file_get_contents($_FILES['joinFile']['tmp_name']);
  366. $file_attachment['name'] = $_FILES['joinFile']['name'];
  367. $file_attachment['mime'] = $_FILES['joinFile']['type'];
  368. }
  369. $customer = new Customer($ct->id_customer);
  370. $params = array(
  371. '{reply}' => Tools::nl2br(Tools::getValue('reply_message')),
  372. '{link}' => Tools::url(
  373. $this->context->link->getPageLink('contact', true),
  374. 'id_customer_thread='.(int)$ct->id.'&token='.$ct->token
  375. ),
  376. '{firstname}' => $customer->firstname,
  377. '{lastname}' => $customer->lastname
  378. );
  379. //#ct == id_customer_thread #tc == token of thread <== used in the synchronization imap
  380. $contact = new Contact((int)$ct->id_contact, (int)$ct->id_lang);
  381. if (Validate::isLoadedObject($contact))
  382. {
  383. $from_name = $contact->name;
  384. $from_email = $contact->email;
  385. }
  386. else
  387. {
  388. $from_name = null;
  389. $from_email = null;
  390. }
  391. if (Mail::Send(
  392. (int)$ct->id_lang,
  393. 'reply_msg',
  394. sprintf(Mail::l('An answer to your message is available #ct%1$s #tc%2$s', $ct->id_lang), $ct->id, $ct->token),
  395. $params, Tools::getValue('msg_email'), null, $from_email, $from_name, $file_attachment, null,
  396. _PS_MAIL_DIR_, true))
  397. {
  398. $ct->status = 'closed';
  399. $ct->update();
  400. }
  401. Tools::redirectAdmin(
  402. self::$currentIndex.'&id_customer_thread='.(int)$id_customer_thread.'&viewcustomer_thread&token='.Tools::getValue('token')
  403. );
  404. }
  405. else
  406. $this->errors[] = Tools::displayError('An error occurred. Your message was not sent. Please contact your system administrator.');
  407. }
  408. }
  409. return parent::postProcess();
  410. }
  411. public function initContent()
  412. {
  413. if (isset($_GET['filename']) && file_exists(_PS_UPLOAD_DIR_.$_GET['filename']) && Validate::isFileName($_GET['filename']))
  414. AdminCustomerThreadsController::openUploadedFile();
  415. return parent::initContent();
  416. }
  417. protected function openUploadedFile()
  418. {
  419. $filename = $_GET['filename'];
  420. $extensions = array(
  421. '.txt' => 'text/plain',
  422. '.rtf' => 'application/rtf',
  423. '.doc' => 'application/msword',
  424. '.docx'=> 'application/msword',
  425. '.pdf' => 'application/pdf',
  426. '.zip' => 'multipart/x-zip',
  427. '.png' => 'image/png',
  428. '.jpeg' => 'image/jpeg',
  429. '.gif' => 'image/gif',
  430. '.jpg' => 'image/jpeg',
  431. );
  432. $extension = false;
  433. foreach ($extensions as $key => $val)
  434. if (substr(Tools::strtolower($filename), -4) == $key || substr(Tools::strtolower($filename), -5) == $key)
  435. {
  436. $extension = $val;
  437. break;
  438. }
  439. if (!$extension || !Validate::isFileName($filename))
  440. die(Tools::displayError());
  441. if (ob_get_level() && ob_get_length() > 0)
  442. ob_end_clean();
  443. header('Content-Type: '.$extension);
  444. header('Content-Disposition:attachment;filename="'.$filename.'"');
  445. readfile(_PS_UPLOAD_DIR_.$filename);
  446. die;
  447. }
  448. public function renderKpis()
  449. {
  450. $time = time();
  451. $kpis = array();
  452. /* The data generation is located in AdminStatsControllerCore */
  453. $helper = new HelperKpi();
  454. $helper->id = 'box-pending-messages';
  455. $helper->icon = 'icon-envelope';
  456. $helper->color = 'color1';
  457. $helper->href = $this->context->link->getAdminLink('AdminCustomerThreads');
  458. $helper->title = $this->l('Pending Discussion Threads', null, null, false);
  459. if (ConfigurationKPI::get('PENDING_MESSAGES') !== false)
  460. $helper->value = ConfigurationKPI::get('PENDING_MESSAGES');
  461. if (ConfigurationKPI::get('PENDING_MESSAGES_EXPIRE') < $time)
  462. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=pending_messages';
  463. $kpis[] = $helper->generate();
  464. $helper = new HelperKpi();
  465. $helper->id = 'box-age';
  466. $helper->icon = 'icon-time';
  467. $helper->color = 'color2';
  468. $helper->title = $this->l('Average Response Time', null, null, false);
  469. $helper->subtitle = $this->l('30 days', null, null, false);
  470. if (ConfigurationKPI::get('AVG_MSG_RESPONSE_TIME') !== false)
  471. $helper->value = ConfigurationKPI::get('AVG_MSG_RESPONSE_TIME');
  472. if (ConfigurationKPI::get('AVG_MSG_RESPONSE_TIME_EXPIRE') < $time)
  473. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=avg_msg_response_time';
  474. $kpis[] = $helper->generate();
  475. $helper = new HelperKpi();
  476. $helper->id = 'box-messages-per-thread';
  477. $helper->icon = 'icon-copy';
  478. $helper->color = 'color3';
  479. $helper->title = $this->l('Messages per Thread', null, null, false);
  480. $helper->subtitle = $this->l('30 day', null, null, false);
  481. if (ConfigurationKPI::get('MESSAGES_PER_THREAD') !== false)
  482. $helper->value = ConfigurationKPI::get('MESSAGES_PER_THREAD');
  483. if (ConfigurationKPI::get('MESSAGES_PER_THREAD_EXPIRE') < $time)
  484. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=messages_per_thread';
  485. $kpis[] = $helper->generate();
  486. $helper = new HelperKpiRow();
  487. $helper->kpis = $kpis;
  488. return $helper->generate();
  489. }
  490. public function renderView()
  491. {
  492. if (!$id_customer_thread = (int)Tools::getValue('id_customer_thread'))
  493. return;
  494. $this->context = Context::getContext();
  495. if (!($thread = $this->loadObject()))
  496. return;
  497. $this->context->cookie->{'customer_threadFilter_cl!id_contact'} = $thread->id_contact;
  498. $employees = Employee::getEmployees();
  499. $messages = CustomerThread::getMessageCustomerThreads($id_customer_thread);
  500. foreach ($messages as $key => $mess)
  501. if (isset($mess['file_name']) && $mess['file_name'] != '')
  502. $messages[$key]['file_name'] = _THEME_PROD_PIC_DIR_.$mess['file_name'];
  503. else
  504. unset($messages[$key]['file_name']);
  505. $next_thread = CustomerThread::getNextThread((int)$thread->id);
  506. $contacts = Contact::getContacts($this->context->language->id);
  507. $actions = array();
  508. if ($next_thread)
  509. $next_thread = array(
  510. 'href' => self::$currentIndex.'&id_customer_thread='.(int)$next_thread.'&viewcustomer_thread&token='.$this->token,
  511. 'name' => $this->l('Reply to the next unanswered message in this thread')
  512. );
  513. if ($thread->status != 'closed')
  514. $actions['closed'] = array(
  515. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=2&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  516. 'label' => $this->l('Mark as "handled"'),
  517. 'name' => 'setstatus',
  518. 'value' => 2
  519. );
  520. else
  521. $actions['open'] = array(
  522. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  523. 'label' => $this->l('Re-open'),
  524. 'name' => 'setstatus',
  525. 'value' => 1
  526. );
  527. if ($thread->status != 'pending1')
  528. $actions['pending1'] = array(
  529. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=3&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  530. 'label' => $this->l('Mark as "pending 1" (will be answered later)'),
  531. 'name' => 'setstatus',
  532. 'value' => 3
  533. );
  534. else
  535. $actions['pending1'] = array(
  536. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  537. 'label' => $this->l('Disable pending status'),
  538. 'name' => 'setstatus',
  539. 'value' => 1
  540. );
  541. if ($thread->status != 'pending2')
  542. $actions['pending2'] = array(
  543. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=4&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  544. 'label' => $this->l('Mark as "pending 2" (will be answered later)'),
  545. 'name' => 'setstatus',
  546. 'value' => 4
  547. );
  548. else
  549. $actions['pending2'] = array(
  550. 'href' => self::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int)Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
  551. 'label' => $this->l('Disable pending status'),
  552. 'name' => 'setstatus',
  553. 'value' => 1
  554. );
  555. if ($thread->id_customer)
  556. {
  557. $customer = new Customer($thread->id_customer);
  558. $orders = Order::getCustomerOrders($customer->id);
  559. if ($orders && count($orders))
  560. {
  561. $total_ok = 0;
  562. $orders_ok = array();
  563. foreach ($orders as $key => $order)
  564. {
  565. if ($order['valid'])
  566. {
  567. $orders_ok[] = $order;
  568. $total_ok += $order['total_paid_real'];
  569. }
  570. $orders[$key]['date_add'] = Tools::displayDate($order['date_add']);
  571. $orders[$key]['total_paid_real'] = Tools::displayPrice($order['total_paid_real'], new Currency((int)$order['id_currency']));
  572. }
  573. }
  574. $products = $customer->getBoughtProducts();
  575. if ($products && count($products))
  576. foreach ($products as $key => $product)
  577. $products[$key]['date_add'] = Tools::displayDate($product['date_add'], null, true);
  578. }
  579. $timeline_items = $this->getTimeline($messages, $thread->id_order);
  580. $first_message = $messages[0];
  581. if (!$messages[0]['id_employee'])
  582. unset($messages[0]);
  583. $contact = '';
  584. foreach ($contacts as $c)
  585. if ($c['id_contact'] == $thread->id_contact)
  586. $contact = $c['name'];
  587. $this->tpl_view_vars = array(
  588. 'id_customer_thread' => $id_customer_thread,
  589. 'thread' => $thread,
  590. 'actions' => $actions,
  591. 'employees' => $employees,
  592. 'current_employee' => $this->context->employee,
  593. 'messages' => $messages,
  594. 'first_message' => $first_message,
  595. 'contact' => $contact,
  596. 'next_thread' => $next_thread,
  597. 'orders' => isset($orders) ? $orders : false,
  598. 'customer' => isset($customer) ? $customer : false,
  599. 'products' => isset($products) ? $products : false,
  600. 'total_ok' => isset($total_ok) ? Tools::displayPrice($total_ok, $this->context->currency) : false,
  601. 'orders_ok' => isset($orders_ok) ? $orders_ok : false,
  602. 'count_ok' => isset($orders_ok) ? count($orders_ok) : false,
  603. 'PS_CUSTOMER_SERVICE_SIGNATURE' => str_replace('\r\n', "\n", Configuration::get('PS_CUSTOMER_SERVICE_SIGNATURE', (int)$thread->id_lang)),
  604. 'timeline_items' => $timeline_items,
  605. );
  606. if ($next_thread)
  607. $this->tpl_view_vars['next_thread'] = $next_thread;
  608. return parent::renderView();
  609. }
  610. public function getTimeline($messages, $id_order)
  611. {
  612. $timeline = array();
  613. foreach ($messages as $message)
  614. {
  615. $content = $this->l('Message to: ').' <span class="badge">'.(!$message['id_employee'] ? $message['subject'] : $message['customer_name']).'</span></br>'.$message['message'];
  616. $timeline[$message['date_add']][] = array(
  617. 'arrow' => 'left',
  618. 'background_color' => '',
  619. 'icon' => 'icon-envelope',
  620. 'content' => $content,
  621. 'date' => $message['date_add'],
  622. );
  623. }
  624. $order = new Order((int)$id_order);
  625. if (Validate::isLoadedObject($order))
  626. {
  627. $order_history = $order->getHistory($this->context->language->id);
  628. foreach ($order_history as $history)
  629. {
  630. $link = $this->context->link->getAdminLink('AdminOrders').'&vieworder&id_order='.(int)$order->id;
  631. $content = '<a class="badge" target="_blank" href="'.$link.'">'.$this->l('Order').' #'.(int)$order->id.'</a></br></br>';
  632. $content .= '<span>'.$this->l('Status:').' '.$history['ostate_name'].'</span>';
  633. $timeline[$history['date_add']][] = array(
  634. 'arrow' => 'right',
  635. 'alt' => true,
  636. 'background_color' => $history['color'],
  637. 'icon' => 'icon-credit-card',
  638. 'content' => $content,
  639. 'date' => $history['date_add'],
  640. 'see_more_link' => $link,
  641. );
  642. }
  643. }
  644. krsort($timeline);
  645. return $timeline;
  646. }
  647. protected function displayMessage($message, $email = false, $id_employee = null)
  648. {
  649. $tpl = $this->createTemplate('message.tpl');
  650. $contacts = Contact::getContacts($this->context->language->id);
  651. foreach ($contacts as $contact)
  652. $contact_array[$contact['id_contact']] = array('id_contact' => $contact['id_contact'], 'name' => $contact['name']);
  653. $contacts = $contact_array;
  654. if (!$email)
  655. {
  656. if (!empty($message['id_product']) && empty($message['employee_name']))
  657. $id_order_product = Order::getIdOrderProduct((int)$message['id_customer'], (int)$message['id_product']);
  658. }
  659. $message['date_add'] = Tools::displayDate($message['date_add'], null, true);
  660. $message['user_agent'] = strip_tags($message['user_agent']);
  661. $message['message'] = preg_replace(
  662. '/(https?:\/\/[a-z0-9#%&_=\(\)\.\? \+\-@\/]{6,1000})([\s\n<])/Uui',
  663. '<a href="\1">\1</a>\2',
  664. html_entity_decode($message['message'],
  665. ENT_QUOTES, 'UTF-8')
  666. );
  667. $is_valid_order_id = true;
  668. $order = new Order((int)$message['id_order']);
  669. if (!Validate::isLoadedObject($order))
  670. $is_valid_order_id = false;
  671. $tpl->assign(array(
  672. 'thread_url' => Tools::getAdminUrl(basename(_PS_ADMIN_DIR_).'/'.
  673. $this->context->link->getAdminLink('AdminCustomerThreads').'&amp;id_customer_thread='
  674. .(int)$message['id_customer_thread'].'&amp;viewcustomer_thread=1'),
  675. 'link' => Context::getContext()->link,
  676. 'current' => self::$currentIndex,
  677. 'token' => $this->token,
  678. 'message' => $message,
  679. 'id_order_product' => isset($id_order_product) ? $id_order_product : null,
  680. 'email' => $email,
  681. 'id_employee' => $id_employee,
  682. 'PS_SHOP_NAME' => Configuration::get('PS_SHOP_NAME'),
  683. 'file_name' => file_exists(_PS_UPLOAD_DIR_.$message['file_name']),
  684. 'contacts' => $contacts,
  685. 'is_valid_order_id' => $is_valid_order_id
  686. ));
  687. return $tpl->fetch();
  688. }
  689. protected function displayButton($content)
  690. {
  691. return '<div><p>'.$content.'</p></div>';
  692. }
  693. public function renderOptions()
  694. {
  695. if (Configuration::get('PS_SAV_IMAP_URL')
  696. && Configuration::get('PS_SAV_IMAP_PORT')
  697. && Configuration::get('PS_SAV_IMAP_USER')
  698. && Configuration::get('PS_SAV_IMAP_PWD'))
  699. $this->tpl_option_vars['use_sync'] = true;
  700. else
  701. $this->tpl_option_vars['use_sync'] = false;
  702. return parent::renderOptions();
  703. }
  704. /**
  705. * AdminController::getList() override
  706. * @see AdminController::getList()
  707. */
  708. public function getList($id_lang, $order_by = null, $order_way = null, $start = 0, $limit = null, $id_lang_shop = false)
  709. {
  710. parent::getList($id_lang, $order_by, $order_way, $start, $limit, $id_lang_shop);
  711. $nb_items = count($this->_list);
  712. for ($i = 0; $i < $nb_items; ++$i)
  713. {
  714. if (isset($this->_list[$i]['messages']))
  715. $this->_list[$i]['messages'] = Tools::htmlentitiesDecodeUTF8($this->_list[$i]['messages']);
  716. }
  717. }
  718. public function updateOptionPsSavImapOpt($value)
  719. {
  720. if ($this->tabAccess['edit'] != '1')
  721. throw new PrestaShopException(Tools::displayError('You do not have permission to edit this.'));
  722. if (!$this->errors && $value)
  723. Configuration::updateValue('PS_SAV_IMAP_OPT', implode('', $value));
  724. }
  725. public function ajaxProcessMarkAsRead()
  726. {
  727. if ($this->tabAccess['edit'] != '1')
  728. throw new PrestaShopException(Tools::displayError('You do not have permission to edit this.'));
  729. $id_thread = Tools::getValue('id_thread');
  730. $messages = CustomerThread::getMessageCustomerThreads($id_thread);
  731. if (count($messages))
  732. 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);
  733. }
  734. public function ajaxProcessSyncImap()
  735. {
  736. if ($this->tabAccess['edit'] != '1')
  737. throw new PrestaShopException(Tools::displayError('You do not have permission to edit this.'));
  738. if (Tools::isSubmit('syncImapMail'))
  739. {
  740. if (!($url = Configuration::get('PS_SAV_IMAP_URL'))
  741. || !($port = Configuration::get('PS_SAV_IMAP_PORT'))
  742. || !($user = Configuration::get('PS_SAV_IMAP_USER'))
  743. || !($password = Configuration::get('PS_SAV_IMAP_PWD')))
  744. die('{"hasError" : true, "errors" : ["Configuration is not correct"]}');
  745. $conf = Configuration::getMultiple(array(
  746. 'PS_SAV_IMAP_OPT_NORSH', 'PS_SAV_IMAP_OPT_SSL',
  747. 'PS_SAV_IMAP_OPT_VALIDATE-CERT', 'PS_SAV_IMAP_OPT_NOVALIDATE-CERT',
  748. 'PS_SAV_IMAP_OPT_TLS', 'PS_SAV_IMAP_OPT_NOTLS'));
  749. $conf_str = '';
  750. if ($conf['PS_SAV_IMAP_OPT_NORSH'])
  751. $conf_str .= '/norsh';
  752. if ($conf['PS_SAV_IMAP_OPT_SSL'])
  753. $conf_str .= '/ssl';
  754. if ($conf['PS_SAV_IMAP_OPT_VALIDATE-CERT'])
  755. $conf_str .= '/validate-cert';
  756. if ($conf['PS_SAV_IMAP_OPT_NOVALIDATE-CERT'])
  757. $conf_str .= '/novalidate-cert';
  758. if ($conf['PS_SAV_IMAP_OPT_TLS'])
  759. $conf_str .= '/tls';
  760. if ($conf['PS_SAV_IMAP_OPT_NOTLS'])
  761. $conf_str .= '/notls';
  762. if (!function_exists('imap_open'))
  763. die('{"hasError" : true, "errors" : ["imap is not installed on this server"]}');
  764. $mbox = @imap_open('{'.$url.':'.$port.$conf_str.'}', $user, $password);
  765. //checks if there is no error when connecting imap server
  766. $errors = imap_errors();
  767. $str_errors = '';
  768. $str_error_delete = '';
  769. if (sizeof($errors) && is_array($errors))
  770. {
  771. $str_errors = '';
  772. foreach($errors as $error)
  773. $str_errors .= '"'.$error.'",';
  774. $str_errors = rtrim($str_errors, ',').'';
  775. }
  776. //checks if imap connexion is active
  777. if (!$mbox)
  778. die('{"hasError" : true, "errors" : ["Cannot connect to the mailbox:.<br />'.addslashes($str_errors).'"]}');
  779. //Returns information about the current mailbox. Returns FALSE on failure.
  780. $check = imap_check($mbox);
  781. if (!$check)
  782. die('{"hasError" : true, "errors" : ["Fail to get information about the current mailbox"]}');
  783. if ($check->Nmsgs == 0)
  784. die('{"hasError" : true, "errors" : ["NO message to sync"]}');
  785. $result = imap_fetch_overview($mbox,"1:{$check->Nmsgs}",0);
  786. foreach ($result as $overview)
  787. {
  788. //check if message exist in database
  789. if (isset($overview->subject))
  790. $subject = $overview->subject;
  791. else
  792. $subject = '';
  793. //Creating an md5 to check if message has been allready processed
  794. $md5 = md5($overview->date.$overview->from.$subject.$overview->msgno);
  795. $exist = Db::getInstance()->getValue(
  796. 'SELECT `md5_header`
  797. FROM `'._DB_PREFIX_.'customer_message_sync_imap`
  798. WHERE `md5_header` = \''.pSQL($md5).'\'');
  799. if ($exist)
  800. {
  801. if (Configuration::get('PS_SAV_IMAP_DELETE_MSG'))
  802. if (!imap_delete($mbox, $overview->msgno))
  803. $str_error_delete = ', "Fail to delete message"';
  804. }
  805. else
  806. {
  807. //check if subject has id_order
  808. preg_match('/\#ct([0-9]*)/', $subject, $matches1);
  809. preg_match('/\#tc([0-9-a-z-A-Z]*)/', $subject, $matches2);
  810. $matchFound = false;
  811. if (isset($matches1[1]) && isset($matches2[1]))
  812. $matchFound = true;
  813. $new_ct = ( Configuration::get('PS_SAV_IMAP_CREATE_THREADS') && !$matchFound && (strpos($subject, '[no_sync]') == false));
  814. if ( $matchFound || $new_ct)
  815. {
  816. if ($new_ct)
  817. {
  818. 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)
  819. || !Validate::isEmail($from = $result[1]))
  820. continue;
  821. // we want to assign unrecognized mails to the right contact category
  822. $contacts = Contact::getContacts($this->context->language->id);
  823. if (!$contacts)
  824. continue;
  825. foreach ($contacts as $contact) {
  826. if (strpos($overview->to , $contact['email']) !== false)
  827. $id_contact = $contact['id_contact'];
  828. }
  829. if (!isset($id_contact)) // if not use the default contact category
  830. $id_contact = $contacts[0]['id_contact'];
  831. $customer = new Customer;
  832. $client = $customer->getByEmail($from); //check if we already have a customer with this email
  833. $ct = new CustomerThread();
  834. if (isset($client->id)) //if mail is owned by a customer assign to him
  835. $ct->id_customer = $client->id;
  836. $ct->email = $from;
  837. $ct->id_contact = $id_contact;
  838. $ct->id_lang = (int)Configuration::get('PS_LANG_DEFAULT');
  839. $ct->id_shop = $this->context->shop->id; //new customer threads for unrecognized mails are not shown without shop id
  840. $ct->status = 'open';
  841. $ct->token = Tools::passwdGen(12);
  842. $ct->add();
  843. }
  844. else
  845. $ct = new CustomerThread((int)$matches1[1]); //check if order exist in database
  846. if (Validate::isLoadedObject($ct) && ((isset($matches2[1]) && $ct->token == $matches2[1]) || $new_ct))
  847. {
  848. $message = imap_fetchbody($mbox, $overview->msgno, 1);
  849. $message = quoted_printable_decode($message);
  850. $message = utf8_encode($message);
  851. $message = quoted_printable_decode($message);
  852. $message = nl2br($message);
  853. $cm = new CustomerMessage();
  854. $cm->id_customer_thread = $ct->id;
  855. $cm->message = $message;
  856. $cm->add();
  857. }
  858. }
  859. Db::getInstance()->execute('INSERT INTO `'._DB_PREFIX_.'customer_message_sync_imap` (`md5_header`) VALUES (\''.pSQL($md5).'\')');
  860. }
  861. }
  862. imap_expunge($mbox);
  863. imap_close($mbox);
  864. die('{"hasError" : false, "errors" : ["'.$str_errors.$str_error_delete.'"]}');
  865. }
  866. }
  867. }