diff --git a/Readme.md b/Readme.md index 0c7c532..9a43906 100644 --- a/Readme.md +++ b/Readme.md @@ -4,17 +4,24 @@ Gain clear insights into important metrics about your customers, using Google Analytics. -## Notes +To use it, you will need to create a Google Analytics account and insert your Google Analytics Identifier into the Module configuration page. -Enhanced Ecommerce must be enabled in Google Analytics settings for full functionality. Otherwise, some data (refunds etc.) will not be visible. Follow [instructions][4]. +### Notes -Google Tag Assistant will report "No HTTP Response detected" error in Prestashop back-office. **This is not a bug.** This happens, because the module does not send page views in BO, not to influence statistics. Only events, like refunds, are sent from BO. +Enhanced Ecommerce must be enabled in Google Analytics settings for full functionality. Otherwise, some data (refunds etc.) will not be visible. Follow [the related instructions][4]. + +### Configure + +1. Install the module into your shop. +2. Create an account on Google Analytics if you do not have one. +3. Go on the "Configure" page of the module to insert your Google Analytics Identifier. +4. The data will then be sent to Google Analytics and you can monitor/explore it. ## Contributing PrestaShop modules are open-source extensions to the PrestaShop e-commerce solution. Everyone is welcome and even encouraged to contribute with their own improvements. -Google Analytics is compatible with all versions of PrestaShop 1.7 and 1.6 +Google Analytics is compatible with all versions of PrestaShop 1.7 and 1.6. ### Requirements diff --git a/classes/Database/Install.php b/classes/Database/Install.php index bd1ef66..7a5c78a 100644 --- a/classes/Database/Install.php +++ b/classes/Database/Install.php @@ -20,6 +20,7 @@ namespace PrestaShop\Module\Ps_Googleanalytics\Database; +use Configuration; use Db; use Ps_Googleanalytics; use Shop; @@ -55,6 +56,7 @@ public function installTables() `id_customer` int(10) NOT NULL, `id_shop` int(11) NOT NULL, `sent` tinyint(1) DEFAULT NULL, + `refund_sent` tinyint(1) DEFAULT NULL, `date_add` datetime DEFAULT NULL, PRIMARY KEY (`id_google_analytics`), KEY `id_order` (`id_order`), @@ -77,6 +79,18 @@ public function installTables() return true; } + /** + * Insert default data to database + * + * @return bool + */ + public function setDefaultConfiguration() + { + Configuration::updateValue('GA_CANCELLED_STATES', json_encode([Configuration::get('PS_OS_CANCELED')])); + + return true; + } + /** * Register Module hooks * @@ -91,6 +105,7 @@ public function registerHooks() $this->module->registerHook('displayFooterProduct') && $this->module->registerHook('displayOrderConfirmation') && $this->module->registerHook('actionProductCancel') && + $this->module->registerHook('actionOrderStatusPostUpdate') && $this->module->registerHook('actionCartSave') && $this->module->registerHook('displayBackOfficeHeader') && $this->module->registerHook('actionCarrierProcess'); diff --git a/classes/Form/ConfigurationForm.php b/classes/Form/ConfigurationForm.php index 7fdb9b4..8823a85 100644 --- a/classes/Form/ConfigurationForm.php +++ b/classes/Form/ConfigurationForm.php @@ -22,7 +22,9 @@ use AdminController; use Configuration; +use Context; use HelperForm; +use OrderState; use Ps_Googleanalytics; use Shop; use Tools; @@ -101,12 +103,12 @@ public function generate() [ 'id' => 'ga_userid_enabled', 'value' => 1, - 'label' => $this->module->l('Enabled'), + 'label' => $this->module->l('Yes'), ], [ 'id' => 'ga_userid_disabled', 'value' => 0, - 'label' => $this->module->l('Disabled'), + 'label' => $this->module->l('No'), ], ], ], [ @@ -118,15 +120,46 @@ public function generate() [ 'id' => 'ga_anonymize_enabled', 'value' => 1, - 'label' => $this->module->l('Enabled'), + 'label' => $this->module->l('Yes'), ], [ 'id' => 'ga_anonymize_disabled', 'value' => 0, - 'label' => $this->module->l('Disabled'), + 'label' => $this->module->l('No'), ], ], ], + [ + 'type' => 'switch', + 'label' => $this->module->l('Enable Back Office Tracking'), + 'name' => 'GA_TRACK_BACKOFFICE_ENABLED', + 'hint' => $this->module->l('Use this option to enable the tracking inside the Back Office'), + 'values' => [ + [ + 'id' => 'ga_track_backoffice', + 'value' => 1, + 'label' => $this->module->l('Yes'), + ], + [ + 'id' => 'ga_do_not_track_backoffice', + 'value' => 0, + 'label' => $this->module->l('No'), + ], + ], + ], + [ + 'type' => 'select', + 'label' => $this->module->l('Cancelled order states'), + 'name' => 'GA_CANCELLED_STATES', + 'desc' => $this->module->l('Choose order states, in which you consider the given order cancelled. This will be usually only the default "Cancelled" state, but some shops may have extra states like "Returned" etc.'), + 'class' => 'chosen', + 'multiple' => true, + 'options' => [ + 'query' => OrderState::getOrderStates((int) Context::getContext()->language->id), + 'id' => 'id_order_state', + 'name' => 'name', + ], + ], ], 'submit' => [ 'title' => $this->module->l('Save'), @@ -142,12 +175,12 @@ public function generate() [ 'id' => 'ga_crossdomain_enabled', 'value' => 1, - 'label' => $this->module->l('Enabled'), + 'label' => $this->module->l('Yes'), ], [ 'id' => 'ga_crossdomain_disabled', 'value' => 0, - 'label' => $this->module->l('Disabled'), + 'label' => $this->module->l('No'), ], ], ]; @@ -158,6 +191,8 @@ public function generate() $helper->fields_value['GA_USERID_ENABLED'] = Configuration::get('GA_USERID_ENABLED'); $helper->fields_value['GA_CROSSDOMAIN_ENABLED'] = Configuration::get('GA_CROSSDOMAIN_ENABLED'); $helper->fields_value['GA_ANONYMIZE_ENABLED'] = Configuration::get('GA_ANONYMIZE_ENABLED'); + $helper->fields_value['GA_TRACK_BACKOFFICE_ENABLED'] = Configuration::get('GA_TRACK_BACKOFFICE_ENABLED'); + $helper->fields_value['GA_CANCELLED_STATES[]'] = json_decode(Configuration::get('GA_CANCELLED_STATES'), true); return $helper->generateForm($fields_form); } @@ -174,6 +209,8 @@ public function treat() $gaUserIdEnabled = Tools::getValue('GA_USERID_ENABLED'); $gaCrossdomainEnabled = Tools::getValue('GA_CROSSDOMAIN_ENABLED'); $gaAnonymizeEnabled = Tools::getValue('GA_ANONYMIZE_ENABLED'); + $gaTrackBackOffice = Tools::getValue('GA_TRACK_BACKOFFICE_ENABLED'); + $gaCancelledStates = Tools::getValue('GA_CANCELLED_STATES'); if (!empty($gaAccountId)) { Configuration::updateValue('GA_ACCOUNT_ID', $gaAccountId); @@ -196,6 +233,18 @@ public function treat() $treatmentResult .= $this->module->displayConfirmation($this->module->l('Settings for Anonymize IP updated successfully')); } + if (null !== $gaTrackBackOffice) { + Configuration::updateValue('GA_TRACK_BACKOFFICE_ENABLED', (bool) $gaTrackBackOffice); + $treatmentResult .= $this->module->displayConfirmation($this->module->l('Settings for Enable Back Office tracking updated successfully')); + } + + if ($gaCancelledStates === false) { + Configuration::updateValue('GA_CANCELLED_STATES', ''); + } else { + Configuration::updateValue('GA_CANCELLED_STATES', json_encode($gaCancelledStates)); + } + $treatmentResult .= $this->module->displayConfirmation($this->module->l('Settings for cancelled order states updated successfully')); + return $treatmentResult; } } diff --git a/classes/Handler/ModuleHandler.php b/classes/Handler/ModuleHandler.php index 8bf56ee..a2b9dc5 100644 --- a/classes/Handler/ModuleHandler.php +++ b/classes/Handler/ModuleHandler.php @@ -25,8 +25,6 @@ class ModuleHandler { /** - * isModuleEnabled - * * @param string $moduleName * * @return bool @@ -47,12 +45,27 @@ public function isModuleEnabled($moduleName) return false; } - return $module->registerHook('displayHome'); + return true; } /** - * uninstallModule + * @param string $moduleName + * @param string $hookName * + * @return bool + */ + public function isModuleEnabledAndHookedOn($moduleName, $hookName) + { + $module = Module::getInstanceByName($moduleName); + + if (false === $this->isModuleEnabled($moduleName)) { + return false; + } + + return $module->isRegisteredInHook($hookName); + } + + /** * @param string $moduleName * * @return bool diff --git a/classes/Hook/HookActionOrderStatusPostUpdate.php b/classes/Hook/HookActionOrderStatusPostUpdate.php new file mode 100644 index 0000000..54415ce --- /dev/null +++ b/classes/Hook/HookActionOrderStatusPostUpdate.php @@ -0,0 +1,96 @@ + + * @copyright 2007-2020 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\Module\Ps_Googleanalytics\Hooks; + +use Configuration; +use Context; +use Db; +use Ps_Googleanalytics; + +class HookActionOrderStatusPostUpdate implements HookInterface +{ + /** + * @var Ps_Googleanalytics + */ + private $module; + + /** + * @var Context + */ + private $context; + + /** + * @var array + */ + private $params; + + public function __construct(Ps_Googleanalytics $module, Context $context) + { + $this->module = $module; + $this->context = $context; + } + + /** + * run + * + * @return void + */ + public function run() + { + // If we do not have an order or a new order status, we return + if (empty($this->params['id_order']) || empty($this->params['newOrderStatus']->id)) { + return; + } + + // We get all states in which the merchant want to have refund sent and check if the new state being set belongs there + $gaCancelledStates = json_decode(Configuration::get('GA_CANCELLED_STATES'), true); + if (empty($gaCancelledStates) || !in_array($this->params['newOrderStatus']->id, $gaCancelledStates)) { + return; + } + + // We check if the refund was already sent to Google Analytics + $gaRefundSent = Db::getInstance()->getValue( + 'SELECT id_order FROM `' . _DB_PREFIX_ . 'ganalytics` WHERE id_order = ' . (int) $this->params['id_order'] . ' AND refund_sent = 1' + ); + + // If it was not already refunded + if ($gaRefundSent === false) { + // We refund it and set the "sent" flag to true + $this->context->cookie->__set('ga_admin_refund', 'MBG.refundByOrderId(' . json_encode(['id' => $this->params['id_order']]) . ');'); + $this->context->cookie->write(); + + // We save this information to database + Db::getInstance()->execute( + 'UPDATE `' . _DB_PREFIX_ . 'ganalytics` SET refund_sent = 1 WHERE id_order = ' . (int) $this->params['id_order'] + ); + } + } + + /** + * setParams + * + * @param array $params + */ + public function setParams($params) + { + $this->params = $params; + } +} diff --git a/classes/Hook/HookDisplayBackOfficeHeader.php b/classes/Hook/HookDisplayBackOfficeHeader.php index 8816964..d734613 100644 --- a/classes/Hook/HookDisplayBackOfficeHeader.php +++ b/classes/Hook/HookDisplayBackOfficeHeader.php @@ -75,7 +75,7 @@ public function run() 'id_order' => (int) Tools::getValue('id_order'), 'id_shop' => (int) $this->context->shop->id, 'sent' => 0, - 'date_add' => 'NOW()', + 'date_add' => ['value' => 'NOW()', 'type' => 'sql'], ] ); } diff --git a/classes/Hook/HookDisplayFooter.php b/classes/Hook/HookDisplayFooter.php index b720af8..819809b 100644 --- a/classes/Hook/HookDisplayFooter.php +++ b/classes/Hook/HookDisplayFooter.php @@ -27,6 +27,8 @@ use PrestaShop\Module\Ps_Googleanalytics\Handler\GanalyticsJsHandler; use PrestaShop\Module\Ps_Googleanalytics\Wrapper\ProductWrapper; use Ps_Googleanalytics; +use RecursiveArrayIterator; +use RecursiveIteratorIterator; use Tools; class HookDisplayFooter implements HookInterface @@ -70,6 +72,11 @@ public function run() $gacart['quantity'] = abs($gacart['quantity']); $gaScripts .= 'MBG.removeFromCart(' . json_encode($gacart) . ');'; } + } elseif (is_array($gacart)) { + $it = new RecursiveIteratorIterator(new RecursiveArrayIterator($gacart)); + foreach ($it as $v) { + $gaScripts .= $v; + } } else { $gaScripts .= $gacart; } diff --git a/classes/Hook/HookDisplayHeader.php b/classes/Hook/HookDisplayHeader.php index 96cc3d4..4a85da1 100644 --- a/classes/Hook/HookDisplayHeader.php +++ b/classes/Hook/HookDisplayHeader.php @@ -31,6 +31,10 @@ class HookDisplayHeader implements HookInterface private $module; private $context; private $params; + + /** + * @var bool + */ private $backOffice; public function __construct(Ps_Googleanalytics $module, Context $context) @@ -47,7 +51,7 @@ public function __construct(Ps_Googleanalytics $module, Context $context) public function run() { if (!Configuration::get('GA_ACCOUNT_ID')) { - return; + return ''; } $this->context->controller->addJs($this->module->getPathUri() . 'views/js/GoogleAnalyticActionLib.js'); @@ -73,6 +77,7 @@ public function run() $this->context->smarty->assign( [ 'backOffice' => $this->backOffice, + 'trackBackOffice' => Configuration::get('GA_TRACK_BACKOFFICE_ENABLED'), 'currentShopId' => $currentShopId, 'userId' => $userId, 'gaAccountId' => Tools::safeOutput(Configuration::get('GA_ACCOUNT_ID')), @@ -100,12 +105,18 @@ public function setParams($params) } /** - * setBackOffice - * - * @param array $backOffice + * @param bool $backOffice */ public function setBackOffice($backOffice) { - $this->module->backOffice = $backOffice; + $this->acknowledgeBackOfficeContext($backOffice); + } + + /** + * @param bool $isBackOffice + */ + public function acknowledgeBackOfficeContext($isBackOffice) + { + $this->backOffice = $isBackOffice; } } diff --git a/classes/Hook/HookDisplayHome.php b/classes/Hook/HookDisplayHome.php index c771542..0d0fa7a 100644 --- a/classes/Hook/HookDisplayHome.php +++ b/classes/Hook/HookDisplayHome.php @@ -53,7 +53,7 @@ public function run() $gaScripts = ''; // Home featured products - if ($moduleHandler->isModuleEnabled('ps_featuredproducts')) { + if ($moduleHandler->isModuleEnabledAndHookedOn('ps_featuredproducts', 'displayHome')) { $category = new Category($this->context->shop->getCategory(), $this->context->language->id); $productWrapper = new ProductWrapper($this->context); $homeFeaturedProducts = $productWrapper->wrapProductList( diff --git a/classes/Hook/HookDisplayOrderConfirmation.php b/classes/Hook/HookDisplayOrderConfirmation.php index 4e693c2..d20f6fd 100644 --- a/classes/Hook/HookDisplayOrderConfirmation.php +++ b/classes/Hook/HookDisplayOrderConfirmation.php @@ -66,7 +66,7 @@ public function run() 'id_order' => (int) $order->id, 'id_shop' => (int) $this->context->shop->id, 'sent' => 0, - 'date_add' => 'NOW()', + 'date_add' => ['value' => 'NOW()', 'type' => 'sql'], ] ); diff --git a/config.xml b/config.xml index 158b509..d5d96db 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_googleanalytics - + diff --git a/controllers/front/ajax.php b/controllers/front/ajax.php index 0e3042c..05b5861 100755 --- a/controllers/front/ajax.php +++ b/controllers/front/ajax.php @@ -41,7 +41,7 @@ public function initContent() (new GanalyticsRepository())->updateData( [ 'sent' => 1, - 'date_add' => 'NOW()', + 'date_add' => ['value' => 'NOW()', 'type' => 'sql'], ], 'id_order = ' . $orderId, 1 diff --git a/logo.png b/logo.png index e1a0212..b390f3f 100755 Binary files a/logo.png and b/logo.png differ diff --git a/ps_googleanalytics.php b/ps_googleanalytics.php index 1cbd2fa..3271184 100755 --- a/ps_googleanalytics.php +++ b/ps_googleanalytics.php @@ -51,7 +51,7 @@ public function __construct() { $this->name = 'ps_googleanalytics'; $this->tab = 'analytics_stats'; - $this->version = '4.0.0'; + $this->version = '4.1.0'; $this->ps_versions_compliancy = ['min' => '1.6', 'max' => _PS_VERSION_]; $this->author = 'PrestaShop'; $this->module_key = 'fd2aaefea84ac1bb512e6f1878d990b8'; @@ -66,7 +66,7 @@ public function __construct() } /** - * back office module configuration page content + * Back office module configuration page content */ public function getContent() { @@ -91,7 +91,8 @@ public function hookDisplayHeader($params, $back_office = false) } /** - * To track transactions + * Confirmation page hook. + * This function is run to track transactions. */ public function hookDisplayOrderConfirmation($params) { @@ -102,7 +103,8 @@ public function hookDisplayOrderConfirmation($params) } /** - * hook footer to load JS script for standards actions such as product clicks + * Footer hook. + * This function is run to load JS script for standards actions such as product clicks */ public function hookDisplayFooter() { @@ -112,7 +114,8 @@ public function hookDisplayFooter() } /** - * hook home to display generate the product list associated to home featured, news products and best sellers Modules + * Homepage hook. + * This function is run to manage analytics for product list associated to home featured, news products and best sellers Modules */ public function hookDisplayHome() { @@ -122,7 +125,8 @@ public function hookDisplayHome() } /** - * hook product page footer to load JS for product details view + * Product page footer hook + * This function is run to load JS for product details view */ public function hookDisplayFooterProduct($params) { @@ -133,21 +137,26 @@ public function hookDisplayFooterProduct($params) } /** - * Hook admin order to send transactions and refunds details + * Hook admin order. + * This function is run to send transactions and refunds details */ public function hookDisplayAdminOrder() { $gaTagHandler = new PrestaShop\Module\Ps_Googleanalytics\Handler\GanalyticsJsHandler($this, $this->context); - echo $gaTagHandler->generate( + $output = $gaTagHandler->generate( $this->context->cookie->__get('ga_admin_refund'), true ); $this->context->cookie->__unset('ga_admin_refund'); + $this->context->cookie->write(); + + return $output; } /** - * admin office header to add google analytics js + * Admin office header hook. + * This function is run to add Google Analytics JavaScript */ public function hookDisplayBackOfficeHeader() { @@ -157,7 +166,8 @@ public function hookDisplayBackOfficeHeader() } /** - * Hook admin office header to add google analytics js + * Product cancel action hook (in Back office). + * This function is run to add Google Analytics JavaScript */ public function hookActionProductCancel($params) { @@ -167,7 +177,18 @@ public function hookActionProductCancel($params) } /** - * hook save cart event to implement addtocart and remove from cart functionality + * Hook called after order status change, used to "refund" order after cancelling it + */ + public function hookActionOrderStatusPostUpdate($params) + { + $hook = new PrestaShop\Module\Ps_Googleanalytics\Hooks\HookActionOrderStatusPostUpdate($this, $this->context); + $hook->setParams($params); + $hook->run(); + } + + /** + * Save cart event hook. + * This function is run to implement 'add to cart' and 'remove from cart' functionalities */ public function hookActionCartSave() { @@ -196,9 +217,9 @@ protected function _debugLog($function, $log) } /** - * This method is trigger at the installation of the module - * - install all module tables - * - register hook used by the module. + * This method is triggered at the installation of the module + * - it installs all module tables + * - it registers the hooks used by this module * * @return bool */ @@ -211,12 +232,13 @@ public function install() return parent::install() && $database->registerHooks() && + $database->setDefaultConfiguration() && $database->installTables(); } /** * Triggered at the uninstall of the module - * - erase tables + * - erases this module SQL tables * * @return bool */ diff --git a/upgrade/upgrade-4.1.0.php b/upgrade/upgrade-4.1.0.php new file mode 100644 index 0000000..8be8bf6 --- /dev/null +++ b/upgrade/upgrade-4.1.0.php @@ -0,0 +1,39 @@ + + * @copyright 2007-2020 PrestaShop SA + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + * International Registered Trademark & Property of PrestaShop SA + */ +if (!defined('_PS_VERSION_')) { + exit; +} + +function upgrade_module_4_1_0($object) +{ + if (!$object->registerHook('actionOrderStatusPostUpdate')) { + return false; + } + + Configuration::updateValue('GA_CANCELLED_STATES', json_encode([Configuration::get('PS_OS_CANCELED')])); + + return Db::getInstance()->execute('ALTER TABLE `' . _DB_PREFIX_ . 'ganalytics` ADD `refund_sent` tinyint(1) NULL AFTER `sent`;'); +} diff --git a/views/js/GoogleAnalyticActionLib.js b/views/js/GoogleAnalyticActionLib.js index 9fbd505..8a9f43d 100755 --- a/views/js/GoogleAnalyticActionLib.js +++ b/views/js/GoogleAnalyticActionLib.js @@ -76,22 +76,19 @@ var GoogleAnalyticEnhancedECommerce = { addToCart: function(Product) { this.add(Product); ga('ec:setAction', 'add'); - ga('send', 'event', 'UX', 'click', 'Add to Cart'); // Send data using an event. + ga('send', 'event', 'UX', 'click', 'Add to Cart'); }, removeFromCart: function(Product) { this.add(Product); ga('ec:setAction', 'remove'); - ga('send', 'event', 'UX', 'click', 'Remove From cart'); // Send data using an event. + ga('send', 'event', 'UX', 'click', 'Remove From cart'); }, addProductImpression: function(Product) { - //ga('send', 'pageview'); }, - /** - id, type, affiliation, revenue, tax, shipping and coupon. - **/ + refundByOrderId: function(Order) { /** * Refund an entire transaction. @@ -103,11 +100,6 @@ var GoogleAnalyticEnhancedECommerce = { }, refundByProduct: function(Order) { - /** - * Refund a single product. - **/ - //this.add(Product); - ga('ec:setAction', 'refund', { 'id': Order.id, // Transaction ID is required for partial refund. }); @@ -148,8 +140,6 @@ var GoogleAnalyticEnhancedECommerce = { }, addTransaction: function(Order) { - - //this.add(Product); ga('ec:setAction', 'purchase', Order); ga('send', 'event','Transaction','purchase', { 'hitCallback': function() { diff --git a/views/templates/hook/ga_tag.tpl b/views/templates/hook/ga_tag.tpl index bd665a6..99f306c 100644 --- a/views/templates/hook/ga_tag.tpl +++ b/views/templates/hook/ga_tag.tpl @@ -28,11 +28,3 @@ {/literal} {/if} - -{if ($jsState != 1 && $isBackoffice === true)} - {literal} - - {/literal} -{/if} diff --git a/views/templates/hook/ps_googleanalytics.tpl b/views/templates/hook/ps_googleanalytics.tpl index 59798a6..cf2e4ba 100755 --- a/views/templates/hook/ps_googleanalytics.tpl +++ b/views/templates/hook/ps_googleanalytics.tpl @@ -44,7 +44,7 @@ {if $gaAnonymizeEnabled} ga('set', 'anonymizeIp', true); {/if} - {if $backOffice} + {if $backOffice && !$trackBackOffice} ga('set', 'nonInteraction', true); {else} ga('send', 'pageview');