* @copyright 2015-2020 Garamo Online L.T.D * @license Commercial license */ if (!defined('_PS_VERSION_')) { exit; } require_once(dirname(__FILE__) . '/vendor/autoload.php'); class Rc_PgAnalytics extends Module { // delimiter for domain list const _DML_DELIMITER_ = "\n"; private $default_values = array( 'RC_PGANALYTICS_PR' => 25, 'RC_PGANALYTICS_SSSR' => 1, 'RC_PGANALYTICS_D1' => 1, 'RC_PGANALYTICS_D2' => 2, 'RC_PGANALYTICS_D3' => 3, 'RC_PGANALYTICS_D4' => 4, 'RC_PGANALYTICS_D5' => 5, 'RC_PGANALYTICS_D6' => 6, 'RC_PGANALYTICS_D7' => 7, 'RC_PGANALYTICS_D8' => 8, 'RC_PGANALYTICS_OPT_HCN' => 'optimize-loading', 'RC_PGANALYTICS_OPT_HTO' => 4000, 'RC_PGANALYTICS_IOS' => '6,7,8', 'RC_PGANALYTICS_ROS' => '6,7' ); public $secret_key; // add custom error messages protected $errors = array(); public function __construct() { $this->name = 'rc_pganalytics'; $this->tab = 'analytics_stats'; $this->version = '4.5.1'; $this->author = 'ReactionCode'; $this->author_address = '0xf21e54Ea7B0D8e4413f99D60a3CC1dc49C5ffEf8'; $this->module_key = '5cb794f64177737254aef9f263fe8dbc'; $this->ps_versions_compliancy = array('min' => '1.7.0.0', 'max' => '1.7.99'); $this->bootstrap = true; // avoid fatal error on PS16 if (method_exists('Tools', 'hash')) { $this->secret_key = Tools::hash($this->name . $this->version . Configuration::get('PS_SHOP_NAME')); } parent::__construct(); $this->displayName = 'Premium Google Analytics Enhanced Ecommerce'; $this->description = $this->l('Enable Google Enhanced Ecommerce Analytics, and knows in detail how users interact in your online Store'); // update default value with referral exclusion list (self domain) $this->default_values['RC_PGANALYTICS_REL'] = parse_url( Tools::getHttpHost(true), PHP_URL_HOST ); } public function install() { // Simple database to register orders set to GA include(dirname(__FILE__).'/sql/install.php'); $hooks = array( 'header', 'orderConfirmation', 'displayBeforeBodyClosingTag', 'backOfficeHeader', 'displayAdminOrderTabOrder', 'displayAdminOrderContentOrder', 'updateOrderStatus' ); $installed = parent::install() && $this->registerHook($hooks); if ($installed) { foreach ($this->default_values as $key => $value) { if (!Configuration::get($key)) { // Set default value for all shops Configuration::updateGlobalValue($key, $value); } } // clear all PS cache Tools::clearSmartyCache(); Tools::clearXMLCache(); Media::clearCache(); Tools::generateIndex(); return true; } else { // if some thing blocks the hook registration uninstall the module $this->uninstall(); return false; } } public function uninstall() { // Delete data base include(dirname(__FILE__).'/sql/uninstall.php'); // Uninstall Module if (!parent::uninstall()) { return false; } return true; } public function getContent() { $message = ''; if (Tools::isSubmit('submit'.$this->name)) { $this->postProcess(); if (count($this->errors) > 0) { $message = $this->displayError(implode('
', $this->errors)); } else { $message = $this->displayConfirmation($this->l('Settings updated successfully')); } } $this->context->controller->addCSS($this->_path.'views/css/admin/common_form.css'); $this->context->controller->addJS($this->_path.'views/js/admin/common_form.js'); $this->context->controller->addJS($this->_path.'views/js/admin/configure/form.js'); return $message.$this->renderForm(); } /* PREPARE HELPER FOR GENERATE FORM */ public function renderForm() { $helper = new HelperForm(); // Module, token and currentIndex $helper->module = $this; $helper->token = Tools::getAdminTokenLite('AdminModules'); $helper->currentIndex = $this->context->link->getAdminLink('AdminModules', false) .'&configure='.$this->name.'&tab_module='.$this->tab.'&module_name='.$this->name; // Language $helper->default_form_language = $this->context->language->id; $helper->allow_employee_form_lang = Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG', 0); // Title and toolbar $helper->title = $this->displayName; $helper->show_toolbar = true; $helper->submit_action = 'submit'.$this->name; $helper->tpl_vars = array( // vertical tabs 'vertical_tabs' => $this->getTabsForm(), // form values 'fields_value' => $this->getConfigFormValues() ); // allow multiple forms to combine with tabs $helper->multiple_fieldsets = true; return $helper->generateForm($this->getConfigForm()); } public function getTabsForm() { $lang_iso = $this->context->language->iso_code === 'es' ? 'es' : 'en'; $doc_base_url = 'https://docs.reactioncode.com/'; $doc_module_urls = array( 'en' => $doc_base_url.'en/modules/prestashop/premium-google-analytics-enhanced-ecommerce/', 'es' => $doc_base_url.'es/modulos/prestashop/premium-google-analytics-enhanced-ecommerce/' ); $doc_module_url = $doc_module_urls[$lang_iso]; $addons_support_url = 'https://addons.prestashop.com/contact-form.php?id_product=18623'; $addons_ratings_url = 'https://addons.prestashop.com/ratings.php'; return array( 'form' => array( 'property_tab' => array( 'name' => $this->l('Property'), 'active' => 1 ), 'tracking_tab' => array( 'name' => $this->l('Tracking Features') ), 'remarketing_tab' => array( 'name' => $this->l('Dynamic Remarketing') ), 'googleads_tab' => array( 'name' => 'Google Ads' ), 'optimize_tab' => array( 'name' => 'Google Optimize' ), 'goal_tab' => array( 'name' => $this->l('Goals') ), 'orderstatus_tab' => array( 'name' => $this->l('Transaction Behaviours') ) ), 'link_tabs' => array( 'documentation' => array( 'icon' => 'book', 'name' => $this->l('Documentation'), 'link' => $doc_module_url, 'target' => '_blank' ), 'support' => array( 'icon' => 'support', 'name' => $this->l('Support'), 'link' => $addons_support_url, 'target' => '_blank' ), 'rate' => array( 'icon' => 'star', 'name' => $this->l('Suggestions and Improvements'), 'link' => $addons_ratings_url, 'target' => '_blank' ) ) ); } /* CONFIGURATION FORM */ public function getConfigForm() { $order_states = OrderState::getOrderStates($this->context->language->id); $products_rate = array( array( 'id' => 10, 'name' => 10 ), array( 'id' => 15, 'name' => 15 ), array( 'id' => 20, 'name' => 20 ), array( 'id' => 25, 'name' => 25 ), array( 'id' => 30, 'name' => 30 ) ); $index_dimension = array(); for ($i=1; $i <= 17; $i++) { $index_dimension[] = array( 'id' => $i, 'name' => $i ); } $hiding_time_value = 2000; $hiding_time = array(); for ($i=1; $i <= 5; $i++) { $hiding_time[] = array( 'id' => $hiding_time_value, 'name' => $hiding_time_value ); $hiding_time_value = $hiding_time_value + 1000; } $config_form = array( 'property_tab' => array( 'form' => array( 'legend' => array( 'title' => $this->l('Settings'), 'icon' => 'icon-cogs' ), 'warning' => $this->l('To achieve a perfect tracking is required to follow a specific integration, please read the documentation'), 'input' => array( array( 'type' => 'switch', 'label' => $this->l('Exclude Internal Traffic'), 'name' => 'RC_PGANALYTICS_NO_IT', 'desc' => $this->l('Disable the GA tracking for your internal traffic.'), 'values' => array( array( 'value' => true, ), array( 'value' => false ) ) ), array( 'type' => 'switch', 'label' => $this->l('Respect Do Not Track'), 'name' => 'RC_PGANALYTICS_DNT', 'desc' => $this->l('Disable customers tracking with browser Do Not Track feature enabled.'), 'values' => array( array( 'value' => true, ), array( 'value' => false ) ) ), array( 'type' => 'text', 'label' => 'Google Analytics ID', 'name' => 'RC_PGANALYTICS_ID', 'class' => 'fixed-width-xl', 'required' => true, 'desc' => $this->l('The format is') . ': UA-XXXX-Y' ), array( 'type' => 'select', 'label' => $this->l('Max Products Rate'), 'name' => 'RC_PGANALYTICS_PR', 'desc' => $this->l('Split the payload in packs of x products, to send to GA.'), 'hint' => $this->l('Depending your language, you can send more or less products at once'), 'options' => array( 'query' => $products_rate, 'id' => 'id', 'name' => 'name' ) ), array( 'type' => 'text', 'label' => $this->l('Site Speed Sample Rate'), 'name' => 'RC_PGANALYTICS_SSSR', 'class' => 'fixed-width-xl', 'desc' => $this->l('Percentage of users to include in site speed data reports'), 'hint' => 'Min: 1 - Max: 100', 'validate' => array( 'type' => 'is_int' ) ) ), 'submit' => array( 'title' => $this->l('Save') ) ) ), 'tracking_tab' => array( 'form' => array( 'legend' => array( 'title' => $this->l('Settings'), 'icon' => 'icon-cogs', ), 'input' => array( array( 'type' => 'switch', 'label' => $this->l('Anonymize IP'), 'name' => 'RC_PGANALYTICS_IF', 'desc' => $this->l('Need in some countries, for privacy policies.'), 'values' => array( array( 'value' => true, ), array( 'value' => false ) ) ), array( 'type' => 'switch', 'label' => 'User-ID', 'name' => 'RC_PGANALYTICS_UF', 'desc' => $this->l('Unify the customer sessions if they use diferent devices.'), 'values' => array( array( 'value' => true, ), array( 'value' => false ) ) ), array( 'type' => 'switch', 'label' => $this->l('Enhanced Link Attribution'), 'name' => 'RC_PGANALYTICS_LF', 'desc' => $this->l('Allows a better track links on your page.'), 'values' => array( array( 'value' => true, ), array( 'value' => false ) ) ), array( 'type' => 'textarea', 'label' => $this->l('Cross Domain List'), 'name' => 'RC_PGANALYTICS_DML', 'class' => 'fixed-width-xl', 'desc' => $this->l('Track your linked domains with the same GA ID'), 'hint' => $this->l('Include one domain by line') ), array( 'type' => 'textarea', 'label' => $this->l('Referral Exclusion List'), 'name' => 'RC_PGANALYTICS_REL', 'class' => 'fixed-width-xl', 'desc' => $this->l('Exclude source domains on manual transactions'), 'hint' => $this->l('Include one domain by line') ) ), 'submit' => array( 'title' => $this->l('Save') ) ) ), 'remarketing_tab' => array( 'form' => array( 'legend' => array( 'title' => $this->l('Settings'), 'icon' => 'icon-cogs', ), 'input' => array( array( 'type' => 'switch', 'label' => $this->l('Retail Remarketing (ecomm Tags)'), 'name' => 'RC_PGANALYTICS_DF', 'desc' => $this->l('Remarketing with Merchant Center'), 'values' => array( array( 'value' => true, ), array( 'value' => false ) ) ), array( 'type' => 'select', 'label' => $this->l('ecomm_prodid Index'), 'name' => 'RC_PGANALYTICS_D1', 'desc' => $this->l('This index must match the index of the GA custom dimension'), 'options' => array( 'query' => $index_dimension, 'id' => 'id', 'name' => 'name' ) ), array( 'type' => 'select', 'label' => $this->l('ecomm_pagetype Index'), 'name' => 'RC_PGANALYTICS_D2', 'desc' => $this->l('This index must match the index of the GA custom dimension'), 'options' => array( 'query' => $index_dimension, 'id' => 'id', 'name' => 'name' ) ), array( 'type' => 'select', 'label' => $this->l('ecomm_totalvalue Index'), 'name' => 'RC_PGANALYTICS_D3', 'desc' => $this->l('This index must match the index of the GA custom dimension'), 'options' => array( 'query' => $index_dimension, 'id' => 'id', 'name' => 'name' ) ), array( 'type' => 'select', 'label' => $this->l('ecomm_category Index'), 'name' => 'RC_PGANALYTICS_D4', 'desc' => $this->l('This index must match the index of the GA custom dimension'), 'options' => array( 'query' => $index_dimension, 'id' => 'id', 'name' => 'name' ) ), array( 'type' => 'text', 'label' => $this->l('Merchant Center Prefix'), 'name' => 'RC_PGANALYTICS_MC_PF', 'class' => 'fixed-width-xl', 'desc' => $this->l('Add a prefix to match with product ID from your merchant center feed'), 'hint' => $this->l('Use {lang} or {country} vars for language or country iso values. {LANG} or {COUNTRY} for uppercase mode') ), array( 'type' => 'text', 'label' => $this->l('Merchant Center Suffix'), 'name' => 'RC_PGANALYTICS_MC_SF', 'class' => 'fixed-width-xl', 'desc' => $this->l('Add a suffix to match with product ID from your merchant center feed'), 'hint' => $this->l('Use {lang} or {country} vars for language or country iso values. {LANG} or {COUNTRY} for uppercase mode') ), array( 'type' => 'text', 'label' => $this->l('Merchant Center Variant'), 'name' => 'RC_PGANALYTICS_MC_VT', 'class' => 'fixed-width-xl', 'desc' => $this->l('Add key for variant ID / Leaves empty for disable.'), 'hint' => $this->l('Ex. Use "v" for split the product ID(55) and variant ID(7) - Result: 55v7') ), array( 'type' => 'switch', 'label' => $this->l('Business Data (dynx Tags)'), 'name' => 'RC_PGANALYTICS_BD', 'desc' => $this->l('Remarketing for countries where Merchant Center is not available'), 'values' => array( array( 'value' => true, ), array( 'value' => false ) ) ), array( 'type' => 'select', 'label' => $this->l('dynx_itemid Index'), 'name' => 'RC_PGANALYTICS_D5', 'desc' => $this->l('This index must match the index of the GA custom dimension'), 'options' => array( 'query' => $index_dimension, 'id' => 'id', 'name' => 'name' ) ), array( 'type' => 'select', 'label' => $this->l('dynx_itemid2 Index'), 'name' => 'RC_PGANALYTICS_D6', 'desc' => $this->l('This index must match the index of the GA custom dimension'), 'options' => array( 'query' => $index_dimension, 'id' => 'id', 'name' => 'name' ) ), array( 'type' => 'select', 'label' => $this->l('dynx_pagetype Index'), 'name' => 'RC_PGANALYTICS_D7', 'desc' => $this->l('This index must match the index of the GA custom dimension'), 'options' => array( 'query' => $index_dimension, 'id' => 'id', 'name' => 'name' ) ), array( 'type' => 'select', 'label' => $this->l('dynx_totalvalue Index'), 'name' => 'RC_PGANALYTICS_D8', 'desc' => $this->l('This index must match the index of the GA custom dimension'), 'options' => array( 'query' => $index_dimension, 'id' => 'id', 'name' => 'name' ) ), array( 'type' => 'text', 'label' => $this->l('Business Data Prefix'), 'name' => 'RC_PGANALYTICS_BD_PF', 'class' => 'fixed-width-xl', 'desc' => $this->l('Add a prefix to match with product ID from your Business Data feed'), 'hint' => $this->l('Use {lang} or {country} vars for language or country iso values. {LANG} or {COUNTRY} for uppercase mode') ), array( 'type' => 'text', 'label' => $this->l('Business Data Variant'), 'name' => 'RC_PGANALYTICS_BD_VT', 'class' => 'fixed-width-xl', 'desc' => $this->l('Add key for variant ID / Leaves empty for disable.'), 'hint' => $this->l('Use variant separator to concatenate product and attribute id in dynx_itemid, or disable it to use dynx_itemid for product id and dynx_itemid2 for attribute id') ) ), 'submit' => array( 'title' => $this->l('Save') ) ) ), 'googleads_tab' => array( 'form' => array( 'legend' => array( 'title' => $this->l('Settings'), 'icon' => 'icon-cogs', ), 'input' => array( array( 'type' => 'text', 'label' => 'Google Ads ID', 'name' => 'RC_PGANALYTICS_AW_ID', 'class' => 'fixed-width-xl', 'desc' => $this->l('The format is') . ': AW-123456789' ), array( 'type' => 'text', 'label' => $this->l('Purchase Conversion Label'), 'name' => 'RC_PGANALYTICS_AW_CL', 'class' => 'fixed-width-xl', 'desc' => $this->l('Conversion label for purchase event'), 'hint' => $this->l('For track conversions is required to set a Google Ads ID') ) ), 'submit' => array( 'title' => $this->l('Save') ) ) ), 'optimize_tab' => array( 'form' => array( 'legend' => array( 'title' => $this->l('Settings'), 'icon' => 'icon-cogs', ), 'input' => array( array( 'type' => 'text', 'label' => 'Google Optimize Id', 'name' => 'RC_PGANALYTICS_OPT_ID', 'class' => 'fixed-width-xl', 'desc' => $this->l('The format is') . ': GTM-XXXXXXX' ), array( 'type' => 'text', 'label' => $this->l('Hiding Class'), 'name' => 'RC_PGANALYTICS_OPT_HCN', 'class' => 'fixed-width-xl', 'desc' => $this->l('CSS class name'), 'hint' => $this->l('An empty value disables the page hiding snippet') ), array( 'type' => 'select', 'label' => $this->l('Hiding Time'), 'name' => 'RC_PGANALYTICS_OPT_HTO', 'class' => 'fixed-width-xl', 'desc' => $this->l('Hiding timeout (milliseconds)'), 'options' => array( 'query' => $hiding_time, 'id' => 'id', 'name' => 'name' ) ) ), 'submit' => array( 'title' => $this->l('Save') ) ) ), 'goal_tab' => array( 'form' => array( 'legend' => array( 'title' => $this->l('Settings'), 'icon' => 'icon-cogs', ), 'input' => array( array( 'type' => 'switch', 'label' => $this->l('Track Sign Ups'), 'name' => 'RC_PGANALYTICS_GOAL_SU', 'desc' => $this->l('Send Goal when new customer has signed up'), 'values' => array( array( 'value' => true, ), array( 'value' => false ) ) ), array( 'type' => 'text', 'label' => $this->l('Sign Up Value'), 'name' => 'RC_PGANALYTICS_EVENT_VAL_SU', 'class' => 'js-event-value fixed-width-xl', 'desc' => $this->l('Virtual value for this event'), 'hint' => $this->l('Only positive integers'), 'validate' => array( 'type' => 'is_int' ) ), array( 'type' => 'switch', 'label' => $this->l('Track Social Actions'), 'name' => 'RC_PGANALYTICS_GOAL_SA', 'desc' => $this->l('Send a goal when customer shares product'), 'hint' => $this->l('compatible with standard social sharing module'), 'values' => array( array( 'value' => true, ), array( 'value' => false ) ) ), array( 'type' => 'text', 'label' => $this->l('Social Action Value'), 'name' => 'RC_PGANALYTICS_EVENT_VAL_SA', 'class' => 'js-event-value fixed-width-xl', 'desc' => $this->l('Virtual value for this event'), 'hint' => $this->l('Only positive integers'), 'validate' => array( 'type' => 'is_int' ) ) ), 'submit' => array( 'title' => $this->l('Save') ) ) ), 'orderstatus_tab' => array( 'form' => array( 'legend' => array( 'title' => $this->l('Settings'), 'icon' => 'icon-cogs', ), 'input' => array( array( 'type' => 'checkbox', 'multiple' => true, 'label' => $this->l('Invalid Order Statuses'), 'name' => 'RC_PGANALYTICS_IOS', 'hint' => $this->l('Select which order statuses do not sends a transaction to GA'), 'desc' => $this->l('Warning! bad selection could has negative impact on transactions'), 'expand' => array( 'default' => 'show', 'show' => array( 'icon' => 'gear', 'text' => $this->l('Show Options'), ), 'hide' => array( 'icon' => 'gear', 'text' => $this->l('Hide Options'), ) ), 'values' => array( 'query' => $order_states, 'id' => 'id_order_state', 'name' => 'name' ) ), array( 'type' => 'checkbox', 'multiple' => true, 'label' => $this->l('Refund statuses'), 'name' => 'RC_PGANALYTICS_ROS', 'hint' => $this->l('Select which order statuses sends a refund to GA'), 'desc' => $this->l('Warning! bad selection could has negative impact on transactions'), 'expand' => array( 'default' => 'show', 'show' => array( 'icon' => 'gear', 'text' => $this->l('Show Options'), ), 'hide' => array( 'icon' => 'gear', 'text' => $this->l('Hide Options'), ) ), 'values' => array( 'query' => $order_states, 'id' => 'id_order_state', 'name' => 'name' ) ) ), 'submit' => array( 'title' => $this->l('Save') ) ) ) ); return $config_form; } /* LOAD VALUES TO FORM */ protected function getConfigFormValues($post_process = false) { // Prepare Domain List $search = ','; $replace = self::_DML_DELIMITER_; $forms = $this->getConfigForm(); $inputs = array(); $config_form_values = array(); foreach ($forms as $form) { if (isset($form['form']['input'])) { $inputs = array_merge($inputs, $form['form']['input']); } } foreach ($inputs as $input) { $input_value = Tools::getValue($input['name'], Configuration::get($input['name'])); if ($input['type'] === 'text') { $input_value = trim($input_value); // validate text input as int if (isset($input['validate'])) { if ($input['validate']['type'] === 'is_int') { // convert all data to an integer $input_value = (int)$input_value; if ($input['name'] === 'RC_PGANALYTICS_SSSR' && ($input_value < 1 || $input_value > 100)) { $input_value = $this->default_values[$input['name']]; } elseif ($input_value < 0) { // set default for negative values $input_value = $this->default_values[$input['name']]; } } } } // special treatment for some input values if ($input['name'] === 'RC_PGANALYTICS_DML' || $input['name'] === 'RC_PGANALYTICS_REL') { // parse form domains list to $input_value = str_replace($search, $replace, $input_value); } // handle checkbox options if ($input['name'] === 'RC_PGANALYTICS_IOS' || $input['name'] === 'RC_PGANALYTICS_ROS') { $input_value = $this->getCheckboxFormValues( $input['name'], $input['values']['query'], $input['values']['id'], $post_process ); } // if input value is array means checkbox options if (is_array($input_value)) { // add all checkbox options to config_form_values $config_form_values = array_merge($config_form_values, $input_value); } else { // add basic value to config_form_values $config_form_values[$input['name']] = $input_value; } } return $config_form_values; } /** * @param $configuration_key * @param $checkbox_values * @param $checkbox_id * @param bool $post_process * @return array */ protected function getCheckboxFormValues($configuration_key, $checkbox_values, $checkbox_id, $post_process = false) { $post_count = 0; $checkbox_fields = array(); $process_values = array(); foreach ($checkbox_values as $value) { $post_field = Tools::getValue($configuration_key.'_'.$value[$checkbox_id]); if ($post_field) { // if post field exist count and process data $post_count ++; if (!$post_process) { // parse data to be loaded in form $checkbox_fields[$configuration_key . '_' . $value[$checkbox_id]] = 'on'; } else { // collect the id results on array to implode later $process_values[] = $value[$checkbox_id]; } } } if ($post_count && $post_process) { // parse data to be saved on database $checkbox_fields[$configuration_key] = implode(',', $process_values); } elseif (!$post_count && !$post_process) { // on first form load will get data from configuration table $db_values = Configuration::get($configuration_key); // split values into array $db_values = $db_values ? explode(',', $db_values) : null; if (!empty($db_values)) { // if has values parse form fields foreach ($db_values as $db_value) { $checkbox_fields[$configuration_key . '_' . $db_value] = 'on'; } } } return $checkbox_fields; } /** * @param $domain_list * @param bool $is_rel * @return array|bool|string */ protected function checkDomainList($domain_list, $is_rel = false) { $filtered_domains = array(); $domains = explode(self::_DML_DELIMITER_, Tools::strtolower($domain_list)); $pattern = '/^(?=.{3,256}$)((?:(?!-)[a-z0-9-]{2,63}(?errors[] = sprintf($this->l('The domain "%s" are not write properly!'), $domain); return false; } } } // check if is referral exclusion list if ($is_rel) { // get default host value $self_host = $this->default_values['RC_PGANALYTICS_REL']; // if is not in domains add it if (!in_array($self_host, $filtered_domains)) { $filtered_domains[] = $self_host; } } $filtered_domains = implode(',', $filtered_domains); return $filtered_domains; } /* SAVE FORM VALUES TO DATABASE */ protected function postProcess() { $form_values = $this->getConfigFormValues(true); if (empty($form_values['RC_PGANALYTICS_ID'])) { $this->errors[] = $this->l('Google Analytics ID is required'); } if (!empty($form_values['RC_PGANALYTICS_DML'])) { $form_values['RC_PGANALYTICS_DML'] = $this->checkDomainList($form_values['RC_PGANALYTICS_DML']); } if (!empty($form_values['RC_PGANALYTICS_REL'])) { $form_values['RC_PGANALYTICS_REL'] = $this->checkDomainList( $form_values['RC_PGANALYTICS_REL'], true ); } foreach ($form_values as $key => $value) { if (($key == 'RC_PGANALYTICS_DML' || $key == 'RC_PGANALYTICS_REL') && !$value) { continue; } else { Configuration::updateValue($key, $value); } } } /* DISPLAY HOOKS */ public function hookHeader() { $analytics_id = (string)Configuration::get('RC_PGANALYTICS_ID'); if (!$analytics_id) { // If not set the Analytics ID no load return false; } // Tracking options $internal_tracking_feature = (int)Configuration::get('RC_PGANALYTICS_NO_IT'); $product_send_rate = (int)Configuration::get('RC_PGANALYTICS_PR'); $simple_speed_sample_rate = (int)Configuration::get('RC_PGANALYTICS_SSSR'); $check_do_not_track = (int)Configuration::get('RC_PGANALYTICS_DNT'); // GA Features $anonymize_ip_feature = (int)Configuration::get('RC_PGANALYTICS_IF'); $link_attribution = (int)Configuration::get('RC_PGANALYTICS_LF'); $user_id_feature = (int)Configuration::get('RC_PGANALYTICS_UF'); // Remarketing Dynamic $remarketing_feature = (int)Configuration::get('RC_PGANALYTICS_DF'); $ecomm_prodid_index = (int)Configuration::get('RC_PGANALYTICS_D1'); $ecomm_pagetype_index = (int)Configuration::get('RC_PGANALYTICS_D2'); $ecomm_totalvalue_index = (int)Configuration::get('RC_PGANALYTICS_D3'); $ecomm_category_index = (int)Configuration::get('RC_PGANALYTICS_D4'); $ga_merchant_prefix = (string)Configuration::get('RC_PGANALYTICS_MC_PF'); $ga_merchant_suffix = (string)Configuration::get('RC_PGANALYTICS_MC_SF'); $ga_merchant_variant = (string)Configuration::get('RC_PGANALYTICS_MC_VT'); // Business Data $business_data_feature = (int)Configuration::get('RC_PGANALYTICS_BD'); $dynx_itemid_index = (int)Configuration::get('RC_PGANALYTICS_D5'); $dynx_itemid2_index = (int)Configuration::get('RC_PGANALYTICS_D6'); $dynx_pagetype_index = (int)Configuration::get('RC_PGANALYTICS_D7'); $dynx_totalvalue_index = (int)Configuration::get('RC_PGANALYTICS_D8'); $ga_business_data_prefix = (string)Configuration::get('RC_PGANALYTICS_BD_PF'); $ga_business_data_variant = (string)Configuration::get('RC_PGANALYTICS_BD_VT'); // AdWords Conversion $adwords_id = (string)Configuration::get('RC_PGANALYTICS_AW_ID'); $adwords_cl = (string)Configuration::get('RC_PGANALYTICS_AW_CL'); // Google Optimize $optimize_id = (string)Configuration::get('RC_PGANALYTICS_OPT_ID'); $optimize_class_name = (string)Configuration::get('RC_PGANALYTICS_OPT_HCN'); $optimize_time_out = (int)Configuration::get('RC_PGANALYTICS_OPT_HTO'); // Cross Domain $domain_list = (string)Configuration::get('RC_PGANALYTICS_DML'); // Goals $goal_sign_up = (int)Configuration::get('RC_PGANALYTICS_GOAL_SU'); $goal_social_action = (int)Configuration::get('RC_PGANALYTICS_GOAL_SA'); // Event values $event_value_sign_up = (int)Configuration::get('RC_PGANALYTICS_EVENT_VAL_SU'); $event_value_social_action = (int)Configuration::get('RC_PGANALYTICS_EVENT_VAL_SA'); // Prestashop basic data $cart_ajax = (int)Configuration::get('PS_BLOCK_CART_AJAX'); $products_per_page = (int)Configuration::get('PS_PRODUCTS_PER_PAGE'); $currency_iso = $this->context->currency->iso_code; $user_id = $this->context->customer->id; // Values for internal tracking $ga_cookie_exist = Rc_PgAnalyticsTools::isAdminGaCookieExist(); $disable_internal_tracking = false; $is_client_id = 0; $client_id = 0; if ($user_id) { // get client id $client_id = Rc_PgAnalyticsClientId::getClientIdByCustomerId($user_id); $is_client_id = 1; } // Process the internal tracking feature - Disables tracking if feature active and user has accessed to BO if ($internal_tracking_feature && $ga_cookie_exist) { $disable_internal_tracking = true; } // Cross domain Feature if (!empty($domain_list)) { # Split domain list into array $domain_list = explode(',', $domain_list); } // Values for sign up goal $max_lapse = 100; $is_new_sign_up = false; $controller_name = Tools::getValue('controller'); $sign_up_types = array('new customer', 'guest customer'); $is_guest = 0; $controllers_with_sign_up = array( 'index', 'order', 'supercheckout' ); // Process sign up feature // index for sign up before checkout // order for sign up on checkout // supercheckout for concrete OPC 3rd party if (in_array($controller_name, $controllers_with_sign_up)) { // get customer date creation on timestamp $customer_date_add = strtotime($this->context->customer->date_add); if ($customer_date_add) { // calc sign up time lapse $sign_up_lapse = time() - $customer_date_add; if ($sign_up_lapse < $max_lapse) { $is_new_sign_up = true; // check if customer type is guest $is_guest = $this->context->customer->is_guest; } } } // merchant center acronyms $ga_merchant_acronyms = Rc_PgAnalyticsTools::getFeedAcronyms($ga_merchant_prefix, $ga_merchant_suffix); // business data acronyms $ga_business_data_acronyms = Rc_PgAnalyticsTools::getFeedAcronyms($ga_business_data_prefix); // generate module token to avoid csrf $token = $this->secret_key; $id_shop = Context::getContext()->shop->id; $gtag_tracking_features = array( // gtag Ids 'analyticsId' => $analytics_id, 'adwordsId' => $adwords_id, 'adwordsCl' => $adwords_cl, // basic 'productSendRate' => $product_send_rate, 'merchantPrefix' => $ga_merchant_acronyms['prefix'], 'merchantSuffix' => $ga_merchant_acronyms['suffix'], 'merchantVariant' => $ga_merchant_variant, 'businessDataPrefix' => $ga_business_data_acronyms['prefix'], 'businessDataVariant' => $ga_business_data_variant, 'currency' => $currency_iso, 'idShop' => $id_shop, // related to tracking 'productsPerPage' => $products_per_page, 'cartAjax' => $cart_ajax, 'token' => $token, 'disableInternalTracking' => $disable_internal_tracking, 'signUpTypes' => $sign_up_types, 'isNewSignUp' => $is_new_sign_up, 'isGuest' => $is_guest, 'checkDoNotTrack' => $check_do_not_track, // tracking config 'config' => array( 'optimizeId' => $optimize_id, 'simpleSpeedSampleRate' => $simple_speed_sample_rate, 'anonymizeIp' => $anonymize_ip_feature, 'linkAttribution' => $link_attribution, 'userIdFeature' => $user_id_feature, 'userIdValue' => $user_id, 'remarketing' => $remarketing_feature, 'crossDomainList' => $domain_list, 'clientId' => $client_id, 'businessData' => $business_data_feature, 'customDimensions' => array( 'ecommProdId' => $ecomm_prodid_index, 'ecommPageType' => $ecomm_pagetype_index, 'ecommTotalValue' => $ecomm_totalvalue_index, 'ecommCategory' => $ecomm_category_index, 'dynxItemId' => $dynx_itemid_index, 'dynxItemId2' => $dynx_itemid2_index, 'dynxPageType' => $dynx_pagetype_index, 'dynxTotalValue' => $dynx_totalvalue_index, ) ), 'goals' => array( 'signUp' => $goal_sign_up, 'socialAction' => $goal_social_action ), 'eventValues' => array( 'signUp' => $event_value_sign_up, 'socialAction' => $event_value_social_action ) ); $this->context->smarty->assign(array( 'gtag_tracking_features' => $gtag_tracking_features, 'analytics_id' => $analytics_id, 'optimize_id' => $optimize_id, 'optimize_class_name' => $optimize_class_name, 'optimize_time_out' => $optimize_time_out, 'is_client_id' => $is_client_id, )); $this->context->controller->registerJavascript( 'modules-'.$this->name, 'modules/'.$this->name.'/views/js/hook/RcAnalyticsEvents.js', array('position' => 'head', 'priority' => 150) ); return $this->fetch('module:'.$this->name.'/views/templates/hook/header.tpl'); } public function hookOrderConfirmation($params) { $obj_order = $params['order']; $invalid_statuses = explode(',', Configuration::get('RC_PGANALYTICS_IOS')); if (Validate::isLoadedObject($obj_order)) { // Validate all orders except invalid statuses if (!in_array($obj_order->current_state, $invalid_statuses)) { // convert object to array $order = get_object_vars($obj_order); $order_id = $order['id']; $id_shop = $order['id_shop']; $id_lang = $order['id_lang']; $order_sent = (bool)Rc_PgAnalyticsOrderSent::getOrderReport($order_id, $id_shop); // common value to know the order status $this->context->smarty->assign(array( 'order_sent' => $order_sent )); if (!$order_sent) { $order_products = $obj_order->getProducts(); $order_products = Rc_PgAnalyticsTools::getNamesWithoutVariant($order_products, $id_lang, $id_shop); $order_products = Rc_PgAnalyticsTools::getGaCategories($order_products); $order_products = Rc_PgAnalyticsTools::getManufacturerNames($order_products); $order_products = Rc_PgAnalyticsTools::getVariants($order_products); // Get affiliation name $affiliation = Rc_PgAnalyticsTools::getAffiliation(); // Get coupon name $coupon = Rc_PgAnalyticsTools::getCoupon($obj_order); $products = Rc_PgAnalyticsTools::tagProducts($order_products, null, null, true); $order = Rc_PgAnalyticsTools::tagOrder($order, $affiliation, $coupon); $this->context->smarty->assign(array( 'id_shop' => $id_shop, 'ga_order' => $order, 'ga_products' => $products )); } } } } public function hookDisplayBeforeBodyClosingTag() { $analytics_id = Configuration::get('RC_PGANALYTICS_ID'); if (!$analytics_id) { // If not set the Analytics ID no load return false; } // Get controller name $controller_name = Tools::getValue('controller'); // List of executed hooks on the page $active_hooks = Hook::$executed_hooks; // Set default values $is_checkout = false; $is_order = false; // list names $lists = array( 'default' => $controller_name, 'filter' => 'filtered_results', 'productView' => 'product_page' ); $controllers_with_product_lists = array( 'product', 'category', 'manufacturer', 'supplier', 'bestsales', 'newproducts', 'search' ); $products_list_cache = array(); $ecomm_pagetype = 'other'; $compliant_module_name = 'default'; // some opc can skip the cart summary $skip_cart_step = false; // List of compliant 3rd party checkout modules (module => controller) $compliant_modules = array( 'default' => 'order', 'supercheckout' => 'supercheckout', 'onepagecheckoutps' => 'order', 'sveacheckout' => 'sveapaypage', 'thecheckout' => 'order' ); // check if some compliant module are enabled foreach (array_keys($compliant_modules) as $compliant_module) { // Check if compliant checkout module is enabled if (Module::isEnabled($compliant_module)) { $compliant_module_name = $compliant_module; break; } } if ($compliant_module_name === 'onepagecheckoutps' && Configuration::get('OPC_REDIRECT_DIRECTLY_TO_OPC')) { $skip_cart_step = true; } // set list and ecomm_pagetype depending controller active switch ($controller_name) { case 'index': $lists['default'] = 'home'; $ecomm_pagetype = 'home'; break; case 'category': $lists['default'] = 'category'; $ecomm_pagetype = 'category'; break; case 'manufacturer': $lists['default'] = 'manufacturer'; $ecomm_pagetype = 'category'; break; case 'supplier': $lists['default'] = 'supplier'; $ecomm_pagetype = 'category'; break; case 'bestsales': $lists['default'] = 'best_sales'; $ecomm_pagetype = 'category'; break; case 'newproducts': $lists['default'] = 'new_products'; $ecomm_pagetype = 'category'; break; case 'product': $lists['default'] = 'accessories'; $ecomm_pagetype = 'product'; break; case 'search': $lists['default'] = 'search_results'; $ecomm_pagetype = 'searchresults'; break; // SUMMARY CART case 'cart': $lists['default'] = 'begin_checkout'; $ecomm_pagetype = 'cart'; break; // CHECKOUT case $compliant_modules[$compliant_module_name]: $ecomm_pagetype = 'cart'; break; } if (in_array('displayOrderConfirmation', $active_hooks) || in_array('displayPaymentReturn', $active_hooks)) { $is_order = true; $ecomm_pagetype = 'purchase'; } if (!$is_order) { // check if page is on checkout process if ($controller_name == 'cart' || $controller_name == 'order' || (!empty($compliant_module_name) && $controller_name === $compliant_modules[$compliant_module_name]) ) { // set checkout flag $is_checkout = true; // Get cart products $products = $this->context->cart->getProducts(); if (!empty($products)) { // add products to cache $products_list_cache = Rc_PgAnalyticsTools::indexProductsCache($products); // Get GA Category $products = Rc_PgAnalyticsTools::getGaCategories($products); // Get Combination names $products = Rc_PgAnalyticsTools::getVariants($products); // Get Manufacturer Name $products = Rc_PgAnalyticsTools::getManufacturerNames($products); // Tag the product data for GA $ga_products = Rc_PgAnalyticsTools::tagProducts($products, null, null, true); $this->context->smarty->assign(array( 'ga_products' => $ga_products )); } } elseif (in_array($controller_name, $controllers_with_product_lists)) { $template_products = array(); if ($controller_name === 'product') { // handle product var $product_view = $this->context->smarty->getTemplateVars('product'); if ($product_view) { $template_products[] = $product_view; } // handle accessories var $accessories = $this->context->smarty->getTemplateVars('accessories'); if ($accessories) { $template_products = array_merge($template_products, $accessories); } } else { // rest of controllers will have listing var $listing = $this->context->smarty->getTemplateVars('listing'); $template_products = $listing['products']; } // index template products if (is_array($template_products) && $template_products) { $products_list_cache = Rc_PgAnalyticsTools::indexProductsCache($template_products); } } } $this->context->smarty->assign(array( 'controller_name' => $controller_name, 'compliant_modules' => $compliant_modules, 'compliant_module_name' => $compliant_module_name, 'skip_cart_step' => $skip_cart_step, 'ecomm_pagetype' => $ecomm_pagetype, 'lists' => $lists, 'is_checkout' => $is_checkout, 'is_order' => $is_order, 'products_list_cache' => $products_list_cache )); return $this->fetch('module:' . $this->name . '/views/templates/hook/footer.tpl'); } public function hookBackOfficeHeader() { if (Configuration::get('RC_PGANALYTICS_NO_IT')) { // If Exclude internal tracking enabled // always confirm GA cookie is created Rc_PgAnalyticsTools::setAdminGaCookie(); } } public function hookDisplayAdminOrderTabOrder() { return $this->display(__FILE__, 'views/templates/admin/controllers/order/tab_order.tpl'); } public function hookDisplayAdminOrderContentOrder($params) { // get actual id_shop $id_shop = Context::getContext()->shop->id; // get real base with or without SSL $base_url = Tools::getHttpHost(true); // get shop config to retrieve virtual uri $shop = Shop::getShop($id_shop); // build module url $module_url = $base_url.$shop['uri'].'modules/'.$this->name.'/'; $tracking_statuses = array( 'st' => $this->l('Setup'), 'bo' => $this->l('Back Office'), 'fo' => $this->l('Front Office') ); $order_id = (int)$params['order']->id; $order_id_shop = (int)$params['order']->id_shop; $tracking_report = Rc_PgAnalyticsOrderSent::getOrderReport($order_id, $order_id_shop); $this->context->smarty->assign(array( 'tracking_report' => $tracking_report, 'tracking_statuses' => $tracking_statuses, 'rc_order_id' => $order_id, 'rc_order_id_shop' => $order_id_shop, 'moduleUrl' => $module_url, 'rcToken' => $this->secret_key )); $this->context->controller->addJS($this->_path.'views/js/admin/controllers/order/content_order.js'); return $this->display(__FILE__, 'views/templates/admin/controllers/order/tab_content_order.tpl'); } /* ACTION HOOKS */ public function hookUpdateOrderStatus($params) { // List of executed hooks on the page $active_hooks = Hook::$executed_hooks; // Get the GA property $analytics_id = Configuration::get('RC_PGANALYTICS_ID'); if (in_array('displayBackOfficeHeader', $active_hooks) && $analytics_id) { // Order state list to avoid send order to GA $invalid_statuses = explode(',', Configuration::get('RC_PGANALYTICS_IOS')); // Order state list to send a refund event $refund_statuses = explode(',', Configuration::get('RC_PGANALYTICS_ROS')); // Get Order ID $id_order = $params['id_order']; if (empty($id_order)) { return false; } // Get the new State ID $order_status = $params['newOrderStatus']->id; // Get all Order Data $obj_order = new Order($id_order); // Get id_lang placed on order $id_lang = $obj_order->id_lang; // Get id_shop placed on order $id_shop = $obj_order->id_shop; // convert object to array $ga_order = get_object_vars($obj_order); $currency = Currency::getCurrency($obj_order->id_currency); $ga_order['currency_iso_code'] = $currency['iso_code']; // Check if order has been sent to GA $order_sent = (bool)Rc_PgAnalyticsOrderSent::getOrderReport($id_order, $id_shop); if (!$order_sent) { // order has not been sent, check if new order_status are not on invalid status list if (!in_array($order_status, $invalid_statuses)) { // Set Coupon name $ga_order['coupon'] = Rc_PgAnalyticsTools::getCoupon($obj_order); // Get affiliation name $ga_order['affiliation'] = Rc_PgAnalyticsTools::getAffiliation(); // Get reference url $ga_order['document_reference'] = Rc_PgAnalyticsTools::getSourceConnection( $id_order, $obj_order->date_add ); // get ga utm campaign $ga_order['ga_utm'] = Rc_PgAnalyticsTools::getGaUtmValues( $ga_order['module'], $ga_order['document_reference'] ); // Get order products $products = $obj_order->getProducts(); if ($products) { // normalize product data $products = Rc_PgAnalyticsTools::getNamesWithoutVariant($products, $id_lang, $id_shop); $products = Rc_PgAnalyticsTools::getGaCategories($products); $products = Rc_PgAnalyticsTools::getManufacturerNames($products); $products = Rc_PgAnalyticsTools::getVariants($products); // Tag the product data for GA $products = Rc_PgAnalyticsTools::tagProducts($products, null, null, true); // Tag order refund to send it to GA $transaction = Rc_PgAnalyticsTools::curlTagTransaction( $analytics_id, $ga_order, $products, 'event', 'purchase' ); // send the order to GA by CURL Rc_PgAnalyticsTools::curlSendGaTransaction($transaction); // set order to database Rc_PgAnalyticsTools::setOrderSend($id_order, $id_shop, 'bo'); } } } else { // order has been sent, check if order has a refund status if (in_array($order_status, $refund_statuses)) { // Get products refund, if products are not refunded will send full order refund $products_refund = Rc_PgAnalyticsTools::getProductsRefund( $id_order, $id_lang, $id_shop ); // Tag order refund to send it to GA $transaction = Rc_PgAnalyticsTools::curlTagTransaction( $analytics_id, $ga_order, $products_refund, 'event', 'refund' ); // Send order refund by CURL Rc_PgAnalyticsTools::curlSendGaTransaction($transaction); } } } } /* AJAX REQUEST */ public function ajaxCall($params) { $response = ''; if (!empty($params['action'])) { $action = $params['action']; if ($action === 'product') { $response = Rc_PgAnalyticsTools::ajaxActionProduct($params); } elseif ($action === 'orderComplete') { $response = Rc_PgAnalyticsTools::ajaxActionOrderComplete($params); } elseif ($action === 'abortedTransaction') { $response = Rc_PgAnalyticsTools::ajaxActionAbortedTransaction($params); } elseif ($action === 'clientId') { $response = Rc_PgAnalyticsTools::ajaxActionClientId($params); } elseif ($action === 'deleteFromControlTable') { $response = Rc_PgAnalyticsTools::ajaxActionDeleteFromControlTable($params); } elseif ($action === 'forceTransaction') { $response = Rc_PgAnalyticsTools::ajaxActionForceTransaction($params); } // check if response is not an array if (!is_array($response)) { // convert response in array to get properly response $response = array( 'result' => $response ); } header('Content-Type: application/json'); die(json_encode($response)); } else { throw new Exception('no action detected'); } } }