<?php /* * Copyright (C) 2017-present, Meta, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /** * @package FacebookPixelPlugin */ namespace FacebookPixelPlugin\Integration; defined('ABSPATH') or die('Direct access not allowed'); use FacebookPixelPlugin\Core\FacebookPixel; use FacebookPixelPlugin\Core\FacebookPluginUtils; use FacebookPixelPlugin\Core\ServerEventFactory; use FacebookPixelPlugin\Core\FacebookServerSideEvent; use FacebookPixelPlugin\Core\PixelRenderer; use FacebookAds\Object\ServerSide\Content; class FacebookWordpressWooCommerce extends FacebookWordpressIntegrationBase { const PLUGIN_FILE = 'facebook-for-woocommerce/facebook-for-woocommerce.php'; const TRACKING_NAME = 'woocommerce'; // Being consistent with the WooCommerce plugin const FB_ID_PREFIX = 'wc_post_id_'; const DIV_ID_FOR_AJAX_PIXEL_EVENTS = 'fb-pxl-ajax-code'; public static function injectPixelCode() { // Add the hooks only if the WooCommerce plugin is not active if(!self::isFacebookForWooCommerceActive()) { add_action('woocommerce_after_checkout_form', array(__CLASS__, 'trackInitiateCheckout'), 40); add_action( 'woocommerce_add_to_cart', array(__CLASS__, 'trackAddToCartEvent'), 40, 4); add_action( 'woocommerce_thankyou', array(__CLASS__, 'trackPurchaseEvent'), 40); add_action( 'woocommerce_payment_complete', array(__CLASS__, 'trackPurchaseEvent'), 40); add_action( 'woocommerce_after_single_product', array(__CLASS__, 'trackViewContentEvent'), 40); // This div will be used to insert the fbq calls // when an event is triggered by an AJAX request add_action( 'wp_footer', array(__CLASS__, 'addDivForAjaxPixelEvent') ); } } public static function addDivForAjaxPixelEvent(){ echo self::getDivForAjaxPixelEvent(); } public static function getDivForAjaxPixelEvent($content = ''){ return "<div id='".self::DIV_ID_FOR_AJAX_PIXEL_EVENTS."'>" .$content."</div>"; } public static function trackViewContentEvent(){ if(FacebookPluginUtils::isInternalUser()){ return; } global $post; if (!isset($post->ID)){ return; } $product = wc_get_product($post->ID); if(!$product){ return; } $server_event = ServerEventFactory::safeCreateEvent( 'ViewContent', array(__CLASS__, 'createViewContentEvent'), array($product), self::TRACKING_NAME ); FacebookServerSideEvent::getInstance()->track($server_event, false); self::enqueuePixelCode($server_event); } public static function createViewContentEvent($product){ $event_data = self::getPIIFromSession(); $product_id = self::getProductId($product); $content_type = $product->is_type('variable') ? 'product_group' : 'product'; $event_data['content_type'] = $content_type; $event_data['currency'] = \get_woocommerce_currency(); $event_data['value'] = $product->get_price(); $event_data['content_ids'] = array($product_id); $event_data['content_name'] = $product->get_title(); $event_data['content_category'] = self::getProductCategory($product->get_id()); return array_filter($event_data); } private static function getProductCategory($product_id){ $categories = get_the_terms( $product_id, 'product_cat' ); return count($categories) > 0 ? $categories[0]->name : null; } public static function trackPurchaseEvent($order_id) { if (FacebookPluginUtils::isInternalUser()) { return; } $server_event = ServerEventFactory::safeCreateEvent( 'Purchase', array(__CLASS__, 'createPurchaseEvent'), array($order_id), self::TRACKING_NAME ); FacebookServerSideEvent::getInstance()->track($server_event); self::enqueuePixelCode($server_event); } public static function createPurchaseEvent($order_id) { $order = wc_get_order($order_id); $content_type = 'product'; $product_ids = array(); $contents = array(); foreach ($order->get_items() as $item) { $product = wc_get_product($item->get_product_id()); if ('product_group' !== $content_type && $product->is_type('variable')) { $content_type = 'product_group'; } $quantity = $item->get_quantity(); $product_id = self::getProductId($product); $content = new Content(); $content->setProductId($product_id); $content->setQuantity($quantity); $content->setItemPrice($item->get_total() / $quantity); $contents[] = $content; $product_ids[] = $product_id; } $event_data = self::getPiiFromBillingInformation($order); $event_data['content_type'] = $content_type; $event_data['currency'] = \get_woocommerce_currency(); $event_data['value'] = $order->get_total(); $event_data['content_ids'] = $product_ids; $event_data['contents'] = $contents; return $event_data; } public static function trackAddToCartEvent( $cart_item_key, $product_id, $quantity, $variation_id) { if (FacebookPluginUtils::isInternalUser()) { return; } $server_event = ServerEventFactory::safeCreateEvent( 'AddToCart', array(__CLASS__, 'createAddToCartEvent'), array($cart_item_key, $product_id, $quantity), self::TRACKING_NAME ); // When AJAX is used to add an item to the cart // The Conversions API event should be sent inmediately // because the wp_footer action is not executed $is_ajax_request = wp_doing_ajax(); FacebookServerSideEvent::getInstance()->track($server_event, $is_ajax_request); // If it is not an ajax request // We show the pixel call in the footer // Otherwise we add a filter // to modify the ajax response // and show the fbq call in the div #fb-pxl-ajax-code if(!$is_ajax_request){ self::enqueuePixelCode($server_event); } else{ FacebookServerSideEvent::getInstance()->setPendingPixelEvent( 'addPixelCodeToAddToCartFragment', $server_event ); add_filter('woocommerce_add_to_cart_fragments', array(__CLASS__, 'addPixelCodeToAddToCartFragment')); } } public static function addPixelCodeToAddToCartFragment($fragments) { $server_event = FacebookServerSideEvent::getInstance() ->getPendingPixelEvent('addPixelCodeToAddToCartFragment'); if( !is_null($server_event) ){ $pixel_code = self::generatePixelCode($server_event, true); $fragments['#'.self::DIV_ID_FOR_AJAX_PIXEL_EVENTS] = self::getDivForAjaxPixelEvent($pixel_code); } return $fragments; } public static function createAddToCartEvent( $cart_item_key, $product_id, $quantity) { $event_data = self::getPIIFromSession(); $event_data['content_type'] = 'product'; $event_data['currency'] = \get_woocommerce_currency(); $cart_item = self::getCartItem($cart_item_key); if (!empty($cart_item_key)) { $event_data['content_ids'] = array(self::getProductId($cart_item['data'])); $event_data['value'] = self::getAddToCartValue($cart_item, $quantity); } return $event_data; } public static function trackInitiateCheckout() { if (FacebookPluginUtils::isInternalUser()) { return; } $server_event = ServerEventFactory::safeCreateEvent( 'InitiateCheckout', array(__CLASS__, 'createInitiateCheckoutEvent'), array(), self::TRACKING_NAME ); FacebookServerSideEvent::getInstance()->track($server_event); self::enqueuePixelCode($server_event); } public static function createInitiateCheckoutEvent() { $event_data = self::getPIIFromSession(); $event_data['content_type'] = 'product'; $event_data['currency'] = \get_woocommerce_currency(); if ($cart = WC()->cart) { $event_data['num_items'] = $cart->get_cart_contents_count(); $event_data['value'] = $cart->total; $event_data['content_ids'] = self::getContentIds($cart); $event_data['contents'] = self::getContents($cart); } return $event_data; } private static function getPiiFromBillingInformation($order) { $pii = array(); $pii['first_name'] = $order->get_billing_first_name(); $pii['last_name'] = $order->get_billing_last_name(); $pii['email'] = $order->get_billing_email(); $pii['zip'] = $order->get_billing_postcode(); $pii['state'] = $order->get_billing_state(); $pii['country'] = $order->get_billing_country(); $pii['city'] = $order->get_billing_city(); $pii['phone'] = $order->get_billing_phone(); return $pii; } private static function getAddToCartValue($cart_item, $quantity) { if (!empty($cart_item)) { $price = $cart_item['line_total'] / $cart_item['quantity']; return $quantity * $price; } return null; } private static function getCartItem($cart_item_key) { if (WC()->cart) { $cart = WC()->cart->get_cart(); if (!empty($cart) && !empty($cart[$cart_item_key])) { return $cart[$cart_item_key]; } } return null; } private static function getContentIds($cart) { $product_ids = []; foreach ($cart->get_cart() as $item) { if (!empty($item['data'])) { $product_ids[] = self::getProductId($item['data']); } } return $product_ids; } private static function getContents($cart) { $contents = []; foreach ($cart->get_cart() as $item) { if (!empty($item['data']) && !empty($item['quantity'])) { $content = new Content(); $content->setProductId(self::getProductId($item['data'])); $content->setQuantity($item['quantity']); $content->setItemPrice($item['line_total'] / $item['quantity']); $contents[] = $content; } } return $contents; } private static function getProductId($product) { $woo_id = $product->get_id(); return $product->get_sku() ? $product->get_sku() . '_' . $woo_id : self::FB_ID_PREFIX . $woo_id; } private static function getPIIFromSession(){ $event_data = FacebookPluginUtils::getLoggedInUserInfo(); $user_id = get_current_user_id(); if($user_id != 0){ $event_data['city'] = get_user_meta($user_id, 'billing_city', true); $event_data['zip'] = get_user_meta($user_id, 'billing_postcode', true); $event_data['country'] = get_user_meta($user_id, 'billing_country', true); $event_data['state'] = get_user_meta($user_id, 'billing_state', true); $event_data['phone'] = get_user_meta($user_id, 'billing_phone', true); } return array_filter($event_data); } private static function isFacebookForWooCommerceActive() { return in_array( 'facebook-for-woocommerce/facebook-for-woocommerce.php', get_option('active_plugins')); } public static function generatePixelCode($server_event, $script_tag = false){ $code = PixelRenderer::render(array($server_event), self::TRACKING_NAME, $script_tag); $code = sprintf(" <!-- Meta Pixel Event Code --> %s <!-- End Meta Pixel Event Code --> ", $code); return $code; } public static function enqueuePixelCode($server_event){ $code = self::generatePixelCode($server_event, false); wc_enqueue_js($code); return $code; } }