diff --git a/src/EventSubscriber/CommerceEventsSubscriber.php b/src/EventSubscriber/CommerceEventsSubscriber.php index c90dd96..1821afd 100644 --- a/src/EventSubscriber/CommerceEventsSubscriber.php +++ b/src/EventSubscriber/CommerceEventsSubscriber.php @@ -5,6 +5,7 @@ namespace Drupal\commerce_google_tag_manager\EventSubscriber; use Drupal\commerce_cart\Event\CartEntityAddEvent; use Drupal\commerce_cart\Event\CartEvents; use Drupal\commerce_cart\Event\CartOrderItemRemoveEvent; +use Drupal\commerce_cart\Event\CartOrderItemUpdateEvent; use Drupal\commerce_google_tag_manager\EventTrackerService; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\state_machine\Event\WorkflowTransitionEvent; @@ -50,12 +51,16 @@ class CommerceEventsSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents() { return [ CartEvents::CART_ENTITY_ADD => 'trackCartAdd', + CartEvents::CART_ORDER_ITEM_UPDATE => 'trackCartUpdate', CartEvents::CART_ORDER_ITEM_REMOVE => 'trackCartRemove', 'commerce_order.place.post_transition' => 'trackPurchase', // trackProductView should run before Dynamic Page Cache, which has // priority 27. // @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber. - KernelEvents::REQUEST => ['trackProductView', 28], + KernelEvents::REQUEST => [ + ['trackProductView', 28], + ['trackCartView', 28] + ], ]; } @@ -86,6 +91,24 @@ class CommerceEventsSubscriber implements EventSubscriberInterface { } } + /** + * Track the "cartView" event. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event + * The event to view the product. + */ + public function trackCartView(GetResponseEvent $event) { + if ($event->getRequest()->getMethod() === 'GET' && $this->routeMatch->getRouteName() === 'commerce_cart.page') { + + $cart_provider = \Drupal::service('commerce_cart.cart_provider'); + $carts = $cart_provider->getCarts(); + $order = array_shift($carts); + + if($order) + $this->eventTracker->cartViews($order); + } + } + /** * Track the "cartRemove" event. * @@ -96,6 +119,16 @@ class CommerceEventsSubscriber implements EventSubscriberInterface { $this->eventTracker->removeFromCart($event->getOrderItem(), 1); } + /** + * Track the "cartUpdate" event. + * + * @param \Drupal\commerce_cart\Event\CartOrderItemUpdateEvent $event + * The cart event. + */ + public function trackCartUpdate(CartOrderItemUpdateEvent $event) { + $this->eventTracker->updateFromCart($event->getOrderItem(), $event->getOriginalOrderItem(), 1); + } + /** * Track the "purchase" event. * @@ -108,4 +141,4 @@ class CommerceEventsSubscriber implements EventSubscriberInterface { $this->eventTracker->purchase($order); } -} +} \ No newline at end of file diff --git a/src/EventTrackerService.php b/src/EventTrackerService.php index 4689f8f..f26b194 100644 --- a/src/EventTrackerService.php +++ b/src/EventTrackerService.php @@ -23,13 +23,13 @@ use Drupal\commerce\Context; */ 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_PRODUCT_IMPRESSIONS = 'view_item_list'; + const EVENT_PRODUCT_DETAIL_VIEWS = 'view_item'; + const EVENT_PRODUCT_CLICK = 'select_item'; + const EVENT_ADD_CART = 'add_to_cart'; + const EVENT_REMOVE_CART = 'remove_from_cart'; + const EVENT_VIEW_CART = 'view_cart'; + const EVENT_CHECKOUT = 'begin_checkout'; const EVENT_PURCHASE = 'purchase'; /** @@ -37,21 +37,21 @@ class EventTrackerService { * * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ - private $eventDispatcher; + protected $eventDispatcher; /** * The Commerce GTM event storage. * * @var \Drupal\commerce_google_tag_manager\EventStorageService */ - private $eventStorage; + protected $eventStorage; /** * The current user. * * @var \Drupal\Core\Session\AccountInterface */ - private $currentUser; + protected $currentUser; /** * The current store. @@ -105,13 +105,13 @@ class EventTrackerService { $products_data = array_map(function ($product_variation) use ($list) { return array_merge( $this->buildProductFromProductVariation($product_variation)->toArray(), - ['list' => $list]); + ['item_list_name' => $list]); }, $product_variations); $data = [ 'event' => self::EVENT_PRODUCT_IMPRESSIONS, 'ecommerce' => [ - 'impressions' => $products_data, + 'items' => $products_data, ], ]; @@ -130,10 +130,10 @@ class EventTrackerService { $data = [ 'event' => self::EVENT_PRODUCT_DETAIL_VIEWS, 'ecommerce' => [ - 'detail' => [ - 'actionField' => ['list' => $list], - 'products' => $this->buildProductsFromProductVariations($product_variations), - ], + 'items' => array_merge( + $this->buildProductsFromProductVariations($product_variations), + ['item_list_name' => $list] + ), ], ]; @@ -152,10 +152,32 @@ class EventTrackerService { $data = [ 'event' => self::EVENT_PRODUCT_CLICK, 'ecommerce' => [ - 'click' => [ - 'actionField' => ['list' => $list], - 'products' => $this->buildProductsFromProductVariations($product_variations), - ], + 'items' => array_merge( + $this->buildProductsFromProductVariations($product_variations), + ['item_list_name' => $list] + ), + ], + ]; + + $this->eventStorage->addEvent($data); + } + + /** + * Track cart views. + * + * @param \Drupal\commerce_order\Entity\OrderInterface $order + * The commerce cart order being viewed. + */ + public function cartViews($order) { + + if(empty($order->getItems())){ + return; + } + + $data = [ + 'event' => self::EVENT_VIEW_CART, + 'ecommerce' => [ + 'items' => $this->buildProductsFromOrderItems($order->getItems()) ], ]; @@ -176,11 +198,8 @@ class EventTrackerService { $data = [ 'event' => self::EVENT_ADD_CART, 'ecommerce' => [ - 'currencyCode' => $order_item->getTotalPrice()->getCurrencyCode(), - 'add' => [ - 'products' => [ - array_merge($product->toArray(), ['quantity' => $quantity]), - ], + 'items' => [ + array_merge($product->toArray(), ['quantity' => $quantity]), ], ], ]; @@ -202,10 +221,8 @@ class EventTrackerService { $data = [ 'event' => self::EVENT_REMOVE_CART, 'ecommerce' => [ - 'remove' => [ - 'products' => [ - array_merge($product->toArray(), ['quantity' => $quantity]), - ], + 'items' => [ + array_merge($product->toArray(), ['quantity' => $order_item->getQuantity()]), ], ], ]; @@ -213,58 +230,59 @@ class EventTrackerService { $this->eventStorage->addEvent($data); } - /** - * Track a checkout step. + /** + * Track the "updateFromCart" event. * - * @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. + * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item + * The commerce order item removed from the cart. + * @param int $quantity + * The removed quantity. */ - public function checkoutStep($step_index, OrderInterface $order) { + public function updateFromCart(OrderItemInterface $order_item, OrderItemInterface $original_order_item, $quantity) { + $product = $this->buildProductFromOrderItem($order_item); + + $event = self::EVENT_REMOVE_CART; + $qt = $order_item->getQuantity() - $original_order_item->getQuantity(); + if($qt > 0){ + $event = self::EVENT_ADD_CART; + } + $data = [ - 'event' => self::EVENT_CHECKOUT, + 'event' => $event, 'ecommerce' => [ - 'checkout' => [ - 'actionField' => [ - 'step' => $step_index, - ], - 'products' => $this->buildProductsFromOrderItems($order->getItems()), - ], + 'items' => [array_merge($product->toArray(), ['quantity' => abs($qt)])], ], ]; $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. + * Track a checkout step. * - * @param string $step_index + * @param int $step_index * The index of the checkout step (1-based). - * @param string $checkout_option - * The option to track with the given step. + * @param \Drupal\commerce_order\Entity\OrderInterface $order + * The commerce order representing the cart. */ - public function checkoutOption($step_index, $checkout_option) { + public function checkoutStep($step_index, OrderInterface $order) { + + if($step_index > 1){ + return; + } + $data = [ - 'event' => self::EVENT_CHECKOUT_OPTION, + 'event' => self::EVENT_CHECKOUT, 'ecommerce' => [ - 'checkout_option' => [ - 'actionField' => [ - 'step' => $step_index, - 'option' => $checkout_option, - ], + 'items' => [ + $this->buildProductsFromOrderItems($order->getItems()), ], ], ]; $this->eventStorage->addEvent($data); + + // Could use an action to track the step? } /** @@ -278,16 +296,15 @@ class EventTrackerService { '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((float) $order->getTotalPrice()->getNumber()), - 'shipping' => self::formatPrice($this->calculateShipping($order)), - 'tax' => $this->formatPrice($this->calculateTax($order)), - 'coupon' => $this->getCouponCode($order), - ], - 'products' => $this->buildProductsFromOrderItems($order->getItems()), + 'transaction_id' => $order->getOrderNumber(), + 'affiliation' => $order->getStore()->getName(), + // The value should be the total value (incl. tax and shipping). + 'value' => self::formatPrice((float) $order->getTotalPrice()->getNumber()), + 'tax' => $this->formatPrice($this->calculateTax($order)), + 'shipping' => self::formatPrice($this->calculateShipping($order)), + 'currency' => $order->getTotalPrice()->getCurrencyCode(), + 'coupon' => $this->getCouponCode($order), + 'items' => $this->buildProductsFromOrderItems($order->getItems()), ], ], ]; @@ -304,7 +321,7 @@ class EventTrackerService { * @return \Drupal\commerce_google_tag_manager\Product * The Enhanced Ecommerce product. */ - private function buildProductFromOrderItem(OrderItemInterface $order_item) { + protected function buildProductFromOrderItem(OrderItemInterface $order_item) { $purchased_entity = $order_item->getPurchasedEntity(); if ($purchased_entity instanceof ProductVariationInterface) { @@ -313,8 +330,8 @@ class EventTrackerService { else { // The purchased entity is not a product variation. $product = (new Product()) - ->setName($order_item->getTitle()) - ->setId($order_item->getPurchasedEntityId()) + ->setItemName($order_item->getTitle()) + ->setItemId($order_item->getPurchasedEntity()->getSku()) ->setPrice(self::formatPrice((float) $order_item->getTotalPrice()->getNumber())); $event = new AlterProductPurchasedEntityEvent($product, $order_item, $purchased_entity); @@ -333,14 +350,14 @@ class EventTrackerService { * @return \Drupal\commerce_google_tag_manager\Product * The Enhanced Ecommerce product. */ - private function buildProductFromProductVariation(ProductVariationInterface $product_variation) { + protected 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()); + ->setItemName($product_variation->getProduct()->getTitle()) + ->setItemId($product_variation->getSku()) + ->setItemVariant($product_variation->getTitle()); // Get price based on resolver(s). /** @var \Drupal\commerce_price\Price $calculated_price */ @@ -364,7 +381,7 @@ class EventTrackerService { * @return array * An array of EnhancedEcommerce products. */ - private function buildProductsFromOrderItems(array $order_items) { + protected function buildProductsFromOrderItems(array $order_items) { return array_map(function ($order_item) { return array_merge( $this->buildProductFromOrderItem($order_item)->toArray(), @@ -382,7 +399,7 @@ class EventTrackerService { * @return array * An array of EnhancedEcommerce products. */ - private function buildProductsFromProductVariations(array $product_variations) { + protected function buildProductsFromProductVariations(array $product_variations) { return array_map(function ($product_variation) { return $this ->buildProductFromProductVariation($product_variation) @@ -423,7 +440,7 @@ class EventTrackerService { * @return float * The tax costs. */ - private function calculateTax(OrderInterface $order) { + protected function calculateTax(OrderInterface $order) { $tax_adjustments = array_filter($order->collectAdjustments(), function (Adjustment $adjustment) { return ($adjustment->getType() === 'tax') && (!empty($adjustment->getSourceId())); }); @@ -446,7 +463,7 @@ class EventTrackerService { * @return float * The shipping total price. */ - private function calculateShipping(OrderInterface $order) { + protected function calculateShipping(OrderInterface $order) { if ($order->hasField('shipments') && !$order->get('shipments')->isEmpty()) { $total = 0; foreach ($order->get('shipments')->referencedEntities() as $shipment) { @@ -469,7 +486,7 @@ class EventTrackerService { * @return string * The coupon values separated by comma. */ - private function getCouponCode(OrderInterface $order) { + protected function getCouponCode(OrderInterface $order) { if (!$order->hasField('coupons') || $order->get('coupons')->isEmpty()) { return ''; } diff --git a/src/Product.php b/src/Product.php index 8eaf7fa..edc97c8 100644 --- a/src/Product.php +++ b/src/Product.php @@ -12,14 +12,14 @@ class Product { * * @var string */ - private $name; + private $item_name; /** * Unique identifier. * * @var string */ - private $id; + private $item_id; /** * The price. @@ -33,21 +33,56 @@ class Product { * * @var string */ - private $brand; + private $item_brand; /** * The category. * * @var string */ - private $category; + private $item_category; + /** + * The category. + * + * @var string + */ + private $item_category2; + + /** + * The category. + * + * @var string + */ + private $item_category3; + + /** + * The category. + * + * @var string + */ + private $item_category4; + + /** + * The category. + * + * @var string + */ + private $item_category5; + + /** + * The discount. + * + * @var string + */ + private $discount; + /** * The product variation. * * @var string */ - private $variant; + private $item_variant; /** * Collection of dimensions for GA. @@ -73,6 +108,7 @@ class Product { $data = []; foreach ($this as $property => $value) { + if (is_array($value)) { foreach ($value as $i => $v) { $data[rtrim($property, 's') . ($i + 1)] = $v; @@ -92,8 +128,8 @@ class Product { * @return string * The name. */ - public function getName() { - return $this->name; + public function getItemName() { + return $this->item_name; } /** @@ -105,8 +141,8 @@ class Product { * @return \Drupal\commerce_google_tag_manager\Product * The Product object. */ - public function setName($name) { - $this->name = $name; + public function setItemName($name) { + $this->item_name = $name; return $this; } @@ -116,8 +152,8 @@ class Product { * @return string * The unique identifier. */ - public function getId() { - return $this->id; + public function getItemId() { + return $this->item_id; } /** @@ -129,8 +165,8 @@ class Product { * @return \Drupal\commerce_google_tag_manager\Product * The Product object. */ - public function setId($id) { - $this->id = $id; + public function setItemId($id) { + $this->item_id = $id; return $this; } @@ -164,8 +200,8 @@ class Product { * @return string * The brand. */ - public function getBrand() { - return $this->brand; + public function getItemBrand() { + return $this->item_brand; } /** @@ -177,19 +213,139 @@ class Product { * @return \Drupal\commerce_google_tag_manager\Product * The Product object. */ - public function setBrand($brand) { - $this->brand = $brand; + public function setItemBrand($brand) { + $this->item_brand = $brand; + return $this; + } + + /** + * Get the category. + * + * @return string + * The category. + */ + public function getItemCategory() { + return $this->item_category; + } + + /** + * Set the category. + * + * @param string $category + * The category. + * + * @return \Drupal\commerce_google_tag_manager\Product + * The Product object. + */ + public function setItemCategory($category) { + $this->item_category = $category; + return $this; + } + + /** + * Get the category. + * + * @return string + * The category. + */ + public function getItemCategory1() { + return $this->item_category1; + } + + /** + * Set the category. + * + * @param string $category + * The category. + * + * @return \Drupal\commerce_google_tag_manager\Product + * The Product object. + */ + public function setItemCategory1($category) { + $this->item_category1 = $category; + return $this; + } + + /** + * Get the category. + * + * @return string + * The category. + */ + public function getItemCategory2() { + return $this->item_category2; + } + + /** + * Set the category. + * + * @param string $category + * The category. + * + * @return \Drupal\commerce_google_tag_manager\Product + * The Product object. + */ + public function setItemCategory2($category) { + $this->item_category2 = $category; + return $this; + } + + /** + * Get the category. + * + * @return string + * The category. + */ + public function getItemCategory3() { + return $this->item_category3; + } + + /** + * Set the category. + * + * @param string $category + * The category. + * + * @return \Drupal\commerce_google_tag_manager\Product + * The Product object. + */ + public function setItemCategory3($category) { + $this->item_category3 = $category; return $this; } + /** + * Get the category. + * + * @return string + * The category. + */ + public function getItemCategory4() { + return $this->item_category4; + } + /** + * Set the category. + * + * @param string $category + * The category. + * + * @return \Drupal\commerce_google_tag_manager\Product + * The Product object. + */ + public function setItemCategory4($category) { + $this->item_category4 = $category; + return $this; + } + + /** * Get the category. * * @return string * The category. */ - public function getCategory() { - return $this->category; + public function getItemCategory5() { + return $this->item_category5; } /** @@ -201,8 +357,32 @@ class Product { * @return \Drupal\commerce_google_tag_manager\Product * The Product object. */ - public function setCategory($category) { - $this->category = $category; + public function setItemCategory5($category) { + $this->item_category5 = $category; + return $this; + } + + /** + * Get the discount. + * + * @return string + * The discount. + */ + public function getDiscount() { + return $this->discount; + } + + /** + * Set the discount. + * + * @param string $discount + * The discount. + * + * @return \Drupal\commerce_google_tag_manager\Product + * The Product object. + */ + public function setDiscount($discount) { + $this->discount = $discount; return $this; } @@ -212,8 +392,8 @@ class Product { * @return string * The variation. */ - public function getVariant() { - return $this->variant; + public function getItemVariant() { + return $this->item_variant; } /** @@ -225,8 +405,8 @@ class Product { * @return \Drupal\commerce_google_tag_manager\Product * The Product object. */ - public function setVariant($variant) { - $this->variant = $variant; + public function setItemVariant($variant) { + $this->item_variant = $variant; return $this; }