* @copyright 2022 ECSoft * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) * International Registered Trademark & Property of ECSoft */ require_once dirname(__FILE__) . '/ua/EcsGtmProGtmParams.php'; require_once dirname(__FILE__) . '/ua/EcsGtmProRefund.php'; require_once dirname(__FILE__) . '/../EcsGtmProTools.php'; require_once dirname(__FILE__) . '/EcsGtmProProduct.php'; require_once dirname(__FILE__) . '/EcsGtmProDataLayer.php'; require_once dirname(__FILE__) . '/ga4/EcsGtmProDataLayerItem.php'; /** * @property EcsGtmPro $module */ class EcsGtmProDataLayerManager { public $module; protected $dlGenerated; protected $remarketingGenerated; protected $dataLayer; protected $format; protected $context; protected $idLang; protected $categoryTree; protected $rootCategory; protected $opc; public function __construct($module, $format) { $this->module = $module; $this->format = $format; $this->context = Context::getContext(); $this->dataLayer = new EcsGtmProDataLayer($this); } public function isEE() { return in_array($this->format, array('gau', 'all')); } public function isGA4() { return in_array($this->format, array('ga4', 'all')); } public function toJson() { return $this->dataLayer->toJson(); } public function addProductList($products, $list = null, $maxProducts = null, $type = null) { $i = 1; if (is_array($products) && !empty($products)) { foreach ($products as $p) { if (is_array($list)) { $list['index'] = $i++; } $this->dataLayer->addItem($p, $type, $list); if ($maxProducts && $i > $maxProducts) { break; } } } } public function generateDataLayer() { if (!$this->dlGenerated) { $controller = Tools::getValue('controller'); $this->dataLayer->pageCategory = $controller; if (!$this->module->gtmConfig->userIdConfig->async) { $this->addUserInfo($this->dataLayer); } if ($this->module->isOrderValidationPage()) { $this->orderConfirmation(); } else { if ($this->module->isOPC()) { $controller = 'orderopc'; } if ($this->module->isHomePage()) { $this->homeDataLayer(); } elseif ($this->module->isProductPage()) { $this->productDataLayer(); } elseif ($this->module->isCategoryPage()) { $this->categoryDataLayer(); } elseif ($this->module->isSearchPage()) { $this->searchResultDataLayer(); } elseif ($this->module->isCartPage() || $this->module->isOrderPage() || $this->module->isPaymentPage() || $this->module->isOPC()) { $this->checkoutDataLayer($controller); } } $this->dlGenerated = true; return true; } return false; } protected function checkoutDataLayer($controller) { $step = $this->getCheckoutStep(); $cartProducts = $this->getCartProducts(); $this->addProductList($cartProducts, null, $this->module->gtmConfig->generalConfig->max_category, 'begin_checkout'); $this->dataLayer->setSubtotalsFromCart($this->context->cart); $this->getCartRulesFromCart($this->context->cart); if ($this->isEE()) { $actionField = new stdClass(); $actionField->step = $step; $this->dataLayer->event = "checkout"; if (!isset($this->dataLayer->ecommerce->checkout)) { $this->dataLayer->ecommerce->checkout = new stdClass(); } $this->dataLayer->ecommerce->checkout->actionField = $actionField; } if ($this->isGA4()) { if ($this->module->isCartPage() || ($this->module->is16() && $step == 1)) { $this->dataLayer->event = 'view_cart'; } else { switch ($step) { case 1: $this->dataLayer->event = 'begin_checkout'; Context::getContext()->cookie->begin_checkout_triggered = 1; break; case 2: $this->dataLayer->event = 'checkout_address'; if (empty(Context::getContext()->cookie->begin_checkout_triggered)) { $this->preDataLayer = new self($this->module, $this->module->gtmConfig->generalConfig->format); $this->preDataLayer->addProductList($cartProducts, null, $this->module->gtmConfig->generalConfig->max_category); $this->preDataLayer->event = 'begin_checkout'; } else { unset(Context::getContext()->cookie->begin_checkout_triggered); } break; case 3: $this->dataLayer->event = 'checkout_shipping'; break; case 4: $this->dataLayer->event = 'checkout_payment'; break; default: $this->dataLayer->event = 'checkout_step'.$step; break; } } } } protected function getCheckoutStep() { $step = 0; if ($this->module->is17() || $this->module->is8x()) { $checkoutProcess = $this->context->smarty->getTemplateVars('checkout_process'); if ($checkoutProcess) { $checkoutProcessClass = new ReflectionObject($checkoutProcess); $renderable = $checkoutProcessClass->getProperty('renderable'); $renderable->setAccessible(true); $checkoutProcessAccess = $renderable->getValue($checkoutProcess); $reflectionMethod = new ReflectionMethod('CheckoutProcess', 'getSteps'); $steps = $reflectionMethod->invoke($checkoutProcessAccess); foreach ($steps as $k => $v) { if ($v->isCurrent()) { $step = $k; break; } } } } else { if (!$this->module->isOPC()) { $step = Tools::getValue('controller') == 'payment' ? 4 : Tools::getValue('step', 0); } } $step++; if ($step > 4) { $step = 4; } return $step; } public function getUserPostcodeFromCart() { $cart = $this->context->cart; if (!Validate::isLoadedObject($cart) && Tools::getIsset('id_cart')) { $cart = new Cart((int) Tools::getValue('id_cart')); } $cart_id = $cart->id; // select id_customer from ps_orders where id_cart = $cart_id $sql = 'SELECT id_customer FROM '._DB_PREFIX_.'orders WHERE id_cart = '.(int)$cart_id; $id_customer = Db::getInstance()->getValue($sql); // select postcode from ps_address where id_customer = $id_customer $sql = 'SELECT postcode FROM '._DB_PREFIX_.'address WHERE id_customer = '.(int)$id_customer; $postcode = Db::getInstance()->getValue($sql); return $postcode; } public function getUserCountryFromCart() { $cart = $this->context->cart; if (!Validate::isLoadedObject($cart) && Tools::getIsset('id_cart')) { $cart = new Cart((int) Tools::getValue('id_cart')); } $cart_id = $cart->id; // select id_customer from ps_orders where id_cart = $cart_id $sql = 'SELECT id_customer FROM '._DB_PREFIX_.'orders WHERE id_cart = '.(int)$cart_id; $id_customer = Db::getInstance()->getValue($sql); // select id_country from ps_address where id_customer = $id_customer $sql = 'SELECT id_country FROM '._DB_PREFIX_.'address WHERE id_customer = '.(int)$id_customer; $id_country = Db::getInstance()->getValue($sql); // select name from ps_country where id_country = $id_country $sql = 'SELECT name FROM '._DB_PREFIX_.'country_lang WHERE id_country = '.(int)$id_country . ' AND id_lang = 1'; $country = Db::getInstance()->getValue($sql); return $country; } public function getUserCityFromCart() { $cart = $this->context->cart; if (!Validate::isLoadedObject($cart) && Tools::getIsset('id_cart')) { $cart = new Cart((int) Tools::getValue('id_cart')); } $cart_id = $cart->id; // select id_customer from ps_orders where id_cart = $cart_id $sql = 'SELECT id_customer FROM '._DB_PREFIX_.'orders WHERE id_cart = '.(int)$cart_id; $id_customer = Db::getInstance()->getValue($sql); // select city from ps_address where id_customer = $id_customer $sql = 'SELECT city FROM '._DB_PREFIX_.'address WHERE id_customer = '.(int)$id_customer; $city = Db::getInstance()->getValue($sql); return $city; } public function getUserAddress1FromCart() { $cart = $this->context->cart; if (!Validate::isLoadedObject($cart) && Tools::getIsset('id_cart')) { $cart = new Cart((int) Tools::getValue('id_cart')); } $cart_id = $cart->id; // select id_customer from ps_orders where id_cart = $cart_id $sql = 'SELECT id_customer FROM '._DB_PREFIX_.'orders WHERE id_cart = '.(int)$cart_id; $id_customer = Db::getInstance()->getValue($sql); // select address1 from ps_address where id_customer = $id_customer $sql = 'SELECT address1 FROM '._DB_PREFIX_.'address WHERE id_customer = '.(int)$id_customer; $address1 = Db::getInstance()->getValue($sql); return $address1; } public function getUserLastNameFromCart() { $cart = $this->context->cart; if (!Validate::isLoadedObject($cart) && Tools::getIsset('id_cart')) { $cart = new Cart((int) Tools::getValue('id_cart')); } $cart_id = $cart->id; // select id_customer from ps_orders where id_cart = $cart_id $sql = 'SELECT id_customer FROM '._DB_PREFIX_.'orders WHERE id_cart = '.(int)$cart_id; $id_customer = Db::getInstance()->getValue($sql); // select lastname from ps_address where id_customer = $id_customer $sql = 'SELECT lastname FROM '._DB_PREFIX_.'address WHERE id_customer = '.(int)$id_customer; $lastname = Db::getInstance()->getValue($sql); return $lastname; } public function getUserFirstNameFromCart() { $cart = $this->context->cart; if (!Validate::isLoadedObject($cart) && Tools::getIsset('id_cart')) { $cart = new Cart((int) Tools::getValue('id_cart')); } $cart_id = $cart->id; // select id_customer from ps_orders where id_cart = $cart_id $sql = 'SELECT id_customer FROM '._DB_PREFIX_.'orders WHERE id_cart = '.(int)$cart_id; $id_customer = Db::getInstance()->getValue($sql); // select firstname from ps_address where id_customer = $id_customer $sql = 'SELECT firstname FROM '._DB_PREFIX_.'address WHERE id_customer = '.(int)$id_customer; $firstname = Db::getInstance()->getValue($sql); return $firstname; } public function getUserPhoneFromCart() { $cart = $this->context->cart; if (!Validate::isLoadedObject($cart) && Tools::getIsset('id_cart')) { $cart = new Cart((int) Tools::getValue('id_cart')); } $cart_id = $cart->id; // select id_customer from ps_orders where id_cart = $cart_id $sql = 'SELECT id_customer FROM '._DB_PREFIX_.'orders WHERE id_cart = '.(int)$cart_id; $id_customer = Db::getInstance()->getValue($sql); // select phone from ps_address where id_customer = $id_customer $sql = 'SELECT phone FROM '._DB_PREFIX_.'address WHERE id_customer = '.(int)$id_customer; $phone = Db::getInstance()->getValue($sql); return $phone; } public function getUserEmailFromCart() { $cart = $this->context->cart; if (!Validate::isLoadedObject($cart) && Tools::getIsset('id_cart')) { $cart = new Cart((int) Tools::getValue('id_cart')); } $cart_id = $cart->id; // select id_customer from ps_orders where id_cart = $cart_id $sql = 'SELECT id_customer FROM '._DB_PREFIX_.'orders WHERE id_cart = '.(int)$cart_id; $id_customer = Db::getInstance()->getValue($sql); // select email from ps_address where id_customer = $id_customer $sql = 'SELECT email FROM '._DB_PREFIX_.'customer WHERE id_customer = '.(int)$id_customer; $email = Db::getInstance()->getValue($sql); return $email; } protected function getCartProducts() { $products = array(); $cart = $this->context->cart; if (!Validate::isLoadedObject($cart) && Tools::getIsset('id_cart')) { $cart = new Cart((int) Tools::getValue('id_cart')); } if (Validate::isLoadedObject($cart)) { $products = $cart->getProducts(); } return $products; } public function homeDataLayer() { $this->dataLayer->pageCategory = false; } public function categoryDataLayer() { $list = array( 'name' => $this->getCategoryName((int) Tools::getValue('id_category')), 'id' => 'cat_'.(int) Tools::getValue('id_category') ); $products = $this->getProductsFromSmarty(); $this->addProductList($products, $list, $this->module->gtmConfig->generalConfig->max_category, 'view_item_list'); } public function productDataLayer() { $this->dataLayer->addItem($this->getDisplayedProduct(), 'view_item'); } public function searchResultDataLayer() { $list = array( 'name' => 'Search Results', 'id' => 'search' ); $products = $this->getProductsFromSmarty(); $this->addProductList($products, $list, $this->module->gtmConfig->generalConfig->max_category, 'search_results'); if ($this->module->is17() || $this->module->is8x()) { $term = $this->context->smarty->getTemplateVars('search_string'); } else { $term = $this->context->smarty->getTemplateVars('search_query'); } $this->dataLayer->search_term = EcsGtmProTools::cleanString(htmlspecialchars($term)); $this->dataLayer->event = 'search'; } public function productClick($id_product, $id_product_attribute) { $product = new Product($id_product); $product->id_product_attribute = $id_product_attribute; $dataLayer = new EcsGtmProDataLayer($this, $this->module->gtmConfig->generalConfig->format); $dataLayer->addItem($product, 'view_item'); $dataLayer->event = 'select_item'; return $dataLayer; } public function orderConfirmation($id_cart = null, $force_resend = false) { if (!$id_cart) { $id_cart = $this->getCartFromOrderConfirmation(); } $this->generateOrderConfirmationFromCart($id_cart, $force_resend); } protected function getCartFromOrderConfirmation() { $id_cart = null; if (isset($this->displayOrderConfirmationOrderObj['id_cart'])) { $id_cart = (int) $this->displayOrderConfirmationOrderObj['id_cart']; } if (!$id_cart) { $id_cart = (int)(Tools::getValue('id_cart')); } if (!$id_cart) { $order_id = (int) Tools::getValue('id_order', Tools::getValue('order')); $order = new Order($order_id); if (Validate::isLoadedObject($order)) { $id_cart = $order->id_cart; } } return $id_cart; } public function addUserInfo($dataLayer = null) { if ($dataLayer == null) { $dataLayer = $this->dataLayer; } if ($this->module->gtmConfig->userIdConfig->add_user_id) { if (!empty($this->context->cookie->id_customer)) { $dataLayer->userLogged = 1; $dataLayer->userId = (string) $this->context->cookie->id_customer; $customer = new Customer($dataLayer->userId); if (Validate::isLoadedObject($customer)) { $groups = $customer->getGroups(); $groupsName = array(); foreach ($groups as $id) { $group = new Group($id); if (Validate::isLoadedObject($group)) { $groupsName[] = $group->name[(int)(Configuration::get('PS_LANG_DEFAULT'))]; } } $dataLayer->userGroups = $groupsName; } } elseif ($this->module->gtmConfig->userIdConfig->add_guest_id) { $dataLayer->userLogged = 0; if (!empty($this->context->cookie->id_guest)) { $dataLayer->userId = "guest_".$this->context->cookie->id_guest; } else { if (empty($this->context->cookie->user_rand_id)) { $this->context->cookie->user_rand_id = rand(100000, 999999); } $dataLayer->userId = "guest_".$this->context->cookie->user_rand_id; } } } return $dataLayer; } protected function getProductsFromSmarty() { $products = $this->context->smarty->getTemplateVars('products'); if ($this->module->is17() || $this->module->is8x()) { if (!$products || !is_array($products)) { $listing = $this->context->smarty->getTemplateVars('listing'); if (isset($listing['products'])) { $products = $listing['products']; } } if (!$products || !is_array($products)) { $cart = $this->context->smarty->getTemplateVars('cart'); if (isset($cart['products'])) { $products = $cart['products']; } } } return $products; } public function getCategoryNameHierarchy($category, $level) { if (!$this->rootCategory) { $this->rootCategory = new Category($this->context->shop->id_category); } $depth = $level + (int)$this->rootCategory->level_depth; if ($category->level_depth >= $depth) { return $this->getCategoryName($category, $depth); } return null; } public function generateRemarketingParams() { if (!$this->remarketingGenerated) { $controller = Tools::getValue('controller'); $this->dataLayer->google_tag_params = new EcsGtmProGtmParams(); $pagetype = $controller; if ($this->module->isOrderValidationPage()) { $pagetype = 'purchase'; $this->dataLayer->google_tag_params->ecomm_prodid = $this->remarketingGetProductIdsFromCart(); $this->dataLayer->google_tag_params->ecomm_totalvalue = (float) round($this->getTotalProductPriceFromCart(), _ECSGTMPRO_PRICE_DECIMAL_); $this->dataLayer->google_tag_params->ecomm_totalvalue_tax_exc = (float) round($this->getTotalProductPriceFromCart(false), _ECSGTMPRO_PRICE_DECIMAL_); // ecommerce user $this -> dataLayer -> orderData -> userEmail = $this -> getUserEmailFromCart(); $this -> dataLayer -> orderData -> userPhone = $this -> getUserPhoneFromCart(); $this -> dataLayer -> orderData -> userFirstName = $this -> getUserFirstNameFromCart(); $this -> dataLayer -> orderData -> userLastName = $this -> getUserLastNameFromCart(); // address1 $this -> dataLayer -> orderData -> userAddress1 = $this -> getUserAddress1FromCart(); // city $this -> dataLayer -> orderData -> userCity = $this -> getUserCityFromCart(); $this -> dataLayer -> orderData -> userRegion = ''; // country $this -> dataLayer -> orderData -> userCountry = $this -> getUserCountryFromCart(); // postcode $this -> dataLayer -> orderData -> userPostcode = $this -> getUserPostcodeFromCart(); } else { if ($this->module->isOPC()) { $controller = 'orderopc'; } if ($this->module->isHomePage()) { $pagetype = 'home'; } elseif ($this->module->isProductPage()) { $product = $this->getDisplayedProduct(); if (Validate::isLoadedObject($product)) { $this->dataLayer->google_tag_params->ecomm_prodid = EcsGtmProProduct::getInstance()->remarketingGetProductIdentifier($product); $this->dataLayer->google_tag_params->ecomm_totalvalue = (float) round((float) Product::getPriceStatic($product->id, true, $product->id_product_attribute), _ECSGTMPRO_PRICE_DECIMAL_); $this->dataLayer->google_tag_params->ecomm_totalvalue_tax_exc = (float) round((float) Product::getPriceStatic($product->id, false, $product->id_product_attribute), _ECSGTMPRO_PRICE_DECIMAL_); $this->dataLayer->google_tag_params->ecomm_category = $this->getCategoryName($product->id_category_default); } } elseif ($this->module->isPaymentPage() || $this->module->isCartPage() || $this->module->isOPC()) { $pagetype = 'cart'; $this->dataLayer->google_tag_params->ecomm_prodid = $this->remarketingGetProductIdsFromCart(); $this->dataLayer->google_tag_params->ecomm_totalvalue = (float) round($this->getTotalProductPriceFromCart(), _ECSGTMPRO_PRICE_DECIMAL_); $this->dataLayer->google_tag_params->ecomm_totalvalue_tax_exc = (float) round($this->getTotalProductPriceFromCart(false), _ECSGTMPRO_PRICE_DECIMAL_); } elseif ($this->module->isCategoryPage()) { $this->dataLayer->google_tag_params->ecomm_category = $this->getCategoryName((int) Tools::getValue('id_category')); } elseif ($this->module->isSearchPage()) { $pagetype = 'searchresults'; } else { $pagetype = 'other'; } } $this->dataLayer->google_tag_params->ecomm_pagetype = $pagetype; $this->dataLayer->google_tag_params->removeNull(); $this->remarketingGenerated = true; } } public function getCartActionDataLayer($idProduct, $ipa, $action, $qty) { if (!in_array($action, array('add', 'remove'))) { return null; } $product = new Product($idProduct); $product->id_product_attribute = $ipa; $product->quantity = (int)$qty; $dataLayer = new self($this->module, $this->module->gtmConfig->generalConfig->format); $dataLayer->dataLayer->addItem($product, ($action == 'add') ? 'add_to_cart' : 'remove_from_cart'); return $dataLayer; } protected function getTotalProductPriceFromCart($includeTax = true, $mod = 'total') { $productTotal = 0.0; $priceField = 'price'; if ($includeTax) { $priceField = 'price_wt'; } $products = $this->getCartProducts(); if (is_array($products) && count($products)) { foreach ($products as $p) { if (isset($p[$priceField])) { $qty = 1; if ($mod == 'total' && !empty($p['quantity'])) { $qty = $p['quantity']; } $productTotal += $qty * ((float) $p[$priceField]); } } } return $productTotal; } protected function remarketingGetProductIdsFromCart() { $productsList = array(); $products = $this->getCartProducts(); if (is_array($products) && count($products)) { foreach ($products as $product) { $productID = EcsGtmProProduct::getInstance()->remarketingGetProductIdentifier($product); if ($productID) { $productsList[] = $productID; } } } return $productsList; } public function getCategoryName($category, $level = null) { $categoryName = ''; $categoryId = null; $id_lang = $this->getDataLanguage(); if (!Validate::isLoadedObject($category)) { $categoryId = (int) $category; if ($categoryId) { $category = new Category($categoryId); } } if (!Validate::isLoadedObject($category)) { return 'category not found ('.$categoryId.')'; } if (!is_null($level)) { while ($category->level_depth > $level) { $category = new Category($category->id_parent); } } switch ($this->module->gtmConfig->dataConfig->category_name) { case 'link_rewrite': $categoryName = !empty($category->link_rewrite) ? $category->link_rewrite : null; break; case 'id': $categoryName = $category->id; break; default: if (Validate::isLoadedObject($category)) { if ($this->categoryTree && is_null($level)) { $c_parents = $category->getParentsCategories($id_lang); $c_parents_names = array(); foreach ($c_parents as $c_parent) { if (!$c_parent['is_root_category']) { $c_parents_names[] = $this->cleanString($c_parent['name']); } } $c_parents_names = array_reverse($c_parents_names); $categoryName = implode('/', $c_parents_names); } else { $categoryName = EcsGtmProTools::cleanString($category->getName()); } } break; } $categoryName = trim($categoryName); if (empty($categoryName)) { $categoryName = 'Unknown category name'; } return $categoryName; } public function getDataLanguage() { return $this->idLang ? $this->idLang : (int) $this->context->language->id; } public function generateGtmTag() { if (!$this->module->isAllowed()) { return ''; } $dataLayerJs = $this->toJson(); $preDataLayerJs = null; if (is_object($this->module->preDataLayer)) { $preDataLayerJs = $this->module->preDataLayer->toJson(); } else { if (isset($this->context->cookie->preDataLayerJs)) { $preDataLayerJs = Context::getContext()->cookie->preDataLayerJs; unset($this->context->cookie->preDataLayerJs); } } if ($this->module->isDebugMode()) { if ($preDataLayerJs) { $this->module->addDebug($preDataLayerJs); } $this->module->addDebug($dataLayerJs); if ($this->module->gtmConfig->userIdConfig->async) { $this->module->addDebug('