diff --git a/CHANGELOG.md b/CHANGELOG.md index 99fb7f6d..98f46c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### 1.5.0 + +- New feature to trigger opt-in if new customer joins with newsletter enabled. [issue #32](https://github.com/sendsmaily/smaily-prestashop-module/issues/32) +- Fix missing template variables. [issue #33](https://github.com/sendsmaily/smaily-prestashop-module/issues/33) +- Use PrestashopLogger for logs created by module. [issue #36](https://github.com/sendsmaily/smaily-prestashop-module/issues/36) + ### 1.4.0 - Added RSS settings tab. diff --git a/README.md b/README.md index 52d3b7d1..44735cdc 100644 --- a/README.md +++ b/README.md @@ -53,14 +53,16 @@ All development for Smaily for Prestashop is [handled via GitHub](https://github 2. Insert your Smaily API authentication information and click **Validate** to get started. 3. Under **Customer Synchronization** tab select if you want to enable customer synchronization. 4. Select additional fields you want to synchronize (email is automatic), change cron token if you like your own. -5. Click **Save** to save customer synchronization settings. -6. Under **Abandoned Cart** tab select if you want to enable abandoned cart synchronization. -7. Select autoresponder for abandoned cart. -8. Select additional fields to send to abandoned cart template. Firstname, lastname and store-url are always added. -9. Add delay time when cart is considered abandoned. Minimum time 15 minutes. Change cron token if you like your own. -10. Click **Save** to save abandoned cart settings. -11. Cron is set up to synchronize contacts when CRON-url is visited. Use host Cpanel, PrestaShop Cron tasks manager or external cron service to automate process. -12. That's it, your PrestaShop store is now integrated with Smaily Plugin! +5. New customers who sign up with newsletter enabled can be added to Smaily by enabling trigger opt-in on customer signup. +6. An autoresponder can be selected for "opt-in on customer sign-up", this will only be triggered if the previous option is enabled. +7. Click **Save** to save customer synchronization settings. +8. Under **Abandoned Cart** tab select if you want to enable abandoned cart synchronization. +9. Select autoresponder for abandoned cart. +10. Select additional fields to send to abandoned cart template. Firstname, lastname and store-url are always added. +11. Add delay time when cart is considered abandoned. Minimum time 15 minutes. Change cron token if you like your own. +12. Click **Save** to save abandoned cart settings. +13. Cron is set up to synchronize contacts when CRON-url is visited. Use host Cpanel, PrestaShop Cron tasks manager or external cron service to automate process. +14. That's it, your PrestaShop store is now integrated with Smaily Plugin! ## Using Newsletter Subscription form diff --git a/USERGUIDE.md b/USERGUIDE.md index e906cc43..3614548b 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -48,7 +48,8 @@ Registered customers, who have opted for the newsletter will be added to Smaily' 1. Enable automatic customer synchronization feature under **Customer synchronization** section. 2. There is an option to import **additional fields** available from the store into Smaily to personalize newsletter emails. 3. The synchronization can be automated using the cron URL. Recommended synchronization interval is once a day or less. - +4. New customers who sign up with newsletter enabled can be added to Smaily, by enabling trigger opt-in on customer signup. +5. An autoresponder can be selected for new customers who sign up with newsletter, the autoresponder won't work if the setting is disabled. ![Customer synchronization section](assets/CustomerSync.png) ## Abandoned cart emails diff --git a/assets/CustomerSync.png b/assets/CustomerSync.png index 9bbc0bf0..1fc13af3 100644 Binary files a/assets/CustomerSync.png and b/assets/CustomerSync.png differ diff --git a/controllers/front/SmailyCartCron.php b/controllers/front/SmailyCartCron.php index 28cdc607..3758ac2c 100644 --- a/controllers/front/SmailyCartCron.php +++ b/controllers/front/SmailyCartCron.php @@ -51,8 +51,7 @@ private function abandonedCart() } // Settings. - $autoresponder = unserialize(Configuration::get('SMAILY_CART_AUTORESPONDER')); - $autoresponder_id = pSQL($autoresponder['id']); + $autoresponder_id = pSQL(Configuration::get('SMAILY_CART_AUTORESPONDER')); $delay = pSQL(Configuration::get('SMAILY_ABANDONED_CART_TIME')); $sync_fields = unserialize(Configuration::get('SMAILY_CART_SYNCRONIZE_ADDITIONAL')); @@ -157,7 +156,13 @@ private function abandonedCart() $response['result']['code'] === 101) { $this->updateSentStatus($id_customer, $id_cart); } else { - $this->module->logTofile('smaily-cart.txt', Tools::jsonEncode($response)); + $this->module->logErrorWithFormatting( + "Failed sending out abandoned cart email for email: %s, cart_id: %s. Smaily response code: %s, message: %s.", + $abandoned_cart['email'], + $abandoned_cart['id_cart'], + $response['result']['code'], + $response['result']['message'] + ); } } echo('Abandoned carts emails sent!'); diff --git a/controllers/front/SmailyCustomerCron.php b/controllers/front/SmailyCustomerCron.php index 9ab87455..77303b27 100644 --- a/controllers/front/SmailyCustomerCron.php +++ b/controllers/front/SmailyCustomerCron.php @@ -43,7 +43,7 @@ public function init() echo('Access denied!'); die(1); } - + if ((int) Configuration::get('SMAILY_ENABLE_CRON') !== 1) { echo('User synchronization disabled!'); die(1); @@ -65,14 +65,14 @@ private function syncContacts() { $unsubscribers_synchronized = $this->removeUnsubscribers(self::UNSUBSCRIBERS_BATCH_LIMIT); if (!$unsubscribers_synchronized) { - $this->module->logTofile('smaily-cron.txt', 'Customer sync failed - unsubscribers are not removed'); + $this->module->logMessageWithSeverity("Customer sync failed - unsubscribers are not removed", 1); return false; } // Don't sync customers if failed to remove unsubscribers. $subscribers_synchronized = $this->sendSubscribersToSmaily(self::SUBSCRIBERS_BATCH_LIMIT); if (!$subscribers_synchronized) { - $this->module->logTofile('smaily-cron.txt', 'Customer sync failed - faild to send subscribers to Smaily'); + $this->module->logMessageWithSeverity("Customer sync failed - failed to send subscribers to Smaily", 1); return false; } @@ -126,6 +126,7 @@ private function removeUnsubscribers($limit = 1000) // Stop if error. if (!isset($unsubscribers['success'])) { + $this->module->logErrorWithFormatting("Failed fetching unsubscribers."); return false; } // Stop if no more subscribers. @@ -147,6 +148,7 @@ function ($item) { $query_result = Db::getInstance()->execute($query); // Stop if query fails. if ($query_result === false) { + $this->module->logErrorWithFormatting("Failed removing subscribed status for unsubscribers."); return false; } @@ -178,6 +180,7 @@ public function sendSubscribersToSmaily($limit) $customers = Db::getInstance()->executeS($sql); // Stop if query fails. if ($customers === false) { + $this->module->logErrorWithFormatting("Failed retrieving newsletter subscribers from DB."); return false; } // Stop if no more qustomers. @@ -195,6 +198,11 @@ public function sendSubscribersToSmaily($limit) $response = $this->module->callApi('contact', $update_data, 'POST'); // Stop if not successful update. if (isset($response['result']['code']) && $response['result']['code'] !== 101) { + $this->module->logErrorWithFormatting( + "Failed sending subscribers to Smaily. Smaily response code: %s, message: %s", + $response['result']['code'], + $response['result']['message'] + ); return false; } diff --git a/smailyforprestashop.php b/smailyforprestashop.php index d9a7cacc..c144f0b4 100644 --- a/smailyforprestashop.php +++ b/smailyforprestashop.php @@ -32,7 +32,7 @@ public function __construct() { $this->name = 'smailyforprestashop'; $this->tab = 'advertising_marketing'; - $this->version = '1.4.0'; + $this->version = '1.5.0'; $this->author = 'Smaily'; $this->need_instance = 0; $this->ps_versions_compliancy = array( @@ -78,12 +78,16 @@ public function install() !Configuration::updateValue('SMAILY_RSS_LIMIT', '50') || !Configuration::updateValue('SMAILY_RSS_SORT_BY', 'date_upd') || !Configuration::updateValue('SMAILY_RSS_SORT_ORDER', 'desc') || + !Configuration::updateValue('SMAILY_OPTIN_ENABLED', 0) || + !Configuration::updateValue('SMAILY_OPTIN_AUTORESPONDER', '') || // Add tab to sidebar !$this->installTab('AdminAdmin', 'AdminSmailyforprestashopAjax', 'Smaily for PrestaShop') || // Add Newsletter subscription form. !$this->registerHook('footerBefore') || !$this->registerHook('leftColumn') || - !$this->registerHook('rightColumn') + !$this->registerHook('rightColumn') || + // User has option to trigger opt-in when customer joins store & newsletter through sign-up. + !$this->registerHook('actionCustomerAccountAdd') ) { return false; } @@ -132,6 +136,8 @@ public function uninstall() !Configuration::deleteByName('SMAILY_RSS_LIMIT') || !Configuration::deleteByName('SMAILY_RSS_SORT_BY') || !Configuration::deleteByName('SMAILY_RSS_SORT_ORDER') || + !Configuration::deleteByName('SMAILY_OPTIN_ENABLED') || + !Configuration::deleteByName('SMAILY_OPTIN_AUTORESPONDER') || // Remove sideTab of smaily module. !$this->uninstallTab('AdminSmailyforprestashopAjax') ) { @@ -164,9 +170,10 @@ public function getContent() ) { // Disable customer sync. Configuration::updateValue('SMAILY_ENABLE_CRON', 0); - // Disable abandoned cart cron and remove autoresponder. + // Disable abandoned cart cron and remove all autoresponders. Configuration::updateValue('SMAILY_ENABLE_ABANDONED_CART', 0); Configuration::updateValue('SMAILY_CART_AUTORESPONDER', ''); + Configuration::updateValue('SMAILY_OPTIN_AUTORESPONDER', ''); // Return success message. $output .= $this->displayConfirmation($this->l('Credentials removed!')); } else { @@ -194,6 +201,8 @@ public function getContent() $escaped_sync_additional[] = pSQL($value); } } + $optin_enabled = pSQL(Tools::getValue('SMAILY_OPTIN_ENABLED')); + $customer_join_autoresponder = pSQL(Tools::getValue('SMAILY_OPTIN_AUTORESPONDER')); // Check if subdomain is saved to db to verify that credentials are validated. if (empty(Configuration::get('SMAILY_SUBDOMAIN'))) { // Display error message. @@ -203,6 +212,8 @@ public function getContent() Configuration::updateValue('SMAILY_ENABLE_CRON', $enable_cron); Configuration::updateValue('SMAILY_CUSTOMER_CRON_TOKEN', $customer_cron_token); Configuration::updateValue('SMAILY_SYNCRONIZE_ADDITIONAL', serialize($escaped_sync_additional)); + Configuration::updateValue('SMAILY_OPTIN_ENABLED', $optin_enabled); + Configuration::updateValue('SMAILY_OPTIN_AUTORESPONDER', $customer_join_autoresponder); // Display success message. $output .= $this->displayConfirmation($this->l('Settings updated')); } @@ -222,17 +233,7 @@ public function getContent() $cart_cron_token = uniqid(); } // Abandoned cart Autoresponder - $cart_autoresponder = pSQL((Tools::getValue('SMAILY_CART_AUTORESPONDER'))); - $cart_autoresponder = str_replace('\"', '"', $cart_autoresponder); - // Get autoresponder array from json string. - $cart_autoresponder = Tools::jsonDecode($cart_autoresponder); - // Clean autoresponder for inserting to database. - $escaped_cart_autoresponder = array(); - if (!empty($cart_autoresponder)) { - foreach ($cart_autoresponder as $key => $value) { - $escaped_cart_autoresponder[ pSQL($key)] = pSQL($value); - } - } + $cart_autoresponder = pSQL(Tools::getValue('SMAILY_CART_AUTORESPONDER')); // Syncronize additional for abandoned cart template. $cart_syncronize_additional = Tools::getValue('SMAILY_CART_SYNCRONIZE_ADDITIONAL'); $cart_escaped_sync_additional = array(); @@ -250,7 +251,7 @@ public function getContent() $output .= $this->displayError($this->l('Select autoresponder for abandoned cart.')); } else { Configuration::updateValue('SMAILY_ENABLE_ABANDONED_CART', $enable_abandoned_cart); - Configuration::updateValue('SMAILY_CART_AUTORESPONDER', serialize($escaped_cart_autoresponder)); + Configuration::updateValue('SMAILY_CART_AUTORESPONDER', $cart_autoresponder); Configuration::updateValue('SMAILY_ABANDONED_CART_TIME', $abandoned_cart_time); Configuration::updateValue('SMAILY_CART_CRON_TOKEN', $cart_cron_token); Configuration::updateValue( @@ -308,10 +309,10 @@ public function getContent() } else { $cart_cron_token = uniqid(); } + // Get customer join autoresponder values for template. + $optin_autoresponder = pSQL(Configuration::get('SMAILY_OPTIN_AUTORESPONDER')); // Get abandoned cart autoresponder values for template. - $cart_autoresponder_for_template = pSQL((Configuration::get('SMAILY_CART_AUTORESPONDER'))); - $cart_autoresponder_for_template = str_replace('\"', '"', $cart_autoresponder_for_template); - $cart_autoresponder_for_template = unserialize($cart_autoresponder_for_template); + $cart_autoresponder = pSQL(Configuration::get('SMAILY_CART_AUTORESPONDER')); $categories = Category::getNestedCategories(null, Context::getContext()->language->id); @@ -323,7 +324,7 @@ public function getContent() 'smaily_subdomain' => pSQL(Configuration::get('SMAILY_SUBDOMAIN')), 'smaily_username' => pSQL(Configuration::get('SMAILY_USERNAME')), 'smaily_password' => pSQL(Configuration::get('SMAILY_PASSWORD')), - 'smaily_cart_autoresponder' => $cart_autoresponder_for_template, + 'smaily_cart_autoresponder' => $cart_autoresponder, 'smaily_abandoned_cart_time' => pSQL(Configuration::get('SMAILY_ABANDONED_CART_TIME')), 'smaily_syncronize_additional' => $sync_array, 'smaily_cart_syncronize_additional' => $cart_sync_array, @@ -334,6 +335,8 @@ public function getContent() 'SmailyCustomerCron', array('token' => $customer_cron_token) ), + 'smaily_customer_cron_token' => $customer_cron_token, + 'smaily_cart_cron_token' => $cart_cron_token, 'smaily_cart_cron_url' => Context::getContext()->link->getModuleLink( 'smailyforprestashop', 'SmailyCartCron', @@ -344,6 +347,8 @@ public function getContent() 'smaily_rss_limit' => pSQL(Configuration::get('SMAILY_RSS_LIMIT')), 'smaily_rss_sort_by' => pSQL(Configuration::get('SMAILY_RSS_SORT_BY')), 'smaily_rss_sort_order' => pSQL(Configuration::get('SMAILY_RSS_SORT_ORDER')), + 'smaily_optin_autoresponder' => $optin_autoresponder, + 'smaily_optin_enabled' => pSQL(Configuration::get('SMAILY_OPTIN_ENABLED')), ) ); // Display settings form. @@ -443,17 +448,72 @@ public function hookDisplayBackOfficeHeader() } /** - * Log API response to text-file. + * Trigger Smaily Opt-in if customer joins with newsletter subscription. * - * @param string $filename Name of the file created. - * @param string $msg Text response from api. - * @return void + * @param array $params Array of parameters being passed to the hook function. + * @return bool Success of the operation. */ - public function logToFile($filename, $response) + public function hookActionCustomerAccountAdd($params) { - $logger = new FileLogger(1); - $logger->setFilename(_PS_MODULE_DIR_. $this->name ."/" . $filename); - $logger->logInfo('Response from API - ' . $response); + if (empty($params['newCustomer'])) { + return false; + } + $email = $params['newCustomer']->email; + if (!Validate::isEmail($email)) { + return false; + } + $is_newsletter_checked = $params['newCustomer']->newsletter === "1"; + $is_subscription_optin_enabled = Configuration::get('SMAILY_OPTIN_ENABLED') === "1"; + if (!$is_newsletter_checked || !$is_subscription_optin_enabled) { + return false; + } + + $autoresponder = Configuration::get('SMAILY_OPTIN_AUTORESPONDER'); + $autoresponder_id = empty($autoresponder) ? '' : (int) $autoresponder; + $query = array( + 'autoresponder' => $autoresponder_id, + 'addresses' => [['email' => $email]] + ); + $response = $this->callApi('autoresponder', $query, 'POST'); + $opt_in_successful = array_key_exists('success', $response) && isset($response['result']['code']) && $response['result']['code'] === 101; + if (array_key_exists('success', $response) && + isset($response['result']['code']) && + $response['result']['code'] === 101) { + return true; // All good. + } else { + // Supply query values and save log of unsuccesful operation. + $this->logErrorWithFormatting( + "Failed to opt-in new customer with email: %s using autoresponder ID: %s. Smaily response code: %s, message: %s.", + $query['addresses'][0]['email'], + $query['autoresponder'], + $response['result']['code'], + $response['result']['message'] + ); + return false; + } + } + + /** + * Add error (severity 3) to Prestashop log with formatted arguments. + * + * @param string $message + * @return void + */ + public function logErrorWithFormatting() { + $args = func_get_args(); + $message = call_user_func_array('sprintf', $args); + PrestaShopLogger::addLog("[SMAILY] " . $message, 3); + } + + /** + * Add information to Prestashop log. + * + * @param string $message + * @param int $severity (1 is informative, 3 error) + * @return void + */ + public function logMessageWithSeverity($message, $severity) { + PrestaShopLogger::addLog("[SMAILY] " . $message, $severity); } /** diff --git a/translations/et.php b/translations/et.php index e0220d33..310e7b61 100644 --- a/translations/et.php +++ b/translations/et.php @@ -43,6 +43,10 @@ $_MODULE['<{smailyforprestashop}prestashop>smaily_configure_bb7b3c6261b41de1bd1fefa377a75f43'] = 'Identifikaator on vajalik turvalisuse tagamiseks. Kasuta automaatselt loodud identifikaatorit või asenda enda valitud väärtusega. '; $_MODULE['<{smailyforprestashop}prestashop>smaily_configure_5588ea18ef47155105d7d11abcb7e4e1'] = 'Croni URL'; $_MODULE['<{smailyforprestashop}prestashop>smaily_configure_9cc2022eb76bc383c713c406b6ae96f2'] = 'Et aktiveerida automaatne kontaktide sünkroniseerimine seadistage Cron oma teenusepakkuja juures.'; +$_MODULE['<{smailyforprestashop}prestashop>smaily_configure_daf2a96781f203ddd0e0c8ba8fa25a2f'] = 'Käivita opt-in kasutaja registreerimisel'; +$_MODULE['<{smailyforprestashop}prestashop>smaily_configure_dc7361b8c5d6e2b9f95b8968533d7b14'] = 'Opt-in käivitatakse ainult siis, kui kasutaja loob konto ja liitub uudiskirjaga. Muutused uudiskirja sätetele adminpaneelis ei käivita opt-in\'i.'; +$_MODULE['<{smailyforprestashop}prestashop>smaily_configure_f31fe73b24620e23d03fd55013e1bf87'] = 'Kasutaja registreerimisel käivitatav automaatika'; +$_MODULE['<{smailyforprestashop}prestashop>smaily_configure_faa5e8094afca015fa4b4a702ed57645'] = 'Käivita iga opt-in automaatika'; $_MODULE['<{smailyforprestashop}prestashop>smaily_configure_c9cc8cce247e49bae79f15173ce97354'] = 'Salvesta'; $_MODULE['<{smailyforprestashop}prestashop>smaily_configure_a27d20b558af715c91592bcee1019dee'] = 'Aktiveeri Unustatud Ostukorvi Meeldetuletus'; $_MODULE['<{smailyforprestashop}prestashop>smaily_configure_872bc575218e7dd2c749633da28c24ab'] = 'Automaatvastaja'; diff --git a/upgrade/Upgrade-1.5.0.php b/upgrade/Upgrade-1.5.0.php new file mode 100644 index 00000000..5636a98c --- /dev/null +++ b/upgrade/Upgrade-1.5.0.php @@ -0,0 +1,43 @@ +. + * + * @author Smaily + * @copyright 2018 Smaily + * @license GPL3 + */ + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Migrates existing abandoned cart fields to new structure. + */ +function upgrade_module_1_5_0($object) +{ + // 1.5.0+ saves only autoresponder ID to database instead of the whole autoresponder serialized. + $cart_autoresponder = pSQL(Configuration::get('SMAILY_CART_AUTORESPONDER')); + $cart_autoresponder = str_replace('\"', '"', $cart_autoresponder); + $cart_autoresponder = unserialize($cart_autoresponder); + $autoresponder_id = isset($cart_autoresponder['id']) ? $cart_autoresponder['id'] : ''; + return (Configuration::updateValue('SMAILY_CART_AUTORESPONDER', $autoresponder_id) && + Configuration::updateValue('SMAILY_OPTIN_ENABLED', 0) && + Configuration::updateValue('SMAILY_OPTIN_AUTORESPONDER', '') && + $object->registerHook('actionCustomerAccountAdd')); +} diff --git a/views/js/smaily_module.js b/views/js/smaily_module.js index 84ddec6d..37769565 100644 --- a/views/js/smaily_module.js +++ b/views/js/smaily_module.js @@ -64,20 +64,31 @@ $(document).ready(function() { $("#smaily-validate-form-group").hide(); // Check if there are any autoresponders. if (result["autoresponders"].length > 0) { + var optin_selected_id = parseInt($("#SMAILY_OPTIN_AUTORESPONDER").attr('data-selected-id')); + var cart_selected_id = parseInt($("#SMAILY_CART_AUTORESPONDER").attr('data-selected-id')); + // Append received autoresponders to "Trigger Opt-in Automation If Customer Joins With Newsletter Subscription". + $.each(result["autoresponders"], function(index, item) { + $("#SMAILY_OPTIN_AUTORESPONDER").append( + $("