Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ docker/bin
.phpcs.cache
node_modules/
.php_cs_fixer.cache
.aider*
2 changes: 2 additions & 0 deletions .phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<rule ref="Squiz.Commenting.FunctionComment">
<exclude name="Squiz.Commenting.FunctionComment.ParamCommentFullStop"/>
<exclude name="Squiz.Commenting.FunctionComment.ParamCommentNotCapital"/>
<exclude name="Squiz.Commenting.FunctionComment.ThrowsNoFullStop"/>
<exclude name="Squiz.Commenting.FunctionComment.ThrowsNotCapital"/>
<exclude name="Squiz.Commenting.FunctionComment.SpacingAfterParamName"/>
<exclude name="Squiz.Commenting.FunctionComment.SpacingAfterParamType"/>
</rule>
Expand Down
162 changes: 130 additions & 32 deletions prestashop1.7/controllers/admin/AdminTawktoController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
*/
class AdminTawktoController extends ModuleAdminController
{
public const NO_CHANGE = 'nochange';

/**
* __construct
*
Expand Down Expand Up @@ -87,11 +89,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 = Tools::jsonDecode($widgetOpts);

if (!empty($widgetOpts->js_api_key)) {
$widgetOpts->js_api_key = self::NO_CHANGE;
}
$displayOpts = Tools::jsonDecode($displayOpts);

$sameUser = true; // assuming there is only one admin by default
$empId = Configuration::get(TawkTo::TAWKTO_WIDGET_USER);
Expand All @@ -113,7 +119,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,
Expand Down Expand Up @@ -221,8 +227,42 @@ 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) {
die(json_encode(['success' => false, 'message' => $e->getMessage()]));
}

// Override current options/fallback if not selected
$currentOpts = Configuration::get($key);
if (!empty($currentOpts)) {
$currentOpts = json_decode($currentOpts, true);
$jsonOpts = array_merge($currentOpts, $jsonOpts);
}

Configuration::updateValue($key, json_encode($jsonOpts));

die(json_encode(['success' => true]));
}

/**
* Process options
*
* @param string $options Selected options
*
* @return array
*
* @throws Exception error processing options
*/
private function processSetOptions(string $options): array
{
// default options
$jsonOpts = [
'always_display' => false,

Expand All @@ -239,40 +279,98 @@ 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) {
[$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($options)) {
return $jsonOpts;
}

$options = explode('&', $options);
foreach ($options as $post) {
[$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);
}
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;
}
}

try {
if (strlen(trim($value)) !== 40) {
throw new Exception('Invalid API key. Please provide value with 40 characters');
}

$jsonOpts['js_api_key'] = $this->encryptData($value);
} catch (Exception $e) {
unset($jsonOpts['js_api_key']);

throw new Exception('Javascript API Key: ' . $e->getMessage());
}

break;
}
}

$key = TawkTo::TAWKTO_WIDGET_OPTS;
Configuration::updateValue($key, json_encode($jsonOpts));
return $jsonOpts;
}

die(Tools::jsonEncode(['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;
}
}
61 changes: 57 additions & 4 deletions prestashop1.7/tawkto.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,19 +168,29 @@ public function hookDisplayFooter()
}

// add customer details as visitor info
$customer_name = null;
$customer_email = null;
$customer_name = '';
$customer_email = '';
$hash = '';
if ($enable_visitor_recognition && !is_null($this->context->customer->id)) {
$customer = $this->context->customer;
$customer_name = $customer->firstname . ' ' . $customer->lastname;
$customer_email = $customer->email;

if (!empty($options->js_api_key)) {
$key = $this->getJsApiKey($options->js_api_key);

if (!empty($key)) {
$hash = hash_hmac('sha256', $customer_email, $key);
}
}
}

$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');
Expand Down Expand Up @@ -283,4 +293,47 @@ private function getArrayFromJson($data)

return $arr;
}

/**
* Retrieve JS API key
*
* @param string $js_api_key Encrypted JS API key
*
* @return string
*/
private function getJsApiKey(string $js_api_key)
{
// Cache::store & Cache::retrieve are not persistent

$key = $this->getDecryptedData($js_api_key);

return $key;
}

/**
* Decrypt data
*
* @param string $data Data to decrypt
*
* @return string
*/
private function getDecryptedData(string $data)
{
$decoded = base64_decode($data);

if ($decoded === false) {
return '';
}

$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) {
return '';
}

return $decrypted_data;
}
}
Loading