├── Api ├── Cart │ └── ContentInterface.php ├── InitiateCheckoutInterface.php ├── Order │ └── ContentInterface.php ├── Product │ └── ContentInterface.php ├── PurchaseInterface.php └── ViewProductContentInterface.php ├── Block ├── AbstractPixel.php ├── Adminhtml │ └── System │ │ └── Config │ │ └── Form │ │ ├── EventList.php │ │ ├── Info.php │ │ ├── InfoConversionApi.php │ │ ├── InfoPlan.php │ │ └── ProtectCustomerData.php ├── Pixel.php └── Pixel │ ├── InitiateCheckout.php │ ├── Other.php │ ├── Purchase.php │ └── ViewProductContent.php ├── LICENSE.txt ├── Model ├── AbstractPixel.php ├── Config.php ├── Config │ └── Source │ │ └── ProductAttribute.php └── Pixel │ ├── Cart │ └── Content.php │ ├── InitiateCheckout.php │ ├── Order │ └── Content.php │ ├── Product │ └── Content.php │ ├── Purchase.php │ └── ViewProductContent.php ├── README.md ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ └── system.xml ├── config.xml ├── csp_whitelist.xml ├── di.xml └── module.xml ├── registration.php └── view ├── adminhtml └── templates │ └── system │ └── config │ └── event │ └── list.phtml └── frontend ├── layout ├── catalog_product_view.xml ├── checkout_index_index.xml ├── checkout_onepage_success.xml ├── default.xml └── redsys_checkout_success.xml └── templates └── pixel.phtml /Api/Cart/ContentInterface.php: -------------------------------------------------------------------------------- 1 | config = $config; 51 | $this->json = $json; 52 | $this->mfSecureRenderer = $mfSecureRenderer ?: \Magento\Framework\App\ObjectManager::getInstance() 53 | ->get(SecureHtmlRendererInterface::class); 54 | parent::__construct($context, $data); 55 | } 56 | 57 | /** 58 | * Get FB Pixel params 59 | * 60 | * @return array 61 | */ 62 | abstract protected function getParameters(): array; 63 | 64 | /** 65 | * Get event name 66 | * 67 | * @return string 68 | */ 69 | abstract protected function getEventName(): string; 70 | 71 | /** 72 | * Init FB pixel 73 | * 74 | * @return string 75 | */ 76 | protected function _toHtml(): string 77 | { 78 | if ($this->config->isEnabled() && $this->config->getFbPixelId()) { 79 | $parameters = $this->getParameters(); 80 | $eventName = $this->getEventName(); 81 | if ($parameters && $eventName) { 82 | $script = ' 83 | fbq("' . $this->getTrackMethod() . '", ' 84 | . $this->json->serialize($eventName) . ', ' 85 | . $this->json->serialize($parameters) . ', ' 86 | . '{ "eventID": "' . $eventName . '" + "." + Math.floor(Math.random() * 1000000) + "." + Date.now() }' 87 | . '); 88 | '; 89 | 90 | return $this->mfSecureRenderer->renderTag('script', ['style' => 'display:none'], $script, false); 91 | } 92 | } 93 | 94 | return ''; 95 | } 96 | 97 | /** 98 | * @return string 99 | */ 100 | protected function getTrackMethod(): string 101 | { 102 | return "track"; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/EventList.php: -------------------------------------------------------------------------------- 1 | moduleManager = $moduleManager; 38 | parent::__construct($context, $data); 39 | } 40 | 41 | /** 42 | * Set template to itself 43 | * 44 | * @return $this 45 | */ 46 | protected function _prepareLayout(): EventList 47 | { 48 | parent::_prepareLayout(); 49 | if (!$this->getTemplate()) { 50 | $this->setTemplate(static::EVENT_LIST_TEMPLATE); 51 | } 52 | return $this; 53 | } 54 | 55 | /** 56 | * Render event list 57 | * 58 | * @param AbstractElement $element 59 | * @return string 60 | */ 61 | public function render(AbstractElement $element): string 62 | { 63 | // Remove scope label 64 | $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); 65 | return parent::render($element); 66 | } 67 | 68 | /** 69 | * Get the event list and scripts contents 70 | * 71 | * @param AbstractElement $element 72 | * @return string 73 | */ 74 | protected function _getElementHtml(AbstractElement $element): string 75 | { 76 | return $this->_toHtml(); 77 | } 78 | 79 | /** 80 | * Retrieve true if FB Pixel Plus is enabled 81 | * 82 | * @return bool 83 | */ 84 | public function isPlusEnabled() 85 | { 86 | return (bool)$this->moduleManager->isEnabled('Magefan_FacebookPixelPlus'); 87 | } 88 | 89 | /** 90 | * Retrieve true if FB Pixel Extra is enabled 91 | * 92 | * @return bool 93 | */ 94 | public function isExtraEnabled() 95 | { 96 | return (bool)$this->moduleManager->isEnabled('Magefan_FacebookPixelExtra'); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/Info.php: -------------------------------------------------------------------------------- 1 | Extra plans only.'; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/InfoPlan.php: -------------------------------------------------------------------------------- 1 | getModuleVersion->execute($this->getModuleName() . $this->getMinPlan())) { 37 | return ''; 38 | } 39 | 40 | $html = ''; 41 | $html .= '
'; 42 | $html .= $this->getText() . ' Read more.'; 43 | $html .= '
'; 44 | 45 | $script = ' 46 | require(["jquery", "Magento_Ui/js/modal/alert", "domReady!"], function($, alert){ 47 | setInterval(function(){ 48 | var $plusSection = $("#' . $this->getSectionId() . '-state").parent(".section-config"); 49 | $plusSection.find(".use-default").css("visibility", "hidden"); 50 | $plusSection.find("input,select").each(function(){ 51 | $(this).attr("readonly", "readonly"); 52 | $(this).removeAttr("disabled"); 53 | if ($(this).data("fpdisabled")) return; 54 | $(this).data("fpdisabled", 1); 55 | $(this).click(function(){ 56 | alert({ 57 | title: "You cannot change this option.", 58 | content: "' . 59 | ( 60 | ($this->getMinPlan() == 'Extra') 61 | ? 'This option is available in Extra plan only.' 62 | : 'This option is available in Plus or Extra plans only.' 63 | ) 64 | . '", 65 | buttons: [{ 66 | text: "Upgrade Plan Now", 67 | class: "action primary accept", 68 | click: function () { 69 | window.open("https://magefan.com/magento-2-google-tag-manager/pricing?utm_source=gtm_config&utm_medium=link&utm_campaign=regular"); 70 | } 71 | }] 72 | }); 73 | }); 74 | }); 75 | }, 1000); 76 | }); 77 | '; 78 | 79 | $html .= $this->mfSecureRenderer->renderTag('script', [], $script, false); 80 | 81 | return $html; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/ProtectCustomerData.php: -------------------------------------------------------------------------------- 1 | getUrl('*/*/*/section/web'); 25 | $comment = 'When enabled, data won\'t be sent to Facebook, until the customer provides consent.'; /*

26 | Note, that this option will work only when Cookie Restriction Mode at 27 | Stores > Configuration > General > Web > Default Cookie Settings 28 | is enabled.'; */ 29 | 30 | $element->setComment($comment); 31 | return parent::render($element); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Block/Pixel.php: -------------------------------------------------------------------------------- 1 | config = $config; 35 | parent::__construct($context, $data); 36 | } 37 | 38 | /** 39 | * Get FB pixel ID 40 | * 41 | * @return string 42 | */ 43 | public function getFbPixelId(): string 44 | { 45 | return $this->config->getFbPixelId(); 46 | } 47 | 48 | /** 49 | * Check if protect customer data is enabled 50 | * 51 | * @return bool 52 | */ 53 | public function isProtectCustomerDataEnabled(): bool 54 | { 55 | return $this->config->isProtectCustomerDataEnabled(); 56 | } 57 | 58 | /** 59 | * Retrieve true if speed optimization is enabled 60 | * 61 | * @return bool 62 | */ 63 | public function isSpeedOptimizationEnabled(): bool 64 | { 65 | return (bool)$this->config->isSpeedOptimizationEnabled(); 66 | } 67 | 68 | /** 69 | * Get current website ID 70 | * 71 | * @return int 72 | * @throws NoSuchEntityException 73 | */ 74 | public function getWebsiteId(): int 75 | { 76 | return (int)$this->_storeManager->getStore()->getWebsiteId(); 77 | } 78 | 79 | /** 80 | * Init FB pixel 81 | * 82 | * @return string 83 | */ 84 | protected function _toHtml(): string 85 | { 86 | if ($this->config->isEnabled()) { 87 | return parent::_toHtml(); 88 | } 89 | 90 | return ''; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Block/Pixel/InitiateCheckout.php: -------------------------------------------------------------------------------- 1 | checkoutSession = $checkoutSession; 53 | $this->initiateCheckout = $initiateCheckout; 54 | parent::__construct($context, $config, $json, $data); 55 | } 56 | 57 | /** 58 | * Get FB Pixel params 59 | * 60 | * @return array 61 | * @throws NoSuchEntityException 62 | * @throws LocalizedException 63 | */ 64 | protected function getParameters(): array 65 | { 66 | $quote = $this->checkoutSession->getQuote(); 67 | return $this->initiateCheckout->get($quote); 68 | } 69 | 70 | /** 71 | * Get event name 72 | * 73 | * @return string 74 | */ 75 | protected function getEventName(): string 76 | { 77 | return self::INITIATE_CHECKOUT; 78 | } 79 | 80 | /** 81 | * @inheritDoc 82 | */ 83 | protected function _toHtml(): string 84 | { 85 | $html = parent::_toHtml(); 86 | 87 | $script = ' 88 | window.mfFbPixelCheckout = ' . json_encode($this->getParameters()) . '; 89 | '; 90 | 91 | $html .= $this->mfSecureRenderer->renderTag('script', [], $script, false); 92 | 93 | return $html; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Block/Pixel/Other.php: -------------------------------------------------------------------------------- 1 | checkoutSession = $checkoutSession; 52 | $this->purchase = $purchase; 53 | parent::__construct($context, $config, $json, $data); 54 | } 55 | 56 | /** 57 | * Get FB Pixel params 58 | * 59 | * @return array 60 | * @throws NoSuchEntityException 61 | */ 62 | protected function getParameters(): array 63 | { 64 | $order = $this->checkoutSession->getLastRealOrder(); 65 | return $this->purchase->get($order); 66 | } 67 | 68 | /** 69 | * Get event name 70 | * 71 | * @return string 72 | */ 73 | protected function getEventName(): string 74 | { 75 | return self::PURCHASE; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Block/Pixel/ViewProductContent.php: -------------------------------------------------------------------------------- 1 | registry = $registry; 53 | $this->viewProductContent = $viewProductContent; 54 | parent::__construct($context, $config, $json, $data); 55 | } 56 | 57 | /** 58 | * Get FB Pixel params 59 | * 60 | * @return array 61 | * @throws NoSuchEntityException 62 | */ 63 | protected function getParameters(): array 64 | { 65 | return $this->viewProductContent->get($this->getCurrentProduct()); 66 | } 67 | 68 | /** 69 | * Get event name 70 | * 71 | * @return string 72 | */ 73 | protected function getEventName(): string 74 | { 75 | return self::VIEW_CONTENT; 76 | } 77 | 78 | /** 79 | * Get current product 80 | * 81 | * @return Product 82 | */ 83 | private function getCurrentProduct(): Product 84 | { 85 | return $this->registry->registry('current_product'); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Please visit Magefan.com for license details (https://magefan.com/end-user-license-agreement) -------------------------------------------------------------------------------- /Model/AbstractPixel.php: -------------------------------------------------------------------------------- 1 | config = $config; 63 | $this->storeManager = $storeManager; 64 | $this->categoryRepository = $categoryRepository; 65 | $this->request = $request ?: ObjectManager::getInstance()->get( 66 | RequestInterface::class 67 | ); 68 | $this->registry = $registry ?: ObjectManager::getInstance()->get( 69 | Registry::class 70 | ); 71 | } 72 | 73 | /** 74 | * Get category names 75 | * 76 | * @param Product $product 77 | * @return array 78 | * @throws NoSuchEntityException 79 | */ 80 | protected function getCategoryNames(Product $product): array 81 | { 82 | $result = []; 83 | 84 | if (!$this->config->getCategoriesAttribute()) { 85 | return $result; 86 | } 87 | 88 | if ($productCategory = $this->getCategoryByProduct($product)) { 89 | $categoryIds = $productCategory->getPathIds(); 90 | foreach ($categoryIds as $categoryId) { 91 | $category = $this->categoryRepository->get($categoryId, $this->storeManager->getStore()->getId()); 92 | if ($category->getLevel() < 2) { 93 | continue; 94 | } 95 | 96 | $result[] = $category->getName(); 97 | } 98 | } 99 | 100 | return $result; 101 | } 102 | 103 | /** 104 | * Get product category 105 | * 106 | * @param Product $product 107 | * @return CategoryInterface|null 108 | * @throws NoSuchEntityException 109 | */ 110 | private function getCategoryByProduct(Product $product): ?CategoryInterface 111 | { 112 | if ('catalog_category_product' == $this->request->getFullActionName()) { 113 | if ($category = $this->registry->registry('current_category')) { 114 | return $category; 115 | } 116 | } 117 | 118 | $productCategory = null; 119 | $categoryIds = $product->getCategoryIds(); 120 | 121 | if ($categoryIds) { 122 | $level = -1; 123 | $store = $this->storeManager->getStore(); 124 | $rootCategoryId = $store->getRootCategoryId(); 125 | 126 | foreach ($categoryIds as $categoryId) { 127 | try { 128 | $category = $this->categoryRepository->get($categoryId, $store->getId()); 129 | if ($category->getIsActive() 130 | && $category->getLevel() > $level 131 | && in_array($rootCategoryId, $category->getPathIds()) 132 | ) { 133 | $level = $category->getLevel(); 134 | $productCategory = $category; 135 | } 136 | } catch (Exception $e) { // phpcs:ignore 137 | /* Do nothing */ 138 | } 139 | } 140 | } 141 | 142 | return $productCategory; 143 | } 144 | 145 | /** 146 | * Get current currency code 147 | * 148 | * @return string 149 | * @throws NoSuchEntityException 150 | */ 151 | protected function getCurrentCurrencyCode(): string 152 | { 153 | return $this->storeManager->getStore()->getCurrentCurrencyCode(); 154 | } 155 | 156 | /** 157 | * Format price 158 | * 159 | * @param float $price 160 | * @return float 161 | */ 162 | protected function formatPrice(float $price): float 163 | { 164 | return (float)number_format($price, 2, '.', ''); 165 | } 166 | 167 | /** 168 | * Get product price 169 | * 170 | * @param Product $product 171 | * @return float 172 | */ 173 | protected function getPrice(Product $product): float 174 | { 175 | $priceInfo = $product->getPriceInfo()->getPrice('final_price')->getAmount(); 176 | $price = $priceInfo->getValue(); 177 | return $this->formatPrice($price); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Model/Config.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 53 | } 54 | 55 | /** 56 | * Retrieve true if module is enabled 57 | * 58 | * @param string|null $storeId 59 | * @return bool 60 | */ 61 | public function isEnabled(?string $storeId = null): bool 62 | { 63 | return (bool)$this->getConfig(self::XML_PATH_EXTENSION_ENABLED, $storeId); 64 | } 65 | 66 | /** 67 | * Retrieve GTM account ID 68 | * 69 | * @param string|null $storeId 70 | * @return string 71 | */ 72 | public function getFbPixelId(?string $storeId = null): string 73 | { 74 | return trim((string)$this->getConfig(self::XML_PATH_FB_PIXEL_ID, $storeId)); 75 | } 76 | 77 | /** 78 | * Retrieve Magento product attribute 79 | * 80 | * @param string|null $storeId 81 | * @return string 82 | */ 83 | public function getProductAttribute(?string $storeId = null): string 84 | { 85 | return trim((string)$this->getConfig(self::XML_PATH_ATTRIBUTES_PRODUCT, $storeId)); 86 | } 87 | 88 | /* 89 | * Retrieve Magento product categories 90 | * 91 | * @param string|null $storeId 92 | * @return string 93 | */ 94 | public function getCategoriesAttribute(?string $storeId = null): string 95 | { 96 | return trim((string)$this->getConfig(self::XML_PATH_ATTRIBUTES_CATEGORIES, $storeId)); 97 | } 98 | 99 | /** 100 | * Retrieve true if speed optimization is enabled 101 | * 102 | * @param string|null $storeId 103 | * @return bool 104 | */ 105 | public function isSpeedOptimizationEnabled(?string $storeId = null): bool 106 | { 107 | return (bool)$this->getConfig(self::XML_PATH_SPEED_OPTIMIZATION_ENABLED, $storeId); 108 | } 109 | 110 | /** 111 | * Retrieve true if protect customer data is enabled 112 | * 113 | * @param string|null $storeId 114 | * @return bool 115 | */ 116 | public function isProtectCustomerDataEnabled(?string $storeId = null): bool 117 | { 118 | return (bool)$this->getConfig(self::XML_PATH_PROTECT_CUSTOMER_DATA, $storeId); 119 | } 120 | 121 | /** 122 | * Retrieve true if cookie restriction mode enabled 123 | * 124 | * @param string|null $storeId 125 | * @return bool 126 | */ 127 | public function isCookieRestrictionModeEnabled(?string $storeId = null): bool 128 | { 129 | return (bool)$this->getConfig(Custom::XML_PATH_WEB_COOKIE_RESTRICTION, $storeId); 130 | } 131 | 132 | /** 133 | * Retrieve store config value 134 | * 135 | * @param string $path 136 | * @param string|null $storeId 137 | * @return mixed 138 | */ 139 | public function getConfig(string $path, ?string $storeId = null) 140 | { 141 | return $this->scopeConfig->getValue($path, ScopeInterface::SCOPE_STORE, $storeId); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Model/Config/Source/ProductAttribute.php: -------------------------------------------------------------------------------- 1 | attributeCollectionFactory = $attributeCollectionFactory; 35 | } 36 | 37 | /** 38 | * Return array of options as value-label pairs 39 | * 40 | * @return array Format: array(array('value' => '', 'label' => '