@@ -421,7 +449,7 @@ jQuery(document).ready(function() {
dataType : 'json',
data : {
controller : 'AdminTawkto',
- action : 'setVisibility',
+ action : 'setOptions',
ajax : true,
id_tab : current_id_tab,
pageId : $('input[name="page_id"]').val(),
@@ -433,6 +461,7 @@ jQuery(document).ready(function() {
if(r.success) {
$('#optionsSuccessMessage').toggle().delay(3000).fadeOut();
} else {
+ $('#optionsFailureMessage').text(r.message).toggle().delay(3000).fadeOut();
}
}
});
diff --git a/prestashop1.7/views/templates/hook/widget.tpl b/prestashop1.7/views/templates/hook/widget.tpl
index b863a0a..ea3d88e 100644
--- a/prestashop1.7/views/templates/hook/widget.tpl
+++ b/prestashop1.7/views/templates/hook/widget.tpl
@@ -22,7 +22,10 @@ var Tawk_API=Tawk_API||{}, Tawk_LoadStart=new Date();
{if $customer_email != ''}
Tawk_API.visitor = {
name : "{$customer_name|escape:'javascript':'UTF-8'}",
- email : "{$customer_email|escape:'javascript':'UTF-8'}"
+ email : "{$customer_email|escape:'javascript':'UTF-8'}",
+ {if $hash != null}
+ , hash : "{$hash}"
+ {/if}
};
{/if}
diff --git a/prestashop8.x/controllers/admin/AdminTawktoController.php b/prestashop8.x/controllers/admin/AdminTawktoController.php
index c1c3208..48ccdbf 100644
--- a/prestashop8.x/controllers/admin/AdminTawktoController.php
+++ b/prestashop8.x/controllers/admin/AdminTawktoController.php
@@ -20,11 +20,20 @@
exit;
}
+/**
+ * Tawkto exception
+ */
+class TawktoException extends Exception
+{
+}
+
/**
* Admin settings controller
*/
class AdminTawktoController extends ModuleAdminController
{
+ public const NO_CHANGE = 'nochange';
+
/**
* __construct
*
@@ -86,11 +95,15 @@ public function renderView()
$optKey = TawkTo::TAWKTO_WIDGET_OPTS;
// returns 'false' if retrieved none.
- $displayOpts = Configuration::get($optKey);
- if (!$displayOpts) {
- $displayOpts = null;
+ $widgetOpts = Configuration::get($optKey);
+ if (!$widgetOpts) {
+ $widgetOpts = null;
+ }
+ $widgetOpts = json_decode($widgetOpts, false);
+
+ if ($widgetOpts && !empty($widgetOpts->js_api_key)) {
+ $widgetOpts->js_api_key = self::NO_CHANGE;
}
- $displayOpts = json_decode($displayOpts);
$sameUser = true; // assuming there is only one admin by default
$empId = Configuration::get(TawkTo::TAWKTO_WIDGET_USER);
@@ -112,7 +125,7 @@ public function renderView()
'controller' => $this->context->link->getAdminLink('AdminTawkto'),
'tab_id' => (int) $this->context->controller->id,
'domain' => $domain,
- 'display_opts' => $displayOpts,
+ 'widget_opts' => $widgetOpts,
'page_id' => $pageId,
'widget_id' => $widgetId,
'same_user' => $sameUser,
@@ -221,8 +234,54 @@ public function ajaxProcessRemoveWidget()
*
* @return void
*/
- public function ajaxProcessSetVisibility()
+ public function ajaxProcessSetOptions()
+ {
+ $key = TawkTo::TAWKTO_WIDGET_OPTS;
+ $jsonOpts = [];
+
+ try {
+ // Process selected options
+ $jsonOpts = $this->processSetOptions(Tools::getValue('options'));
+ } catch (Exception $e) {
+ if ($e instanceof TawktoException) {
+ die(json_encode(['success' => false, 'message' => $e->getMessage()]));
+ }
+
+ die(json_encode(['success' => false, 'message' => 'An error occurred while saving options']));
+ }
+
+ // Override current options/fallback if not selected
+ $currentOpts = Configuration::get($key);
+ if (!empty($currentOpts)) {
+ $currentOpts = json_decode($currentOpts, true);
+ if (is_array($currentOpts)) {
+ $jsonOpts = array_merge($currentOpts, $jsonOpts);
+ }
+ }
+
+ if (!isset($jsonOpts['config_version'])) {
+ $jsonOpts['config_version'] = 0;
+ } else {
+ ++$jsonOpts['config_version'];
+ }
+
+ Configuration::updateValue($key, json_encode($jsonOpts));
+
+ die(json_encode(['success' => true]));
+ }
+
+ /**
+ * Process options
+ *
+ * @param string $params Selected options
+ *
+ * @return array
+ *
+ * @throws TawktoException Error processing options
+ */
+ private function processSetOptions(string $params): array
{
+ // default options
$jsonOpts = [
'always_display' => false,
@@ -239,38 +298,99 @@ public function ajaxProcessSetVisibility()
'show_oncustom' => json_encode([]),
'enable_visitor_recognition' => false,
+ 'js_api_key' => '',
];
- $options = Tools::getValue('options');
- if (!empty($options)) {
- $options = explode('&', $options);
- foreach ($options as $post) {
- list($column, $value) = explode('=', $post);
- switch ($column) {
- case 'hide_oncustom':
- case 'show_oncustom':
- // replace newlines and returns with comma, and convert to array for saving
- $value = urldecode($value);
- $value = str_ireplace(["\r\n", "\r", "\n"], ',', $value);
- if (!empty($value)) {
- $value = explode(',', $value);
- $jsonOpts[$column] = json_encode($value);
- }
+ if (empty($params)) {
+ return $jsonOpts;
+ }
+
+ parse_str($params, $options);
+ foreach ($options as $column => $value) {
+ switch ($column) {
+ case 'hide_oncustom':
+ case 'show_oncustom':
+ // replace newlines and returns with comma, and convert to array for saving
+ $value = urldecode($value);
+ $value = str_ireplace(["\r\n", "\r", "\n"], ',', $value);
+ if (!empty($value)) {
+ $value = explode(',', $value);
+ $jsonOpts[$column] = json_encode($value);
+ }
+ break;
+
+ case 'show_onfrontpage':
+ case 'show_oncategory':
+ case 'show_onproduct':
+ case 'always_display':
+ case 'enable_visitor_recognition':
+ $jsonOpts[$column] = ($value == 1);
+ break;
+
+ case 'js_api_key':
+ if ($value === self::NO_CHANGE) {
+ unset($jsonOpts['js_api_key']);
break;
- case 'show_onfrontpage':
- case 'show_oncategory':
- case 'show_onproduct':
- case 'always_display':
- case 'enable_visitor_recognition':
- $jsonOpts[$column] = ($value == 1);
+ }
+
+ if ($value === '') {
break;
- }
+ }
+
+ $value = trim($value);
+
+ if (strlen($value) !== 40) {
+ throw new TawktoException('Invalid API key.');
+ }
+
+ try {
+ $jsonOpts['js_api_key'] = $this->encryptData($value);
+ } catch (Exception $e) {
+ error_log($e->getMessage());
+
+ throw new TawktoException('Error saving Javascript API Key.');
+ }
+
+ break;
}
}
- $key = TawkTo::TAWKTO_WIDGET_OPTS;
- Configuration::updateValue($key, json_encode($jsonOpts));
+ return $jsonOpts;
+ }
- die(json_encode(['success' => true]));
+ /**
+ * Encrypt data
+ *
+ * @param string $data Data to encrypt
+ *
+ * @return string Encrypted data
+ *
+ * @throws Exception Error encrypting data
+ */
+ private function encryptData(string $data)
+ {
+ if (!defined('_COOKIE_KEY_')) {
+ throw new Exception('Cookie key not defined');
+ }
+
+ try {
+ $iv = random_bytes(16);
+ } catch (Exception $e) {
+ throw new Exception('Failed to generate IV');
+ }
+
+ $encrypted = openssl_encrypt($data, 'AES-256-CBC', _COOKIE_KEY_, 0, $iv);
+
+ if ($encrypted === false) {
+ throw new Exception('Failed to encrypt data');
+ }
+
+ $encrypted = base64_encode($iv . $encrypted);
+
+ if ($encrypted === false) {
+ throw new Exception('Failed to encode data');
+ }
+
+ return $encrypted;
}
}
diff --git a/prestashop8.x/tawkto.php b/prestashop8.x/tawkto.php
index 7f43d53..207803f 100644
--- a/prestashop8.x/tawkto.php
+++ b/prestashop8.x/tawkto.php
@@ -34,6 +34,7 @@ class Tawkto extends Module
public const TAWKTO_WIDGET_OPTS = 'TAWKTO_WIDGET_OPTS';
public const TAWKTO_WIDGET_USER = 'TAWKTO_WIDGET_USER';
public const TAWKTO_SELECTED_WIDGET = 'TAWKTO_SELECTED_WIDGET';
+ public const TAWKTO_VISITOR_SESSION = 'TAWKTO_VISITOR_SESSION';
/**
* __construct
@@ -109,7 +110,11 @@ public function hookDisplayFooter()
$widgetId = $current_widget['widget_id'];
$result = Configuration::get(self::TAWKTO_WIDGET_OPTS);
- $enable_visitor_recognition = true; // default value
+ // default values
+ $enable_visitor_recognition = true;
+ $js_api_key = '';
+ $config_version = 0;
+
if ($result) {
$options = json_decode($result);
$current_page = (string) $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
@@ -118,6 +123,14 @@ public function hookDisplayFooter()
$enable_visitor_recognition = $options->enable_visitor_recognition;
}
+ if (isset($options->js_api_key)) {
+ $js_api_key = $options->js_api_key;
+ }
+
+ if (isset($options->config_version)) {
+ $config_version = $options->config_version;
+ }
+
// prepare visibility
if (false == $options->always_display) {
// show on specified urls
@@ -167,19 +180,23 @@ public function hookDisplayFooter()
}
// add customer details as visitor info
- $customer_name = null;
- $customer_email = null;
+ $customer_name = '';
+ $customer_email = '';
+ $hash = null;
if ($enable_visitor_recognition && !is_null($this->context->customer->id)) {
$customer = $this->context->customer;
$customer_name = $customer->firstname . ' ' . $customer->lastname;
$customer_email = $customer->email;
+
+ $hash = $this->getVisitorHash($customer_email, $js_api_key, $config_version);
}
$this->context->smarty->assign([
'widget_id' => $widgetId,
'page_id' => $pageId,
- 'customer_name' => (!is_null($customer_name)) ? $customer_name : '',
- 'customer_email' => (!is_null($customer_email)) ? $customer_email : '',
+ 'customer_name' => $customer_name,
+ 'customer_email' => $customer_email,
+ 'hash' => $hash,
]);
return $this->display(__FILE__, 'widget.tpl');
@@ -281,4 +298,85 @@ private function getArrayFromJson($data)
return $arr;
}
+
+ /**
+ * Get visitor hash
+ *
+ * @param string $email Visitor email
+ * @param string $js_api_key JS API key
+ * @param int $config_version Config version
+ *
+ * @return string|null
+ */
+ private function getVisitorHash(string $email, string $js_api_key, int $config_version)
+ {
+ if (empty($js_api_key)) {
+ return null;
+ }
+
+ if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
+ session_start();
+ }
+
+ if (isset($_SESSION[self::TAWKTO_VISITOR_SESSION])) {
+ $current_session = $_SESSION[self::TAWKTO_VISITOR_SESSION];
+
+ if (isset($current_session['hash'])
+ && $current_session['email'] === $email
+ && $current_session['config_version'] === $config_version) {
+ return $current_session['hash'];
+ }
+ }
+
+ try {
+ $key = $this->getDecryptedData($js_api_key);
+ } catch (Exception $e) {
+ error_log($e->getMessage());
+
+ return null;
+ }
+
+ $hash = hash_hmac('sha256', $email, $key);
+
+ $_SESSION[self::TAWKTO_VISITOR_SESSION] = [
+ 'hash' => $hash,
+ 'email' => $email,
+ 'config_version' => $config_version,
+ ];
+
+ return $hash;
+ }
+
+ /**
+ * Decrypt data
+ *
+ * @param string $data Data to decrypt
+ *
+ * @return string
+ *
+ * @throws Exception error decrypting data
+ */
+ private function getDecryptedData(string $data)
+ {
+ if (!defined('_COOKIE_KEY_')) {
+ throw new Exception('Cookie key not defined');
+ }
+
+ $decoded = base64_decode($data);
+
+ if ($decoded === false) {
+ throw new Exception('Failed to decode data');
+ }
+
+ $iv = substr($decoded, 0, 16);
+ $encrypted_data = substr($decoded, 16);
+
+ $decrypted_data = openssl_decrypt($encrypted_data, 'AES-256-CBC', _COOKIE_KEY_, 0, $iv);
+
+ if ($decrypted_data === false) {
+ throw new Exception('Failed to decrypt data');
+ }
+
+ return $decrypted_data;
+ }
}
diff --git a/prestashop8.x/views/templates/admin/tawkto/helpers/view/view.tpl b/prestashop8.x/views/templates/admin/tawkto/helpers/view/view.tpl
index 981ca48..80d1a44 100644
--- a/prestashop8.x/views/templates/admin/tawkto/helpers/view/view.tpl
+++ b/prestashop8.x/views/templates/admin/tawkto/helpers/view/view.tpl
@@ -67,6 +67,13 @@
.tawk-tooltip:hover .tawk-tooltiptext {
visibility: visible;
}
+
+.options-alert {
+ width: 50%;
+ float: left;
+ font-weight: bold;
+ display: none;
+}