1<?php
2/**
3 * 2007-2016 PrestaShop
4 *
5 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA
6 * Copyright (C) 2017-2018 thirty bees
7 *
8 * NOTICE OF LICENSE
9 *
10 * This source file is subject to the Open Software License (OSL 3.0)
11 * that is bundled with this package in the file LICENSE.txt.
12 * It is also available through the world-wide-web at this URL:
13 * http://opensource.org/licenses/osl-3.0.php
14 * If you did not receive a copy of the license and are unable to
15 * obtain it through the world-wide-web, please send an email
16 * to license@thirtybees.com so we can send you a copy immediately.
17 *
18 * DISCLAIMER
19 *
20 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
21 * versions in the future. If you wish to customize PrestaShop for your
22 * needs please refer to https://www.thirtybees.com for more information.
23 *
24 * @author    thirty bees <contact@thirtybees.com>
25 * @author    PrestaShop SA <contact@prestashop.com>
26 * @copyright 2017-2018 thirty bees
27 * @copyright 2007-2016 PrestaShop SA
28 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
29 *  PrestaShop is an internationally registered trademark & property of PrestaShop SA
30 */
31
32/**
33 * Class AdminCustomerThreadsControllerCore
34 *
35 * @since 1.0.0
36 */
37class AdminCustomerThreadsControllerCore extends AdminController
38{
39    /**
40     * AdminCustomerThreadsControllerCore constructor.
41     *
42     * @since 1.0.0
43     */
44    public function __construct()
45    {
46        $this->bootstrap = true;
47        $this->context = Context::getContext();
48        $this->table = 'customer_thread';
49        $this->className = 'CustomerThread';
50        $this->lang = false;
51
52        $contactArray = [];
53        $contacts = Contact::getContacts($this->context->language->id);
54
55        foreach ($contacts as $contact) {
56            $contactArray[$contact['id_contact']] = $contact['name'];
57        }
58
59        $languageArray = [];
60        $languages = Language::getLanguages();
61        foreach ($languages as $language) {
62            $languageArray[$language['id_lang']] = $language['name'];
63        }
64
65        $iconArray = [
66            'open'     => ['class' => 'icon-circle text-success', 'alt' => $this->l('Open')],
67            'closed'   => ['class' => 'icon-circle text-danger', 'alt' => $this->l('Closed')],
68            'pending1' => ['class' => 'icon-circle text-warning', 'alt' => $this->l('Pending 1')],
69            'pending2' => ['class' => 'icon-circle text-warning', 'alt' => $this->l('Pending 2')],
70        ];
71
72        $statusArray = [];
73        foreach ($iconArray as $k => $v) {
74            $statusArray[$k] = $v['alt'];
75        }
76
77        $this->fields_list = [
78            'id_customer_thread' => [
79                'title' => $this->l('ID'),
80                'align' => 'center',
81                'class' => 'fixed-width-xs',
82            ],
83            'customer'           => [
84                'title'          => $this->l('Customer'),
85                'filter_key'     => 'customer',
86                'tmpTableFilter' => true,
87            ],
88            'email'              => [
89                'title'      => $this->l('Email'),
90                'filter_key' => 'a!email',
91            ],
92            'contact'            => [
93                'title'       => $this->l('Type'),
94                'type'        => 'select',
95                'list'        => $contactArray,
96                'filter_key'  => 'cl!id_contact',
97                'filter_type' => 'int',
98            ],
99            'language'           => [
100                'title'       => $this->l('Language'),
101                'type'        => 'select',
102                'list'        => $languageArray,
103                'filter_key'  => 'l!id_lang',
104                'filter_type' => 'int',
105            ],
106            'status'             => [
107                'title'       => $this->l('Status'),
108                'type'        => 'select',
109                'list'        => $statusArray,
110                'icon'        => $iconArray,
111                'align'       => 'center',
112                'filter_key'  => 'a!status',
113                'filter_type' => 'string',
114            ],
115            'employee'           => [
116                'title'          => $this->l('Employee'),
117                'filter_key'     => 'employee',
118                'tmpTableFilter' => true,
119            ],
120            'messages'           => [
121                'title'          => $this->l('Messages'),
122                'filter_key'     => 'messages',
123                'tmpTableFilter' => true,
124                'maxlength'      => 40,
125            ],
126            'private'            => [
127                'title'      => $this->l('Private'),
128                'type'       => 'select',
129                'filter_key' => 'private',
130                'align'      => 'center',
131                'cast'       => 'intval',
132                'callback'   => 'printOptinIcon',
133                'list'       => [
134                    '0' => $this->l('No'),
135                    '1' => $this->l('Yes'),
136                ],
137            ],
138            'date_upd'           => [
139                'title'        => $this->l('Last message'),
140                'havingFilter' => true,
141                'type'         => 'datetime',
142            ],
143        ];
144
145        $this->bulk_actions = [
146            'delete' => [
147                'text'    => $this->l('Delete selected'),
148                'confirm' => $this->l('Delete selected items?'),
149                'icon'    => 'icon-trash',
150            ],
151        ];
152
153        $this->shopLinkType = 'shop';
154
155        $this->fields_options = [
156            'contact' => [
157                'title'  => $this->l('Contact options'),
158                'fields' => [
159                    'PS_CUSTOMER_SERVICE_FILE_UPLOAD' => [
160                        'title' => $this->l('Allow file uploading'),
161                        'hint'  => $this->l('Allow customers to upload files using the contact page.'),
162                        'type'  => 'bool',
163                    ],
164                    'PS_CUSTOMER_SERVICE_SIGNATURE'   => [
165                        'title' => $this->l('Default message'),
166                        'hint'  => $this->l('Please fill out the message fields that appear by default when you answer a thread on the customer service page.'),
167                        'type'  => 'textareaLang',
168                        'lang'  => true,
169                    ],
170                ],
171                'submit' => ['title' => $this->l('Save')],
172            ],
173            'general' => [
174                'title'  => $this->l('Customer service options'),
175                'fields' => [
176                    'PS_SAV_IMAP_URL'                 => [
177                        'title' => $this->l('IMAP URL'),
178                        'hint'  => $this->l('URL for your IMAP server (ie.: mail.server.com).'),
179                        'type'  => 'text',
180                    ],
181                    'PS_SAV_IMAP_PORT'                => [
182                        'title'        => $this->l('IMAP port'),
183                        'hint'         => $this->l('Port to use to connect to your IMAP server.'),
184                        'type'         => 'text',
185                        'defaultValue' => 143,
186                    ],
187                    'PS_SAV_IMAP_USER'                => [
188                        'title' => $this->l('IMAP user'),
189                        'hint'  => $this->l('User to use to connect to your IMAP server.'),
190                        'type'  => 'text',
191                    ],
192                    'PS_SAV_IMAP_PWD'                 => [
193                        'title' => $this->l('IMAP password'),
194                        'hint'  => $this->l('Password to use to connect your IMAP server.'),
195                        'type'  => 'text',
196                    ],
197                    'PS_SAV_IMAP_DELETE_MSG'          => [
198                        'title' => $this->l('Delete messages'),
199                        'hint'  => $this->l('Delete messages after synchronization. If you do not enable this option, the synchronization will take more time.'),
200                        'type'  => 'bool',
201                    ],
202                    'PS_SAV_IMAP_CREATE_THREADS'      => [
203                        'title' => $this->l('Create new threads'),
204                        'hint'  => $this->l('Create new threads for unrecognized emails.'),
205                        'type'  => 'bool',
206                    ],
207                    'PS_SAV_IMAP_OPT_NORSH'           => [
208                        'title' => $this->l('IMAP options').' (/norsh)',
209                        'type'  => 'bool',
210                        'hint'  => $this->l('Do not use RSH or SSH to establish a preauthenticated IMAP sessions.'),
211                    ],
212                    'PS_SAV_IMAP_OPT_SSL'             => [
213                        'title' => $this->l('IMAP options').' (/ssl)',
214                        'type'  => 'bool',
215                        'hint'  => $this->l('Use the Secure Socket Layer (TLS/SSL) to encrypt the session.'),
216                    ],
217                    'PS_SAV_IMAP_OPT_VALIDATE-CERT'   => [
218                        'title' => $this->l('IMAP options').' (/validate-cert)',
219                        'type'  => 'bool',
220                        'hint'  => $this->l('Validate certificates from the TLS/SSL server.'),
221                    ],
222                    'PS_SAV_IMAP_OPT_NOVALIDATE-CERT' => [
223                        'title' => $this->l('IMAP options').' (/novalidate-cert)',
224                        'type'  => 'bool',
225                        'hint'  => $this->l('Do not validate certificates from the TLS/SSL server. This is only needed if a server uses self-signed certificates.'),
226                    ],
227                    'PS_SAV_IMAP_OPT_TLS'             => [
228                        'title' => $this->l('IMAP options').' (/tls)',
229                        'type'  => 'bool',
230                        'hint'  => $this->l('Force use of start-TLS to encrypt the session, and reject connection to servers that do not support it.'),
231                    ],
232                    'PS_SAV_IMAP_OPT_NOTLS'           => [
233                        'title' => $this->l('IMAP options').' (/notls)',
234                        'type'  => 'bool',
235                        'hint'  => $this->l('Do not use start-TLS to encrypt the session, even with servers that support it.'),
236                    ],
237                ],
238                'submit' => ['title' => $this->l('Save')],
239            ],
240        ];
241
242        parent::__construct();
243    }
244
245    /**
246     * Render list
247     *
248     * @return string
249     *
250     * @since 1.0.0
251     */
252    public function renderList()
253    {
254        // Check the new IMAP messages before rendering the list
255        $this->renderProcessSyncImap();
256
257        $this->addRowAction('view');
258        $this->addRowAction('delete');
259
260        $this->_select = '
261			CONCAT(c.`firstname`," ",c.`lastname`) as customer, cl.`name` as contact, l.`name` as language, group_concat(message) as messages, cm.private,
262			(
263				SELECT IFNULL(CONCAT(LEFT(e.`firstname`, 1),". ",e.`lastname`), "--")
264				FROM `'._DB_PREFIX_.'customer_message` cm2
265				INNER JOIN '._DB_PREFIX_.'employee e
266					ON e.`id_employee` = cm2.`id_employee`
267				WHERE cm2.id_employee > 0
268					AND cm2.`id_customer_thread` = a.`id_customer_thread`
269				ORDER BY cm2.`date_add` DESC LIMIT 1
270			) as employee';
271
272        $this->_join = '
273			LEFT JOIN `'._DB_PREFIX_.'customer` c
274				ON c.`id_customer` = a.`id_customer`
275			LEFT JOIN `'._DB_PREFIX_.'customer_message` cm
276				ON cm.`id_customer_thread` = a.`id_customer_thread`
277			LEFT JOIN `'._DB_PREFIX_.'lang` l
278				ON l.`id_lang` = a.`id_lang`
279			LEFT JOIN `'._DB_PREFIX_.'contact_lang` cl
280				ON (cl.`id_contact` = a.`id_contact` AND cl.`id_lang` = '.(int) $this->context->language->id.')';
281
282        if ($idOrder = Tools::getValue('id_order')) {
283            $this->_where .= ' AND id_order = '.(int) $idOrder;
284        }
285
286        $this->_group = 'GROUP BY cm.id_customer_thread';
287        $this->_orderBy = 'date_upd';
288        $this->_orderWay = 'DESC';
289
290        $contacts = CustomerThread::getContacts();
291
292        $categories = Contact::getCategoriesContacts();
293
294        $params = [
295            $this->l('Total threads')                     => $all = CustomerThread::getTotalCustomerThreads(),
296            $this->l('Threads pending')                   => $pending = CustomerThread::getTotalCustomerThreads('status LIKE "%pending%"'),
297            $this->l('Total number of customer messages') => CustomerMessage::getTotalCustomerMessages('id_employee = 0'),
298            $this->l('Total number of employee messages') => CustomerMessage::getTotalCustomerMessages('id_employee != 0'),
299            $this->l('Unread threads')                    => $unread = CustomerThread::getTotalCustomerThreads('status = "open"'),
300            $this->l('Closed threads')                    => $all - ($unread + $pending),
301        ];
302
303        $this->tpl_list_vars = [
304            'contacts'   => $contacts,
305            'categories' => $categories,
306            'params'     => $params,
307        ];
308
309        return parent::renderList();
310    }
311
312    /**
313     * Call the IMAP synchronization during the render process.
314     */
315    public function renderProcessSyncImap()
316    {
317        // To avoid an error if the IMAP isn't configured, we check the configuration here, like during
318        // the synchronization. All parameters will exists.
319        if (!(Configuration::get('PS_SAV_IMAP_URL')
320            || Configuration::get('PS_SAV_IMAP_PORT')
321            || Configuration::get('PS_SAV_IMAP_USER')
322            || Configuration::get('PS_SAV_IMAP_PWD'))
323        ) {
324            return;
325        }
326
327        // Executes the IMAP synchronization.
328        $syncErrors = $this->syncImap();
329
330        // Show the errors.
331        if (isset($syncErrors['hasError']) && $syncErrors['hasError']) {
332            if (isset($syncErrors['errors'])) {
333                foreach ($syncErrors['errors'] as &$error) {
334                    $this->displayWarning($error);
335                }
336            }
337        }
338    }
339
340    /**
341     * Imap synchronization method.
342     *
343     * @return array Errors list.
344     */
345    public function syncImap()
346    {
347        if (!($url = Configuration::get('PS_SAV_IMAP_URL'))
348            || !($port = Configuration::get('PS_SAV_IMAP_PORT'))
349            || !($user = Configuration::get('PS_SAV_IMAP_USER'))
350            || !($password = Configuration::get('PS_SAV_IMAP_PWD'))
351        ) {
352            return ['hasError' => true, 'errors' => ['IMAP configuration is not correct']];
353        }
354
355        $conf = Configuration::getMultiple(
356            [
357                'PS_SAV_IMAP_OPT_NORSH',
358                'PS_SAV_IMAP_OPT_SSL',
359                'PS_SAV_IMAP_OPT_VALIDATE-CERT',
360                'PS_SAV_IMAP_OPT_NOVALIDATE-CERT',
361                'PS_SAV_IMAP_OPT_TLS',
362                'PS_SAV_IMAP_OPT_NOTLS',
363            ]
364        );
365
366        $confStr = '';
367        if ($conf['PS_SAV_IMAP_OPT_NORSH']) {
368            $confStr .= '/norsh';
369        }
370        if ($conf['PS_SAV_IMAP_OPT_SSL']) {
371            $confStr .= '/ssl';
372        }
373        if ($conf['PS_SAV_IMAP_OPT_VALIDATE-CERT']) {
374            $confStr .= '/validate-cert';
375        }
376        if ($conf['PS_SAV_IMAP_OPT_NOVALIDATE-CERT']) {
377            $confStr .= '/novalidate-cert';
378        }
379        if ($conf['PS_SAV_IMAP_OPT_TLS']) {
380            $confStr .= '/tls';
381        }
382        if ($conf['PS_SAV_IMAP_OPT_NOTLS']) {
383            $confStr .= '/notls';
384        }
385
386        if (!function_exists('imap_open')) {
387            return ['hasError' => true, 'errors' => ['imap is not installed on this server']];
388        }
389
390        $mbox = @imap_open('{'.$url.':'.$port.$confStr.'}', $user, $password);
391
392        //checks if there is no error when connecting imap server
393        $errors = imap_errors();
394        if (is_array($errors)) {
395            $errors = array_unique($errors);
396        }
397        $strErrors = '';
398        $strErrorDelete = '';
399
400        if (count($errors) && is_array($errors)) {
401            $strErrors = '';
402            foreach ($errors as $error) {
403                $strErrors .= $error.', ';
404            }
405            $strErrors = rtrim(trim($strErrors), ',');
406        }
407        //checks if imap connexion is active
408        if (!$mbox) {
409            return ['hasError' => true, 'errors' => ['Cannot connect to the mailbox :<br />'.($strErrors)]];
410        }
411
412        //Returns information about the current mailbox. Returns FALSE on failure.
413        $check = imap_check($mbox);
414        if (!$check) {
415            return ['hasError' => true, 'errors' => ['Fail to get information about the current mailbox']];
416        }
417
418        if ($check->Nmsgs == 0) {
419            return ['hasError' => true, 'errors' => ['NO message to sync']];
420        }
421
422        $result = imap_fetch_overview($mbox, "1:{$check->Nmsgs}", 0);
423        foreach ($result as $overview) {
424            //check if message exist in database
425            if (isset($overview->subject)) {
426                $subject = $overview->subject;
427            } else {
428                $subject = '';
429            }
430            //Creating an md5 to check if message has been allready processed
431            $md5 = md5($overview->date.$overview->from.$subject.$overview->msgno);
432            $exist = Db::getInstance()->getValue(
433                (new DbQuery())
434                    ->select('`md5_header`')
435                    ->from('customer_message_sync_imap')
436                    ->where('`md5_header` = \''.pSQL($md5).'\'')
437            );
438            if ($exist) {
439                if (Configuration::get('PS_SAV_IMAP_DELETE_MSG')) {
440                    if (!imap_delete($mbox, $overview->msgno)) {
441                        $strErrorDelete = ', Fail to delete message';
442                    }
443                }
444            } else {
445                //check if subject has id_order
446                preg_match('/\#ct([0-9]*)/', $subject, $matches1);
447                preg_match('/\#tc([0-9-a-z-A-Z]*)/', $subject, $matches2);
448                $matchFound = false;
449                if (isset($matches1[1]) && isset($matches2[1])) {
450                    $matchFound = true;
451                }
452
453                $newCt = (Configuration::get('PS_SAV_IMAP_CREATE_THREADS') && !$matchFound && (strpos($subject, '[no_sync]') == false));
454
455                if ($matchFound || $newCt) {
456                    if ($newCt) {
457                        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)
458                            || !Validate::isEmail($from = Tools::convertEmailToIdn($result[1]))
459                        ) {
460                            continue;
461                        }
462
463                        // we want to assign unrecognized mails to the right contact category
464                        $contacts = Contact::getContacts($this->context->language->id);
465                        if (!$contacts) {
466                            continue;
467                        }
468
469                        foreach ($contacts as $contact) {
470                            if (strpos($overview->to, $contact['email']) !== false) {
471                                $idContact = $contact['id_contact'];
472                            }
473                        }
474
475                        if (!isset($idContact)) { // if not use the default contact category
476                            $idContact = $contacts[0]['id_contact'];
477                        }
478
479                        $customer = new Customer();
480                        $client = $customer->getByEmail($from); //check if we already have a customer with this email
481                        $ct = new CustomerThread();
482                        if (isset($client->id)) { //if mail is owned by a customer assign to him
483                            $ct->id_customer = $client->id;
484                        }
485                        $ct->email = $from;
486                        $ct->id_contact = $idContact;
487                        $ct->id_lang = (int) Configuration::get('PS_LANG_DEFAULT');
488                        $ct->id_shop = $this->context->shop->id; //new customer threads for unrecognized mails are not shown without shop id
489                        $ct->status = 'open';
490                        $ct->token = Tools::passwdGen(12);
491                        $ct->add();
492                    } else {
493                        $ct = new CustomerThread((int) $matches1[1]);
494                    } //check if order exist in database
495
496                    if (Validate::isLoadedObject($ct) && ((isset($matches2[1]) && $ct->token == $matches2[1]) || $newCt)) {
497                        $message = imap_fetchbody($mbox, $overview->msgno, 1);
498                        if (base64_encode(base64_decode($message)) === $message) {
499                            $message = base64_decode($message);
500                        }
501                        $message = quoted_printable_decode($message);
502                        $message = utf8_encode($message);
503                        $message = quoted_printable_decode($message);
504                        $message = nl2br($message);
505                        $message = mb_substr($message, 0, (int) CustomerMessage::$definition['fields']['message']['size']);
506
507                        $cm = new CustomerMessage();
508                        $cm->id_customer_thread = $ct->id;
509                        if (empty($message) || !Validate::isCleanHtml($message)) {
510                            $strErrors .= Tools::displayError(sprintf('Invalid Message Content for subject: %1s', $subject));
511                        } else {
512                            $cm->message = $message;
513                            $cm->add();
514                        }
515                    }
516                }
517                Db::getInstance()->insert(
518                    'customer_message_sync_imap',
519                    [
520                        'md5_header' => pSQL($md5),
521                    ]
522                );
523            }
524        }
525        imap_expunge($mbox);
526        imap_close($mbox);
527        if ($strErrors.$strErrorDelete) {
528            return ['hasError' => true, 'errors' => [$strErrors.$strErrorDelete]];
529        } else {
530            return ['hasError' => false, 'errors' => ''];
531        }
532    }
533
534    /**
535     * @return void
536     *
537     * @since 1.0.0
538     */
539    public function initToolbar()
540    {
541        parent::initToolbar();
542        unset($this->toolbar_btn['new']);
543    }
544
545    /**
546     * @param mixed    $value
547     * @param Customer $customer
548     *
549     * @return string
550     *
551     * @since 1.0.0
552     */
553    public function printOptinIcon($value, $customer)
554    {
555        return ($value ? '<i class="icon-check"></i>' : '<i class="icon-remove"></i>');
556    }
557
558    /**
559     * @return bool
560     *
561     * @since 1.0.0
562     */
563    public function postProcess()
564    {
565        if ($idCustomerThread = (int) Tools::getValue('id_customer_thread')) {
566            if (($idContact = (int) Tools::getValue('id_contact'))) {
567                Db::getInstance()->execute(
568                    '
569					UPDATE '._DB_PREFIX_.'customer_thread
570					SET id_contact = '.(int) $idContact.'
571					WHERE id_customer_thread = '.(int) $idCustomerThread
572                );
573            }
574            if ($idStatus = (int) Tools::getValue('setstatus')) {
575                $statusArray = [1 => 'open', 2 => 'closed', 3 => 'pending1', 4 => 'pending2'];
576                Db::getInstance()->execute(
577                    '
578					UPDATE '._DB_PREFIX_.'customer_thread
579					SET status = "'.$statusArray[$idStatus].'"
580					WHERE id_customer_thread = '.(int) $idCustomerThread.' LIMIT 1
581				'
582                );
583            }
584            if (isset($_POST['id_employee_forward'])) {
585                $messages = Db::getInstance()->getRow(
586                    '
587					SELECT ct.*, cm.*, cl.name subject, CONCAT(e.firstname, \' \', e.lastname) employee_name,
588						CONCAT(c.firstname, \' \', c.lastname) customer_name, c.firstname
589					FROM '._DB_PREFIX_.'customer_thread ct
590					LEFT JOIN '._DB_PREFIX_.'customer_message cm
591						ON (ct.id_customer_thread = cm.id_customer_thread)
592					LEFT JOIN '._DB_PREFIX_.'contact_lang cl
593						ON (cl.id_contact = ct.id_contact AND cl.id_lang = '.(int) $this->context->language->id.')
594					LEFT OUTER JOIN '._DB_PREFIX_.'employee e
595						ON e.id_employee = cm.id_employee
596					LEFT OUTER JOIN '._DB_PREFIX_.'customer c
597						ON (c.email = ct.email)
598					WHERE ct.id_customer_thread = '.(int) Tools::getValue('id_customer_thread').'
599					ORDER BY cm.date_add DESC
600				'
601                );
602                $output = $this->displayMessage($messages, true, (int) Tools::getValue('id_employee_forward'));
603                $cm = new CustomerMessage();
604                $cm->id_employee = (int) $this->context->employee->id;
605                $cm->id_customer_thread = (int) Tools::getValue('id_customer_thread');
606                $cm->ip_address = (int) ip2long(Tools::getRemoteAddr());
607                $currentEmployee = $this->context->employee;
608                $idEmployee = (int) Tools::getValue('id_employee_forward');
609                $employee = new Employee($idEmployee);
610                $email = Tools::convertEmailToIdn(Tools::getValue('email'));
611                $message = Tools::getValue('message_forward');
612                if (($error = $cm->validateField('message', $message, null, [], true)) !== true) {
613                    $this->errors[] = $error;
614                } elseif ($idEmployee && $employee && Validate::isLoadedObject($employee)) {
615                    $params = [
616                        '{messages}'  => stripslashes($output),
617                        '{employee}'  => $currentEmployee->firstname.' '.$currentEmployee->lastname,
618                        '{comment}'   => stripslashes(Tools::nl2br($_POST['message_forward'])),
619                        '{firstname}' => $employee->firstname,
620                        '{lastname}'  => $employee->lastname,
621                    ];
622
623                    if (Mail::Send(
624                        $this->context->language->id,
625                        'forward_msg',
626                        Mail::l('Fwd: Customer message', $this->context->language->id),
627                        $params,
628                        $employee->email,
629                        $employee->firstname.' '.$employee->lastname,
630                        $currentEmployee->email,
631                        $currentEmployee->firstname.' '.$currentEmployee->lastname,
632                        null,
633                        null,
634                        _PS_MAIL_DIR_,
635                        true
636                    )) {
637                        $cm->private = 1;
638                        $cm->message = $this->l('Message forwarded to').' '.$employee->firstname.' '.$employee->lastname."\n".$this->l('Comment:').' '.$message;
639                        $cm->add();
640                    }
641                } elseif ($email && Validate::isEmail($email)) {
642                    $params = [
643                        '{messages}'  => Tools::nl2br(stripslashes($output)),
644                        '{employee}'  => $currentEmployee->firstname.' '.$currentEmployee->lastname,
645                        '{comment}'   => stripslashes($_POST['message_forward']),
646                        '{firstname}' => '',
647                        '{lastname}'  => '',
648                    ];
649
650                    if (Mail::Send(
651                        $this->context->language->id,
652                        'forward_msg',
653                        Mail::l('Fwd: Customer message', $this->context->language->id),
654                        $params,
655                        $email,
656                        null,
657                        $currentEmployee->email,
658                        $currentEmployee->firstname.' '.$currentEmployee->lastname,
659                        null,
660                        null,
661                        _PS_MAIL_DIR_,
662                        true
663                    )) {
664                        $cm->message = $this->l('Message forwarded to').' '.Tools::convertEmailFromIdn($email)."\n".$this->l('Comment:').' '.$message;
665                        $cm->add();
666                    }
667                } else {
668                    $this->errors[] = '<div class="alert error">'.Tools::displayError('The email address is invalid.').'</div>';
669                }
670            }
671            if (Tools::isSubmit('submitReply')) {
672                $ct = new CustomerThread($idCustomerThread);
673
674                ShopUrl::cacheMainDomainForShop((int) $ct->id_shop);
675
676                $cm = new CustomerMessage();
677                $cm->id_employee = (int) $this->context->employee->id;
678                $cm->id_customer_thread = $ct->id;
679                $cm->ip_address = (int) ip2long(Tools::getRemoteAddr());
680                $cm->message = Tools::getValue('reply_message');
681                if (($error = $cm->validateField('message', $cm->message, null, [], true)) !== true) {
682                    $this->errors[] = $error;
683                } elseif (isset($_FILES) && !empty($_FILES['joinFile']['name']) && $_FILES['joinFile']['error'] != 0) {
684                    $this->errors[] = Tools::displayError('An error occurred during the file upload process.');
685                } elseif ($cm->add()) {
686                    $fileAttachment = null;
687                    if (!empty($_FILES['joinFile']['name'])) {
688                        $fileAttachment['content'] = file_get_contents($_FILES['joinFile']['tmp_name']);
689                        $fileAttachment['name'] = $_FILES['joinFile']['name'];
690                        $fileAttachment['mime'] = $_FILES['joinFile']['type'];
691                    }
692                    $customer = new Customer($ct->id_customer);
693                    $params = [
694                        '{reply}'     => Tools::nl2br(Tools::getValue('reply_message')),
695                        '{link}'      => Tools::url(
696                            $this->context->link->getPageLink('contact', true, null, null, false, $ct->id_shop),
697                            'id_customer_thread='.(int) $ct->id.'&token='.$ct->token
698                        ),
699                        '{firstname}' => $customer->firstname,
700                        '{lastname}'  => $customer->lastname,
701                    ];
702                    //#ct == id_customer_thread    #tc == token of thread   <== used in the synchronization imap
703                    $contact = new Contact((int) $ct->id_contact, (int) $ct->id_lang);
704
705                    if (Validate::isLoadedObject($contact)) {
706                        $fromName = $contact->name;
707                        $fromEmail = $contact->email;
708                    } else {
709                        $fromName = null;
710                        $fromEmail = null;
711                    }
712
713                    if (Mail::Send(
714                        (int) $ct->id_lang,
715                        'reply_msg',
716                        sprintf(Mail::l('An answer to your message is available #ct%1$s #tc%2$s', $ct->id_lang), $ct->id, $ct->token),
717                        $params,
718                        Tools::getValue('msg_email'),
719                        null,
720                        Tools::convertEmailToIdn($fromEmail),
721                        $fromName,
722                        $fileAttachment,
723                        null,
724                        _PS_MAIL_DIR_,
725                        true,
726                        $ct->id_shop
727                    )) {
728                        $ct->status = 'closed';
729                        $ct->update();
730                    }
731                    Tools::redirectAdmin(
732                        static::$currentIndex.'&id_customer_thread='.(int) $idCustomerThread.'&viewcustomer_thread&token='.Tools::getValue('token')
733                    );
734                } else {
735                    $this->errors[] = Tools::displayError('An error occurred. Your message was not sent. Please contact your system administrator.');
736                }
737            }
738        }
739
740        return parent::postProcess();
741    }
742
743    /**
744     * @param      $message
745     * @param bool $email
746     * @param null $idEmployee
747     *
748     * @return string
749     *
750     * @since 1.0.
751     */
752    protected function displayMessage($message, $email = false, $idEmployee = null)
753    {
754        $tpl = $this->createTemplate('message.tpl');
755
756        $contacts = Contact::getContacts($this->context->language->id);
757        $contactArray = [];
758        foreach ($contacts as $contact) {
759            $contactArray[$contact['id_contact']] = ['id_contact' => $contact['id_contact'], 'name' => $contact['name']];
760        }
761        $contacts = $contactArray;
762
763        if (!$email) {
764            if (!empty($message['id_product']) && empty($message['employee_name'])) {
765                $idOrderProduct = Order::getIdOrderProduct((int) $message['id_customer'], (int) $message['id_product']);
766            }
767        }
768        $message['date_add'] = Tools::displayDate($message['date_add'], null, true);
769        $message['user_agent'] = strip_tags($message['user_agent']);
770        $message['message'] = preg_replace(
771            '/(https?:\/\/[a-z0-9#%&_=\(\)\.\? \+\-@\/]{6,1000})([\s\n<])/Uui',
772            '<a href="\1">\1</a>\2',
773            html_entity_decode(
774                $message['message'],
775                ENT_QUOTES,
776                'UTF-8'
777            )
778        );
779
780        $isValidOrderId = true;
781        $order = new Order((int) $message['id_order']);
782
783        if (!Validate::isLoadedObject($order)) {
784            $isValidOrderId = false;
785        }
786
787        $tpl->assign(
788            [
789                'thread_url'        => Tools::getAdminUrl(basename(_PS_ADMIN_DIR_).'/'.$this->context->link->getAdminLink('AdminCustomerThreads').'&amp;id_customer_thread='.(int) $message['id_customer_thread'].'&amp;viewcustomer_thread=1'),
790                'link'              => $this->context->link,
791                'current'           => static::$currentIndex,
792                'token'             => $this->token,
793                'message'           => $message,
794                'id_order_product'  => isset($idOrderProduct) ? $idOrderProduct : null,
795                'email'             => Tools::convertEmailFromIdn($email),
796                'id_employee'       => $idEmployee,
797                'PS_SHOP_NAME'      => Configuration::get('PS_SHOP_NAME'),
798                'file_name'         => file_exists(_PS_UPLOAD_DIR_.$message['file_name']),
799                'contacts'          => $contacts,
800                'is_valid_order_id' => $isValidOrderId,
801            ]
802        );
803
804        return $tpl->fetch();
805    }
806
807    /**
808     * Initialize content
809     *
810     * @return void
811     *
812     * @since 1.0.0
813     */
814    public function initContent()
815    {
816        if (isset($_GET['filename']) && file_exists(_PS_UPLOAD_DIR_.$_GET['filename']) && Validate::isFileName($_GET['filename'])) {
817            static::openUploadedFile();
818        }
819
820        parent::initContent();
821    }
822
823    /**
824     * Render KPIs
825     *
826     * @return mixed
827     *
828     * @since 1.0.0
829     */
830    public function renderKpis()
831    {
832        $time = time();
833        $kpis = [];
834
835        /* The data generation is located in AdminStatsControllerCore */
836
837        $helper = new HelperKpi();
838        $helper->id = 'box-pending-messages';
839        $helper->icon = 'icon-envelope';
840        $helper->color = 'color1';
841        $helper->href = $this->context->link->getAdminLink('AdminCustomerThreads');
842        $helper->title = $this->l('Pending Discussion Threads', null, null, false);
843        if (ConfigurationKPI::get('PENDING_MESSAGES') !== false) {
844            $helper->value = ConfigurationKPI::get('PENDING_MESSAGES');
845        }
846        $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=pending_messages';
847        $helper->refresh = (bool) (ConfigurationKPI::get('PENDING_MESSAGES_EXPIRE') < $time);
848        $kpis[] = $helper->generate();
849
850        $helper = new HelperKpi();
851        $helper->id = 'box-age';
852        $helper->icon = 'icon-time';
853        $helper->color = 'color2';
854        $helper->title = $this->l('Average Response Time', null, null, false);
855        $helper->subtitle = $this->l('30 days', null, null, false);
856        if (ConfigurationKPI::get('AVG_MSG_RESPONSE_TIME') !== false) {
857            $helper->value = ConfigurationKPI::get('AVG_MSG_RESPONSE_TIME');
858        }
859        $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=avg_msg_response_time';
860        $helper->refresh = (bool) (ConfigurationKPI::get('AVG_MSG_RESPONSE_TIME_EXPIRE') < $time);
861        $kpis[] = $helper->generate();
862
863        $helper = new HelperKpi();
864        $helper->id = 'box-messages-per-thread';
865        $helper->icon = 'icon-copy';
866        $helper->color = 'color3';
867        $helper->title = $this->l('Messages per Thread', null, null, false);
868        $helper->subtitle = $this->l('30 day', null, null, false);
869        if (ConfigurationKPI::get('MESSAGES_PER_THREAD') !== false) {
870            $helper->value = ConfigurationKPI::get('MESSAGES_PER_THREAD');
871        }
872        $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=messages_per_thread';
873        $helper->refresh = (bool) (ConfigurationKPI::get('MESSAGES_PER_THREAD_EXPIRE') < $time);
874        $kpis[] = $helper->generate();
875
876        $helper = new HelperKpiRow();
877        $helper->kpis = $kpis;
878
879        return $helper->generate();
880    }
881
882    /**
883     * Render view
884     *
885     * @return string
886     *
887     * @since 1.0.0
888     */
889    public function renderView()
890    {
891        if (!$idCustomerThread = (int) Tools::getValue('id_customer_thread')) {
892            return '';
893        }
894
895        if (!($thread = $this->loadObject())) {
896            return '';
897        }
898        $this->context->cookie->{'customer_threadFilter_cl!id_contact'} = $thread->id_contact;
899
900        $employees = Employee::getEmployees();
901
902        $messages = CustomerThread::getMessageCustomerThreads($idCustomerThread);
903
904        foreach ($messages as $key => $mess) {
905            if ($mess['id_employee']) {
906                $employee = new Employee($mess['id_employee']);
907                $messages[$key]['employee_image'] = $employee->getImage();
908            }
909            if (isset($mess['file_name']) && $mess['file_name'] != '') {
910                $messages[$key]['file_name'] = _THEME_PROD_PIC_DIR_.$mess['file_name'];
911            } else {
912                unset($messages[$key]['file_name']);
913            }
914
915            if ($mess['id_product']) {
916                $product = new Product((int) $mess['id_product'], false, $this->context->language->id);
917                if (Validate::isLoadedObject($product)) {
918                    $messages[$key]['product_name'] = $product->name;
919                    $messages[$key]['product_link'] = $this->context->link->getAdminLink('AdminProducts').'&updateproduct&id_product='.(int) $product->id;
920                }
921            }
922        }
923
924        $nextThread = CustomerThread::getNextThread((int) $thread->id);
925
926        $contacts = Contact::getContacts($this->context->language->id);
927
928        $actions = [];
929
930        if ($nextThread) {
931            $nextThread = [
932                'href' => static::$currentIndex.'&id_customer_thread='.(int) $nextThread.'&viewcustomer_thread&token='.$this->token,
933                'name' => $this->l('Reply to the next unanswered message in this thread'),
934            ];
935        }
936
937        if ($thread->status != 'closed') {
938            $actions['closed'] = [
939                'href'  => static::$currentIndex.'&viewcustomer_thread&setstatus=2&id_customer_thread='.(int) Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
940                'label' => $this->l('Mark as "handled"'),
941                'name'  => 'setstatus',
942                'value' => 2,
943            ];
944        } else {
945            $actions['open'] = [
946                'href'  => static::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int) Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
947                'label' => $this->l('Re-open'),
948                'name'  => 'setstatus',
949                'value' => 1,
950            ];
951        }
952
953        if ($thread->status != 'pending1') {
954            $actions['pending1'] = [
955                'href'  => static::$currentIndex.'&viewcustomer_thread&setstatus=3&id_customer_thread='.(int) Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
956                'label' => $this->l('Mark as "pending 1" (will be answered later)'),
957                'name'  => 'setstatus',
958                'value' => 3,
959            ];
960        } else {
961            $actions['pending1'] = [
962                'href'  => static::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int) Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
963                'label' => $this->l('Disable pending status'),
964                'name'  => 'setstatus',
965                'value' => 1,
966            ];
967        }
968
969        if ($thread->status != 'pending2') {
970            $actions['pending2'] = [
971                'href'  => static::$currentIndex.'&viewcustomer_thread&setstatus=4&id_customer_thread='.(int) Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
972                'label' => $this->l('Mark as "pending 2" (will be answered later)'),
973                'name'  => 'setstatus',
974                'value' => 4,
975            ];
976        } else {
977            $actions['pending2'] = [
978                'href'  => static::$currentIndex.'&viewcustomer_thread&setstatus=1&id_customer_thread='.(int) Tools::getValue('id_customer_thread').'&viewmsg&token='.$this->token,
979                'label' => $this->l('Disable pending status'),
980                'name'  => 'setstatus',
981                'value' => 1,
982            ];
983        }
984
985        if ($thread->id_customer) {
986            $customer = new Customer($thread->id_customer);
987            $orders = Order::getCustomerOrders($customer->id);
988            if ($orders && count($orders)) {
989                $totalOk = 0;
990                $ordersOk = [];
991                foreach ($orders as $key => $order) {
992                    if ($order['valid']) {
993                        $ordersOk[] = $order;
994                        $totalOk += $order['total_paid_real'] / $order['conversion_rate'];
995                    }
996                    $orders[$key]['date_add'] = Tools::displayDate($order['date_add']);
997                    $orders[$key]['total_paid_real'] = Tools::displayPrice($order['total_paid_real'], new Currency((int) $order['id_currency']));
998                }
999            }
1000
1001            $products = $customer->getBoughtProducts();
1002            if ($products && count($products)) {
1003                foreach ($products as $key => $product) {
1004                    $products[$key]['date_add'] = Tools::displayDate($product['date_add'], null, true);
1005                }
1006            }
1007        }
1008        $timelineItems = $this->getTimeline($messages, $thread->id_order);
1009        $firstMessage = $messages[0];
1010
1011        if (!$messages[0]['id_employee']) {
1012            unset($messages[0]);
1013        }
1014
1015        $contact = '';
1016        foreach ($contacts as $c) {
1017            if ($c['id_contact'] == $thread->id_contact) {
1018                $contact = $c['name'];
1019            }
1020        }
1021
1022        $this->tpl_view_vars = [
1023            'id_customer_thread'            => $idCustomerThread,
1024            'thread'                        => $thread,
1025            'actions'                       => $actions,
1026            'employees'                     => $employees,
1027            'current_employee'              => $this->context->employee,
1028            'messages'                      => $messages,
1029            'first_message'                 => $firstMessage,
1030            'contact'                       => $contact,
1031            'next_thread'                   => $nextThread,
1032            'orders'                        => isset($orders) ? $orders : false,
1033            'customer'                      => isset($customer) ? $customer : false,
1034            'products'                      => isset($products) ? $products : false,
1035            'total_ok'                      => isset($totalOk) ? Tools::displayPrice($totalOk, $this->context->currency) : false,
1036            'orders_ok'                     => isset($ordersOk) ? $ordersOk : false,
1037            'count_ok'                      => isset($ordersOk) ? count($ordersOk) : false,
1038            'PS_CUSTOMER_SERVICE_SIGNATURE' => str_replace('\r\n', "\n", Configuration::get('PS_CUSTOMER_SERVICE_SIGNATURE', (int) $thread->id_lang)),
1039            'timeline_items'                => $timelineItems,
1040        ];
1041
1042        if ($nextThread) {
1043            $this->tpl_view_vars['next_thread'] = $nextThread;
1044        }
1045
1046        return parent::renderView();
1047    }
1048
1049    /**
1050     * Get timeline
1051     *
1052     * @param $messages
1053     * @param $idOrder
1054     *
1055     * @return array
1056     *
1057     * @since 1.0.0
1058     */
1059    public function getTimeline($messages, $idOrder)
1060    {
1061        $timeline = [];
1062        foreach ($messages as $message) {
1063            $product = new Product((int) $message['id_product'], false, $this->context->language->id);
1064
1065            $content = '';
1066            if (!$message['private']) {
1067                $content .= $this->l('Message to: ').' <span class="badge">'.(!$message['id_employee'] ? $message['subject'] : $message['customer_name']).'</span><br/>';
1068            }
1069            if (Validate::isLoadedObject($product)) {
1070                $content .= '<br/>'.$this->l('Product: ').'<span class="label label-info">'.$product->name.'</span><br/><br/>';
1071            }
1072            $content .= Tools::safeOutput($message['message']);
1073
1074            $timeline[$message['date_add']][] = [
1075                'arrow'            => 'left',
1076                'background_color' => '',
1077                'icon'             => 'icon-envelope',
1078                'content'          => $content,
1079                'date'             => $message['date_add'],
1080            ];
1081        }
1082
1083        $order = new Order((int) $idOrder);
1084        if (Validate::isLoadedObject($order)) {
1085            $orderHistory = $order->getHistory($this->context->language->id);
1086            foreach ($orderHistory as $history) {
1087                $linkOrder = $this->context->link->getAdminLink('AdminOrders').'&vieworder&id_order='.(int) $order->id;
1088
1089                $content = '<a class="badge" target="_blank" href="'.Tools::safeOutput($linkOrder).'">'.$this->l('Order').' #'.(int) $order->id.'</a><br/><br/>';
1090
1091                $content .= '<span>'.$this->l('Status:').' '.$history['ostate_name'].'</span>';
1092
1093                $timeline[$history['date_add']][] = [
1094                    'arrow'            => 'right',
1095                    'alt'              => true,
1096                    'background_color' => $history['color'],
1097                    'icon'             => 'icon-credit-card',
1098                    'content'          => $content,
1099                    'date'             => $history['date_add'],
1100                    'see_more_link'    => $linkOrder,
1101                ];
1102            }
1103        }
1104        krsort($timeline);
1105
1106        return $timeline;
1107    }
1108
1109    /**
1110     * Render options
1111     *
1112     * @return string
1113     *
1114     * @since 1.0.0
1115     */
1116    public function renderOptions()
1117    {
1118        if (Configuration::get('PS_SAV_IMAP_URL')
1119            && Configuration::get('PS_SAV_IMAP_PORT')
1120            && Configuration::get('PS_SAV_IMAP_USER')
1121            && Configuration::get('PS_SAV_IMAP_PWD')
1122        ) {
1123            $this->tpl_option_vars['use_sync'] = true;
1124        } else {
1125            $this->tpl_option_vars['use_sync'] = false;
1126        }
1127
1128        return parent::renderOptions();
1129    }
1130
1131    /**
1132     * AdminController::getList() override
1133     *
1134     * @see AdminController::getList()
1135     *
1136     * @param int         $idLang
1137     * @param string|null $orderBy
1138     * @param string|null $orderWay
1139     * @param int         $start
1140     * @param int|null    $limit
1141     * @param int|bool    $idLangShop
1142     *
1143     * @throws PrestaShopException
1144     *
1145     * @since 1.0.0
1146     */
1147    public function getList($idLang, $orderBy = null, $orderWay = null, $start = 0, $limit = null, $idLangShop = false)
1148    {
1149        parent::getList($idLang, $orderBy, $orderWay, $start, $limit, $idLangShop);
1150
1151        $nbItems = count($this->_list);
1152        for ($i = 0; $i < $nbItems; ++$i) {
1153            if (isset($this->_list[$i]['messages'])) {
1154                $this->_list[$i]['messages'] = Tools::htmlentitiesDecodeUTF8($this->_list[$i]['messages']);
1155            }
1156            if (isset($this->_list[$i]['email'])) {
1157                $this->_list[$i]['email'] = Tools::convertEmailFromIdn($this->_list[$i]['email']);
1158            }
1159        }
1160    }
1161
1162    /**
1163     * @param $value
1164     *
1165     * @throws PrestaShopException
1166     *
1167     * @since 1.0.0
1168     */
1169    public function updateOptionPsSavImapOpt($value)
1170    {
1171        if ($this->tabAccess['edit'] != '1') {
1172            throw new PrestaShopException(Tools::displayError('You do not have permission to edit this.'));
1173        }
1174
1175        if (!$this->errors && $value) {
1176            Configuration::updateValue('PS_SAV_IMAP_OPT', implode('', $value));
1177        }
1178    }
1179
1180    /**
1181     * @throws PrestaShopException
1182     *
1183     * @since 1.0.0
1184     */
1185    public function ajaxProcessMarkAsRead()
1186    {
1187        if ($this->tabAccess['edit'] != '1') {
1188            throw new PrestaShopException(Tools::displayError('You do not have permission to edit this.'));
1189        }
1190
1191        $idThread = Tools::getValue('id_thread');
1192        $messages = CustomerThread::getMessageCustomerThreads($idThread);
1193        if (count($messages)) {
1194            Db::getInstance()->execute('UPDATE `'._DB_PREFIX_.'customer_message` set `read` = 1 WHERE `id_employee` = '.(int) $this->context->employee->id.' AND `id_customer_thread` = '.(int) $idThread);
1195        }
1196    }
1197
1198    /**
1199     * Call the IMAP synchronization during an AJAX process.
1200     *
1201     * @throws PrestaShopException
1202     *
1203     * @since 1.0.0
1204     */
1205    public function ajaxProcessSyncImap()
1206    {
1207        if ($this->tabAccess['edit'] != '1') {
1208            throw new PrestaShopException(Tools::displayError('You do not have permission to edit this.'));
1209        }
1210
1211        if (Tools::isSubmit('syncImapMail')) {
1212            $this->ajaxDie(json_encode($this->syncImap()));
1213        }
1214    }
1215
1216    protected function openUploadedFile()
1217    {
1218        $filename = $_GET['filename'];
1219
1220        $extensions = [
1221            '.txt'  => 'text/plain',
1222            '.rtf'  => 'application/rtf',
1223            '.doc'  => 'application/msword',
1224            '.docx' => 'application/msword',
1225            '.pdf'  => 'application/pdf',
1226            '.zip'  => 'multipart/x-zip',
1227            '.png'  => 'image/png',
1228            '.jpeg' => 'image/jpeg',
1229            '.gif'  => 'image/gif',
1230            '.jpg'  => 'image/jpeg',
1231        ];
1232
1233        $extension = false;
1234        foreach ($extensions as $key => $val) {
1235            if (substr(mb_strtolower($filename), -4) == $key || substr(mb_strtolower($filename), -5) == $key) {
1236                $extension = $val;
1237                break;
1238            }
1239        }
1240
1241        if (!$extension || !Validate::isFileName($filename)) {
1242            die(Tools::displayError());
1243        }
1244
1245        if (ob_get_level() && ob_get_length() > 0) {
1246            ob_end_clean();
1247        }
1248        header('Content-Type: '.$extension);
1249        header('Content-Disposition:attachment;filename="'.$filename.'"');
1250        readfile(_PS_UPLOAD_DIR_.$filename);
1251        die;
1252    }
1253
1254    /**
1255     * @param $content
1256     *
1257     * @return string
1258     *
1259     * @since 1.0.0
1260     */
1261    protected function displayButton($content)
1262    {
1263        return '<div><p>'.$content.'</p></div>';
1264    }
1265}
1266