1511 lines
43 KiB
PHP
1511 lines
43 KiB
PHP
<?php
|
|
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
|
*
|
|
* This source code is licensed under the license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @package MetaCommerce
|
|
*/
|
|
|
|
use WooCommerce\Facebook\Events\Event;
|
|
use WooCommerce\Facebook\Framework\Api\Exception as ApiException;
|
|
use WooCommerce\Facebook\Framework\Helper;
|
|
use WooCommerce\Facebook\Framework\Logger;
|
|
use WooCommerce\Facebook\Integrations\CostOfGoods\CostOfGoods;
|
|
|
|
if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) :
|
|
|
|
if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
|
|
include_once 'includes/fbutils.php';
|
|
}
|
|
|
|
if ( ! class_exists( 'WC_Facebookcommerce_Pixel' ) ) {
|
|
include_once 'facebook-commerce-pixel-event.php';
|
|
}
|
|
|
|
/**
|
|
* Class WC_Facebookcommerce_EventsTracker
|
|
*
|
|
* This class is responsible for tracking events and sending them to Facebook.
|
|
*/
|
|
class WC_Facebookcommerce_EventsTracker {
|
|
/** @var bool disable VO while product is not GA */
|
|
const IS_VO_ENABLED = false;
|
|
|
|
/** @var string URL for the client-side CAPI param builder script */
|
|
const CAPI_PARAM_BUILDER_JS_URL = 'https://unpkg.com/meta-capi-param-builder-clientjs/dist/clientParamBuilder.bundle.js';
|
|
|
|
/** @var \WC_Facebookcommerce_Pixel instance */
|
|
private $pixel;
|
|
|
|
/** @var string name of the session variable used to store search event data */
|
|
private $search_event_data_session_variable = 'wc_facebook_search_event_data';
|
|
|
|
/** @var Event search event instance */
|
|
private $search_event;
|
|
|
|
/** @var array with events tracked */
|
|
private $tracked_events;
|
|
|
|
/** @var array array with epnding events */
|
|
private $pending_events = array();
|
|
|
|
/** @var AAMSettings aam settings instance, used to filter advanced matching fields*/
|
|
private $aam_settings;
|
|
|
|
/** @var bool whether the pixel should be enabled */
|
|
private $is_pixel_enabled;
|
|
|
|
/** @var CostOfGoods CostOfGoods provider instance. Used to calculate the profit margin */
|
|
private $cogs_provider;
|
|
|
|
/** @var array|null Pending pixel event data for Store API response */
|
|
private $pending_store_api_pixel_event = null;
|
|
|
|
/**
|
|
* @var \FacebookAds\ParamBuilder|null shared ParamBuilder instance
|
|
*/
|
|
private static $param_builder = null;
|
|
|
|
/**
|
|
* Order meta keys used by the tracker.
|
|
*/
|
|
const META_PURCHASE_TRACKED_BROWSER = '_meta_purchase_tracked_browser';
|
|
const META_PURCHASE_TRACKED_SERVER = '_meta_purchase_tracked_server';
|
|
const META_EVENT_ID = '_meta_event_id';
|
|
|
|
/**
|
|
* Events tracker constructor.
|
|
*
|
|
* @param array $user_info
|
|
* @param AAMSettings $aam_settings
|
|
*/
|
|
public function __construct( $user_info, $aam_settings ) {
|
|
|
|
if ( ! $this->is_pixel_enabled() ) {
|
|
return;
|
|
}
|
|
|
|
$this->pixel = new \WC_Facebookcommerce_Pixel( $user_info );
|
|
$this->aam_settings = $aam_settings;
|
|
$this->tracked_events = array();
|
|
|
|
// Initialize external JS hooks early so script is enqueued before wp_enqueue_scripts fires.
|
|
\WC_Facebookcommerce_Pixel::init_external_js_hooks();
|
|
|
|
$this->param_builder_server_setup();
|
|
$this->add_hooks();
|
|
$this->cogs_provider = new CostOfGoods();
|
|
}
|
|
|
|
public static function get_param_builder() {
|
|
if ( null === self::$param_builder ) {
|
|
try {
|
|
$site_url = get_site_url();
|
|
self::$param_builder = new \FacebookAds\ParamBuilder( array( $site_url ) );
|
|
|
|
self::$param_builder->processRequest(
|
|
$site_url,
|
|
$_GET,
|
|
$_COOKIE,
|
|
isset( $_SERVER['HTTP_REFERER'] ) ?
|
|
sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) :
|
|
null
|
|
);
|
|
} catch ( \Exception $exception ) {
|
|
Logger::log(
|
|
'Error initializing CAPI Parameter Builder: ' . $exception->getMessage(),
|
|
array(),
|
|
array(
|
|
'should_send_log_to_meta' => true,
|
|
'should_save_log_in_woocommerce' => true,
|
|
'woocommerce_log_level' => \WC_Log_Levels::ERROR,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
return self::$param_builder;
|
|
}
|
|
|
|
/**
|
|
* Initializes the CAPI server side Parameter Builder and sets cookies as needed.
|
|
*/
|
|
public function param_builder_server_setup() {
|
|
try {
|
|
|
|
if ( ! (bool) apply_filters( 'facebook_for_woocommerce_integration_pixel_enabled', true ) ) {
|
|
return;
|
|
}
|
|
|
|
$cookie_to_set = self::get_param_builder()->getCookiesToSet();
|
|
|
|
if ( ! headers_sent() ) {
|
|
foreach ( $cookie_to_set as $cookie ) {
|
|
setcookie(
|
|
$cookie->name,
|
|
$cookie->value,
|
|
time() + $cookie->max_age,
|
|
'/',
|
|
$cookie->domain
|
|
);
|
|
}
|
|
}
|
|
} catch ( \Exception $exception ) {
|
|
Logger::log(
|
|
'Error setting up server side CAPI Parameter Builder: ' . $exception->getMessage(),
|
|
array(),
|
|
array(
|
|
'should_send_log_to_meta' => true,
|
|
'should_save_log_in_woocommerce' => true,
|
|
'woocommerce_log_level' => \WC_Log_Levels::ERROR,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Determines whether the Pixel should be enabled.
|
|
*
|
|
* @since 2.2.0
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function is_pixel_enabled() {
|
|
|
|
if ( null === $this->is_pixel_enabled ) {
|
|
|
|
/**
|
|
* Filters whether the Pixel should be enabled.
|
|
*
|
|
* @param bool $enabled default true
|
|
*/
|
|
$this->is_pixel_enabled = (bool) apply_filters( 'facebook_for_woocommerce_integration_pixel_enabled', true );
|
|
}
|
|
|
|
return $this->is_pixel_enabled;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add events tracker hooks.
|
|
*
|
|
* @since 2.2.0
|
|
*/
|
|
private function add_hooks() {
|
|
|
|
// inject Pixel
|
|
add_action( 'wp_head', array( $this, 'inject_base_pixel' ) );
|
|
add_action( 'wp_footer', array( $this, 'inject_base_pixel_noscript' ) );
|
|
|
|
// set up CAPI Param Builder libraries
|
|
add_action( 'wp_enqueue_scripts', array( $this, 'param_builder_client_setup' ) );
|
|
|
|
// ViewContent for individual products
|
|
add_action( 'woocommerce_after_single_product', array( $this, 'inject_view_content_event' ) );
|
|
add_action( 'woocommerce_after_single_product', array( $this, 'maybe_inject_search_event' ) );
|
|
|
|
// ViewCategory events
|
|
add_action( 'woocommerce_after_shop_loop', array( $this, 'inject_view_category_event' ) );
|
|
|
|
// Search events
|
|
add_action( 'pre_get_posts', array( $this, 'inject_search_event' ) );
|
|
add_filter( 'woocommerce_redirect_single_search_result', array( $this, 'maybe_add_product_search_event_to_session' ) );
|
|
|
|
// AddToCart events
|
|
add_action( 'woocommerce_add_to_cart', array( $this, 'inject_add_to_cart_event' ), 40, 4 );
|
|
// AddToCart while AJAX is enabled (Classic WooCommerce)
|
|
add_action( 'woocommerce_ajax_added_to_cart', array( $this, 'add_filter_for_add_to_cart_fragments' ) );
|
|
// AddToCart for WooCommerce Blocks (Store API)
|
|
if ( did_action( 'woocommerce_blocks_loaded' ) ) {
|
|
$this->register_store_api_endpoint_data();
|
|
} else {
|
|
add_action( 'woocommerce_blocks_loaded', array( $this, 'register_store_api_endpoint_data' ) );
|
|
}
|
|
// AddToCart while using redirect to cart page
|
|
if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add', 'no' ) ) {
|
|
add_action( 'wp_head', array( WC_Facebookcommerce_Utils::class, 'print_deferred_events' ) );
|
|
add_action( 'shutdown', array( WC_Facebookcommerce_Utils::class, 'save_deferred_events' ) );
|
|
}
|
|
|
|
// InitiateCheckout events
|
|
add_action( 'woocommerce_after_checkout_form', array( $this, 'inject_initiate_checkout_event' ) );
|
|
|
|
// InitiateCheckout events for checkout block.
|
|
add_action( 'woocommerce_blocks_checkout_enqueue_data', array( $this, 'inject_initiate_checkout_event' ) );
|
|
|
|
// Purchase and Subscribe events
|
|
add_action( 'woocommerce_new_order', array( $this, 'inject_purchase_event' ), 10 );
|
|
add_action( 'woocommerce_process_shop_order_meta', array( $this, 'inject_purchase_event' ), 20 );
|
|
add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'inject_purchase_event' ), 30 );
|
|
add_action( 'woocommerce_thankyou', array( $this, 'inject_purchase_event' ), 40 );
|
|
|
|
// Lead events through Contact Form 7
|
|
add_action( 'wpcf7_contact_form', array( $this, 'inject_lead_event_hook' ), 11 );
|
|
|
|
// Flush pending events on shutdown
|
|
add_action( 'shutdown', array( $this, 'send_pending_events' ) );
|
|
}
|
|
|
|
|
|
/**
|
|
* Prints the base JavaScript pixel code
|
|
*
|
|
* phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
*/
|
|
public function inject_base_pixel() {
|
|
if ( $this->is_pixel_enabled() ) {
|
|
echo $this->pixel->pixel_base_code();
|
|
$this->inject_page_view_event();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Prints the base <noscript> pixel code.
|
|
*
|
|
* This is necessary to avoid W3 validation errors.
|
|
*
|
|
* phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
*/
|
|
public function inject_base_pixel_noscript() {
|
|
if ( $this->is_pixel_enabled() ) {
|
|
echo $this->pixel->pixel_base_code_noscript();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enqueues the Facebook CAPI Param Builder script.
|
|
*/
|
|
public function param_builder_client_setup() {
|
|
// Client js setup
|
|
if ( ! facebook_for_woocommerce()->get_connection_handler()->is_connected() ) {
|
|
return;
|
|
}
|
|
|
|
wp_enqueue_script(
|
|
'facebook-capi-param-builder',
|
|
self::CAPI_PARAM_BUILDER_JS_URL,
|
|
array(),
|
|
null,
|
|
true
|
|
);
|
|
// Add inline script that executes after the external script has loaded
|
|
wp_add_inline_script(
|
|
'facebook-capi-param-builder',
|
|
'if (typeof clientParamBuilder !== "undefined") {
|
|
clientParamBuilder.processAndCollectAllParams(window.location.href);
|
|
}'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Triggers the PageView event
|
|
*/
|
|
public function inject_page_view_event() {
|
|
if ( ! $this->is_pixel_enabled() ) {
|
|
return;
|
|
}
|
|
|
|
$event_name = 'PageView';
|
|
$event_data = array(
|
|
'event_name' => $event_name,
|
|
'user_data' => $this->pixel->get_user_info(),
|
|
);
|
|
|
|
$event = new Event( $event_data );
|
|
|
|
$this->send_api_event( $event, false );
|
|
|
|
$event_data['event_id'] = $event->get_id();
|
|
|
|
$this->pixel->inject_event( $event_name, $event_data );
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggers ViewCategory for product category listings
|
|
*/
|
|
public function inject_view_category_event() {
|
|
global $wp_query;
|
|
|
|
if ( ! $this->is_pixel_enabled() || ! is_product_category() ) {
|
|
return;
|
|
}
|
|
|
|
$products = array_values(
|
|
array_map(
|
|
function ( $post ) {
|
|
return wc_get_product( $post );
|
|
},
|
|
$wp_query->posts
|
|
)
|
|
);
|
|
|
|
// if any product is a variant, fire the pixel with
|
|
// content_type: product_group
|
|
$content_type = 'product';
|
|
$product_ids = array();
|
|
$contents = array();
|
|
|
|
foreach ( $products as $product ) {
|
|
|
|
if ( ! $product ) {
|
|
continue;
|
|
}
|
|
|
|
$contents[] = array(
|
|
'id' => \WC_Facebookcommerce_Utils::get_fb_retailer_id( $product ),
|
|
'quantity' => 1, // consider category results a quantity of 1
|
|
);
|
|
|
|
$product_ids = array_merge(
|
|
$product_ids,
|
|
WC_Facebookcommerce_Utils::get_fb_content_ids( $product )
|
|
);
|
|
if ( WC_Facebookcommerce_Utils::is_variable_type( $product->get_type() ) ) {
|
|
$content_type = 'product_group';
|
|
}
|
|
}
|
|
|
|
$category = get_queried_object();
|
|
|
|
$event_name = 'ViewCategory';
|
|
$event_data = array(
|
|
'event_name' => $event_name,
|
|
'custom_data' => array(
|
|
'content_name' => $category->name,
|
|
'content_category' => $category->name,
|
|
'content_ids' => wp_json_encode( array_slice( $product_ids, 0, 10 ) ),
|
|
'content_type' => $content_type,
|
|
'contents' => $contents,
|
|
),
|
|
'user_data' => $this->pixel->get_user_info(),
|
|
);
|
|
|
|
$event = new Event( $event_data );
|
|
|
|
$this->send_api_event( $event );
|
|
|
|
$event_data['event_id'] = $event->get_id();
|
|
|
|
$this->pixel->inject_event( $event_name, $event_data, 'trackCustom' );
|
|
}
|
|
|
|
|
|
/**
|
|
* Attempts to add a session variable to indicate that a product search event occurred.
|
|
*
|
|
* The session variable is used to catch search events that have a single result.
|
|
* In those cases WooCommerce redirects customers to the product page instead of showing the search results.
|
|
*
|
|
* The plugin can't inject a Pixel event code on redirect responses, but it can check for the presence of the variable on the product page.
|
|
*
|
|
* This method is hooked to woocommerce_redirect_single_search_result which is triggered right before redirecting.
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 2.1.2
|
|
*
|
|
* @param bool $redirect whether to redirect to the product page
|
|
* @return bool
|
|
*/
|
|
public function maybe_add_product_search_event_to_session( $redirect ) {
|
|
|
|
if ( $redirect && $this->search_event && $this->is_single_search_result() ) {
|
|
|
|
$this->add_product_search_event_to_session( $this->search_event );
|
|
}
|
|
|
|
return $redirect;
|
|
}
|
|
|
|
|
|
/**
|
|
* Determines whether the current request is a product search with a single result.
|
|
*
|
|
* @since 2.1.2
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function is_single_search_result() {
|
|
global $wp_query;
|
|
|
|
return is_search() && 1 === absint( $wp_query->found_posts ) && is_post_type_archive( 'product' );
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds search event data to the session.
|
|
*
|
|
* This does nothing if there is no session set.
|
|
*
|
|
* @since 2.1.2
|
|
*
|
|
* @param Event $event
|
|
*
|
|
* @return void
|
|
*/
|
|
private function add_product_search_event_to_session( Event $event ) {
|
|
|
|
if ( isset( WC()->session ) && is_callable( array( WC()->session, 'has_session' ) ) && WC()->session->has_session() ) {
|
|
WC()->session->set( $this->search_event_data_session_variable, $event->get_data() );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Injects a frontend search event if the session has stored event data.
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 2.1.2
|
|
*/
|
|
public function maybe_inject_search_event() {
|
|
|
|
if ( ! $this->is_pixel_enabled() ) {
|
|
return;
|
|
}
|
|
|
|
$this->search_event = $this->get_product_search_event_from_session();
|
|
|
|
if ( ! $this->search_event ) {
|
|
return;
|
|
}
|
|
|
|
$this->delete_session_data( $this->search_event_data_session_variable );
|
|
$this->actually_inject_search_event();
|
|
}
|
|
|
|
|
|
/**
|
|
* Attempts to create an Event instance for a product search event using session data.
|
|
*
|
|
* @since 2.1.2
|
|
*
|
|
* @return Event|null
|
|
*/
|
|
private function get_product_search_event_from_session() {
|
|
|
|
if ( ! isset( WC()->session ) || ! is_callable( array( WC()->session, 'get' ) ) ) {
|
|
return null;
|
|
}
|
|
|
|
$data = WC()->session->get( $this->search_event_data_session_variable );
|
|
|
|
if ( ! is_array( $data ) || empty( $data ) ) {
|
|
return null;
|
|
}
|
|
|
|
return new Event( $data );
|
|
}
|
|
|
|
|
|
/**
|
|
* Deletes a session variable.
|
|
*
|
|
* @since 2.1.2
|
|
*
|
|
* @param string $key name of the variable to delete
|
|
*/
|
|
private function delete_session_data( $key ) {
|
|
|
|
if ( isset( WC()->session->$key ) ) {
|
|
unset( WC()->session->$key );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggers Search for result pages (deduped)
|
|
*
|
|
* @internal
|
|
*
|
|
* @param WP_Query $query the query object
|
|
*/
|
|
public function inject_search_event( $query ) {
|
|
|
|
if ( ! $this->is_pixel_enabled() || ! $query->is_main_query() ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! is_admin() && is_search() && '' !== get_search_query() && 'product' === get_query_var( 'post_type' ) ) {
|
|
|
|
if ( $this->pixel->is_last_event( 'Search' ) ) {
|
|
return;
|
|
}
|
|
|
|
// needs to run before wc_template_redirect, normally hooked with priority 10
|
|
add_action( 'template_redirect', array( $this, 'send_search_event' ), 5 );
|
|
add_action( 'woocommerce_before_shop_loop', array( $this, 'actually_inject_search_event' ) );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sends a server-side Search event.
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function send_search_event() {
|
|
|
|
$event = $this->get_search_event();
|
|
|
|
if ( null === $event ) {
|
|
return;
|
|
}
|
|
|
|
$this->send_api_event( $event );
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates an Event instance to track a search request.
|
|
*
|
|
* The event instance is stored in memory to return a single instance per request.
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @return Event
|
|
*/
|
|
private function get_search_event() {
|
|
global $wp_query;
|
|
|
|
if ( null === $this->search_event ) {
|
|
|
|
// if any product is a variant, fire the pixel with content_type: product_group
|
|
$content_type = 'product';
|
|
$product_ids = array();
|
|
$contents = array();
|
|
$total_value = 0.00;
|
|
|
|
if ( empty( $wp_query->posts ) ) {
|
|
return null;
|
|
}
|
|
|
|
foreach ( $wp_query->posts as $post ) {
|
|
|
|
$product = wc_get_product( $post );
|
|
|
|
if ( ! $product instanceof \WC_Product ) {
|
|
continue;
|
|
}
|
|
|
|
$product_ids = array_merge( $product_ids, WC_Facebookcommerce_Utils::get_fb_content_ids( $product ) );
|
|
|
|
$contents[] = array(
|
|
'id' => \WC_Facebookcommerce_Utils::get_fb_retailer_id( $product ),
|
|
'quantity' => 1, // consider the search results a quantity of 1
|
|
);
|
|
|
|
$total_value += (float) $product->get_price();
|
|
|
|
if ( WC_Facebookcommerce_Utils::is_variable_type( $product->get_type() ) ) {
|
|
$content_type = 'product_group';
|
|
}
|
|
}
|
|
|
|
$event_data = array(
|
|
'event_name' => 'Search',
|
|
'custom_data' => array(
|
|
'content_type' => $content_type,
|
|
'content_ids' => wp_json_encode( array_slice( $product_ids, 0, 10 ) ),
|
|
'contents' => $contents,
|
|
'search_string' => get_search_query(),
|
|
'value' => Helper::number_format( $total_value ),
|
|
'currency' => get_woocommerce_currency(),
|
|
),
|
|
'user_data' => $this->pixel->get_user_info(),
|
|
);
|
|
|
|
$this->search_event = new Event( $event_data );
|
|
}
|
|
|
|
return $this->search_event;
|
|
}
|
|
|
|
|
|
/**
|
|
* Injects a Search event on result pages.
|
|
*
|
|
* @internal
|
|
*/
|
|
public function actually_inject_search_event() {
|
|
|
|
$event = $this->get_search_event();
|
|
|
|
if ( null === $event ) {
|
|
return;
|
|
}
|
|
|
|
$this->pixel->inject_event(
|
|
$event->get_name(),
|
|
array(
|
|
'event_id' => $event->get_id(),
|
|
'event_name' => $event->get_name(),
|
|
'custom_data' => $event->get_custom_data(),
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggers ViewContent event on product pages
|
|
*
|
|
* @internal
|
|
*/
|
|
public function inject_view_content_event() {
|
|
global $post;
|
|
|
|
if ( ! $this->is_pixel_enabled() || ! isset( $post->ID ) ) {
|
|
return;
|
|
}
|
|
|
|
$product = wc_get_product( $post->ID );
|
|
|
|
if ( ! $product instanceof \WC_Product ) {
|
|
return;
|
|
}
|
|
|
|
// if product is variable or grouped, fire the pixel with content_type: product_group
|
|
if ( $product->is_type( array( 'variable', 'grouped' ) ) ) {
|
|
$content_type = 'product_group';
|
|
} else {
|
|
$content_type = 'product';
|
|
}
|
|
|
|
if ( WC_Facebookcommerce_Utils::is_variable_type( $product->get_type() ) ) {
|
|
$product_price = $product->get_variation_price( 'min' );
|
|
} else {
|
|
$product_price = $product->get_price();
|
|
}
|
|
|
|
$categories = \WC_Facebookcommerce_Utils::get_product_categories( $product->get_id() );
|
|
|
|
$event_data = array(
|
|
'event_name' => 'ViewContent',
|
|
'custom_data' => array(
|
|
'content_name' => \WC_Facebookcommerce_Utils::clean_string( $product->get_title() ),
|
|
'content_ids' => wp_json_encode( \WC_Facebookcommerce_Utils::get_fb_content_ids( $product ) ),
|
|
'content_type' => $content_type,
|
|
'contents' => wp_json_encode(
|
|
array(
|
|
array(
|
|
'id' => \WC_Facebookcommerce_Utils::get_fb_retailer_id( $product ),
|
|
'quantity' => 1,
|
|
),
|
|
)
|
|
),
|
|
'content_category' => $categories['name'],
|
|
'value' => $product_price,
|
|
'currency' => get_woocommerce_currency(),
|
|
),
|
|
'user_data' => $this->pixel->get_user_info(),
|
|
);
|
|
|
|
$event = new Event( $event_data );
|
|
|
|
$this->send_api_event( $event );
|
|
|
|
$event_data['event_id'] = $event->get_id();
|
|
|
|
$this->pixel->inject_event( 'ViewContent', $event_data );
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggers an AddToCart event when a product is added to cart.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param string $cart_item_key the cart item key
|
|
* @param int $product_id the product identifier
|
|
* @param int $quantity the added product quantity
|
|
* @param int $variation_id the product variation identifier
|
|
*/
|
|
public function inject_add_to_cart_event( $cart_item_key, $product_id, $quantity, $variation_id ) {
|
|
|
|
// bail if pixel tracking disabled or invalid variables
|
|
if ( ! $this->is_pixel_enabled() || ! $product_id || ! $quantity ) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Make sure to proceed only if the cart item exists.
|
|
* Some other plugins may clone the WC_Cart object. Calling clone APIs may trigger the 'woocommerce_add_to_cart' action.
|
|
* We want to make sure to proceed only if the cart item exists inside the original WC_Cart object.
|
|
*/
|
|
$cart = WC()->cart;
|
|
if ( ! isset( $cart->cart_contents[ $cart_item_key ] ) ) {
|
|
return;
|
|
}
|
|
// phpcs:ignore Universal.Operators.DisallowShortTernary.Found
|
|
$product = wc_get_product( $variation_id ?: $product_id );
|
|
|
|
// bail if invalid product or error
|
|
if ( ! $product instanceof \WC_Product ) {
|
|
return;
|
|
}
|
|
|
|
// Build custom_data once for reuse
|
|
$custom_data = array(
|
|
'content_ids' => wp_json_encode( \WC_Facebookcommerce_Utils::get_fb_content_ids( $product ) ),
|
|
'content_name' => \WC_Facebookcommerce_Utils::clean_string( $product->get_title() ),
|
|
'content_type' => 'product',
|
|
'contents' => wp_json_encode(
|
|
array(
|
|
array(
|
|
'id' => \WC_Facebookcommerce_Utils::get_fb_retailer_id( $product ),
|
|
'quantity' => $quantity,
|
|
),
|
|
)
|
|
),
|
|
'value' => (float) $product->get_price() * $quantity,
|
|
'currency' => get_woocommerce_currency(),
|
|
);
|
|
|
|
$event_data = array(
|
|
'event_name' => 'AddToCart',
|
|
'custom_data' => $custom_data,
|
|
'user_data' => $this->pixel->get_user_info(),
|
|
);
|
|
|
|
$event = new WooCommerce\Facebook\Events\Event( $event_data );
|
|
|
|
$this->send_api_event( $event );
|
|
|
|
// send the event ID to prevent duplication
|
|
$event_data['event_id'] = $event->get_id();
|
|
|
|
// store the ID in the session to be sent in AJAX JS event tracking as well
|
|
WC()->session->set( 'facebook_for_woocommerce_add_to_cart_event_id', $event->get_id() );
|
|
|
|
// Store pending pixel event for WooCommerce Blocks Store API
|
|
// Reuse custom_data and add event_id for deduplication
|
|
$pending_pixel_params = array_merge( $custom_data, array( 'event_id' => $event->get_id() ) );
|
|
$this->pending_store_api_pixel_event = array(
|
|
'event' => 'AddToCart',
|
|
'params' => $pending_pixel_params,
|
|
);
|
|
|
|
$this->pixel->inject_event( 'AddToCart', $event_data );
|
|
}
|
|
|
|
|
|
/**
|
|
* Setups a filter to add an add to cart fragment whenever a product is added to the cart through Ajax.
|
|
*
|
|
* @see \WC_Facebookcommerce_EventsTracker::add_add_to_cart_event_fragment
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 1.10.2
|
|
*/
|
|
public function add_filter_for_add_to_cart_fragments() {
|
|
|
|
if ( 'no' === get_option( 'woocommerce_cart_redirect_after_add', 'no' ) ) {
|
|
add_filter( 'woocommerce_add_to_cart_fragments', array( $this, 'add_add_to_cart_event_fragment' ) );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds an add to cart fragment to trigger an AddToCart event.
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 1.10.2
|
|
*
|
|
* @param array $fragments add to cart fragments
|
|
* @return array
|
|
*
|
|
* phpcs:disable WordPress.Security.NonceVerification.Missing
|
|
*/
|
|
public function add_add_to_cart_event_fragment( $fragments ) {
|
|
|
|
$product_id = isset( $_POST['product_id'] ) ? (int) $_POST['product_id'] : '';
|
|
$quantity = isset( $_POST['quantity'] ) ? (int) $_POST['quantity'] : '';
|
|
$product = wc_get_product( $product_id );
|
|
|
|
if ( ! $product instanceof \WC_Product || empty( $quantity ) ) {
|
|
return $fragments;
|
|
}
|
|
|
|
if ( $this->is_pixel_enabled() ) {
|
|
|
|
$params = array(
|
|
'content_ids' => wp_json_encode( \WC_Facebookcommerce_Utils::get_fb_content_ids( $product ) ),
|
|
'content_name' => \WC_Facebookcommerce_Utils::clean_string( $product->get_title() ),
|
|
'content_type' => 'product',
|
|
'contents' => wp_json_encode(
|
|
array(
|
|
array(
|
|
'id' => \WC_Facebookcommerce_Utils::get_fb_retailer_id( $product ),
|
|
'quantity' => $quantity,
|
|
),
|
|
)
|
|
),
|
|
'value' => (float) $product->get_price() * $quantity,
|
|
'currency' => get_woocommerce_currency(),
|
|
);
|
|
|
|
// send the event ID to prevent duplication
|
|
$event_id = WC()->session->get( 'facebook_for_woocommerce_add_to_cart_event_id' );
|
|
if ( ! empty( $event_id ) ) {
|
|
$params['event_id'] = $event_id;
|
|
}
|
|
|
|
$script = $this->pixel->get_event_script( 'AddToCart', $params );
|
|
|
|
$fragments['div.wc-facebook-pixel-event-placeholder'] = '<div class="wc-facebook-pixel-event-placeholder">' . $script . '</div>';
|
|
}
|
|
|
|
return $fragments;
|
|
}
|
|
|
|
|
|
/**
|
|
* Setups a filter to add an add to cart fragment to trigger an AddToCart event on added_to_cart JS event.
|
|
*
|
|
* This method is used by code snippets and should not be removed.
|
|
*
|
|
* @see \WC_Facebookcommerce_EventsTracker::add_conditional_add_to_cart_event_fragment
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 1.10.2
|
|
*/
|
|
public function add_filter_for_conditional_add_to_cart_fragment() {
|
|
|
|
if ( 'no' === get_option( 'woocommerce_cart_redirect_after_add', 'no' ) ) {
|
|
add_filter( 'woocommerce_add_to_cart_fragments', array( $this, 'add_conditional_add_to_cart_event_fragment' ) );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds an add to cart fragment to trigger an AddToCart event on added_to_cart JS event.
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 1.10.2
|
|
*
|
|
* @param array $fragments add to cart fragments
|
|
* @return array
|
|
*/
|
|
public function add_conditional_add_to_cart_event_fragment( $fragments ) {
|
|
|
|
if ( $this->is_pixel_enabled() ) {
|
|
$params = array(
|
|
'content_ids' => $this->get_cart_content_ids(),
|
|
'content_name' => $this->get_cart_content_names(),
|
|
'content_type' => 'product',
|
|
'contents' => $this->get_cart_contents(),
|
|
'value' => $this->get_cart_total(),
|
|
'currency' => get_woocommerce_currency(),
|
|
);
|
|
|
|
// send the event ID to prevent duplication
|
|
$event_id = WC()->session->get( 'facebook_for_woocommerce_add_to_cart_event_id' );
|
|
if ( ! empty( $event_id ) ) {
|
|
$params['event_id'] = $event_id;
|
|
}
|
|
|
|
$script = $this->pixel->get_conditional_one_time_event_script( 'AddToCart', $params, 'added_to_cart' );
|
|
|
|
$fragments['div.wc-facebook-pixel-event-placeholder'] = '<div class="wc-facebook-pixel-event-placeholder">' . $script . '</div>';
|
|
}
|
|
|
|
return $fragments;
|
|
}
|
|
|
|
|
|
/**
|
|
* Registers endpoint data for WooCommerce Blocks Store API.
|
|
*
|
|
* This allows us to inject pixel event data into the Store API response
|
|
* so that pixel-events.js can read and fire the event for WooCommerce Blocks.
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 3.x.x
|
|
*/
|
|
public function register_store_api_endpoint_data() {
|
|
if ( ! function_exists( 'woocommerce_store_api_register_endpoint_data' ) ) {
|
|
return;
|
|
}
|
|
|
|
woocommerce_store_api_register_endpoint_data(
|
|
array(
|
|
'endpoint' => \Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema::IDENTIFIER,
|
|
'namespace' => 'facebook-for-woocommerce',
|
|
'data_callback' => array( $this, 'get_store_api_pixel_event_data' ),
|
|
'schema_callback' => array( $this, 'get_store_api_pixel_event_schema' ),
|
|
'schema_type' => ARRAY_A,
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns pixel event data for Store API response.
|
|
*
|
|
* Called by WooCommerce Blocks Store API to include our data in the cart response.
|
|
* The data is read and consumed by pixel-events.js.
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 3.x.x
|
|
*
|
|
* @return array Pixel event data or empty array if no pending event.
|
|
*/
|
|
public function get_store_api_pixel_event_data() {
|
|
if ( ! empty( $this->pending_store_api_pixel_event ) ) {
|
|
$pending_event = $this->pending_store_api_pixel_event;
|
|
$this->pending_store_api_pixel_event = null;
|
|
return $pending_event;
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns schema for pixel event data in Store API.
|
|
*
|
|
* @internal
|
|
*
|
|
* @since 3.x.x
|
|
*
|
|
* @return array Schema definition.
|
|
*/
|
|
public function get_store_api_pixel_event_schema() {
|
|
return array(
|
|
'event' => array(
|
|
'description' => __( 'Facebook Pixel event name', 'facebook-for-woocommerce' ),
|
|
'type' => 'string',
|
|
'context' => array( 'view', 'edit' ),
|
|
'readonly' => true,
|
|
),
|
|
'params' => array(
|
|
'description' => __( 'Facebook Pixel event parameters including event_id for deduplication', 'facebook-for-woocommerce' ),
|
|
'type' => 'object',
|
|
'context' => array( 'view', 'edit' ),
|
|
'readonly' => true,
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggers an InitiateCheckout event when customer reaches checkout page.
|
|
*
|
|
* @internal
|
|
*/
|
|
public function inject_initiate_checkout_event() {
|
|
|
|
if ( ! $this->is_pixel_enabled() || null === WC()->cart || WC()->cart->get_cart_contents_count() === 0 || $this->pixel->is_last_event( 'InitiateCheckout' ) ) {
|
|
return;
|
|
}
|
|
|
|
$event_name = 'InitiateCheckout';
|
|
$event_data = array(
|
|
'event_name' => $event_name,
|
|
'custom_data' => array(
|
|
'num_items' => $this->get_cart_num_items(),
|
|
'content_ids' => $this->get_cart_content_ids(),
|
|
'content_name' => $this->get_cart_content_names(),
|
|
'content_type' => 'product',
|
|
'contents' => $this->get_cart_contents(),
|
|
'value' => $this->get_cart_total(),
|
|
'currency' => get_woocommerce_currency(),
|
|
),
|
|
'user_data' => $this->pixel->get_user_info(),
|
|
);
|
|
|
|
// if there is only one item in the cart, send its first category
|
|
$cart = WC()->cart;
|
|
if ( ( $cart ) && count( $cart->get_cart() ) === 1 ) {
|
|
|
|
$item = current( $cart->get_cart() );
|
|
|
|
if ( isset( $item['data'] ) && $item['data'] instanceof \WC_Product ) {
|
|
|
|
$categories = \WC_Facebookcommerce_Utils::get_product_categories( $item['data']->get_id() );
|
|
|
|
if ( ! empty( $categories['name'] ) ) {
|
|
$event_data['custom_data']['content_category'] = $categories['name'];
|
|
}
|
|
}
|
|
}
|
|
|
|
$event = new Event( $event_data );
|
|
|
|
$this->send_api_event( $event );
|
|
|
|
$event_data['event_id'] = $event->get_id();
|
|
|
|
$this->pixel->inject_event( $event_name, $event_data );
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggers a Purchase event when checkout is completed.
|
|
*
|
|
* This may happen either when:
|
|
* - WooCommerce signals a payment transaction complete (most gateways)
|
|
* - The order status is changed through the Woo dashboard to Processing or Completed
|
|
* - The Payment Completed event is fired, which happens in case of some external payment gateways.
|
|
* - Customer reaches Thank You page skipping payment (for gateways that do not require payment, e.g. Cheque, BACS, Cash on delivery...)
|
|
*
|
|
* The method checks if the event was not triggered already avoiding a duplicate.
|
|
* Finally, if the order contains subscriptions, it will also track an associated Subscription event.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param int $order_id order identifier
|
|
*/
|
|
public function inject_purchase_event( $order_id ) {
|
|
|
|
if ( \WC_Facebookcommerce_Utils::is_admin_user() ) {
|
|
return;
|
|
}
|
|
|
|
$event_name = 'Purchase';
|
|
|
|
$valid_purchase_order_states = array( 'processing', 'completed', 'on-hold', 'pending' );
|
|
|
|
if ( ! $this->is_pixel_enabled() ) {
|
|
return;
|
|
}
|
|
|
|
$order = wc_get_order( $order_id );
|
|
|
|
if ( ! $order ) {
|
|
return;
|
|
}
|
|
|
|
// Log which hook triggered this purchase event.
|
|
$hook_name = current_action();
|
|
|
|
// Determine if this is a browser or server event.
|
|
$is_browser = 'woocommerce_thankyou' === $hook_name;
|
|
|
|
// Capture whether CAPI has already been sent before we set any meta.
|
|
$capi_already_sent = $order->meta_exists( self::META_PURCHASE_TRACKED_SERVER );
|
|
|
|
// If the event is triggered by a hook that is not related to the browser, it is a server event.
|
|
$meta_flag = $is_browser ? self::META_PURCHASE_TRACKED_BROWSER : self::META_PURCHASE_TRACKED_SERVER;
|
|
|
|
// Get the status of the order to ensure we track the actual purchases and not the ones that have a failed payment.
|
|
$order_state = $order->get_status();
|
|
|
|
// Return if this Purchase event order state is invalid.
|
|
if ( ! in_array( $order_state, $valid_purchase_order_states, true ) ) {
|
|
return;
|
|
}
|
|
|
|
// Return if this Purchase event has already been tracked for this context (browser or server).
|
|
if (
|
|
( $is_browser && $order->meta_exists( self::META_PURCHASE_TRACKED_BROWSER ) ) ||
|
|
( ! $is_browser && $order->meta_exists( self::META_PURCHASE_TRACKED_SERVER ) )
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Use a session flag to ensure this Purchase event is not tracked multiple times along multiple processes.
|
|
$purchase_tracked_flag = '_wc_' . facebook_for_woocommerce()->get_id() . '_purchase_tracked_' . $order_id . '_' . ( $is_browser ? 'browser' : 'server' );
|
|
|
|
// Return if this Purchase event has already been tracked.
|
|
if ( 'yes' === get_transient( $purchase_tracked_flag ) ) {
|
|
return;
|
|
}
|
|
|
|
// Ensure a single event_id is shared across browser and server for deduplication.
|
|
$event_id = $order->get_meta( self::META_EVENT_ID );
|
|
|
|
if ( empty( $event_id ) ) {
|
|
$temp_event = new Event( [] );
|
|
$event_id = $temp_event->get_id();
|
|
$order->add_meta_data( self::META_EVENT_ID, $event_id, true );
|
|
}
|
|
|
|
// Mark the order as tracked for the session.
|
|
set_transient( $purchase_tracked_flag, 'yes', 45 * MINUTE_IN_SECONDS );
|
|
|
|
// Mark the order as tracked for the context (browser or server).
|
|
$order->add_meta_data( $meta_flag, true, true );
|
|
|
|
// Save the metadata.
|
|
$order->save();
|
|
|
|
Logger::log(
|
|
'Purchase event fired for order ' . $order_id . ' by hook ' . $hook_name . ' (context: ' . ( $is_browser ? 'browser' : 'server' ) . ').',
|
|
array(),
|
|
array(
|
|
'should_send_log_to_meta' => false,
|
|
'should_save_log_in_woocommerce' => true,
|
|
'woocommerce_log_level' => \WC_Log_Levels::INFO,
|
|
)
|
|
);
|
|
|
|
$content_type = 'product';
|
|
$contents = array();
|
|
$product_ids = array( array() );
|
|
$product_names = array();
|
|
$products = array();
|
|
|
|
foreach ( $order->get_items() as $item ) {
|
|
|
|
$product = $item->get_product();
|
|
|
|
if ( $product ) {
|
|
$product_ids[] = \WC_Facebookcommerce_Utils::get_fb_content_ids( $product );
|
|
$product_names[] = \WC_Facebookcommerce_Utils::clean_string( $product->get_title() );
|
|
|
|
if ( 'product_group' !== $content_type && $product->is_type( 'variable' ) ) {
|
|
$content_type = 'product_group';
|
|
}
|
|
|
|
$quantity = $item->get_quantity();
|
|
$products[] = array(
|
|
'product' => $product,
|
|
'qty' => $quantity,
|
|
);
|
|
|
|
$content = new \stdClass();
|
|
|
|
$content->id = \WC_Facebookcommerce_Utils::get_fb_retailer_id( $product );
|
|
$content->quantity = $quantity;
|
|
|
|
$contents[] = $content;
|
|
}
|
|
}
|
|
|
|
// Advanced matching information is extracted from the order
|
|
$event_data = array(
|
|
'event_name' => $event_name,
|
|
'custom_data' => array(
|
|
'content_ids' => wp_json_encode( array_merge( ...$product_ids ) ),
|
|
'content_name' => wp_json_encode( $product_names ),
|
|
'contents' => wp_json_encode( $contents ),
|
|
'content_type' => $content_type,
|
|
'value' => $order->get_total(),
|
|
'currency' => ( method_exists( $order, 'get_currency' ) ? $order->get_currency() : get_woocommerce_currency() ),
|
|
'order_id' => $order_id,
|
|
),
|
|
'user_data' => $this->get_user_data_from_billing_address( $order ),
|
|
'event_id' => $event_id,
|
|
);
|
|
|
|
if ( self::IS_VO_ENABLED ) {
|
|
$cogs = $this->cogs_provider->calculate_cogs_for_products( $products );
|
|
|
|
if ( false !== $cogs ) {
|
|
$order_value_excluding_tax_including_discounts = $order->get_total()
|
|
- $order->get_total_tax()
|
|
- $order->get_shipping_total()
|
|
- $order->get_shipping_tax();
|
|
|
|
$net_profit = $order_value_excluding_tax_including_discounts - $cogs;
|
|
if ( $net_profit > 0 ) {
|
|
$event_data['custom_data']['net_revenue'] = \WC_Facebookcommerce_Utils::truncate_float_number( $net_profit, 2 );
|
|
}
|
|
}
|
|
}
|
|
|
|
$event = new Event( $event_data );
|
|
|
|
// Send CAPI event if not already sent. Events with the same event_id get deduplicated on Meta's side.
|
|
if ( ! $capi_already_sent ) {
|
|
$this->send_api_event( $event );
|
|
}
|
|
|
|
$this->pixel->inject_event( $event_name, $event_data );
|
|
|
|
$this->inject_subscribe_event( $order_id );
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggers a Subscribe event when a given order contains subscription products.
|
|
*
|
|
* @see \WC_Facebookcommerce_EventsTracker::inject_purchase_event()
|
|
*
|
|
* @internal
|
|
*
|
|
* @param int $order_id order identifier
|
|
*/
|
|
public function inject_subscribe_event( $order_id ) {
|
|
|
|
if ( ! function_exists( 'wcs_get_subscriptions_for_order' ) || ! $this->is_pixel_enabled() || $this->pixel->is_last_event( 'Subscribe' ) ) {
|
|
return;
|
|
}
|
|
|
|
foreach ( wcs_get_subscriptions_for_order( $order_id ) as $subscription ) {
|
|
|
|
// TODO consider 'StartTrial' event for free trial Subscriptions, which is the same as here (minus sign_up_fee) and tracks "when a person starts a free trial of a product or service" {FN 2020-03-20}
|
|
$event_name = 'Subscribe';
|
|
|
|
// TODO consider including (int|float) 'predicted_ltv': "Predicted lifetime value of a subscriber as defined by the advertiser and expressed as an exact value." {FN 2020-03-20}
|
|
$event_data = array(
|
|
'event_name' => $event_name,
|
|
'custom_data' => array(
|
|
'sign_up_fee' => $subscription->get_sign_up_fee(),
|
|
'value' => $subscription->get_total(),
|
|
'currency' => ( method_exists( $subscription, 'get_currency' ) ? $subscription->get_currency() : get_woocommerce_currency() ),
|
|
),
|
|
'user_data' => $this->pixel->get_user_info(),
|
|
);
|
|
|
|
$event = new Event( $event_data );
|
|
|
|
$this->send_api_event( $event );
|
|
|
|
$event_data['event_id'] = $event->get_id();
|
|
|
|
$this->pixel->inject_event( $event_name, $event_data );
|
|
}
|
|
}
|
|
|
|
|
|
/** Contact Form 7 Support **/
|
|
public function inject_lead_event_hook() {
|
|
add_action( 'wp_footer', array( $this, 'inject_lead_event' ), 11 );
|
|
}
|
|
|
|
public function inject_lead_event() {
|
|
if ( ! is_admin() ) {
|
|
$this->pixel->inject_conditional_event(
|
|
'Lead',
|
|
array(),
|
|
'wpcf7submit',
|
|
'{ em: event.detail.inputs.filter(ele => ele.name.includes("email"))[0].value }'
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sends an API event.
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @param Event $event event object
|
|
* @param bool $send_now optional, defaults to true
|
|
*/
|
|
protected function send_api_event( Event $event, bool $send_now = true ) {
|
|
$this->tracked_events[] = $event;
|
|
|
|
if ( $send_now ) {
|
|
try {
|
|
facebook_for_woocommerce()->get_api()->send_pixel_events( facebook_for_woocommerce()->get_integration()->get_facebook_pixel_id(), array( $event ) );
|
|
} catch ( ApiException $exception ) {
|
|
facebook_for_woocommerce()->log( 'Could not send Pixel event: ' . $exception->getMessage() );
|
|
}
|
|
} else {
|
|
$this->pending_events[] = $event;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the cart content items count.
|
|
*
|
|
* @since 1.10.2
|
|
*
|
|
* @return int
|
|
*/
|
|
private function get_cart_num_items() {
|
|
|
|
return WC()->cart ? WC()->cart->get_cart_contents_count() : 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets all content IDs from cart.
|
|
*
|
|
* @since 1.10.2
|
|
*
|
|
* @return string JSON data
|
|
*/
|
|
private function get_cart_content_ids() {
|
|
|
|
$product_ids = array( array() );
|
|
|
|
$cart = WC()->cart;
|
|
if ( $cart ) {
|
|
|
|
foreach ( $cart->get_cart() as $item ) {
|
|
|
|
if ( isset( $item['data'] ) && $item['data'] instanceof \WC_Product ) {
|
|
|
|
$product_ids[] = \WC_Facebookcommerce_Utils::get_fb_content_ids( $item['data'] );
|
|
}
|
|
}
|
|
}
|
|
|
|
return wp_json_encode( array_unique( array_merge( ...$product_ids ) ) );
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets all content names from cart.
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @return string JSON data
|
|
*/
|
|
private function get_cart_content_names() {
|
|
|
|
$product_names = array();
|
|
|
|
$cart = WC()->cart;
|
|
if ( $cart ) {
|
|
|
|
foreach ( $cart->get_cart() as $item ) {
|
|
|
|
if ( isset( $item['data'] ) && $item['data'] instanceof \WC_Product ) {
|
|
|
|
$product_names[] = \WC_Facebookcommerce_Utils::clean_string( $item['data']->get_title() );
|
|
}
|
|
}
|
|
}
|
|
|
|
return wp_json_encode( array_unique( $product_names ) );
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the cart content data.
|
|
*
|
|
* @since 1.10.2
|
|
*
|
|
* @return string JSON data
|
|
*/
|
|
private function get_cart_contents() {
|
|
|
|
$cart_contents = array();
|
|
|
|
$cart = WC()->cart;
|
|
if ( $cart ) {
|
|
|
|
foreach ( $cart->get_cart() as $item ) {
|
|
|
|
if ( ! isset( $item['data'], $item['quantity'] ) || ! $item['data'] instanceof \WC_Product ) {
|
|
continue;
|
|
}
|
|
|
|
$content = new \stdClass();
|
|
|
|
$content->id = \WC_Facebookcommerce_Utils::get_fb_retailer_id( $item['data'] );
|
|
$content->quantity = $item['quantity'];
|
|
|
|
$cart_contents[] = $content;
|
|
}
|
|
}
|
|
|
|
return wp_json_encode( $cart_contents );
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the cart total.
|
|
*
|
|
* @return float|int
|
|
*/
|
|
private function get_cart_total() {
|
|
|
|
return WC()->cart ? WC()->cart->total : 0;
|
|
}
|
|
|
|
/**
|
|
* Gets advanced matching information from a given order
|
|
*
|
|
* @since 2.0.3
|
|
*
|
|
* @param \WC_Order $order
|
|
*
|
|
* @return array
|
|
*/
|
|
private function get_user_data_from_billing_address( $order ) {
|
|
if ( null === $this->aam_settings || ! $this->aam_settings->get_enable_automatic_matching() ) {
|
|
return array();
|
|
}
|
|
$user_data = $this->pixel->get_user_info();
|
|
self::update_array_if_not_null( $order->get_billing_first_name(), $user_data, 'fn' );
|
|
self::update_array_if_not_null( $order->get_billing_last_name(), $user_data, 'ln' );
|
|
self::update_array_if_not_null( $order->get_billing_email(), $user_data, 'em' );
|
|
self::update_array_if_not_null( $order->get_billing_postcode(), $user_data, 'zp' );
|
|
self::update_array_if_not_null( $order->get_billing_state(), $user_data, 'st' );
|
|
self::update_array_if_not_null( $order->get_billing_country(), $user_data, 'country' );
|
|
self::update_array_if_not_null( $order->get_billing_city(), $user_data, 'ct' );
|
|
self::update_array_if_not_null( $order->get_billing_phone(), $user_data, 'ph' );
|
|
self::update_array_if_not_null( strval( $order->get_user_id() ), $user_data, 'external_id' );
|
|
|
|
foreach ( $user_data as $field => $value ) {
|
|
if ( null === $value || '' === $value ||
|
|
! in_array( $field, $this->aam_settings->get_enabled_automatic_matching_fields(), true )
|
|
) {
|
|
unset( $user_data[ $field ] );
|
|
}
|
|
}
|
|
|
|
return $user_data;
|
|
}
|
|
|
|
/**
|
|
* Checks the value, if it's not null, updates the array at array_key with value
|
|
*
|
|
* @param mixed $value
|
|
* @param array $array
|
|
* @param string $array_key
|
|
*/
|
|
private static function update_array_if_not_null( $value, &$array, $array_key ) {
|
|
if ( ! empty( $value ) ) {
|
|
$array[ $array_key ] = $value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the events tracked by this object
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_tracked_events() {
|
|
return $this->tracked_events;
|
|
}
|
|
|
|
/**
|
|
* Gets the pending events awaiting to be sent
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_pending_events() {
|
|
return $this->pending_events;
|
|
}
|
|
|
|
/**
|
|
* Send pending events.
|
|
*/
|
|
public function send_pending_events() {
|
|
|
|
$pending_events = $this->get_pending_events();
|
|
|
|
if ( empty( $pending_events ) ) {
|
|
return;
|
|
}
|
|
|
|
foreach ( $pending_events as $event ) {
|
|
|
|
$this->send_api_event( $event );
|
|
}
|
|
}
|
|
}
|
|
|
|
endif;
|