You are here

class EventTrackerService in Commerce Google Tag Manager 8.2

Same name and namespace in other branches
  1. 8 src/EventTrackerService.php \Drupal\commerce_google_tag_manager\EventTrackerService

Track different events from Google's Enhanced Ecommerce.

Hierarchy

Expanded class hierarchy of EventTrackerService

See also

https://developers.google.com/tag-manager/enhanced-ecommerce

7 files declare their use of EventTrackerService
AlterEventDataEventTest.php in tests/src/Unit/AlterEventDataEventTest.php
CommerceEventsSubscriber.php in src/EventSubscriber/CommerceEventsSubscriber.php
EventStorageServiceTest.php in tests/src/Unit/EventStorageServiceTest.php
EventStorageServiceTest.php in tests/src/Kernel/EventStorageServiceTest.php
EventTrackerServiceTest.php in tests/src/Unit/EventTrackerServiceTest.php

... See full list

1 string reference to 'EventTrackerService'
commerce_google_tag_manager.services.yml in ./commerce_google_tag_manager.services.yml
commerce_google_tag_manager.services.yml
1 service uses EventTrackerService
commerce_google_tag_manager.event_tracker in ./commerce_google_tag_manager.services.yml
Drupal\commerce_google_tag_manager\EventTrackerService

File

src/EventTrackerService.php, line 24

Namespace

Drupal\commerce_google_tag_manager
View source
class EventTrackerService {
  const EVENT_PRODUCT_IMPRESSIONS = 'productImpressions';
  const EVENT_PRODUCT_DETAIL_VIEWS = 'productDetailViews';
  const EVENT_PRODUCT_CLICK = 'productClick';
  const EVENT_ADD_CART = 'addToCart';
  const EVENT_REMOVE_CART = 'removeFromCart';
  const EVENT_CHECKOUT = 'checkout';
  const EVENT_CHECKOUT_OPTION = 'checkoutOption';
  const EVENT_PURCHASE = 'purchase';

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  private $eventDispatcher;

  /**
   * The Commerce GTM event storage.
   *
   * @var \Drupal\commerce_google_tag_manager\EventStorageService
   */
  private $eventStorage;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  private $currentUser;

  /**
   * The current store.
   *
   * @var \Drupal\commerce_store\CurrentStoreInterface
   */
  protected $currentStore;

  /**
   * The price calculator.
   *
   * @var \Drupal\commerce_order\PriceCalculatorInterface
   */
  protected $priceCalculator;

  /**
   * Constructs the EventTrackerService service.
   *
   * @param \Drupal\commerce_google_tag_manager\EventStorageService $event_storage
   *   The Commerce GTM event storage.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher.
   * @param \Drupal\commerce_store\CurrentStoreInterface $current_store
   *   The current store.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   * @param \Drupal\commerce_order\PriceCalculatorInterface $price_calculator
   *   The price calculator.
   */
  public function __construct(EventStorageService $event_storage, EventDispatcherInterface $event_dispatcher, CurrentStoreInterface $current_store, AccountInterface $current_user, PriceCalculatorInterface $price_calculator) {
    $this->eventDispatcher = $event_dispatcher;
    $this->eventStorage = $event_storage;
    $this->currentStore = $current_store;
    $this->currentUser = $current_user;
    $this->priceCalculator = $price_calculator;
  }

  /**
   * Track product impressions.
   *
   * @param \Drupal\commerce_product\Entity\ProductVariationInterface[] $product_variations
   *   The commerce product variation entities being viewed.
   * @param string $list
   *   The name of the list showing the products.
   */
  public function productImpressions(array $product_variations, $list = '') {
    $products_data = array_map(function ($product_variation) use ($list) {
      return array_merge($this
        ->buildProductFromProductVariation($product_variation)
        ->toArray(), [
        'list' => $list,
      ]);
    }, $product_variations);
    $data = [
      'event' => self::EVENT_PRODUCT_IMPRESSIONS,
      'ecommerce' => [
        'impressions' => $products_data,
      ],
    ];
    $this->eventStorage
      ->addEvent($data);
  }

  /**
   * Track product detail views.
   *
   * @param \Drupal\commerce_product\Entity\ProductVariation[] $product_variations
   *   The commerce product variations being viewed.
   * @param string $list
   *   An optional name of a list.
   */
  public function productDetailViews(array $product_variations, $list = '') {
    $data = [
      'event' => self::EVENT_PRODUCT_DETAIL_VIEWS,
      'ecommerce' => [
        'detail' => [
          'actionField' => [
            'list' => $list,
          ],
          'products' => $this
            ->buildProductsFromProductVariations($product_variations),
        ],
      ],
    ];
    $this->eventStorage
      ->addEvent($data);
  }

  /**
   * Track a "product click" event.
   *
   * @param array $product_variations
   *   A commerce product variation that was clicked.
   * @param string $list
   *   An optional name of a list.
   */
  public function productClick(array $product_variations, $list = '') {
    $data = [
      'event' => self::EVENT_PRODUCT_CLICK,
      'ecommerce' => [
        'click' => [
          'actionField' => [
            'list' => $list,
          ],
          'products' => $this
            ->buildProductsFromProductVariations($product_variations),
        ],
      ],
    ];
    $this->eventStorage
      ->addEvent($data);
  }

  /**
   * Track the "addToCart" event.
   *
   * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
   *   The oder item added to the cart.
   * @param int $quantity
   *   Quantity added to cart.
   */
  public function addToCart(OrderItemInterface $order_item, $quantity) {
    $product = $this
      ->buildProductFromOrderItem($order_item);
    $data = [
      'event' => self::EVENT_ADD_CART,
      'ecommerce' => [
        'currencyCode' => $order_item
          ->getTotalPrice()
          ->getCurrencyCode(),
        'add' => [
          'products' => [
            array_merge($product
              ->toArray(), [
              'quantity' => $quantity,
            ]),
          ],
        ],
      ],
    ];
    $this->eventStorage
      ->addEvent($data);
  }

  /**
   * Track the "removeFromCart" event.
   *
   * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
   *   The commerce order item removed from the cart.
   * @param int $quantity
   *   The removed quantity.
   */
  public function removeFromCart(OrderItemInterface $order_item, $quantity) {
    $product = $this
      ->buildProductFromOrderItem($order_item);
    $data = [
      'event' => self::EVENT_REMOVE_CART,
      'ecommerce' => [
        'remove' => [
          'products' => [
            array_merge($product
              ->toArray(), [
              'quantity' => $quantity,
            ]),
          ],
        ],
      ],
    ];
    $this->eventStorage
      ->addEvent($data);
  }

  /**
   * Track a checkout step.
   *
   * @param int $step_index
   *   The index of the checkout step (1-based).
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The commerce order representing the cart.
   */
  public function checkoutStep($step_index, OrderInterface $order) {
    $data = [
      'event' => self::EVENT_CHECKOUT,
      'ecommerce' => [
        'checkout' => [
          'actionField' => [
            'step' => $step_index,
          ],
          'products' => $this
            ->buildProductsFromOrderItems($order
            ->getItems()),
        ],
      ],
    ];
    $this->eventStorage
      ->addEvent($data);

    // Throw an event to add possible checkout step options by event listeners.
    $event = new TrackCheckoutStepEvent($step_index, $order);
    $this->eventDispatcher
      ->dispatch(EnhancedEcommerceEvents::TRACK_CHECKOUT_STEP, $event);
  }

  /**
   * Track a checkout option.
   *
   * This allows to track additional metadata for any checkout step.
   *
   * @param string $step_index
   *   The index of the checkout step (1-based).
   * @param string $checkout_option
   *   The option to track with the given step.
   */
  public function checkoutOption($step_index, $checkout_option) {
    $data = [
      'event' => self::EVENT_CHECKOUT_OPTION,
      'ecommerce' => [
        'checkout_option' => [
          'actionField' => [
            'step' => $step_index,
            'option' => $checkout_option,
          ],
        ],
      ],
    ];
    $this->eventStorage
      ->addEvent($data);
  }

  /**
   * Track a purchase of the given order entity.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   A commerce order entity.
   */
  public function purchase(OrderInterface $order) {
    $data = [
      'event' => self::EVENT_PURCHASE,
      'ecommerce' => [
        'purchase' => [
          'actionField' => [
            'id' => $order
              ->getOrderNumber(),
            'affiliation' => $order
              ->getStore()
              ->getName(),
            // The revenu should be the total value (incl. tax and shipping).
            'revenue' => self::formatPrice((double) $order
              ->getTotalPrice()
              ->getNumber()),
            'shipping' => self::formatPrice($this
              ->calculateShipping($order)),
            'tax' => $this
              ->formatPrice($this
              ->calculateTax($order)),
            'coupon' => $this
              ->getCouponCode($order),
          ],
          'products' => $this
            ->buildProductsFromOrderItems($order
            ->getItems()),
        ],
      ],
    ];
    $this->eventStorage
      ->addEvent($data);
  }

  /**
   * Build the Enhanced Ecommerce product from a given commerce order item.
   *
   * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
   *   A commerce order item.
   *
   * @return \Drupal\commerce_google_tag_manager\Product
   *   The Enhanced Ecommerce product.
   */
  private function buildProductFromOrderItem(OrderItemInterface $order_item) {
    $purchased_entity = $order_item
      ->getPurchasedEntity();
    if ($purchased_entity instanceof ProductVariationInterface) {
      $product = $this
        ->buildProductFromProductVariation($purchased_entity);
    }
    else {

      // The purchased entity is not a product variation.
      $product = (new Product())
        ->setName($order_item
        ->getTitle())
        ->setId($order_item
        ->getPurchasedEntityId())
        ->setPrice(self::formatPrice((double) $order_item
        ->getTotalPrice()
        ->getNumber()));
      $event = new AlterProductPurchasedEntityEvent($product, $order_item, $purchased_entity);
      $this->eventDispatcher
        ->dispatch(EnhancedEcommerceEvents::ALTER_PRODUCT_PURCHASED_ENTITY, $event);
    }
    return $product;
  }

  /**
   * Build Enhanced Ecommerce product from a given commerce product variation.
   *
   * @param \Drupal\commerce_product\Entity\ProductVariationInterface $product_variation
   *   A commerce product variation.
   *
   * @return \Drupal\commerce_google_tag_manager\Product
   *   The Enhanced Ecommerce product.
   */
  private function buildProductFromProductVariation(ProductVariationInterface $product_variation) {
    $context = new Context($this->currentUser, $this->currentStore
      ->getStore());
    $product = new Product();
    $product
      ->setName($product_variation
      ->getProduct()
      ->getTitle())
      ->setId($product_variation
      ->getProduct()
      ->id())
      ->setVariant($product_variation
      ->getTitle());

    // Get price based on resolver(s).

    /** @var \Drupal\commerce_price\Price $calculated_price */
    $calculated_price = $this->priceCalculator
      ->calculate($product_variation, 1, $context)
      ->getCalculatedPrice();
    if ($calculated_price) {
      $product
        ->setPrice(self::formatPrice((double) $calculated_price
        ->getNumber()));
    }
    $event = new AlterProductEvent($product, $product_variation);
    $this->eventDispatcher
      ->dispatch(EnhancedEcommerceEvents::ALTER_PRODUCT, $event);
    return $product;
  }

  /**
   * Build the Enhanced Ecommerce products from given commerce order items.
   *
   * @param array $order_items
   *   The commerce order items.
   *
   * @return array
   *   An array of EnhancedEcommerce products.
   */
  private function buildProductsFromOrderItems(array $order_items) {
    return array_map(function ($order_item) {
      return array_merge($this
        ->buildProductFromOrderItem($order_item)
        ->toArray(), [
        'quantity' => (int) $order_item
          ->getQuantity(),
      ]);
    }, $order_items);
  }

  /**
   * Build Enhanced Ecommerce products from given commerce product variations.
   *
   * @param array $product_variations
   *   The commerce product variations.
   *
   * @return array
   *   An array of EnhancedEcommerce products.
   */
  private function buildProductsFromProductVariations(array $product_variations) {
    return array_map(function ($product_variation) {
      return $this
        ->buildProductFromProductVariation($product_variation)
        ->toArray();
    }, $product_variations);
  }

  /**
   * Format the given price into a compliant Google's Enhanced Ecommerce.
   *
   * The given price will be truncate to contain only 2 decimals.
   * No round up are operate, so 11,999 will become 11,99.
   *
   * @param float $price
   *   The price to format.
   *
   * @return string
   *   The formatted price.
   */
  public static function formatPrice($price) {
    if ($price == 0) {
      return '0';
    }

    // Truncate decimals without rounding.
    $number = bcdiv((double) $price, 1, 2);

    // Format the number as requested by Google's Enhanced Ecommerce.
    return number_format($number, 2, '.', '');
  }

  /**
   * Calculate the tax costs from the given order.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order containing potential tax.
   *
   * @return float
   *   The tax costs.
   */
  private function calculateTax(OrderInterface $order) {
    $tax_adjustments = array_filter($order
      ->collectAdjustments(), function (Adjustment $adjustment) {
      return $adjustment
        ->getType() === 'tax' && !empty($adjustment
        ->getSourceId());
    });
    $total = 0;

    /** @var \Drupal\commerce_order\Adjustment $tax_adjustment */
    foreach ($tax_adjustments as $tax_adjustment) {
      $total += (double) $tax_adjustment
        ->getAmount()
        ->getNumber();
    }
    return $total;
  }

  /**
   * Calculate the total shipping costs from the given order.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order containing potential shipping.
   *
   * @return float
   *   The shipping total price.
   */
  private function calculateShipping(OrderInterface $order) {
    if ($order
      ->hasField('shipments') && !$order
      ->get('shipments')
      ->isEmpty()) {
      $total = 0;
      foreach ($order
        ->get('shipments')
        ->referencedEntities() as $shipment) {

        /** @var \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment */
        $total += (double) $shipment
          ->getAmount()
          ->getNumber();
      }
      return $total;
    }
    return 0;
  }

  /**
   * Get the coupon code(s) used with the given commerce order.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order containing potential coupon code(s).
   *
   * @return string
   *   The coupon values separated by comma.
   */
  private function getCouponCode(OrderInterface $order) {
    if (!$order
      ->hasField('coupons') || $order
      ->get('coupons')
      ->isEmpty()) {
      return '';
    }
    $coupon_codes = array_map(function ($coupon) {

      /** @var \Drupal\commerce_promotion\Entity\CouponInterface $coupon */
      return $coupon
        ->getCode();
    }, $order
      ->get('coupons')
      ->referencedEntities());
    if (count($coupon_codes) === 1) {
      return $coupon_codes[0];
    }
    return implode(', ', $coupon_codes);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
EventTrackerService::$currentStore protected property The current store.
EventTrackerService::$currentUser private property The current user.
EventTrackerService::$eventDispatcher private property The event dispatcher.
EventTrackerService::$eventStorage private property The Commerce GTM event storage.
EventTrackerService::$priceCalculator protected property The price calculator.
EventTrackerService::addToCart public function Track the "addToCart" event.
EventTrackerService::buildProductFromOrderItem private function Build the Enhanced Ecommerce product from a given commerce order item.
EventTrackerService::buildProductFromProductVariation private function Build Enhanced Ecommerce product from a given commerce product variation.
EventTrackerService::buildProductsFromOrderItems private function Build the Enhanced Ecommerce products from given commerce order items.
EventTrackerService::buildProductsFromProductVariations private function Build Enhanced Ecommerce products from given commerce product variations.
EventTrackerService::calculateShipping private function Calculate the total shipping costs from the given order.
EventTrackerService::calculateTax private function Calculate the tax costs from the given order.
EventTrackerService::checkoutOption public function Track a checkout option.
EventTrackerService::checkoutStep public function Track a checkout step.
EventTrackerService::EVENT_ADD_CART constant
EventTrackerService::EVENT_CHECKOUT constant
EventTrackerService::EVENT_CHECKOUT_OPTION constant
EventTrackerService::EVENT_PRODUCT_CLICK constant
EventTrackerService::EVENT_PRODUCT_DETAIL_VIEWS constant
EventTrackerService::EVENT_PRODUCT_IMPRESSIONS constant
EventTrackerService::EVENT_PURCHASE constant
EventTrackerService::EVENT_REMOVE_CART constant
EventTrackerService::formatPrice public static function Format the given price into a compliant Google's Enhanced Ecommerce.
EventTrackerService::getCouponCode private function Get the coupon code(s) used with the given commerce order.
EventTrackerService::productClick public function Track a "product click" event.
EventTrackerService::productDetailViews public function Track product detail views.
EventTrackerService::productImpressions public function Track product impressions.
EventTrackerService::purchase public function Track a purchase of the given order entity.
EventTrackerService::removeFromCart public function Track the "removeFromCart" event.
EventTrackerService::__construct public function Constructs the EventTrackerService service.