├── .github ├── FUNDING.yml └── workflows │ ├── extdn-integration-tests-post-install.sh │ ├── extdn-integration-tests-pre-install.sh │ ├── extdn-integration-tests.yml │ ├── extdn-phpstan-pre-install.sh │ ├── extdn-phpstan.yml │ └── extdn-unit-tests.yml ├── .gitignore ├── .module.ini ├── ARCHITECTURE.md ├── Api ├── CheckoutSessionDataProviderInterface.php ├── CustomerSessionDataProviderInterface.php └── Data │ ├── EventInterface.php │ ├── MergeTagInterface.php │ ├── ProcessorInterface.php │ ├── ProductTagInterface.php │ └── TagInterface.php ├── CHANGELOG.md ├── Config ├── Backend │ └── ContainerId.php ├── Config.php ├── Frontend │ ├── ContainerId.php │ └── Funding.php ├── Source │ ├── CategoryAttributes.php │ ├── CustomerAttributes.php │ ├── OrderStateOptions.php │ ├── ProductAttributes.php │ ├── ProductListValue.php │ └── ViewCartOccurancesOptions.php ├── XmlConfig.php └── XmlConfig │ ├── CacheType.php │ ├── Converter.php │ ├── Reader.php │ └── SchemaLocator.php ├── CustomerData └── GtmCheckout.php ├── DataLayer ├── Event │ ├── AddPaymentInfo.php │ ├── AddShippingInfo.php │ ├── AddToCart.php │ ├── AddToWishlist.php │ ├── BeginCheckout.php │ ├── Login.php │ ├── Logout.php │ ├── Promotion │ │ └── PromotionItem.php │ ├── Purchase.php │ ├── Refund.php │ ├── RemoveFromCart.php │ ├── SelectPromotion.php │ ├── SignUp.php │ ├── ViewCart.php │ ├── ViewPromotion.php │ └── ViewSearchResult.php ├── Mapper │ ├── CartItemDataMapper.php │ ├── CategoryDataMapper.php │ ├── CustomerDataMapper.php │ ├── GuestDataMapper.php │ ├── OrderDataMapper.php │ ├── OrderItemDataMapper.php │ └── ProductDataMapper.php ├── Processor │ ├── Base.php │ ├── Cart.php │ ├── Category.php │ ├── Checkout.php │ ├── Product.php │ └── SuccessPage.php ├── Tag │ ├── AccountId.php │ ├── Cart │ │ ├── CartItems.php │ │ └── CartValue.php │ ├── Category │ │ ├── CategorySize.php │ │ ├── CurrentCategory.php │ │ └── Products.php │ ├── CurrencyCode.php │ ├── EnhancedConversions.php │ ├── EnhancedConversions │ │ └── Sha256EmailAddress.php │ ├── Event.php │ ├── LiveOnly.php │ ├── Order │ │ ├── Order.php │ │ └── OrderItems.php │ ├── Page │ │ ├── Breadcrumbs.php │ │ └── VirtualPage.php │ ├── PagePath.php │ ├── PageTitle.php │ ├── PageType.php │ ├── Product │ │ ├── CurrentCategoryName.php │ │ ├── CurrentPrice.php │ │ ├── CurrentProduct.php │ │ ├── ProductCategory.php │ │ └── ProductTagInterface.php │ └── Version.php └── TagParser.php ├── Exception ├── BlockNotFound.php ├── EmptyProductDataMapperException.php ├── InvalidConfig.php └── NotUsingSetProductSkusException.php ├── FAQ.md ├── INSTALL.md ├── LICENSE.txt ├── Logger └── Debugger.php ├── Observer ├── AddAdditionalLayoutHandles.php ├── TriggerAddToCartDataLayerEvent.php ├── TriggerAddToWishlistDataLayerEvent.php ├── TriggerLoginDataLayerEvent.php ├── TriggerLogoutDataLayerEvent.php ├── TriggerPurchaseDataLayerEvent.php ├── TriggerRemoveFromCartDataLayerEvent.php └── TriggerSignUpDataLayerEvent.php ├── PWA.md ├── Plugin ├── AddCspInlineScripts.php ├── AddDataToCartSection.php ├── AddDataToCustomerSection.php ├── AddProductDetails.php ├── GetProductsFromCategoryBlockPlugin.php ├── TriggerAddGuestPaymentInfoDataLayerEvent.php ├── TriggerAddPaymentInfoDataLayerEvent.php ├── TriggerAddShippingInfoDataLayerEvent.php └── TriggerViewSearchResultDataLayerEvent.php ├── README.md ├── SessionDataProvider ├── CheckoutSessionDataProvider.php └── CustomerSessionDataProvider.php ├── TESTING.md ├── TUTORIAL.md ├── Test ├── Functional │ ├── BaseTest.php │ └── Util │ │ └── CategoryProviderTest.php ├── Integration │ ├── Block │ │ ├── DataLayerTest.php │ │ └── ScriptTest.php │ ├── DataLayer │ │ ├── Event │ │ │ ├── AddToCartTest.php │ │ │ ├── PurchaseTest.php │ │ │ └── ViewCartTest.php │ │ ├── Mapper │ │ │ ├── CategoryDataMapperTest.php │ │ │ └── ProductDataMapperTest.php │ │ ├── Tag │ │ │ └── VersionTest.php │ │ └── TagParserTest.php │ ├── FixtureTrait │ │ ├── CreateCustomer.php │ │ ├── GetCategory.php │ │ ├── GetCustomer.php │ │ ├── GetOrder.php │ │ ├── GetProduct.php │ │ └── Reindex.php │ ├── ModuleTest.php │ ├── Observer │ │ ├── TriggerLoginDataLayerEventTest.php │ │ ├── TriggerLogoutDataLayerEventTest.php │ │ └── TriggerPurchaseDataLayerEventTest.php │ ├── Page │ │ ├── AddToWishlistTest.php │ │ ├── CategoryPageTest.php │ │ ├── CheckoutOnepageSuccessTest.php │ │ ├── LoginTest.php │ │ ├── LogoutTest.php │ │ └── ProductPageTest.php │ ├── PageTestCase.php │ ├── Plugin │ │ ├── AddDataToCartSectionTest.php │ │ ├── AddDataToCustomerSectionTest.php │ │ ├── GetProductsFromCategoryBlockPluginTest.php │ │ └── TriggerAddShippingInfoDataLayerEventTest.php │ ├── SessionDataProvider │ │ └── CustomerSessionDataProviderTest.php │ ├── Stub │ │ └── FulltextStub.php │ └── Util │ │ └── ProductDataMapperTest.php └── Unit │ ├── Config │ └── ConfigTest.php │ └── Util │ └── CamelCaseTest.php ├── USAGE.md ├── Util ├── Attribute │ └── GetAttributeValue.php ├── CamelCase.php ├── CategoryProvider.php ├── Debug.php ├── GetCategoryPath.php ├── GetCurrentCategory.php ├── GetCurrentCategoryProducts.php ├── GetCurrentProduct.php ├── OrderTotals.php ├── PriceFormatter.php └── ProductProvider.php ├── ViewModel ├── Commons.php └── DataLayer.php ├── composer.json ├── docs └── gtm-example.json ├── etc ├── acl.xml ├── adminhtml │ └── system.xml ├── cache.xml ├── config.xml ├── csp_whitelist.xml ├── data_layer.xml ├── data_layer.xsd ├── di.xml ├── frontend │ ├── di.xml │ ├── events.xml │ └── sections.xml ├── module.xml ├── view.xml └── webapi_rest │ └── di.xml ├── package.json ├── phpstan.neon ├── phpunit.xml ├── registration.php └── view ├── adminhtml ├── requirejs-config.js ├── templates │ └── funding.phtml └── web │ └── js │ └── validation-mixin.js └── frontend ├── layout ├── breeze_layout.xml ├── catalog_category_view.xml ├── catalog_product_view.xml ├── checkout_cart_index.xml ├── checkout_index_index.xml ├── checkout_onepage_success.xml ├── default.xml └── hyva_default.xml ├── requirejs-config.js ├── templates ├── hyva │ ├── data-layer.phtml │ ├── script-additions.phtml │ ├── script-logger.phtml │ ├── script-product-clicks.phtml │ └── script-pusher.phtml ├── iframe.phtml ├── luma │ ├── data-layer.phtml │ ├── script-additions.phtml │ ├── script-begin-checkout.phtml │ └── script-product-clicks.phtml ├── product │ └── details.phtml └── script.phtml └── web └── js ├── checkout └── payment-validator │ ├── register.js │ └── reload-customer-data.js ├── generic.js ├── logger.js ├── mixins ├── catalog-add-to-cart-mixin.js ├── minicart-mixin.js ├── shipping-save-processor-mixin.js └── step-navigator-mixin.js ├── product └── clicks.js └── push.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: yireo 2 | custom: ["https://www.paypal.me/yireo"] 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/extdn-integration-tests-post-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | bin/magento setup:config:set --cache-backend=redis --cache-backend-redis-server=redis --cache-backend-redis-db=0 -n -------------------------------------------------------------------------------- /.github/workflows/extdn-integration-tests-pre-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | composer config minimum-stability dev 3 | composer config prefer-stable false 4 | 5 | composer require --dev yireo/magento2-integration-test-helper --no-update 6 | 7 | composer require yireo/magento2-replace-bundled:^4.0 --no-update 8 | composer require yireo/magento2-replace-inventory:^4.0 --no-update 9 | composer require yireo/magento2-replace-pagebuilder:^4.0 --no-update 10 | -------------------------------------------------------------------------------- /.github/workflows/extdn-integration-tests.yml: -------------------------------------------------------------------------------- 1 | name: ExtDN Integration Tests 2 | on: [push] 3 | 4 | jobs: 5 | integration-tests: 6 | name: Magento 2 Integration Tests 7 | runs-on: ubuntu-latest 8 | services: 9 | mysql: 10 | image: mysql:8.0 11 | env: 12 | MYSQL_ROOT_PASSWORD: root 13 | ports: 14 | - 3306:3306 15 | options: --tmpfs /tmp:rw --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" 16 | es: 17 | image: docker.io/wardenenv/elasticsearch:7.8 18 | ports: 19 | - 9200:9200 20 | env: 21 | 'discovery.type': single-node 22 | 'xpack.security.enabled': false 23 | ES_JAVA_OPTS: "-Xms64m -Xmx512m" 24 | options: --health-cmd="curl localhost:9200/_cluster/health?wait_for_status=yellow&timeout=60s" --health-interval=10s --health-timeout=5s --health-retries=3 25 | redis: 26 | image: redis 27 | ports: 28 | - 6379:6379 29 | options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 30 | env: 31 | MAGENTO_MARKETPLACE_USERNAME: ${{ secrets.MAGENTO_MARKETPLACE_USERNAME }} 32 | MAGENTO_MARKETPLACE_PASSWORD: ${{ secrets.MAGENTO_MARKETPLACE_PASSWORD }} 33 | MODULE_NAME: ${{ secrets.MODULE_NAME }} 34 | COMPOSER_NAME: ${{ secrets.COMPOSER_NAME }} 35 | steps: 36 | - uses: actions/checkout@v4 37 | - name: Cache Composer dependencies 38 | uses: actions/cache@v4 39 | with: 40 | path: /tmp/composer-cache 41 | key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} 42 | 43 | - uses: extdn/github-actions-m2/magento-integration-tests/8.3@master 44 | env: 45 | MAGENTO_VERSION: '2.4.7-p3' 46 | COMPOSER_VERSION: 2 47 | with: 48 | magento_pre_install_script: .github/workflows/extdn-integration-tests-pre-install.sh 49 | magento_post_install_script: .github/workflows/extdn-integration-tests-post-install.sh 50 | -------------------------------------------------------------------------------- /.github/workflows/extdn-phpstan-pre-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | composer config minimum-stability dev 3 | composer config prefer-stable false 4 | 5 | composer require yireo/magento2-integration-test-helper --no-update 6 | 7 | composer config --no-plugins allow-plugins true 8 | 9 | composer require --dev phpstan/extension-installer:^1.0 --no-update 10 | composer require --dev bitexpert/phpstan-magento:^0.32 --no-update 11 | 12 | composer require yireo/magento2-replace-bundled:^4.0 --no-update 13 | composer require yireo/magento2-replace-inventory:^4.0 --no-update 14 | composer require yireo/magento2-replace-pagebuilder:^4.0 --no-update 15 | -------------------------------------------------------------------------------- /.github/workflows/extdn-phpstan.yml: -------------------------------------------------------------------------------- 1 | name: ExtDN PHPStan 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | phpstan: 6 | name: PHPStan 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | 11 | - name: "Determine composer cache directory" 12 | id: "determine-composer-cache-directory" 13 | run: "echo \"::set-output name=directory::$(composer config cache-dir)\"" 14 | 15 | - name: Cache Composer dependencies 16 | uses: actions/cache@v4 17 | with: 18 | path: "${{ steps.determine-composer-cache-directory.outputs.directory }}" 19 | key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} 20 | 21 | - uses: extdn/github-actions-m2/magento-phpstan/8.3@master 22 | with: 23 | composer_name: yireo/magento2-googletagmanager2 24 | composer_version: 2 25 | phpstan_level: 2 26 | magento_pre_install_script: .github/workflows/extdn-phpstan-pre-install.sh 27 | -------------------------------------------------------------------------------- /.github/workflows/extdn-unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: ExtDN Unit Tests 2 | on: [push] 3 | 4 | jobs: 5 | unit-tests: 6 | name: Magento 2 Unit Tests 7 | runs-on: ubuntu-latest 8 | env: 9 | MAGENTO_MARKETPLACE_USERNAME: ${{ secrets.MAGENTO_MARKETPLACE_USERNAME }} 10 | MAGENTO_MARKETPLACE_PASSWORD: ${{ secrets.MAGENTO_MARKETPLACE_PASSWORD }} 11 | MODULE_NAME: ${{ secrets.MODULE_NAME }} 12 | COMPOSER_NAME: ${{ secrets.COMPOSER_NAME }} 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Cache Composer dependencies 17 | uses: actions/cache@v4 18 | with: 19 | path: /tmp/composer-cache 20 | key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} 21 | 22 | - uses: extdn/github-actions-m2/magento-unit-tests/8.3@master 23 | env: 24 | MAGENTO_VERSION: '2.4.7-p3' 25 | COMPOSER_VERSION: 2 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | phpunit-log/ 3 | .idea/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.module.ini: -------------------------------------------------------------------------------- 1 | EXTENSION_VENDOR="Yireo" 2 | EXTENSION_NAME="GoogleTagManager2" 3 | COMPOSER_NAME="yireo/magento2-googletagmanager2" 4 | PHP_VERSIONS=("7.4", "8.1", "8.2", "8.3","8.4") 5 | PHPSTAN_LEVEL=2 6 | -------------------------------------------------------------------------------- /Api/CheckoutSessionDataProviderInterface.php: -------------------------------------------------------------------------------- 1 | validate()) { 14 | throw new InvalidConfig('Invalid container ID "' . $this->getValue() . '". It should start with "GTM-"'); 15 | } 16 | 17 | return parent::beforeSave(); 18 | } 19 | 20 | private function validate(): bool 21 | { 22 | if ($this->_appState->getMode() === State::MODE_DEVELOPER) { 23 | return true; 24 | } 25 | 26 | if (empty($this->getValue())) { 27 | return true; 28 | } 29 | 30 | if (preg_match('/^GTM-/', $this->getValue())) { 31 | return true; 32 | } 33 | 34 | return false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Config/Frontend/ContainerId.php: -------------------------------------------------------------------------------- 1 | toHtml(); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Config/Source/CategoryAttributes.php: -------------------------------------------------------------------------------- 1 | categoryAttributeRepository = $categoryAttributeRepository; 22 | $this->searchCriteriaBuilder = $searchCriteriaBuilder; 23 | $this->sortOrderFactory = $sortOrderFactory; 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function toOptionArray(): array 30 | { 31 | $options = [['value' => '', 'label' => '']]; 32 | 33 | $this->searchCriteriaBuilder->addFilter('is_visible', 1); 34 | $sortOrder = $this->sortOrderFactory->create(['field' => 'attribute_code', 'direction' => 'asc']); 35 | $this->searchCriteriaBuilder->addSortOrder($sortOrder); 36 | $searchCriteria = $this->searchCriteriaBuilder->create(); 37 | 38 | $searchResult = $this->categoryAttributeRepository->getList($searchCriteria); 39 | foreach ($searchResult->getItems() as $categoryAttribute) { 40 | $options[] = [ 41 | 'value' => $categoryAttribute->getAttributeCode(), 42 | 'label' => $categoryAttribute->getAttributeCode() . ': '.$categoryAttribute->getDefaultFrontendLabel() 43 | ]; 44 | } 45 | 46 | return $options; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Config/Source/CustomerAttributes.php: -------------------------------------------------------------------------------- 1 | attributeRepository = $attributeRepository; 26 | $this->searchCriteriaBuilder = $searchCriteriaBuilder; 27 | $this->sortOrderFactory = $sortOrderFactory; 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function toOptionArray(): array 34 | { 35 | $options = [['value' => '', 'label' => '']]; 36 | 37 | $sortOrder = $this->sortOrderFactory->create(['field' => 'attribute_code', 'direction' => 'asc']); 38 | $this->searchCriteriaBuilder->addSortOrder($sortOrder); 39 | $searchCriteria = $this->searchCriteriaBuilder->create(); 40 | 41 | $searchResult = $this->attributeRepository->getList('customer', $searchCriteria); 42 | foreach ($searchResult->getItems() as $customerAttribute) { 43 | /** @var Attribute $customerAttribute */ 44 | if (false === $this->isAttributeDisplayedInFrontend($customerAttribute)) { 45 | continue; 46 | } 47 | 48 | 49 | $options[] = [ 50 | 'value' => $customerAttribute->getAttributeCode(), 51 | 'label' => $customerAttribute->getAttributeCode() . ': ' . $customerAttribute->getDefaultFrontendLabel() 52 | ]; 53 | } 54 | 55 | return $options; 56 | } 57 | 58 | private function isAttributeDisplayedInFrontend(Attribute $attribute): bool 59 | { 60 | $forms = $attribute->getUsedInForms(); 61 | foreach ($forms as $form) { 62 | if (preg_match('/^customer_/', $form)) { 63 | return true; 64 | } 65 | } 66 | 67 | return false; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Config/Source/OrderStateOptions.php: -------------------------------------------------------------------------------- 1 | getStates() as $state) { 15 | $options[] = ['value' => $state, 'label' => $state]; 16 | } 17 | 18 | return $options; 19 | } 20 | 21 | private function getStates(): array 22 | { 23 | return [ 24 | Order::STATE_NEW, 25 | Order::STATE_PAYMENT_REVIEW, 26 | Order::STATE_PENDING_PAYMENT, 27 | Order::STATE_PROCESSING, 28 | Order::STATE_COMPLETE, 29 | Order::STATE_CANCELED, 30 | Order::STATE_CLOSED, 31 | Order::STATE_HOLDED, 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Config/Source/ProductAttributes.php: -------------------------------------------------------------------------------- 1 | productAttributeRepository = $productAttributeRepository; 24 | $this->searchCriteriaBuilder = $searchCriteriaBuilder; 25 | $this->sortOrderFactory = $sortOrderFactory; 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function toOptionArray(): array 32 | { 33 | $options = [['value' => '', 'label' => '']]; 34 | 35 | $this->searchCriteriaBuilder->addFilter('is_visible', 1); 36 | $this->searchCriteriaBuilder->addFilter('is_visible_on_front', 1); 37 | $sortOrder = $this->sortOrderFactory->create(['field' => 'attribute_code', 'direction' => 'asc']); 38 | $this->searchCriteriaBuilder->addSortOrder($sortOrder); 39 | $searchCriteria = $this->searchCriteriaBuilder->create(); 40 | 41 | $searchResult = $this->productAttributeRepository->getList($searchCriteria); 42 | foreach ($searchResult->getItems() as $productAttribute) { 43 | if (in_array($productAttribute->getAttributeCode(), self::REQUIRED_ATTRIBUTES)) { 44 | continue; 45 | } 46 | 47 | $options[] = [ 48 | 'value' => $productAttribute->getAttributeCode(), 49 | 'label' => $productAttribute->getAttributeCode() . ': ' . $productAttribute->getDefaultFrontendLabel(), 50 | 'disabled' => true 51 | ]; 52 | } 53 | 54 | return $options; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Config/Source/ProductListValue.php: -------------------------------------------------------------------------------- 1 | self::PRODUCT_FIRST_CATEGORY, 20 | 'label' => __('Product First Category'), 21 | ], 22 | [ 23 | 'value' => self::CURRENT_CATEGORY, 24 | 'label' => __('Current Category'), 25 | ], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Config/Source/ViewCartOccurancesOptions.php: -------------------------------------------------------------------------------- 1 | self::CART_PAGE_ONLY, 'label' => __('Cart-page only')], 19 | ['value' => self::EVERYWHERE, 'label' => __('Everywhere (including minicart)')], 20 | ]; 21 | 22 | return $options; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Config/XmlConfig.php: -------------------------------------------------------------------------------- 1 | dataStorage = $dataStorage; 20 | } 21 | 22 | public function getDefault(): array 23 | { 24 | return $this->dataStorage->get('default'); 25 | } 26 | 27 | public function getEvents(): array 28 | { 29 | return $this->dataStorage->get('events'); 30 | } 31 | 32 | public function getEvent(string $eventName): array 33 | { 34 | $events = $this->getEvents(); 35 | foreach ($events as $eventId => $eventData) { 36 | if ($eventName === $eventId) { 37 | return $eventData; 38 | } 39 | } 40 | 41 | return []; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Config/XmlConfig/CacheType.php: -------------------------------------------------------------------------------- 1 | get(self::TYPE_IDENTIFIER), self::CACHE_TAG); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Config/XmlConfig/Reader.php: -------------------------------------------------------------------------------- 1 | 'name']; 10 | } 11 | -------------------------------------------------------------------------------- /Config/XmlConfig/SchemaLocator.php: -------------------------------------------------------------------------------- 1 | moduleReader = $moduleReader; 22 | } 23 | 24 | /** 25 | * @inheritdoc 26 | */ 27 | public function getSchema() 28 | { 29 | return $this->getXsdPath(); 30 | } 31 | 32 | /** 33 | * @inheritdoc 34 | */ 35 | public function getPerFileSchema() 36 | { 37 | return $this->getXsdPath(); 38 | } 39 | 40 | private function getXsdPath(): string 41 | { 42 | return $this->moduleReader->getModuleDir( 43 | Dir::MODULE_ETC_DIR, 44 | 'Yireo_GoogleTagManager2') 45 | . '/' . 'data_layer.xsd'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /CustomerData/GtmCheckout.php: -------------------------------------------------------------------------------- 1 | checkoutSessionDataProvider = $checkoutSessionDataProvider; 19 | } 20 | 21 | /** 22 | * @return array 23 | */ 24 | public function getSectionData(): array 25 | { 26 | $gtmEvents = $this->checkoutSessionDataProvider->get(); 27 | $this->checkoutSessionDataProvider->clear(); 28 | return ['gtm_events' => $gtmEvents]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DataLayer/Event/AddPaymentInfo.php: -------------------------------------------------------------------------------- 1 | cartItems = $cartItems; 29 | $this->cartRepository = $cartRepository; 30 | $this->priceFormatter = $priceFormatter; 31 | } 32 | 33 | /** 34 | * @return string[] 35 | */ 36 | public function get(): array 37 | { 38 | /** @var Cart $cart */ 39 | $cart = $this->cartRepository->get($this->cartId); 40 | return [ 41 | 'event' => 'add_payment_info', 42 | 'ecommerce' => [ 43 | 'currency' => $cart->getQuoteCurrencyCode(), 44 | 'value' => $this->priceFormatter->format((float)$cart->getSubtotal()), 45 | 'coupon' => $cart->getCouponCode(), 46 | 'payment_type' => $this->paymentMethod, 47 | 'items' => $this->cartItems->get() 48 | ] 49 | ]; 50 | } 51 | 52 | /** 53 | * @param string $paymentMethod 54 | * @return AddPaymentInfo 55 | */ 56 | public function setPaymentMethod(string $paymentMethod): AddPaymentInfo 57 | { 58 | $this->paymentMethod = $paymentMethod; 59 | return $this; 60 | } 61 | 62 | /** 63 | * @param int $cartId 64 | * @return AddPaymentInfo 65 | */ 66 | public function setCartId(int $cartId): AddPaymentInfo 67 | { 68 | $this->cartId = $cartId; 69 | return $this; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /DataLayer/Event/AddToCart.php: -------------------------------------------------------------------------------- 1 | productDataMapper = $productDataMapper; 31 | $this->currencyCode = $currencyCode; 32 | $this->priceFormatter = $priceFormatter; 33 | } 34 | 35 | /** 36 | * @return string[] 37 | * @throws LocalizedException 38 | * @throws NoSuchEntityException 39 | */ 40 | public function get(): array 41 | { 42 | $qty = ($this->qty > 0) ? $this->qty : 1; 43 | 44 | $itemData = $this->productDataMapper->mapByProduct($this->product); 45 | $itemData['quantity'] = $qty; 46 | $value = $itemData['price'] * $qty; 47 | 48 | return [ 49 | 'event' => 'add_to_cart', 50 | 'ecommerce' => [ 51 | 'currency' => $this->currencyCode->get(), 52 | 'value' => $this->priceFormatter->format((float)$value), 53 | 'items' => [$itemData] 54 | ] 55 | ]; 56 | } 57 | 58 | /** 59 | * @param Product $product 60 | * @return AddToCart 61 | */ 62 | public function setProduct(Product $product): AddToCart 63 | { 64 | $this->product = $product; 65 | return $this; 66 | } 67 | 68 | public function setQty(int $qty): AddToCart 69 | { 70 | $this->qty = $qty; 71 | return $this; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /DataLayer/Event/AddToWishlist.php: -------------------------------------------------------------------------------- 1 | productDataMapper = $productDataMapper; 23 | } 24 | 25 | /** 26 | * @return string[] 27 | * @throws LocalizedException 28 | * @throws NoSuchEntityException 29 | */ 30 | public function get(): array 31 | { 32 | $itemData = $this->productDataMapper->mapByProduct($this->product); 33 | 34 | return [ 35 | 'event' => 'add_to_wishlist', 36 | 'ecommerce' => [ 37 | 'items' => [$itemData] 38 | ] 39 | ]; 40 | } 41 | 42 | /** 43 | * @param ProductInterface $product 44 | * @return AddToWishlist 45 | */ 46 | public function setProduct(ProductInterface $product): AddToWishlist 47 | { 48 | $this->product = $product; 49 | return $this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /DataLayer/Event/BeginCheckout.php: -------------------------------------------------------------------------------- 1 | quote = $quote; 31 | $this->cartItems = $cartItems; 32 | $this->cartValue = $cartValue; 33 | $this->currencyCode = $currencyCode; 34 | } 35 | 36 | public function get(): array 37 | { 38 | return [ 39 | 'event' => 'begin_checkout', 40 | 'ecommerce' => [ 41 | 'currency' => $this->currencyCode->get(), 42 | 'value' => $this->cartValue->get(), 43 | 'coupon' => $this->quote->getCouponCode(), 44 | 'items' => $this->cartItems->get() 45 | ] 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DataLayer/Event/Login.php: -------------------------------------------------------------------------------- 1 | customerDataMapper = $customerDataMapper; 19 | } 20 | 21 | public function setCustomer(CustomerInterface $customer): Login 22 | { 23 | $this->customer = $customer; 24 | return $this; 25 | } 26 | 27 | public function get(): array 28 | { 29 | return [ 30 | 'event' => 'login', 31 | 'customer' => $this->customerDataMapper->mapByCustomer($this->customer) 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DataLayer/Event/Logout.php: -------------------------------------------------------------------------------- 1 | 'logout' 13 | ]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DataLayer/Event/Promotion/PromotionItem.php: -------------------------------------------------------------------------------- 1 | id = $id; 28 | $this->name = $name; 29 | $this->createName = $createName; 30 | $this->createSlot = $createSlot; 31 | $this->locationId = $locationId; 32 | } 33 | 34 | public function get(): array 35 | { 36 | return [ 37 | 'promotion_id' => $this->id, 38 | 'promotion_name' => $this->name, 39 | 'creative_name' => $this->createName, 40 | 'creative_slot' => $this->createSlot, 41 | 'location_id' => $this->locationId, 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DataLayer/Event/Refund.php: -------------------------------------------------------------------------------- 1 | orderItems = $orderItems; 28 | $this->config = $config; 29 | $this->priceFormatter = $priceFormatter; 30 | $this->orderTotals = $orderTotals; 31 | } 32 | 33 | /** 34 | * @return string[] 35 | */ 36 | public function get(): array 37 | { 38 | $order = $this->order; 39 | return [ 40 | 'event' => 'refund', 41 | 'ecommerce' => [ 42 | 'transaction_id' => $order->getIncrementId(), 43 | 'affiliation' => $this->config->getStoreName(), 44 | 'currency' => $order->getOrderCurrencyCode(), 45 | 'value' => $this->priceFormatter->format($this->orderTotals->getValueTotal($order)), 46 | 'tax' => $this->priceFormatter->format((float)$order->getTaxAmount()), 47 | 'shipping' => $this->priceFormatter->format($this->orderTotals->getShippingTotal($order)), 48 | 'coupon' => $order->getCouponCode(), 49 | 'payment_method' => $order->getPayment() ? $order->getPayment()->getMethod() : '', 50 | 'items' => $this->orderItems->setOrder($order)->get(), 51 | ] 52 | ]; 53 | } 54 | 55 | /** 56 | * @param OrderInterface $order 57 | * @return Refund 58 | */ 59 | public function setOrder(OrderInterface $order): Refund 60 | { 61 | $this->order = $order; 62 | return $this; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /DataLayer/Event/RemoveFromCart.php: -------------------------------------------------------------------------------- 1 | cartItemDataMapper = $cartItemDataMapper; 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function get(): array 26 | { 27 | $cartItemData = $this->cartItemDataMapper->mapByCartItem($this->cartItem); 28 | return [ 29 | 'event' => 'remove_from_cart', 30 | 'ecommerce' => [ 31 | 'items' => [$cartItemData] 32 | ] 33 | ]; 34 | } 35 | 36 | /** 37 | * @param CartItemInterface $cartItem 38 | * @return RemoveFromCart 39 | */ 40 | public function setCartItem(CartItemInterface $cartItem): RemoveFromCart 41 | { 42 | $this->cartItem = $cartItem; 43 | return $this; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DataLayer/Event/SelectPromotion.php: -------------------------------------------------------------------------------- 1 | promotionItems as $promotionItem) { 23 | $promotionsItemsData[] = $promotionItem->get(); 24 | } 25 | 26 | return [ 27 | 'event' => 'select_promotion', 28 | 'ecommerce' => [ 29 | 'items' => $promotionsItemsData 30 | ] 31 | ]; 32 | } 33 | 34 | /** 35 | * @param PromotionItem[] $promotionItems 36 | * @return SelectPromotion 37 | */ 38 | public function setPromotionItems(array $promotionItems): SelectPromotion 39 | { 40 | $this->promotionItems = $promotionItems; 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DataLayer/Event/SignUp.php: -------------------------------------------------------------------------------- 1 | 'sign_up', 13 | 'method' => 'Standard' // @TODO: implement mapping based on the route used? 14 | ]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DataLayer/Event/ViewPromotion.php: -------------------------------------------------------------------------------- 1 | promotionItems as $promotionItem) { 23 | $promotionsItemsData[] = $promotionItem->get(); 24 | } 25 | 26 | return [ 27 | 'event' => 'view_promotion', 28 | 'ecommerce' => [ 29 | 'items' => $promotionsItemsData 30 | ] 31 | ]; 32 | } 33 | 34 | /** 35 | * @param PromotionItem[] $promotionItems 36 | * @return ViewPromotion 37 | */ 38 | public function setPromotionItems(array $promotionItems): ViewPromotion 39 | { 40 | $this->promotionItems = $promotionItems; 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DataLayer/Event/ViewSearchResult.php: -------------------------------------------------------------------------------- 1 | 'view_search_result', 15 | 'search_term' => $this->searchTerm, 16 | ]; 17 | } 18 | 19 | public function setSearchTerm(?string $searchTerm): void 20 | { 21 | $this->searchTerm = $searchTerm; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DataLayer/Mapper/CategoryDataMapper.php: -------------------------------------------------------------------------------- 1 | config = $config; 24 | $this->getAttributeValue = $getAttributeValue; 25 | } 26 | 27 | /** 28 | * @param CategoryInterface $category 29 | * @return array 30 | * @throws LocalizedException 31 | */ 32 | public function mapByCategory(CategoryInterface $category): array 33 | { 34 | $prefix = 'category_'; 35 | $categoryData = []; 36 | $categoryFields = $this->getCategoryFields(); 37 | foreach ($categoryFields as $categoryAttributeCode) { 38 | $dataLayerKey = $prefix . $categoryAttributeCode; 39 | $attributeValue = $this->getAttributeValue->getCategoryAttributeValue($category, $categoryAttributeCode); 40 | if (empty($attributeValue)) { 41 | continue; 42 | } 43 | 44 | $categoryData[$dataLayerKey] = $attributeValue; 45 | } 46 | 47 | return $categoryData; 48 | } 49 | 50 | /** 51 | * @return string[] 52 | */ 53 | private function getCategoryFields(): array 54 | { 55 | return array_filter(array_merge(['id', 'name'], $this->config->getCategoryEavAttributeCodes())); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DataLayer/Mapper/CustomerDataMapper.php: -------------------------------------------------------------------------------- 1 | camelCase = $camelCase; 27 | $this->config = $config; 28 | $this->getAttributeValue = $getAttributeValue; 29 | } 30 | 31 | /** 32 | * @param CustomerInterface $customer 33 | * @param string $prefix 34 | * @return array 35 | */ 36 | public function mapByCustomer(CustomerInterface $customer, string $prefix = ''): array 37 | { 38 | $customerData = []; 39 | $customerFields = $this->getCustomerFields(); 40 | foreach ($customerFields as $customerAttributeCode) { 41 | $dataLayerKey = lcfirst($prefix . $this->camelCase->to($customerAttributeCode)); 42 | $attributeValue = $this->getAttributeValue->getCustomerAttributeValue($customer, $customerAttributeCode); 43 | 44 | if (empty($attributeValue)) { 45 | continue; 46 | } 47 | 48 | $customerData[$dataLayerKey] = $attributeValue; 49 | } 50 | 51 | return $customerData; 52 | } 53 | 54 | /** 55 | * @return string[] 56 | */ 57 | private function getCustomerFields(): array 58 | { 59 | return array_filter(array_merge(['id'], $this->config->getCustomerEavAttributeCodes())); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DataLayer/Mapper/GuestDataMapper.php: -------------------------------------------------------------------------------- 1 | config = $config; 24 | $this->getAttributeValue = $getAttributeValue; 25 | } 26 | 27 | /** 28 | * @param Order $order 29 | * @return array 30 | * @throws LocalizedException 31 | */ 32 | public function mapByOrder(Order $order): array 33 | { 34 | $prefix = 'customer_'; 35 | $guestData = []; 36 | $guestFields = $this->getGuestFields(); 37 | foreach ($guestFields as $guestAttributeCode) { 38 | $guestAttributeCode = $prefix . $guestAttributeCode; 39 | $dataLayerKey = $prefix . $guestAttributeCode; 40 | $attributeValue = $this->getAttributeValue->getAttributeValue($order, 'order', $guestAttributeCode); 41 | if (empty($attributeValue)) { 42 | continue; 43 | } 44 | 45 | $guestData[$dataLayerKey] = $attributeValue; 46 | } 47 | 48 | return $guestData; 49 | } 50 | 51 | /** 52 | * @return string[] 53 | */ 54 | private function getGuestFields(): array 55 | { 56 | return array_filter(array_merge(['id'], $this->config->getCustomerEavAttributeCodes())); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /DataLayer/Processor/Base.php: -------------------------------------------------------------------------------- 1 | xmlConfig = $xmlConfig; 16 | } 17 | 18 | public function process(array $data): array 19 | { 20 | $default = $this->xmlConfig->getDefault(); 21 | return array_merge($data, $default); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DataLayer/Processor/Cart.php: -------------------------------------------------------------------------------- 1 | config = $config; 19 | } 20 | 21 | public function get(): string 22 | { 23 | return $this->config->getId(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DataLayer/Tag/Cart/CartItems.php: -------------------------------------------------------------------------------- 1 | cartModel = $cartModel; 31 | $this->cartItemDataMapper = $cartItemDataMapper; 32 | $this->productProvider = $productProvider; 33 | } 34 | 35 | /** 36 | * @return array 37 | * @throws NoSuchEntityException 38 | */ 39 | public function get(): array 40 | { 41 | $cartItems = $this->cartModel->getQuote()->getAllVisibleItems(); 42 | if (!$cartItems) { 43 | return []; 44 | } 45 | 46 | $this->productProvider->addProductSkus($this->getSkusFromCartItems($cartItems)); 47 | $cartItemsData = []; 48 | 49 | foreach ($cartItems as $cartItem) { 50 | $cartItemData = $this->cartItemDataMapper->mapByCartItem($cartItem); 51 | $cartItemsData[] = $cartItemData; 52 | } 53 | 54 | return $cartItemsData; 55 | } 56 | 57 | /** 58 | * @param Item[] $cartItems 59 | * @return array 60 | */ 61 | private function getSkusFromCartItems(array $cartItems): array 62 | { 63 | $productSkus = []; 64 | foreach ($cartItems as $cartItem) { 65 | $productSkus[] = $cartItem->getSku(); 66 | } 67 | 68 | return array_unique($productSkus); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /DataLayer/Tag/Cart/CartValue.php: -------------------------------------------------------------------------------- 1 | cartModel = $cartModel; 23 | $this->priceFormatter = $priceFormatter; 24 | } 25 | 26 | /** 27 | * @return float 28 | */ 29 | public function get(): float 30 | { 31 | return $this->priceFormatter->format((float)$this->cartModel->getQuote()->getSubtotal()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DataLayer/Tag/Category/CategorySize.php: -------------------------------------------------------------------------------- 1 | size = $size; 18 | } 19 | 20 | /** 21 | * @return int 22 | */ 23 | public function get(): int 24 | { 25 | return $this->size; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DataLayer/Tag/Category/CurrentCategory.php: -------------------------------------------------------------------------------- 1 | getCurrentCategory = $getCurrentCategory; 24 | $this->categoryDataMapper = $categoryDataMapper; 25 | } 26 | 27 | /** 28 | * @return string[] 29 | * @throws NoSuchEntityException 30 | */ 31 | public function merge(): array 32 | { 33 | $currentCategory = $this->getCurrentCategory->get(); 34 | return $this->categoryDataMapper->mapByCategory($currentCategory); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DataLayer/Tag/Category/Products.php: -------------------------------------------------------------------------------- 1 | getCurrentCategoryProducts = $getCurrentCategoryProducts; 32 | $this->getCurrentCategory = $getCurrentCategory; 33 | $this->productDataMapper = $productDataMapper; 34 | $this->config = $config; 35 | } 36 | 37 | /** 38 | * @return array 39 | * @throws NoSuchEntityException 40 | */ 41 | public function get(): array 42 | { 43 | $productsData = []; 44 | $i = 1; 45 | foreach ($this->getCurrentCategoryProducts->getProducts() as $product) { 46 | if ($this->config->getMaximumCategoryProducts() > 0 && $i > $this->config->getMaximumCategoryProducts()) { 47 | break; 48 | } 49 | 50 | $product->setCategory($this->getCurrentCategory->get()); 51 | $productData = $this->productDataMapper->mapByProduct($product); 52 | $productData['quantity'] = 1; 53 | $productData['index'] = $i; 54 | $productsData[] = $productData; 55 | $i++; 56 | } 57 | 58 | return $productsData; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /DataLayer/Tag/CurrencyCode.php: -------------------------------------------------------------------------------- 1 | storeManager = $storeManager; 21 | $this->logger = $logger; 22 | } 23 | 24 | public function get(): string 25 | { 26 | try { 27 | $store = $this->storeManager->getStore(); 28 | /** @var Store $store */ 29 | return $store->getCurrentCurrencyCode() ?: ''; 30 | } catch (NoSuchEntityException $e) { 31 | $this->logger->warning('Cannot retrieve currency code for current store. ' . $e->getMessage()); 32 | return ''; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DataLayer/Tag/EnhancedConversions/Sha256EmailAddress.php: -------------------------------------------------------------------------------- 1 | checkoutSession = $checkoutSession; 24 | $this->orderRepository = $orderRepository; 25 | } 26 | 27 | public function get(): string 28 | { 29 | $order = $this->getOrder(); 30 | return hash('sha256', trim(strtolower($order->getCustomerEmail()))); 31 | } 32 | 33 | /** 34 | * @return OrderInterface 35 | */ 36 | private function getOrder(): OrderInterface 37 | { 38 | return $this->orderRepository->get($this->checkoutSession->getLastRealOrder()->getId()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DataLayer/Tag/Event.php: -------------------------------------------------------------------------------- 1 | state = $state; 18 | } 19 | 20 | public function get(): bool 21 | { 22 | return $this->state->getMode() === State::MODE_PRODUCTION; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DataLayer/Tag/Order/Order.php: -------------------------------------------------------------------------------- 1 | checkoutSession = $checkoutSession; 34 | $this->orderRepository = $orderRepository; 35 | $this->config = $config; 36 | $this->priceFormatter = $priceFormatter; 37 | $this->orderTotals = $orderTotals; 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | public function merge(): array 44 | { 45 | $order = $this->getOrder(); 46 | return [ 47 | 'currency' => (string)$order->getOrderCurrencyCode(), 48 | 'value' => $this->priceFormatter->format($this->orderTotals->getValueTotal($order)), 49 | 'tax' => $this->priceFormatter->format((float)$order->getTaxAmount()), 50 | 'shipping' => $this->priceFormatter->format($this->orderTotals->getShippingTotal($order)), 51 | 'affiliation' => $this->config->getStoreName(), 52 | 'transaction_id' => $order->getIncrementId(), 53 | 'coupon' => $order->getCouponCode(), 54 | 'payment_method' => $order->getPayment() ? $order->getPayment()->getMethod() : '' 55 | ]; 56 | } 57 | 58 | /** 59 | * @return OrderInterface 60 | */ 61 | private function getOrder(): OrderInterface 62 | { 63 | return $this->orderRepository->get($this->checkoutSession->getLastRealOrder()->getId()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /DataLayer/Tag/Order/OrderItems.php: -------------------------------------------------------------------------------- 1 | checkoutSession = $checkoutSession; 26 | $this->orderItemDataMapper = $orderItemDataMapper; 27 | } 28 | 29 | /** 30 | * @return array 31 | */ 32 | public function get(): array 33 | { 34 | $order = $this->order; 35 | if (empty($order)) { 36 | $order = $this->checkoutSession->getLastRealOrder(); 37 | } 38 | 39 | /** @var Order $order */ 40 | $orderItemsData = []; 41 | foreach ($order->getAllVisibleItems() as $orderItem) { 42 | $orderItemsData[] = $this->orderItemDataMapper->mapByOrderItem($orderItem); 43 | } 44 | 45 | return $orderItemsData; 46 | } 47 | 48 | /** 49 | * @param OrderInterface $order 50 | * @return OrderItems 51 | */ 52 | public function setOrder(OrderInterface $order): OrderItems 53 | { 54 | $this->order = $order; 55 | return $this; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DataLayer/Tag/Page/Breadcrumbs.php: -------------------------------------------------------------------------------- 1 | catalogHelper = $catalogHelper; 16 | } 17 | 18 | public function get(): array 19 | { 20 | $data = []; 21 | $breadcrumbs = $this->catalogHelper->getBreadcrumbPath(); 22 | foreach ($breadcrumbs as $breadcrumb) { 23 | if (is_array($breadcrumb) && isset($breadcrumb['label'])) { 24 | $data[] = $breadcrumb['label']; 25 | } 26 | } 27 | 28 | return $data; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DataLayer/Tag/Page/VirtualPage.php: -------------------------------------------------------------------------------- 1 | storeManager = $storeManager; 17 | } 18 | 19 | public function get(): string 20 | { 21 | /** @var Store $store */ 22 | $store = $this->storeManager->getStore(); 23 | $url = $store->getCurrentUrl(); 24 | $urlData = parse_url($url); 25 | return isset($urlData['path']) ? rtrim($urlData['path'], '/') : ''; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DataLayer/Tag/PagePath.php: -------------------------------------------------------------------------------- 1 | url = $url; 16 | } 17 | 18 | public function get(): string 19 | { 20 | return $this->url->getCurrentUrl(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DataLayer/Tag/PageTitle.php: -------------------------------------------------------------------------------- 1 | pageTitle = $pageTitle; 16 | } 17 | 18 | public function get(): string 19 | { 20 | return $this->pageTitle->get(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DataLayer/Tag/PageType.php: -------------------------------------------------------------------------------- 1 | request = $request; 16 | } 17 | 18 | public function get(): string 19 | { 20 | $moduleName = $this->request->getModuleName(); 21 | $controllerName = $this->request->getControllerName(); 22 | $actionName = $this->request->getActionName(); 23 | return $moduleName . '/' . $controllerName . '/' . $actionName; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DataLayer/Tag/Product/CurrentCategoryName.php: -------------------------------------------------------------------------------- 1 | getCurrentProduct = $getCurrentProduct; 23 | $this->productCategory = $productCategory; 24 | } 25 | 26 | /** 27 | * @return string 28 | * @throws NoSuchEntityException 29 | */ 30 | public function get(): string 31 | { 32 | $currentProduct = $this->getCurrentProduct->get(); 33 | return $this->productCategory->setProduct($currentProduct)->get(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DataLayer/Tag/Product/CurrentPrice.php: -------------------------------------------------------------------------------- 1 | getCurrentProduct = $getCurrentProduct; 26 | $this->priceFormatter = $priceFormatter; 27 | } 28 | 29 | /** 30 | * @return float 31 | * @throws NoSuchEntityException 32 | */ 33 | public function get(): float 34 | { 35 | /** @var Product $product */ 36 | $product = $this->getCurrentProduct->get(); 37 | return $this->priceFormatter->format( 38 | (float) $product->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue() 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DataLayer/Tag/Product/CurrentProduct.php: -------------------------------------------------------------------------------- 1 | getCurrentProduct = $getCurrentProduct; 26 | $this->productDataMapper = $productDataMapper; 27 | } 28 | 29 | /** 30 | * @return string[] 31 | * @throws NoSuchEntityException 32 | */ 33 | public function merge(): array 34 | { 35 | return $this->productDataMapper->mapByProduct($this->getProduct()); 36 | } 37 | 38 | /** 39 | * @return ProductInterface 40 | * @throws NoSuchEntityException 41 | */ 42 | public function getProduct(): ProductInterface 43 | { 44 | if ($this->product instanceof ProductInterface) { 45 | return $this->product; 46 | } 47 | 48 | return $this->getCurrentProduct->get(); 49 | } 50 | 51 | /** 52 | * @param ProductInterface $product 53 | * @return void 54 | */ 55 | public function setProduct(ProductInterface $product) 56 | { 57 | $this->product = $product; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /DataLayer/Tag/Product/ProductCategory.php: -------------------------------------------------------------------------------- 1 | categoryProvider = $categoryProvider; 23 | } 24 | 25 | /** 26 | * @param Product $product 27 | * @return $this 28 | */ 29 | public function setProduct(Product $product): ProductCategory 30 | { 31 | $this->product = $product; 32 | return $this; 33 | } 34 | 35 | /** 36 | * @return string 37 | */ 38 | public function get(): string 39 | { 40 | /** @var Category|null $category */ 41 | $category = $this->product->getCategory(); 42 | if (is_object($category) && $category instanceof CategoryInterface) { 43 | return $category->getName(); 44 | } 45 | 46 | try { 47 | return $this->categoryProvider->getFirstByProduct($this->product)->getName(); 48 | } catch (NoSuchEntityException $e) { 49 | return ''; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /DataLayer/Tag/Product/ProductTagInterface.php: -------------------------------------------------------------------------------- 1 | composerRegistrar = $composerRegistrar; 19 | } 20 | 21 | public function get(): string 22 | { 23 | $path = $this->composerRegistrar->getPath('module', 'Yireo_GoogleTagManager2'); 24 | $composerPath = $path.'/composer.json'; 25 | $composerData = json_decode(file_get_contents($composerPath), true); 26 | return $composerData['version']; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DataLayer/TagParser.php: -------------------------------------------------------------------------------- 1 | $tagValue) { 22 | $data = $this->convertTag($tagName, $tagValue, $data); 23 | } 24 | 25 | foreach ($processors as $processor) { 26 | $data = array_replace_recursive($data, $processor->process($data)); 27 | } 28 | 29 | return $data; 30 | } 31 | 32 | /** 33 | * @param string $tagName 34 | * @param mixed $tagValue 35 | * @param array $data 36 | * @return array 37 | */ 38 | private function convertTag($tagName, $tagValue, array $data): array 39 | { 40 | if ($tagValue instanceof MergeTagInterface) { 41 | unset($data[$tagName]); 42 | return array_merge($data, $tagValue->merge()); 43 | } 44 | 45 | if (is_object($tagValue)) { 46 | $data[$tagName] = $this->getValueFromFromTagValueObject($tagValue); 47 | return $data; 48 | } 49 | 50 | if (is_array($tagValue)) { 51 | foreach ($tagValue as $key => $value) { 52 | $tagValue = $this->convertTag($key, $value, $tagValue); 53 | } 54 | 55 | $data[$tagName] = $tagValue; 56 | return $data; 57 | } 58 | 59 | if (is_null($tagValue)) { 60 | unset($data[$tagName]); 61 | } 62 | 63 | return $data; 64 | } 65 | 66 | /** 67 | * @param ArgumentInterface $tagValueObject 68 | * @return mixed 69 | * @throws RuntimeException 70 | */ 71 | private function getValueFromFromTagValueObject(ArgumentInterface $tagValueObject) 72 | { 73 | if ($tagValueObject instanceof TagInterface || $tagValueObject instanceof EventInterface) { 74 | return $tagValueObject->get(); 75 | } 76 | 77 | throw new RuntimeException('Unknown object in data layer: ' . get_class($tagValueObject)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Exception/BlockNotFound.php: -------------------------------------------------------------------------------- 1 | config = $config; 34 | $this->logger = $logger; 35 | } 36 | 37 | /** 38 | * @param string $msg 39 | * @param mixed $data 40 | * 41 | * @return bool 42 | */ 43 | public function debug(string $msg, $data = null): bool 44 | { 45 | if ($this->config->isDebug() === false) { 46 | return false; 47 | } 48 | 49 | if (!empty($data)) { 50 | $msg .= ': ' . var_export($data, true); 51 | } 52 | 53 | $this->logger->notice($msg); 54 | return true; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Observer/AddAdditionalLayoutHandles.php: -------------------------------------------------------------------------------- 1 | request = $request; 19 | $this->layout = $layout; 20 | } 21 | 22 | public function execute(Observer $observer) 23 | { 24 | $handles = []; 25 | $handles[] = 'yireo_googletagmanager2'; 26 | $handles[] = 'yireo_googletagmanager2_'.$this->getSystemPath(); 27 | 28 | foreach ($handles as $handle) { 29 | $this->layout->getUpdate()->addHandle($handle); 30 | } 31 | } 32 | 33 | private function getSystemPath(): string 34 | { 35 | $parts = explode('/', $this->request->getFullActionName()); 36 | return implode('_', array_slice($parts, 0, 3)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Observer/TriggerAddToCartDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | checkoutSessionDataProvider = $checkoutSessionDataProvider; 21 | $this->addToCartEvent = $addToCartEvent; 22 | } 23 | 24 | public function execute(Observer $observer) 25 | { 26 | /** @var ProductInterface $product */ 27 | $product = $observer->getData('product'); 28 | $qty = (int)$observer->getData('request')->getParam('qty'); 29 | if ($qty === 0) { 30 | $qty = 1; 31 | } 32 | 33 | $this->checkoutSessionDataProvider->add( 34 | 'add_to_cart_event', 35 | $this->addToCartEvent->setProduct($product)->setQty($qty)->get() 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Observer/TriggerAddToWishlistDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | customerSessionDataProvider = $customerSessionDataProvider; 21 | $this->addToWishlistEvent = $addToWishlistEvent; 22 | } 23 | 24 | public function execute(Observer $observer) 25 | { 26 | /** @var ProductInterface $product */ 27 | $product = $observer->getData('product'); 28 | $this->customerSessionDataProvider->add( 29 | 'add_to_wishlist_event', 30 | $this->addToWishlistEvent->setProduct($product)->get() 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Observer/TriggerLoginDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | customerSessionDataProvider = $customerSessionDataProvider; 20 | $this->loginEvent = $loginEvent; 21 | } 22 | 23 | public function execute(Observer $observer) 24 | { 25 | $customer = $observer->getEvent()->getCustomer(); 26 | $eventData = $this->loginEvent->setCustomer($customer)->get(); 27 | $this->customerSessionDataProvider->add('login_event', $eventData); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Observer/TriggerLogoutDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | customerSessionDataProvider = $customerSessionDataProvider; 20 | $this->logoutEvent = $logoutEvent; 21 | } 22 | 23 | public function execute(Observer $observer) 24 | { 25 | $this->customerSessionDataProvider->add('logout_event', $this->logoutEvent->get()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Observer/TriggerPurchaseDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | checkoutSessionDataProvider = $checkoutSessionDataProvider; 21 | $this->purchaseEvent = $purchaseEvent; 22 | } 23 | 24 | public function execute(Observer $observer) 25 | { 26 | /** @var OrderInterface $order */ 27 | $order = $observer->getData('order'); 28 | $purchaseEventData = $this->purchaseEvent->setOrder($order)->get(); 29 | if (empty($purchaseEventData)) { 30 | return; 31 | } 32 | 33 | $this->checkoutSessionDataProvider->add( 34 | 'purchase_event', 35 | $purchaseEventData 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Observer/TriggerRemoveFromCartDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | checkoutSessionDataProvider = $checkoutSessionDataProvider; 21 | $this->removeFromCartEvent = $removeFromCartEvent; 22 | } 23 | 24 | public function execute(Observer $observer) 25 | { 26 | /** @var CartItemInterface $quoteItem */ 27 | $quoteItem = $observer->getData('quote_item'); 28 | $this->checkoutSessionDataProvider->add( 29 | 'remove_from_cart_event', 30 | $this->removeFromCartEvent->setCartItem($quoteItem)->get() 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Observer/TriggerSignUpDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | customerSessionDataProvider = $customerSessionDataProvider; 20 | $this->signUpEvent = $signUpEvent; 21 | } 22 | 23 | public function execute(Observer $observer) 24 | { 25 | $eventData = $this->signUpEvent->get(); 26 | $this->customerSessionDataProvider->add('sign_up_event', $eventData); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PWA.md: -------------------------------------------------------------------------------- 1 | # PWA compatibility 2 | This frontend extension will be completely replaced by React or Vue functionality, and therefore, this extension will not be made compatible with PWA. 3 | -------------------------------------------------------------------------------- /Plugin/AddCspInlineScripts.php: -------------------------------------------------------------------------------- 1 | replaceInlineScripts = $replaceInlineScripts; 16 | } 17 | 18 | public function afterToHtml(Template $block, $html): string 19 | { 20 | if (false === strstr((string)$block->getNameInLayout(), 'yireo_googletagmanager2.')) { 21 | return (string)$html; 22 | } 23 | 24 | return $this->replaceInlineScripts->replace((string)$html); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Plugin/AddDataToCartSection.php: -------------------------------------------------------------------------------- 1 | checkoutCart = $checkoutCart; 35 | $this->checkoutSessionDataProvider = $checkoutSessionDataProvider; 36 | $this->viewCartEvent = $viewCartEvent; 37 | } 38 | 39 | /** 40 | * @param CustomerData $subject 41 | * @param array $result 42 | * @return array 43 | */ 44 | public function afterGetSectionData(CustomerData $subject, $result) 45 | { 46 | $quoteId = $this->checkoutCart->getQuote()->getId(); 47 | if (empty($result) || !is_array($result) || empty($quoteId)) { 48 | return $result; 49 | } 50 | 51 | $gtmData = []; 52 | 53 | $gtmEvents = $this->checkoutSessionDataProvider->get(); 54 | $gtmEvents['view_cart_event'] = $this->viewCartEvent->get(); 55 | $this->checkoutSessionDataProvider->clear(); 56 | 57 | return array_merge($result, ['gtm' => $gtmData, 'gtm_events' => $gtmEvents]); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Plugin/AddProductDetails.php: -------------------------------------------------------------------------------- 1 | layout = $layout; 19 | } 20 | 21 | /** 22 | * @param AbstractProduct $abstractProduct 23 | * @param mixed $html 24 | * @param ProductInterface $product 25 | * @return string 26 | */ 27 | public function afterGetProductDetailsHtml(AbstractProduct $abstractProduct, $html, ProductInterface $product) 28 | { 29 | try { 30 | $block = $this->getProductDetailsBlock(); 31 | } catch (BlockNotFound $blockNotFound) { 32 | return $html; 33 | } 34 | 35 | $html .= $block->setData('product', $product)->toHtml(); 36 | return $html; 37 | } 38 | 39 | /** 40 | * @return AbstractBlock 41 | * @throws BlockNotFound 42 | */ 43 | private function getProductDetailsBlock(): AbstractBlock 44 | { 45 | $block = $this->layout->getBlock('yireo_googletagmanager2.product-details'); 46 | if ($block instanceof AbstractBlock) { 47 | return $block; 48 | } 49 | 50 | throw new BlockNotFound('Block "yireo_googletagmanager2.product-details" not found'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Plugin/GetProductsFromCategoryBlockPlugin.php: -------------------------------------------------------------------------------- 1 | categorySize = $categorySize; 27 | $this->getCurrentCategoryProducts = $getCurrentCategoryProducts; 28 | $this->config = $config; 29 | } 30 | 31 | /** 32 | * @param ListProduct $listProductBlock 33 | * @param AbstractCollection $collection 34 | * @return AbstractCollection 35 | */ 36 | public function afterGetLoadedProductCollection( 37 | ListProduct $listProductBlock, 38 | AbstractCollection $collection 39 | ): AbstractCollection { 40 | $maximumCategoryProducts = $this->config->getMaximumCategoryProducts(); 41 | if ($maximumCategoryProducts <= 0) { 42 | return $collection; 43 | } 44 | $i = 0; 45 | foreach ($collection as $product) { 46 | if ($i > $maximumCategoryProducts) { 47 | break; 48 | } 49 | 50 | $this->getCurrentCategoryProducts->addProduct($product); 51 | $i++; 52 | } 53 | 54 | $this->categorySize->setSize($collection->count()); 55 | return $collection; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Plugin/TriggerAddGuestPaymentInfoDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | checkoutSessionDataProvider = $checkoutSessionDataProvider; 24 | $this->addPaymentInfo = $addPaymentInfo; 25 | $this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId; 26 | } 27 | 28 | public function afterSavePaymentInformationAndPlaceOrder( 29 | GuestPaymentInformationManagementInterface $subject, 30 | $orderId, 31 | $cartId, 32 | $email, 33 | PaymentInterface $paymentMethod, 34 | ?AddressInterface $billingAddress = null 35 | ) { 36 | $cartId = $this->maskedQuoteIdToQuoteId->execute($cartId); 37 | $addPaymentInfoEventData = $this->addPaymentInfo 38 | ->setPaymentMethod($paymentMethod->getMethod()) 39 | ->setCartId((int)$cartId) 40 | ->get(); 41 | $this->checkoutSessionDataProvider->add('add_payment_info_event', $addPaymentInfoEventData); 42 | 43 | return $orderId; 44 | } 45 | 46 | public function afterSavePaymentInformation( 47 | GuestPaymentInformationManagementInterface $subject, 48 | $orderId, 49 | $cartId, 50 | $email, 51 | PaymentInterface $paymentMethod, 52 | ?AddressInterface $billingAddress = null 53 | ) { 54 | $cartId = $this->maskedQuoteIdToQuoteId->execute($cartId); 55 | $addPaymentInfoEventData = $this->addPaymentInfo 56 | ->setPaymentMethod($paymentMethod->getMethod()) 57 | ->setCartId((int)$cartId) 58 | ->get(); 59 | $this->checkoutSessionDataProvider->add('add_payment_info_event', $addPaymentInfoEventData); 60 | 61 | return $orderId; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Plugin/TriggerAddShippingInfoDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | checkoutSessionDataProvider = $checkoutSessionDataProvider; 21 | $this->addShippingInfo = $addShippingInfo; 22 | } 23 | 24 | /** 25 | * @param ShippingInformationManagementInterface $subject 26 | * @param PaymentDetailsInterface $paymentDetails 27 | * @param mixed $cartId 28 | * @param ShippingInformationInterface $addressInformation 29 | * @return PaymentDetailsInterface 30 | */ 31 | public function afterSaveAddressInformation( 32 | ShippingInformationManagementInterface $subject, 33 | PaymentDetailsInterface $paymentDetails, 34 | $cartId, 35 | ShippingInformationInterface $addressInformation 36 | ) { 37 | 38 | $event = $this->addShippingInfo->get(); 39 | if (array_key_exists('event', $event)) { 40 | $this->checkoutSessionDataProvider->add('add_shipping_info_event', $event); 41 | } 42 | 43 | return $paymentDetails; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Plugin/TriggerViewSearchResultDataLayerEvent.php: -------------------------------------------------------------------------------- 1 | viewSearchResultEvent = $viewSearchResultEvent; 19 | $this->customerSessionDataProvider = $customerSessionDataProvider; 20 | } 21 | 22 | public function afterExecute(Index $subject, $return) 23 | { 24 | $searchTerm = $subject->getRequest()->getParam('q'); 25 | $this->viewSearchResultEvent->setSearchTerm($searchTerm); 26 | $this->customerSessionDataProvider->add('view_search_result', $this->viewSearchResultEvent->get()); 27 | return $return; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SessionDataProvider/CheckoutSessionDataProvider.php: -------------------------------------------------------------------------------- 1 | checkoutSession = $checkoutSession; 20 | $this->debugger = $debugger; 21 | } 22 | 23 | public function add(string $identifier, array $data) 24 | { 25 | $gtmData = $this->get(); 26 | $gtmData[$identifier] = $data; 27 | $this->debugger->debug('CheckoutSessionDataProvider::add(): ' . $identifier, $data); 28 | $this->checkoutSession->setYireoGtmData($gtmData); 29 | } 30 | 31 | public function get(): array 32 | { 33 | $gtmData = $this->checkoutSession->getYireoGtmData(); 34 | if (is_array($gtmData)) { 35 | return $gtmData; 36 | } 37 | 38 | return []; 39 | } 40 | 41 | public function clear() 42 | { 43 | $this->checkoutSession->setYireoGtmData([]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SessionDataProvider/CustomerSessionDataProvider.php: -------------------------------------------------------------------------------- 1 | customerSession = $customerSession; 19 | $this->debugger = $debugger; 20 | } 21 | 22 | public function add(string $identifier, array $data) 23 | { 24 | $gtmData = $this->get(); 25 | $gtmData[$identifier] = $data; 26 | $this->debugger->debug('CustomerSessionDataProvider::add(): ' . $identifier, $data); 27 | $this->customerSession->setYireoGtmData($gtmData); 28 | } 29 | 30 | public function get(): array 31 | { 32 | $gtmData = $this->customerSession->getYireoGtmData(); 33 | if (is_array($gtmData)) { 34 | return $gtmData; 35 | } 36 | 37 | return []; 38 | } 39 | 40 | public function clear() 41 | { 42 | $this->customerSession->setYireoGtmData([]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | ## Unit testing 3 | This extension ships with PHPUnit tests. The generic PHPUnit configuration in Magento 2 will pick up on these 4 | tests. To only test Yireo extensions, simply run PHPUnit from within this folder. Note that this assumes that 5 | the extension is installed via composer. For instance: 6 | 7 | phpunit 8 | 9 | Also note that Mockery (`mockery/mockery`) is used for the integration tests. If you want to test this module, you need to install its dev-dependencies: 10 | 11 | composer require yireo/magento2-googletagmanager2 --dev 12 | 13 | The JavaScript code ships with MochaJS unit tests. To install the stuff, simply run (within this extension directory) the following: 14 | 15 | npm install 16 | npm run mocha 17 | 18 | Or just use `yarn`: 19 | 20 | yarn 21 | npm run mocha 22 | 23 | 24 | -------------------------------------------------------------------------------- /Test/Functional/BaseTest.php: -------------------------------------------------------------------------------- 1 | get(ComponentRegistrar::class); 15 | $path = $componentRegistrar->getPath('module', 'Yireo_GoogleTagManager2'); 16 | $this->assertTrue(file_exists($path.'/registration.php')); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Test/Functional/Util/CategoryProviderTest.php: -------------------------------------------------------------------------------- 1 | expectException(NotUsingSetProductSkusException::class); 17 | $categoryProvider = ObjectManager::getInstance()->get(CategoryProvider::class); 18 | $categoryProvider->getLoadedCategories(); 19 | } 20 | 21 | public function testGetById() 22 | { 23 | $categoryProvider = ObjectManager::getInstance()->get(CategoryProvider::class); 24 | $categoryProvider->addCategoryIds([11, 38]); 25 | $category = $categoryProvider->getById(11); 26 | $this->assertEquals(11, $category->getId()); 27 | } 28 | 29 | public function testGetCategories() 30 | { 31 | $categoryProvider = ObjectManager::getInstance()->get(CategoryProvider::class); 32 | $categoryProvider->addCategoryIds([11, 38]); 33 | $categories = $categoryProvider->getLoadedCategories(); 34 | $this->assertEquals(2, count($categories)); 35 | 36 | $categoryProvider->addCategoryIds(['12', 14]); 37 | $categories = $categoryProvider->getLoadedCategories(); 38 | $this->assertEquals(4, count($categories)); 39 | 40 | $categoryProvider->addCategoryIds([12, 14]); 41 | $categories = $categoryProvider->getLoadedCategories(); 42 | $this->assertEquals(4, count($categories)); 43 | } 44 | 45 | public function testGetByProduct() 46 | { 47 | $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); 48 | $product = $productRepository->getById(1); 49 | 50 | $categoryProvider = ObjectManager::getInstance()->get(CategoryProvider::class); 51 | $categoryProvider->addCategoryIds([11, 38]); 52 | $categories = $categoryProvider->getAllByProduct($product); 53 | $this->assertEquals(2, count($categories)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Test/Integration/Block/ScriptTest.php: -------------------------------------------------------------------------------- 1 | assertEnabledFlagIsWorking(); 26 | 27 | $this->layout->getUpdate()->addPageHandles(['empty', '1column']); 28 | $this->layout->generateXml(); 29 | 30 | $this->dispatch('/'); 31 | 32 | $this->assertContainerInLayout('before.body.end'); 33 | $this->assertStringContainsString('Yireo_GoogleTagManager2', $this->layout->getUpdate()->asString()); 34 | 35 | $body = $this->getResponse()->getBody(); // @phpstan-ignore-line 36 | $this->assertTrue((bool)strpos($body, 'yireoGoogleTagManager'), 'Script not found in HTML body: ' . $body); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Test/Integration/DataLayer/Event/AddToCartTest.php: -------------------------------------------------------------------------------- 1 | getProductBySku('simple1002'); 30 | $addToCartEvent = ObjectManager::getInstance()->get(AddToCart::class); 31 | $data = $addToCartEvent->setProduct($product)->get(); 32 | $this->assertCount(1, $data['ecommerce']['items']); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Test/Integration/DataLayer/Mapper/CategoryDataMapperTest.php: -------------------------------------------------------------------------------- 1 | getCategoryByName('Category 999'); 25 | $categoryDataMapper = ObjectManager::getInstance()->get(CategoryDataMapper::class); 26 | $categoryData = $categoryDataMapper->mapByCategory($category); 27 | $this->assertEquals('Category 999', $categoryData['category_name']); 28 | $this->assertEquals($category->getId(), $categoryData['category_id']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Test/Integration/DataLayer/Mapper/ProductDataMapperTest.php: -------------------------------------------------------------------------------- 1 | getProductBySku('simple1002'); 25 | $productDataMapper = ObjectManager::getInstance()->get(ProductDataMapper::class); 26 | $productData = $productDataMapper->mapByProduct($product); 27 | $this->assertArrayHasKey('item_name', $productData); 28 | $this->assertStringContainsString('Simple Product', $productData['item_name']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Test/Integration/DataLayer/Tag/VersionTest.php: -------------------------------------------------------------------------------- 1 | get(Version::class); 14 | $this->assertTrue((bool)preg_match('/^([0-9]+)\.([0-9]+)\.([0-9]+)$/', $version->get())); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Test/Integration/DataLayer/TagParserTest.php: -------------------------------------------------------------------------------- 1 | 'example1', 16 | 'example2' => null, 17 | ]; 18 | 19 | $tagParser = ObjectManager::getInstance()->get(TagParser::class); 20 | $data = $tagParser->parse($data, []); 21 | $this->assertTrue(count($data) === 1); 22 | } 23 | 24 | public function testIfAddedTagsAreCorrect() 25 | { 26 | $mock = $this->createMock(TagInterface::class); 27 | $mock->method('get')->willReturn(['exampleKey' => 'exampleValue']); 28 | $data = [ 29 | 'example1' => $mock 30 | ]; 31 | 32 | $tagParser = ObjectManager::getInstance()->get(TagParser::class); 33 | $data = $tagParser->parse($data, []); 34 | $this->assertTrue(count($data) === 1); 35 | $this->assertTrue(isset($data['example1'])); 36 | $this->assertTrue(is_array($data['example1'])); 37 | $this->assertEquals('exampleValue', $data['example1']['exampleKey'], var_export($data, true)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Test/Integration/FixtureTrait/CreateCustomer.php: -------------------------------------------------------------------------------- 1 | get(CustomerRepositoryInterface::class); 21 | try { 22 | return $customerRepository->getById($id); 23 | } catch (NoSuchEntityException $e) { 24 | } catch (LocalizedException $e) { 25 | } 26 | 27 | /** @var Customer $customer */ 28 | $customer = $objectManager->create(Customer::class); 29 | $customer->setWebsiteId(1) 30 | ->setId($id) 31 | ->setEmail('customer@example.com') 32 | ->setPassword('password') 33 | ->setGroupId(1) 34 | ->setStoreId(1) 35 | ->setIsActive(1) 36 | ->setPrefix('Mr.') 37 | ->setFirstname('John') 38 | ->setMiddlename('A') 39 | ->setLastname('Smith') 40 | ->setSuffix('Esq.') 41 | ->setDefaultBilling(1) 42 | ->setDefaultShipping(1) 43 | ->setTaxvat('12') 44 | ->setGender(0) 45 | ->addData($data); 46 | 47 | $customer->isObjectNew(true); 48 | // @phpstan-ignore-next-line 49 | $customer->save(); 50 | 51 | $customerRegistry = $objectManager->get(CustomerRegistry::class); 52 | $customerRegistry->remove($customer->getId()); 53 | return $customerRepository->getById($customer->getId()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Test/Integration/FixtureTrait/GetCategory.php: -------------------------------------------------------------------------------- 1 | get(GetCategoryByName::class); 18 | return $getCategoryByName->execute($categoryName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Test/Integration/FixtureTrait/GetCustomer.php: -------------------------------------------------------------------------------- 1 | get(CustomerRepositoryInterface::class); 22 | return $customerRepository->getById($customerId); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Test/Integration/FixtureTrait/GetOrder.php: -------------------------------------------------------------------------------- 1 | get(OrderRepositoryInterface::class); 18 | $searchCriteriaBuilder = ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); 19 | $searchCriteriaBuilder->setPageSize(1); 20 | $searchItems = $orderRepository->getList($searchCriteriaBuilder->create()); 21 | $orders = $searchItems->getItems(); 22 | return array_shift($orders); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Test/Integration/FixtureTrait/GetProduct.php: -------------------------------------------------------------------------------- 1 | get(ProductRepositoryInterface::class); 22 | return $productRepository->getById($productId); 23 | } 24 | 25 | /** 26 | * @param string $productSku 27 | * @return ProductInterface 28 | * @throws NoSuchEntityException 29 | */ 30 | public function getProductBySku(string $productSku): ProductInterface 31 | { 32 | $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); 33 | return $productRepository->get($productSku); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Test/Integration/FixtureTrait/Reindex.php: -------------------------------------------------------------------------------- 1 | get(IndexerFactory::class); 15 | $indexer = $indexerFactory->create()->load($indexerId); 16 | $indexer->reindexAll(); 17 | } 18 | 19 | public function reindexAll() 20 | { 21 | $objectManager = ObjectManager::getInstance(); 22 | $config = $objectManager->get(ConfigInterface::class); 23 | $indexerFactory = $objectManager->get(IndexerFactory::class); 24 | foreach (array_keys($config->getIndexers()) as $indexerId) { 25 | $indexer = $indexerFactory->create()->load($indexerId); 26 | $indexer->reindexAll(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Test/Integration/ModuleTest.php: -------------------------------------------------------------------------------- 1 | assertModuleIsEnabled('Yireo_GoogleTagManager2'); 19 | $this->assertModuleIsRegistered('Yireo_GoogleTagManager2'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Test/Integration/Observer/TriggerLoginDataLayerEventTest.php: -------------------------------------------------------------------------------- 1 | createCustomer(); 34 | 35 | ObjectManager::getInstance()->get(CheckoutSessionDataProvider::class)->clear(); 36 | $customer = $this->getCustomer(); 37 | 38 | $eventManager = ObjectManager::getInstance()->get(ManagerInterface::class); 39 | $eventManager->dispatch('customer_data_object_login', ['customer' => $customer]); 40 | 41 | $data = ObjectManager::getInstance()->get(CustomerSessionDataProvider::class)->get(); 42 | $this->assertArrayHasKey('login_event', $data, var_export($data, true)); 43 | $this->assertArrayHasKey('event', $data['login_event']); 44 | $this->assertEquals('login', $data['login_event']['event']); 45 | } 46 | } -------------------------------------------------------------------------------- /Test/Integration/Observer/TriggerLogoutDataLayerEventTest.php: -------------------------------------------------------------------------------- 1 | createCustomer(); 34 | 35 | ObjectManager::getInstance()->get(CheckoutSessionDataProvider::class)->clear(); 36 | $customer = $this->getCustomer(); 37 | 38 | $eventManager = ObjectManager::getInstance()->get(ManagerInterface::class); 39 | $eventManager->dispatch('customer_logout', ['customer' => $customer]); 40 | 41 | $data = ObjectManager::getInstance()->get(CustomerSessionDataProvider::class)->get(); 42 | $this->assertArrayHasKey('logout_event', $data, var_export($data, true)); 43 | $this->assertArrayHasKey('event', $data['logout_event']); 44 | $this->assertEquals('logout', $data['logout_event']['event']); 45 | } 46 | } -------------------------------------------------------------------------------- /Test/Integration/Observer/TriggerPurchaseDataLayerEventTest.php: -------------------------------------------------------------------------------- 1 | get(CheckoutSessionDataProvider::class)->clear(); 25 | $order = $this->getOrder(); 26 | 27 | $eventManager = ObjectManager::getInstance()->get(ManagerInterface::class); 28 | $eventManager->dispatch('sales_order_place_after', ['order' => $order]); 29 | 30 | $data = ObjectManager::getInstance()->get(CheckoutSessionDataProvider::class)->get(); 31 | $this->assertArrayHasKey('purchase_event', $data); 32 | $this->assertArrayHasKey('event', $data['purchase_event']); 33 | $this->assertEquals('purchase', $data['purchase_event']['event']); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /Test/Integration/Page/LoginTest.php: -------------------------------------------------------------------------------- 1 | createCustomer(); 24 | 25 | /** @var HttpRequest $request */ 26 | $request = $this->getRequest(); 27 | 28 | $request->setPostValue([ 29 | 'login' => [ 30 | 'username' => 'customer@example.com', 31 | 'password' => 'password', 32 | ], 33 | 'formKey' => $this->objectManager->get(FormKey::class)->getFormKey(), 34 | ]); 35 | $request->setMethod(HttpRequest::METHOD_POST); 36 | $this->dispatch('customer/account/loginPost'); 37 | 38 | $customerSectionPool = $this->objectManager->get(SectionPool::class); 39 | $data = $customerSectionPool->getSectionsData(['customer']); 40 | 41 | $this->assertArrayHasKey('login_event', $data['customer']['gtm_events']); 42 | $this->assertEquals('login', $data['customer']['gtm_events']['login_event']['event']); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Test/Integration/Page/LogoutTest.php: -------------------------------------------------------------------------------- 1 | createCustomer(); 23 | $this->loginCustomer(); 24 | 25 | $this->objectManager->get(CustomerSessionDataProvider::class)->clear(); 26 | 27 | $this->dispatch('customer/account/logout'); 28 | 29 | $customerSectionPool = $this->objectManager->get(SectionPool::class); 30 | $data = $customerSectionPool->getSectionsData(['customer']); 31 | 32 | $this->assertArrayHasKey('logout_event', $data['customer']['gtm_events']); 33 | $this->assertEquals('logout', $data['customer']['gtm_events']['logout_event']['event']); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Test/Integration/Page/ProductPageTest.php: -------------------------------------------------------------------------------- 1 | assertEnabledFlagIsWorking(); 32 | 33 | $product = $this->getProductBySku('simple1002'); 34 | 35 | $this->dispatch('catalog/product/view/id/' . $product->getId()); 36 | $this->assertRequestActionName('view'); 37 | 38 | $body = $this->getResponse()->getBody(); 39 | $this->assertStringContainsString($product->getName(), $body); 40 | 41 | $block = $this->layout->getBlock('yireo_googletagmanager2.data-layer'); 42 | $this->assertNotEmpty($block); 43 | 44 | $this->assertDataLayerEquals('product', 'page_type'); 45 | 46 | $event = $this->getEventFromDataLayerEvents('view_item_event', 'view_item'); 47 | $this->assertArrayHasKey('ecommerce', $event); 48 | $this->assertNotEmpty($event['ecommerce']['items']); 49 | 50 | $productData = array_shift($event['ecommerce']['items']); 51 | $this->assertNonEmptyValueInArray('item_name', $productData); 52 | $this->assertNonEmptyValueInArray('item_id', $productData); 53 | $this->assertNonEmptyValueInArray('price', $productData); 54 | $this->assertNonEmptyValueInArray('item_list_id', $productData); 55 | $this->assertNonEmptyValueInArray('item_list_name', $productData); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Test/Integration/Plugin/AddDataToCartSectionTest.php: -------------------------------------------------------------------------------- 1 | assertInterceptorPluginIsRegistered( 20 | Cart::class, 21 | AddDataToCartSection::class, 22 | 'Yireo_GoogleTagManager2::addAdditionalDataToCartSection' 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Test/Integration/Plugin/AddDataToCustomerSectionTest.php: -------------------------------------------------------------------------------- 1 | assertInterceptorPluginIsRegistered( 20 | Customer::class, 21 | AddDataToCustomerSection::class, 22 | 'Yireo_GoogleTagManager2::addDataToCustomerSection' 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Test/Integration/Plugin/GetProductsFromCategoryBlockPluginTest.php: -------------------------------------------------------------------------------- 1 | assertInterceptorPluginIsRegistered( 20 | ListProductBlock::class, 21 | GetProductsFromCategoryBlockPlugin::class, 22 | 'Yireo_GoogleTagManager2::getProductsFromCategoryBlockPlugin' 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Test/Integration/Plugin/TriggerAddShippingInfoDataLayerEventTest.php: -------------------------------------------------------------------------------- 1 | assertInterceptorPluginIsRegistered( 20 | ShippingInformationManagementInterface::class, 21 | TriggerAddShippingInfoDataLayerEvent::class, 22 | 'Yireo_GoogleTagManager2::triggerAddShippingInfoDataLayerEvent' 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Test/Integration/SessionDataProvider/CustomerSessionDataProviderTest.php: -------------------------------------------------------------------------------- 1 | get(SerializerInterface::class); 19 | 20 | $customerSessionDataProvider = ObjectManager::getInstance()->get(CustomerSessionDataProvider::class); 21 | $customerSessionDataProvider->add('foobar', ['foo' => 'bar']); 22 | 23 | $this->getRequest()->setParams(['sections' => 'customer']); 24 | $this->dispatch('customer/section/load'); 25 | $body = $this->getResponse()->getBody(); // @phpstan-ignore-line 26 | $data = $serializer->unserialize($body, true); 27 | $this->assertEquals('bar', $data['customer']['gtm_events']['foobar']['foo']); 28 | 29 | $this->getRequest()->setParams(['sections' => 'customer']); 30 | $this->dispatch('customer/section/load'); 31 | $body = $this->getResponse()->getBody(); // @phpstan-ignore-line 32 | $data = $serializer->unserialize($body, true); 33 | $this->assertEmpty($data['customer']['gtm_events']); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Test/Integration/Stub/FulltextStub.php: -------------------------------------------------------------------------------- 1 | createProduct( 19 | 1, 20 | 'Product 1', 21 | 'product1', 22 | 1.42, 23 | ); 24 | 25 | $productDataMapper = ObjectManager::getInstance()->get(ProductDataMapper::class); 26 | $productData = $productDataMapper->mapByProduct($product); 27 | 28 | $this->assertNonEmptyValueInArray('item_id', $productData); 29 | $this->assertSame(1, $productData['magento_id']); 30 | $this->assertSame('product1', $productData['magento_sku']); 31 | $this->assertSame('product1', $productData['item_id']); 32 | $this->assertSame('product1', $productData['item_sku']); 33 | $this->assertSame('Product 1', $productData['item_name']); 34 | $this->assertSame(1.42, $productData['price']); 35 | } 36 | 37 | /** 38 | * @param int $id 39 | * @param string $name 40 | * @param string $sku 41 | * @param float $price 42 | * @return ProductInterface 43 | */ 44 | private function createProduct(int $id, string $name, string $sku, float $price): ProductInterface 45 | { 46 | $productFactory = ObjectManager::getInstance()->get(ProductInterfaceFactory::class); 47 | $product = $productFactory->create(); 48 | $product->setId($id); 49 | $product->setName($name); 50 | $product->setSku($sku); 51 | $product->setPrice($price); 52 | return $product; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Test/Unit/Util/CamelCaseTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('Foobar', $camelCase->to('foobar')); 26 | $this->assertEquals('FooBar', $camelCase->to('foo_bar')); 27 | $this->assertEquals('FooBar', $camelCase->to('Foo_Bar')); 28 | } 29 | 30 | public function testFromCamelCase() 31 | { 32 | $camelCase = new CamelCase(); 33 | $this->assertEquals('foobar', $camelCase->from('foobar')); 34 | $this->assertEquals('foo_bar', $camelCase->from('fooBar')); 35 | $this->assertEquals('foo_bar', $camelCase->from('FooBar')); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Util/CamelCase.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2022 Yireo (https://www.yireo.com/) 8 | * @license Open Source License (OSL v3) 9 | */ 10 | 11 | namespace Yireo\GoogleTagManager2\Util; 12 | 13 | class CamelCase 14 | { 15 | /** 16 | * @param string $string 17 | * @return string 18 | */ 19 | public function from(string $string): string 20 | { 21 | return strtolower(trim(preg_replace('/([A-Z]|[0-9]+)/', "_$1", $string), '_')); 22 | } 23 | 24 | /** 25 | * @param string $string 26 | * @return string 27 | */ 28 | public function to(string $string): string 29 | { 30 | return str_replace('_', '', ucwords($string, '_')); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Util/Debug.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Yireo (https://www.yireo.com/) 8 | * @license Open Source License (OSL v3) 9 | */ 10 | 11 | namespace Yireo\GoogleTagManager2\Util; 12 | 13 | use Psr\Log\LoggerInterface; 14 | use Yireo\GoogleTagManager2\Config\Config; 15 | 16 | class Debug 17 | { 18 | /** 19 | * @var Config 20 | */ 21 | private $config; 22 | 23 | /** 24 | * @var LoggerInterface 25 | */ 26 | private $logger; 27 | 28 | /** 29 | * Data constructor. 30 | * 31 | * @param Config $config 32 | * @param LoggerInterface $logger 33 | */ 34 | public function __construct( 35 | Config $config, 36 | LoggerInterface $logger 37 | ) { 38 | $this->config = $config; 39 | $this->logger = $logger; 40 | } 41 | 42 | /** 43 | * Debugging method 44 | * 45 | * @param string $string String to debug 46 | * @param mixed|null $variable Tag to dump to debug message 47 | * 48 | * @return bool 49 | */ 50 | public function debug(string $string, $variable = null) 51 | { 52 | if ($this->config->isDebug() === false) { 53 | return false; 54 | } 55 | 56 | if ($variable !== null) { 57 | $string .= ': ' . var_export($variable, true); 58 | } 59 | 60 | $this->logger->info('Yireo_GoogleTagManager: ' . $string); 61 | 62 | return true; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Util/GetCategoryPath.php: -------------------------------------------------------------------------------- 1 | categoryRepository = $categoryRepository; 29 | } 30 | 31 | /** 32 | * @param ?CategoryModel $category 33 | * @return string 34 | */ 35 | public function getCategoryPath(?CategoryModel $category = null): string 36 | { 37 | if (!$category instanceof CategoryModel) { 38 | return "direct"; 39 | } 40 | 41 | $categoryPath = $category->getPath(); 42 | $categoryIdArray = explode('/', $categoryPath); 43 | $categoryNames = []; 44 | 45 | foreach ($categoryIdArray as $categoryId) { 46 | if (!in_array($categoryId, self::ROOT_CATEGORY_IDS)) { 47 | try { 48 | $category = $this->categoryRepository->get($categoryId); 49 | } catch (NoSuchEntityException $e) { 50 | continue; 51 | } 52 | $categoryNames[] = $category->getName(); 53 | } 54 | } 55 | return implode('/', $categoryNames); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Util/GetCurrentCategory.php: -------------------------------------------------------------------------------- 1 | request = $request; 24 | $this->categoryRepository = $categoryRepository; 25 | $this->storeManager = $storeManager; 26 | } 27 | 28 | /** 29 | * @return CategoryInterface 30 | * @throws NoSuchEntityException 31 | */ 32 | public function get(): CategoryInterface 33 | { 34 | $categoryId = (int)$this->request->getParam('id'); 35 | try { 36 | $category = $this->categoryRepository->get($categoryId); 37 | } catch (NoSuchEntityException $e) { 38 | /** @var Store $store */ 39 | $store = $this->storeManager->getStore(); 40 | $category = $this->categoryRepository->get($store->getRootCategoryId()); 41 | } 42 | 43 | return $category; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Util/GetCurrentCategoryProducts.php: -------------------------------------------------------------------------------- 1 | products[$product->getId()] = $product; 14 | } 15 | 16 | /** 17 | * @return array 18 | */ 19 | public function getProducts(): array 20 | { 21 | return $this->products; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Util/GetCurrentProduct.php: -------------------------------------------------------------------------------- 1 | request = $request; 24 | $this->productRepository = $productRepository; 25 | $this->storeManager = $storeManager; 26 | } 27 | 28 | /** 29 | * @return ProductInterface 30 | * @throws NoSuchEntityException 31 | */ 32 | public function get(): ProductInterface 33 | { 34 | $productId = (int)$this->request->getParam('id'); 35 | if ($this->request->getActionName() === 'configure' || empty($productId)) { 36 | $productId = (int)$this->request->getParam('product_id'); 37 | } 38 | 39 | return $this->productRepository->getById($productId, false, $this->storeManager->getStore()->getId()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Util/OrderTotals.php: -------------------------------------------------------------------------------- 1 | getSubtotal() - abs((float)$order->getDiscountAmount()); 13 | } 14 | 15 | public function getShippingTotal(OrderInterface $order): float 16 | { 17 | return (float)$order->getShippingAmount() - (float)$order->getShippingDiscountAmount(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Util/PriceFormatter.php: -------------------------------------------------------------------------------- 1 | dataLayer = $dataLayer; 41 | $this->config = $config; 42 | $this->serializer = $serializer; 43 | } 44 | 45 | /** 46 | * @return array 47 | */ 48 | public function getConfiguration(): array 49 | { 50 | $configuration = []; 51 | $configuration['cookie_restriction_mode'] = $this->config->getCookieRestrictionModeName(); 52 | $configuration['website_id'] = $this->config->getCurrentWebsiteId(); 53 | $configuration['data_layer'] = $this->dataLayer->getDataLayer(); 54 | $configuration['id'] = $this->config->getId(); 55 | $configuration['debug'] = $this->config->isDebug(); 56 | $configuration['gtm_url'] = $this->config->getGoogleTagmanagerUrl(); 57 | return $configuration; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | public function getConfigurationAsJson(): string 64 | { 65 | return $this->serializer->serialize($this->getConfiguration()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /etc/cache.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | XML definitions for data layer construction 7 | 8 | 9 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0 7 | 8 | 0 9 | 0 10 | 100 11 | material 12 | id,name 13 | id 14 | everywhere 15 | 1 16 | product_first_category 17 | 0 18 | 19 | new,payment_review,pending_payment,holded,processing,complete 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /etc/csp_whitelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | *.googletagmanager.com 8 | tagmanager.google.com 9 | 10 | 11 | 12 | 13 | *.google-analytics.com 14 | *.analytics.google.com 15 | *.googletagmanager.com 16 | 17 | 18 | 19 | 20 | tagmanager.google.com 21 | fonts.google.com 22 | 23 | 24 | 25 | 26 | *.googletagmanager.com 27 | *.google-analytics.com 28 | www.googletagmanager.com 29 | ssl.gstatic.com 30 | www.gstatic.com 31 | 32 | 33 | 34 | 35 | fonts.gstatic.com 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /etc/data_layer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 17 | 18 | -------------------------------------------------------------------------------- /etc/data_layer.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Yireo\GoogleTagManager2\CustomerData\GtmCheckout 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /etc/frontend/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /etc/frontend/sections.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 |
6 | 7 | 8 |
9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /etc/view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Yireo_GoogleTagManager2::js 5 | 6 | 7 | -------------------------------------------------------------------------------- /etc/webapi_rest/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Yireo_GoogleTagManager2", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/yireo/Yireo_GoogleTagManager2.git" 6 | }, 7 | "devDependencies": { 8 | "amd-loader": "^0.0.8", 9 | "casperjs": "^1.1", 10 | "jsdom": "^11.0", 11 | "mocha": "10.1.0", 12 | "requirejs": "^2.3" 13 | }, 14 | "scripts": { 15 | "mocha": "./node_modules/mocha/bin/mocha view/frontend/web/js/test/mocha", 16 | "casper": "./node_modules/casperjs/bin/casperjs --engine=slimerjs view/frontend/web/js/test/casper/index.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | excludePaths: 3 | - */Test/*/* 4 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | Test/Unit 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | * 17 | 18 | Test 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Yireo_GoogleTagManager2/js/generic 11 | true 12 | 13 | 14 | Yireo_GoogleTagManager2/js/push 15 | true 16 | 17 | 18 | Yireo_GoogleTagManager2/js/product/clicks 19 | true 20 | 21 | 22 | Yireo_GoogleTagManager2/js/logger 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /view/frontend/layout/catalog_product_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 14 | 15 | 16 | 17 | Yireo\GoogleTagManager2\DataLayer\Processor\Product 18 | 19 | 20 | 21 | 22 | view_item 23 | 24 | 25 | 26 | Yireo\GoogleTagManager2\DataLayer\Tag\Product\CurrentProduct 27 | 28 | 29 | Yireo\GoogleTagManager2\DataLayer\Tag\Product\CurrentPrice 30 | Yireo\GoogleTagManager2\DataLayer\Tag\CurrencyCode 31 | 32 | 33 | 34 | 35 | 36 | product 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Yireo\GoogleTagManager2\DataLayer\Tag\Product\CurrentProduct 45 | Yireo\GoogleTagManager2\ViewModel\DataLayer 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /view/frontend/layout/checkout_cart_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 14 | 15 | 16 | 17 | Yireo\GoogleTagManager2\DataLayer\Processor\Cart 18 | 19 | 20 | 21 | Yireo\GoogleTagManager2\DataLayer\Event\ViewCart 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /view/frontend/layout/checkout_onepage_success.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 14 | 15 | 16 | 17 | Yireo\GoogleTagManager2\DataLayer\Processor\SuccessPage 18 | 19 | 20 | 21 | Yireo\GoogleTagManager2\DataLayer\Event\Purchase 22 | 23 | 24 | 25 | Yireo\GoogleTagManager2\DataLayer\Tag\EnhancedConversions 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /view/frontend/layout/hyva_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 14 | 15 | 19 | 20 | 24 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 | .products a.product 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /view/frontend/requirejs-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * GoogleTagManager2 plugin for Magento 3 | * 4 | * @author Yireo (https://www.yireo.com/) 5 | * @copyright Copyright (c) 2022 Yireo (https://www.yireo.com/) 6 | * @license Open Software License 7 | */ 8 | 9 | var config = { 10 | map: { 11 | '*': { 12 | 'yireoGoogleTagManager': 'Yireo_GoogleTagManager2/js/generic', 13 | 'yireoGoogleTagManagerPush': 'Yireo_GoogleTagManager2/js/push', 14 | 'yireoGoogleTagManagerProductClicks': 'Yireo_GoogleTagManager2/js/product/clicks', 15 | 'yireoGoogleTagManagerLogger': 'Yireo_GoogleTagManager2/js/logger' 16 | } 17 | }, 18 | config: { 19 | mixins: { 20 | 'Magento_Checkout/js/model/shipping-save-processor/default': { 21 | 'Yireo_GoogleTagManager2/js/mixins/shipping-save-processor-mixin': true 22 | }, 23 | 'Magento_Catalog/js/catalog-add-to-cart': { 24 | 'Yireo_GoogleTagManager2/js/mixins/catalog-add-to-cart-mixin': false 25 | }, 26 | 'Magento_Checkout/js/model/step-navigator': { 27 | 'Yireo_GoogleTagManager2/js/mixins/step-navigator-mixin': true 28 | }, 29 | 'mage/dropdown': { 30 | 'Yireo_GoogleTagManager2/js/mixins/minicart-mixin': true 31 | }, 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /view/frontend/templates/hyva/data-layer.phtml: -------------------------------------------------------------------------------- 1 | getDataLayerViewModel(); 9 | $dataLayerJson = $dataLayerViewModel->getDataLayerAsJson(); 10 | $dataLayerEventsJsonChunks = $dataLayerViewModel->getDataLayerEventsAsJsonChunks(); 11 | ?> 12 | 18 | -------------------------------------------------------------------------------- /view/frontend/templates/hyva/script-logger.phtml: -------------------------------------------------------------------------------- 1 | 3 | 23 | -------------------------------------------------------------------------------- /view/frontend/templates/hyva/script-product-clicks.phtml: -------------------------------------------------------------------------------- 1 | getProductPath(); 16 | ?> 17 | 56 | -------------------------------------------------------------------------------- /view/frontend/templates/hyva/script-pusher.phtml: -------------------------------------------------------------------------------- 1 | 11 | 66 | -------------------------------------------------------------------------------- /view/frontend/templates/iframe.phtml: -------------------------------------------------------------------------------- 1 | getConfig(); 9 | ?> 10 | 11 | -------------------------------------------------------------------------------- /view/frontend/templates/luma/data-layer.phtml: -------------------------------------------------------------------------------- 1 | getDataLayerViewModel(); 9 | $dataLayerJson = $dataLayerViewModel->getDataLayerAsJson(); 10 | $dataLayerEventsJsonChunks = $dataLayerViewModel->getDataLayerEventsAsJsonChunks(); 11 | ?> 12 | 20 | -------------------------------------------------------------------------------- /view/frontend/templates/luma/script-additions.phtml: -------------------------------------------------------------------------------- 1 | getCommonsViewModel(); 17 | ?> 18 | 21 | -------------------------------------------------------------------------------- /view/frontend/templates/luma/script-begin-checkout.phtml: -------------------------------------------------------------------------------- 1 | getData('begin_checkout_event'); 9 | $encodedEvent = json_encode($beginCheckoutEvent->get()); 10 | ?> 11 | 14 | -------------------------------------------------------------------------------- /view/frontend/templates/luma/script-product-clicks.phtml: -------------------------------------------------------------------------------- 1 | getProductPath(); 15 | ?> 16 | 21 | -------------------------------------------------------------------------------- /view/frontend/templates/product/details.phtml: -------------------------------------------------------------------------------- 1 | getDataLayer(); 12 | $productDetails = $block->getProductDetails(); 13 | $product = $block->getProduct(); 14 | if ($product instanceof ProductInterface) { 15 | $productDetails->setProduct($product); 16 | } 17 | 18 | $product = $productDetails->getProduct(); 19 | $productData = $dataLayer->toJson($productDetails->merge()); 20 | ?> 21 | 24 | -------------------------------------------------------------------------------- /view/frontend/templates/script.phtml: -------------------------------------------------------------------------------- 1 | getConfig(); 9 | $gtmIds = explode(',', $config->getId()); 10 | ?> 11 | 12 | waitForUserInteraction() === false) 14 | ? "'load', 'keydown', 'mouseover', 'scroll', 'touchstart', 'wheel'" 15 | : "'keydown', 'mouseover', 'scroll', 'touchstart', 'wheel'"; 16 | ?> 17 | 49 | -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/payment-validator/register.js: -------------------------------------------------------------------------------- 1 | define( 2 | [ 3 | 'uiComponent', 4 | 'Magento_Checkout/js/model/payment/additional-validators', 5 | 'Yireo_GoogleTagManager2/js/checkout/payment-validator/reload-customer-data', 6 | 'yireoGoogleTagManagerLogger' 7 | ], 8 | function (Component, additionalValidators, reloadCustomerDataValidator, logger) { 9 | 'use strict'; 10 | logger('Registering additional payment validator'); 11 | additionalValidators.registerValidator(reloadCustomerDataValidator); 12 | return Component.extend({}); 13 | } 14 | ); -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/payment-validator/reload-customer-data.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'Magento_Customer/js/customer-data', 3 | 'yireoGoogleTagManagerLogger' 4 | ], function (customerData, logger) { 5 | 'use strict'; 6 | return { 7 | validate: function () { 8 | logger('payment-validator-reload-customer-data'); 9 | customerData.reload(['gtm-checkout'], true); 10 | return true; 11 | } 12 | } 13 | }); -------------------------------------------------------------------------------- /view/frontend/web/js/logger.js: -------------------------------------------------------------------------------- 1 | define([], function () { 2 | return function (...args) { 3 | const debug = window.YIREO_GOOGLETAGMANAGER2_DEBUG || false; 4 | if (false === debug) { 5 | return; 6 | } 7 | 8 | var color = 'gray'; 9 | if (args[0].toLowerCase().startsWith('push')) { 10 | color = 'green'; 11 | } 12 | 13 | if (args[0].toLowerCase().startsWith('warning')) { 14 | color = 'orange'; 15 | } 16 | 17 | var css = 'color:white; background-color:' + color + '; padding:1px;' 18 | console.log('%cYireo_GoogleTagManager2', css, ...args); 19 | }; 20 | }); 21 | -------------------------------------------------------------------------------- /view/frontend/web/js/mixins/catalog-add-to-cart-mixin.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'yireoGoogleTagManagerPush' 4 | ], function ($, pusher) { 5 | 'use strict'; 6 | 7 | const enabled = window.YIREO_GOOGLETAGMANAGER2_ENABLED; 8 | if (enabled === null || enabled === undefined) { 9 | return function (targetWidget) { 10 | return $.mage.catalogAddToCart; 11 | }; 12 | } 13 | 14 | var mixin = { 15 | submitForm: function (form) { 16 | const formData = Object.fromEntries(new FormData(form[0]).entries()); 17 | const productId = formData.product; 18 | 19 | const debugClicks = window['YIREO_GOOGLETAGMANAGER2_DEBUG_CLICKS'] || false; 20 | const productData = window['YIREO_GOOGLETAGMANAGER2_PRODUCT_DATA_ID_' + productId] || {}; 21 | productData.quantity = formData.qty || 1; 22 | 23 | const eventData = { 24 | 'event': 'add_to_cart', 25 | 'ecommerce': { 26 | 'items': [productData] 27 | } 28 | }; 29 | 30 | if (debugClicks && confirm("Press to continue with add-to-cart") === false) { 31 | return; 32 | } 33 | 34 | pusher(eventData, 'push [catalog-add-to-cart-mixin.js]'); 35 | return this._super(form); 36 | } 37 | }; 38 | 39 | return function (targetWidget) { 40 | $.widget('mage.catalogAddToCart', targetWidget, mixin); 41 | return $.mage.catalogAddToCart; 42 | }; 43 | }); 44 | -------------------------------------------------------------------------------- /view/frontend/web/js/mixins/minicart-mixin.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | ], function ($) { 4 | 'use strict'; 5 | 6 | const enabled = window.YIREO_GOOGLETAGMANAGER2_ENABLED; 7 | if (enabled === null || enabled === undefined) { 8 | return function (targetWidget) { 9 | return $.mage.dropdownDialog; 10 | }; 11 | } 12 | 13 | var mixin = { 14 | open: function () { 15 | const rt = this._super(); 16 | window.dispatchEvent(new CustomEvent('minicart_collapse')); 17 | return rt; 18 | } 19 | }; 20 | 21 | return function (targetWidget) { 22 | $.widget('mage.dropdownDialog', targetWidget, mixin); 23 | return $.mage.dropdownDialog; 24 | }; 25 | }); 26 | -------------------------------------------------------------------------------- /view/frontend/web/js/mixins/shipping-save-processor-mixin.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'mage/utils/wrapper', 3 | 'Magento_Customer/js/customer-data', 4 | 'yireoGoogleTagManagerLogger' 5 | ], function (wrapper, customerData, logger) { 6 | 'use strict'; 7 | 8 | return function (shippingSaveProcessor) { 9 | shippingSaveProcessor.saveShippingInformation = wrapper.wrapSuper(shippingSaveProcessor.saveShippingInformation, function (type) { 10 | const rt = this._super(type); 11 | rt.done(function() { 12 | logger('customerData reload (shipping-save-processor-mixin.js)', type); 13 | customerData.reload(['gtm-checkout'], true); 14 | }); 15 | 16 | return rt; 17 | }) 18 | 19 | return shippingSaveProcessor; 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /view/frontend/web/js/mixins/step-navigator-mixin.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'mage/utils/wrapper', 3 | 'yireoGoogleTagManagerPush', 4 | 'yireoGoogleTagManagerLogger', 5 | ], function (wrapper, pusher, logger, stepNavigator) { 6 | 'use strict'; 7 | return function (stepNavigator) { 8 | const enabled = window.YIREO_GOOGLETAGMANAGER2_ENABLED; 9 | if (enabled === null || enabled === undefined) { 10 | return stepNavigator; 11 | } 12 | 13 | stepNavigator.steps.subscribe(function (steps) { 14 | const firstStep = steps[0]; 15 | const eventData = window.YIREO_GOOGLETAGMANAGER2_BEGIN_CHECKOUT; 16 | 17 | if (firstStep === undefined || firstStep == null || firstStep.length <= 0) { 18 | logger('Error: No steps detected. Triggering event anyway :o') 19 | pusher(eventData, 'push (page event "begin_checkout") [step-navigator-mixin.js]'); 20 | return; 21 | } 22 | 23 | if (!firstStep.isVisible()) { 24 | return; 25 | } 26 | 27 | if (eventData === null || eventData === undefined) { 28 | logger('skipped "begin_checkout" event because data is empty') 29 | return; 30 | } 31 | 32 | pusher(eventData, 'push (page event "begin_checkout") [step-navigator-mixin.js]'); 33 | }); 34 | 35 | return stepNavigator; 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /view/frontend/web/js/product/clicks.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'yireoGoogleTagManagerPush' 4 | ], function($, pusher) { 5 | return function(config, element) { 6 | const productPath = config.productPath || '.product-items a.product'; 7 | $(productPath).click(function(event) { 8 | const debugClicks = window['YIREO_GOOGLETAGMANAGER2_DEBUG_CLICKS'] || false; 9 | const $parent = $(this).closest('[id^=product-item-info_]'); 10 | const regex = /_(\d+)$/; 11 | const matches = $parent.attr('id').match(regex); 12 | const productId = matches[1]; 13 | const productData = window['YIREO_GOOGLETAGMANAGER2_PRODUCT_DATA_ID_' + productId] || {}; 14 | productData.item_id = productId; 15 | 16 | const eventData = { 17 | 'event': 'select_item', 18 | 'ecommerce': { 19 | 'items': [productData] 20 | } 21 | } 22 | 23 | pusher(eventData, 'push (page event "select_item") [clicks.js]'); 24 | 25 | if (debugClicks && confirm("Press to continue with redirect") === false) { 26 | event.preventDefault(); 27 | } 28 | }); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /view/frontend/web/js/push.js: -------------------------------------------------------------------------------- 1 | define(['yireoGoogleTagManagerLogger'], function (logger) { 2 | return function (eventData, message) { 3 | window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS = window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS || []; 4 | 5 | const metaData = Object.assign({}, eventData.meta); 6 | 7 | const cleanEventData = Object.assign({}, eventData); 8 | if (cleanEventData.meta) { 9 | delete cleanEventData.meta; 10 | } 11 | 12 | if (cleanEventData.length === 0) { 13 | return; 14 | } 15 | 16 | if (metaData && metaData.allowed_pages && metaData.allowed_pages.length > 0 17 | && false === metaData.allowed_pages.some(page => window.location.pathname.includes(page))) { 18 | logger('Warning: Skipping event, not in allowed pages', window.location.pathname, eventData); 19 | return; 20 | } 21 | 22 | // Prevent the same event from being triggered twice, when containing the same data 23 | const eventHash = btoa(encodeURIComponent(JSON.stringify(cleanEventData))); 24 | if (window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS.includes(eventHash)) { 25 | logger('Warning: Event already triggered', eventData); 26 | return; 27 | } 28 | 29 | if (!message) { 30 | message = 'push (unknown) [unknown]'; 31 | } 32 | 33 | logger(message, eventData); 34 | window.dataLayer = window.dataLayer || []; 35 | 36 | if (cleanEventData.ecommerce) { 37 | window.dataLayer.push({ecommerce: null}); 38 | } 39 | 40 | window.dataLayer.push(cleanEventData); 41 | window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS.push(eventHash); 42 | }; 43 | }); 44 | --------------------------------------------------------------------------------