From 5cab33bc10ec81ec1d8a7fbc55caace9f9df27cb Mon Sep 17 00:00:00 2001 From: Dima Date: Fri, 30 Jun 2023 17:16:19 +0300 Subject: [PATCH 01/32] Add Pinterest Conversions API integration. --- src/API/Conversions.php | 76 +++++++++++++++++++ .../PinterestConversionsEventIdProvider.php | 68 +++++++++++++++++ src/Tracking.php | 9 ++- 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/API/Conversions.php create mode 100644 src/API/PinterestConversionsEventIdProvider.php diff --git a/src/API/Conversions.php b/src/API/Conversions.php new file mode 100644 index 000000000..a6bc447aa --- /dev/null +++ b/src/API/Conversions.php @@ -0,0 +1,76 @@ + '', + 'event_name' => $event_name, + 'action_source' => 'website', + 'event_time' => time(), + 'user_data' => [ + 'client_ip_address' => '', + 'client_user_agent' => '', + ], + 'event_id' => PinterestConversionsEventIdProvider::get_event_id( $event_name ), + 'event_source_url' => '', + + ] + ); + + try { + APIV5::make_request( + '', + 'POST', + $data + ); + } catch ( Exception $e ) { + // Do nothing. + } + } + + /** + * Custom data related to add to cart and checkout events. + * + * @since x.x.x + * @link https://developers.pinterest.com/docs/conversions/best/#Required#Custom%20data%20object + * + * @param array $event_data + * @return array|string[][] + */ + private static function add_cart_and_checkout_custom_data( array $event_data ): array { + return array_merge( + $event_data, + [ + 'custom_data' => [ + 'currency' => '', + 'value' => '', + 'content_ids' => '', + 'contents' => '', + 'num_items' => '', + 'order_id' => '', + ], + ] + ); + } + + private static function add_search_custom_data( array $event_data ): array { + return array_merge( + $event_data, + [ + 'custom_data' => [ + 'search_string' => '', + ], + ] + ); + } +} diff --git a/src/API/PinterestConversionsEventIdProvider.php b/src/API/PinterestConversionsEventIdProvider.php new file mode 100644 index 000000000..176eb7535 --- /dev/null +++ b/src/API/PinterestConversionsEventIdProvider.php @@ -0,0 +1,68 @@ + 'page_view', + 'addtocart' => 'add_to_cart', + 'checkout' => 'checkout', + 'lead' => 'lead', + 'purchase' => 'purchase', + 'search' => 'search', + 'viewcategory' => 'view_category', + 'viewitem' => 'view_item', + 'viewsearchresults' => 'view_search_results', + ]; + + /** + * @since x.x.x + * + * @param string $event_name + * @return string + */ + public static function get_event_id( string $event_name ): string { + $event_name = static::$tag_api_name_map[ strtolower( $event_name ) ] ?? $event_name; + return static::$event_ids[ $event_name ] ?? static::generate_event_id( $event_name ); + } + + /** + * @since x.x.x + * + * @param string $event_name + * @return string + */ + private static function generate_event_id( string $event_name ): string { + $id = uniqid( 'pinterest-for-woocommerce-conversions-event-id-for-' . $event_name ); + static::$event_ids[ $event_name ] = $id; + return $id; + } + + /** + * @since x.x.x + * + * @param string $pinterest_tag_event_name + * @return mixed|string + */ + public static function get_event_name_by_pinterest_tag_event_name( string $pinterest_tag_event_name ) { + return static::$tag_api_name_map[ strtolower( $pinterest_tag_event_name ) ] ?? $pinterest_tag_event_name; + } +} diff --git a/src/Tracking.php b/src/Tracking.php index d9d61d827..0732da5ca 100644 --- a/src/Tracking.php +++ b/src/Tracking.php @@ -13,6 +13,8 @@ } use Automattic\WooCommerce\Pinterest\API\AdvertiserConnect; +use Conversions; +use PinterestConversionsEventIdProvider; use WC_Product; use Automattic\WooCommerce\Utilities\NumberUtil; use \Premmerce\WooCommercePinterest\PinterestPlugin; @@ -504,10 +506,15 @@ private static function category_visit_event() { * @return void */ private static function add_event( $event, $data = array() ) { - $action = did_action( 'wp_head' ) ? 'print_event' : 'enqueue_event'; + + // Adding event_id to data array as part of Pinterest API for Conversions even deduplication program. + $data['event_id'] = PinterestConversionsEventIdProvider::get_event_id( $event ); + call_user_func_array( array( __CLASS__, $action ), array( $event, $data ) ); + // Firing Pinterest Conversions event as well. + Conversions::add_event( $event, $data ); } From fa1fb3c08fc6f5685c77c4387917cee5fcf8bf66 Mon Sep 17 00:00:00 2001 From: Dima Date: Fri, 30 Jun 2023 21:50:52 +0300 Subject: [PATCH 02/32] Update Pinterest conversions code with a new code structure. --- src/API/Conversions.php | 85 ++++++++++------ src/API/Conversions/CartData.php | 96 +++++++++++++++++++ src/API/Conversions/CustomData.php | 12 +++ src/API/Conversions/NoData.php | 10 ++ .../PinterestConversionsEventIdProvider.php | 18 +++- src/API/Conversions/SearchData.php | 28 ++++++ src/API/Conversions/UserData.php | 40 ++++++++ src/Tracking.php | 22 ++++- 8 files changed, 276 insertions(+), 35 deletions(-) create mode 100644 src/API/Conversions/CartData.php create mode 100644 src/API/Conversions/CustomData.php create mode 100644 src/API/Conversions/NoData.php rename src/API/{ => Conversions}/PinterestConversionsEventIdProvider.php (81%) create mode 100644 src/API/Conversions/SearchData.php create mode 100644 src/API/Conversions/UserData.php diff --git a/src/API/Conversions.php b/src/API/Conversions.php index a6bc447aa..a4e6a098d 100644 --- a/src/API/Conversions.php +++ b/src/API/Conversions.php @@ -1,30 +1,51 @@ user_data = $user_data; + $this->custom_data = $custom_data; + } + /** * An array of event names to event IDs map for the Pinterest Conversions API and Pinterest Tag. * + * @since x.x.x + * * @param string $event_name * @param array $data */ - public static function add_event( string $event_name, array $data = [] ) { + public function add_event( string $event_name, array $data = array() ) { $data = array_merge( $data, - [ + array( 'ad_account_id' => '', 'event_name' => $event_name, 'action_source' => 'website', 'event_time' => time(), - 'user_data' => [ - 'client_ip_address' => '', - 'client_user_agent' => '', - ], - 'event_id' => PinterestConversionsEventIdProvider::get_event_id( $event_name ), + 'user_data' => array( + 'client_ip_address' => $this->user_data->get_client_ip_address(), + 'client_user_agent' => $this->user_data->get_client_user_agent(), + ), + 'event_id' => PinterestConversionsEventIdProvider::get_event_id( $event_name ), 'event_source_url' => '', - - ] + ) ); try { @@ -39,7 +60,7 @@ public static function add_event( string $event_name, array $data = [] ) { } /** - * Custom data related to add to cart and checkout events. + * Adds custom data related to add to cart and checkout events. * * @since x.x.x * @link https://developers.pinterest.com/docs/conversions/best/#Required#Custom%20data%20object @@ -47,30 +68,38 @@ public static function add_event( string $event_name, array $data = [] ) { * @param array $event_data * @return array|string[][] */ - private static function add_cart_and_checkout_custom_data( array $event_data ): array { + private function add_cart_and_checkout_custom_data( array $event_data ): array { return array_merge( $event_data, - [ - 'custom_data' => [ - 'currency' => '', - 'value' => '', - 'content_ids' => '', - 'contents' => '', - 'num_items' => '', - 'order_id' => '', - ], - ] + array( + 'custom_data' => array( + 'currency' => $this->custom_data->get_currency(), + 'value' => $this->custom_data->get_value(), + 'content_ids' => $this->custom_data->get_content_ids(), + 'contents' => $this->custom_data->get_contents(), + 'num_items' => $this->custom_data->get_num_items(), + 'order_id' => $this->custom_data->get_order_id(), + ), + ) ); } - private static function add_search_custom_data( array $event_data ): array { + /** + * Adds custom data related to search events. + * + * @since x.x.x + * + * @param array $event_data + * @return array + */ + private function add_search_custom_data( array $event_data ): array { return array_merge( $event_data, - [ - 'custom_data' => [ - 'search_string' => '', - ], - ] + array( + 'custom_data' => array( + 'search_string' => $this->custom_data->get_search_string(), + ), + ) ); } } diff --git a/src/API/Conversions/CartData.php b/src/API/Conversions/CartData.php new file mode 100644 index 000000000..151bf12e9 --- /dev/null +++ b/src/API/Conversions/CartData.php @@ -0,0 +1,96 @@ +currency = $currency; + $this->value = $value; + $this->content_ids = $content_ids; + $this->contents = $contents; + $this->num_items = $num_items; + $this->order_id = $order_id; + } + + /** + * @return string + */ + public function get_currency(): string { + return $this->currency; + } + + /** + * @return string + */ + public function get_value(): string { + return $this->value; + } + + /** + * @return array + */ + public function get_content_ids(): array { + return $this->content_ids; + } + + /** + * @return array + */ + public function get_contents(): array { + return $this->contents; + } + + /** + * @return int + */ + public function get_num_items(): int { + return $this->num_items; + } + + /** + * @return string + */ + public function get_order_id(): string { + return $this->order_id; + } +} diff --git a/src/API/Conversions/CustomData.php b/src/API/Conversions/CustomData.php new file mode 100644 index 000000000..a0de473e5 --- /dev/null +++ b/src/API/Conversions/CustomData.php @@ -0,0 +1,12 @@ +search_string = $search_string; + } + + /** + * @return string + */ + public function get_search_string(): string + { + return $this->search_string; + } +} diff --git a/src/API/Conversions/UserData.php b/src/API/Conversions/UserData.php new file mode 100644 index 000000000..2672ea922 --- /dev/null +++ b/src/API/Conversions/UserData.php @@ -0,0 +1,40 @@ +client_ip_address = $client_ip_address; + $this->client_user_agent = $client_user_agent; + } + + /** + * @return string + */ + public function get_client_ip_address(): string { + return $this->client_ip_address; + } + + /** + * @return string + */ + public function get_client_user_agent(): string { + return $this->client_user_agent; + } +} diff --git a/src/Tracking.php b/src/Tracking.php index 0732da5ca..c69ea414b 100644 --- a/src/Tracking.php +++ b/src/Tracking.php @@ -13,11 +13,12 @@ } use Automattic\WooCommerce\Pinterest\API\AdvertiserConnect; -use Conversions; -use PinterestConversionsEventIdProvider; -use WC_Product; +use Automattic\WooCommerce\Pinterest\API\Conversions; +use Automattic\WooCommerce\Pinterest\API\Conversions\PinterestConversionsEventIdProvider; use Automattic\WooCommerce\Utilities\NumberUtil; -use \Premmerce\WooCommercePinterest\PinterestPlugin; +use Premmerce\WooCommercePinterest\PinterestPlugin; +use WC_Geolocation; +use WC_Product; /** * Class adding Save Pin support. @@ -514,7 +515,18 @@ private static function add_event( $event, $data = array() ) { call_user_func_array( array( __CLASS__, $action ), array( $event, $data ) ); // Firing Pinterest Conversions event as well. - Conversions::add_event( $event, $data ); + $user_data = new Conversions\UserData( WC_Geolocation::get_ip_address(), wc_get_user_agent() ); + $custom_data = new Conversions\NoData(); + + if ( in_array( $event, array( 'add_to_cart', 'checkout' ), true ) ) { + $custom_data = new Conversions\CartData( '', '', array(), array(), 0, '' ); + } + + if ( 'search' === $event ) { + $custom_data = new Conversions\SearchData( '' ); + } + + ( new Conversions( $user_data, $custom_data ) )->add_event( $event, $data ); } From 298c3a17d330687c19e4c665ec9adfa704d70107 Mon Sep 17 00:00:00 2001 From: Dima Date: Fri, 30 Jun 2023 21:55:46 +0300 Subject: [PATCH 03/32] Fix php code styling for event id provider. --- .../PinterestConversionsEventIdProvider.php | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/API/Conversions/PinterestConversionsEventIdProvider.php b/src/API/Conversions/PinterestConversionsEventIdProvider.php index f89c9529f..1a5952e98 100644 --- a/src/API/Conversions/PinterestConversionsEventIdProvider.php +++ b/src/API/Conversions/PinterestConversionsEventIdProvider.php @@ -1,6 +1,9 @@ 'page_view', - 'addtocart' => 'add_to_cart', - 'checkout' => 'checkout', - 'lead' => 'lead', - 'purchase' => 'purchase', - 'search' => 'search', - 'viewcategory' => 'view_category', - 'viewitem' => 'view_item', - 'viewsearchresults' => 'view_search_results', + private static $tag_api_name_map = [ + 'pagevisit' => 'page_view', + 'addtocart' => 'add_to_cart', + 'checkout' => 'checkout', + 'lead' => 'lead', + 'purchase' => 'purchase', + 'search' => 'search', + 'viewcategory' => 'view_category', + 'viewitem' => 'view_item', + 'viewsearchresults' => 'view_search_results', ]; /** From 259d920718e1b4380e6d0e379251b75947803c92 Mon Sep 17 00:00:00 2001 From: Dima Date: Fri, 30 Jun 2023 23:06:26 +0300 Subject: [PATCH 04/32] Update even data with required values. Add minor php code styling changes. --- src/API/Conversions.php | 19 ++++++++++++++++--- src/API/Conversions/CartData.php | 6 +++++- src/API/Conversions/CustomData.php | 4 +++- src/API/Conversions/NoData.php | 4 +++- .../PinterestConversionsEventIdProvider.php | 2 +- src/API/Conversions/SearchData.php | 9 ++++++--- src/API/Conversions/UserData.php | 4 +++- src/Tracking.php | 5 +++-- 8 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/API/Conversions.php b/src/API/Conversions.php index a4e6a098d..1197b796a 100644 --- a/src/API/Conversions.php +++ b/src/API/Conversions.php @@ -1,5 +1,7 @@ '', + 'ad_account_id' => $ad_account_id, 'event_name' => $event_name, 'action_source' => 'website', 'event_time' => time(), @@ -48,9 +52,18 @@ public function add_event( string $event_name, array $data = array() ) { ) ); + $conversions_event_name = PinterestConversionsEventIdProvider::get_event_name_by_pinterest_tag_event_name( $event ); + if ( in_array( $conversions_event_name, array( 'add_to_cart', 'checkout', 'purchase' ), true ) ) { + $data = $this->add_cart_and_checkout_custom_data( $data ); + } + + if ( in_array( $conversions_event_name, array( 'search', 'view_search_results' ), true ) ) { + $data = $this->add_search_custom_data( $data ); + } + try { APIV5::make_request( - '', + "ad_accounts/{$ad_account_id}/events", 'POST', $data ); diff --git a/src/API/Conversions/CartData.php b/src/API/Conversions/CartData.php index 151bf12e9..c19fa8d37 100644 --- a/src/API/Conversions/CartData.php +++ b/src/API/Conversions/CartData.php @@ -1,8 +1,12 @@ search_string; } } diff --git a/src/API/Conversions/UserData.php b/src/API/Conversions/UserData.php index 2672ea922..d93851ffb 100644 --- a/src/API/Conversions/UserData.php +++ b/src/API/Conversions/UserData.php @@ -1,5 +1,7 @@ Date: Fri, 4 Aug 2023 01:03:47 +0300 Subject: [PATCH 05/32] Intermediate code commit. Refactor tracking into separate pinterest tag and conversions api handlers. --- src/Tracking.php | 785 ++++-------------------- src/Tracking/Conversions.php | 144 +++++ src/Tracking/Conversions/CartData.php | 100 +++ src/Tracking/Conversions/CustomData.php | 14 + src/Tracking/Conversions/NoData.php | 12 + src/Tracking/Conversions/SearchData.php | 31 + src/Tracking/Conversions/UserData.php | 42 ++ src/Tracking/Data.php | 5 + src/Tracking/Data/Category.php | 31 + src/Tracking/Data/Checkout.php | 61 ++ src/Tracking/Data/Event.php | 15 + src/Tracking/Data/None.php | 7 + src/Tracking/Data/Product.php | 72 +++ src/Tracking/Data/Search.php | 21 + src/Tracking/EventIdProvider.php | 85 +++ src/Tracking/Tag.php | 279 +++++++++ src/Tracking/Tracker.php | 18 + 17 files changed, 1059 insertions(+), 663 deletions(-) create mode 100644 src/Tracking/Conversions.php create mode 100644 src/Tracking/Conversions/CartData.php create mode 100644 src/Tracking/Conversions/CustomData.php create mode 100644 src/Tracking/Conversions/NoData.php create mode 100644 src/Tracking/Conversions/SearchData.php create mode 100644 src/Tracking/Conversions/UserData.php create mode 100644 src/Tracking/Data.php create mode 100644 src/Tracking/Data/Category.php create mode 100644 src/Tracking/Data/Checkout.php create mode 100644 src/Tracking/Data/Event.php create mode 100644 src/Tracking/Data/None.php create mode 100644 src/Tracking/Data/Product.php create mode 100644 src/Tracking/Data/Search.php create mode 100644 src/Tracking/EventIdProvider.php create mode 100644 src/Tracking/Tag.php create mode 100644 src/Tracking/Tracker.php diff --git a/src/Tracking.php b/src/Tracking.php index e5fb6f1c6..7873b5ae3 100644 --- a/src/Tracking.php +++ b/src/Tracking.php @@ -1,769 +1,228 @@ \n\n\n"; + const EVENT_VIEW_CATEGORY = 'ViewCategory'; /** - * The base tracking snippet with Enchanced match support. - * Documentation: https://help.pinterest.com/en/business/article/enhanced-match - * - * @var string + * @var array $trackers A list of available trackers. */ - private static $base_tag_em = "\n\n\n"; + private $trackers = array(); + public function __construct() { + // Tracks page visit events. + add_action('wp_footer', array( $this, 'handle_page_visit' ) ); - /** - * The noscript base tracking snippet. - * Documentation: https://help.pinterest.com/en/business/article/install-the-pinterest-tag - * - * @var string - */ - private static $noscript_base_tag = ''; + // Tracks category visit events. + add_action( 'wp_footer', array( $this, 'handle_view_category' ) ); - /** - * The user/customer specific key used to store async events that are to be printed the next - * time we print out events. - * - * @var string - */ - private static $deferred_conversion_events_transient_key = null; - - /** - * Initiate class. - */ - public static function maybe_init() { - - if ( ! self::tracking_enabled() || wp_doing_cron() || is_admin() ) { - return; - } - - if ( is_object( WC()->session ) ) { - self::$deferred_conversion_events_transient_key = PINTEREST_FOR_WOOCOMMERCE_PREFIX . '_async_events_' . md5( WC()->session->get_customer_id() ); - } - - // Enqueue our JS files. - add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) ); - - // Base tag. - self::base_tag(); - - // WC events. - if ( function_exists( 'WC' ) ) { - - if ( ! wp_doing_ajax() ) { - add_action( 'wp_head', array( __CLASS__, 'late_events_handling' ), 20 ); - } - - // AddToCart - ajax. - if ( 'yes' === get_option( 'woocommerce_enable_ajax_add_to_cart' ) && 'yes' !== get_option( 'woocommerce_cart_redirect_after_add' ) ) { - add_action( 'wp_enqueue_scripts', array( __CLASS__, 'ajax_tracking_snippet' ), 20 ); - add_filter( - 'woocommerce_loop_add_to_cart_args', - array( __CLASS__, 'filter_add_to_cart_attributes' ), - 10, - 2 - ); - } - - // AddToCart - non-ajax. - add_action( 'woocommerce_add_to_cart', array( __CLASS__, 'hook_add_to_cart_event' ), 20, 4 ); - - // Checkout. - add_action( 'woocommerce_before_thankyou', array( __CLASS__, 'hook_checkout_event' ), 10, 1 ); - - } + // Tracks add to cart events. + add_action( 'woocommerce_add_to_cart', array( $this, 'handle_add_to_cart' ), 10, 6 ); - self::load_async_events(); + // Tracks checkout events. + add_action( 'woocommerce_checkout_order_created', array( $this, 'handle_checkout' ), 10, 2 ); - add_action( 'shutdown', array( __CLASS__, 'save_async_events' ) ); - - // Print to head. - add_action( 'wp_head', array( __CLASS__, 'print_script' ) ); - - // Print noscript to body. - add_action( 'wp_body_open', array( __CLASS__, 'print_noscript' ), 0 ); - - add_action( 'admin_init', array( __CLASS__, 'verify_advertiser_connection' ) ); + add_action( '', array( $this, 'handle_search' ) ); } - /** - * Loads any stored events to be printed. + * Used as a callback for the wp_footer hook. * * @return void */ - private static function load_async_events() { - - $async_events = get_transient( self::$deferred_conversion_events_transient_key ); - - if ( $async_events ) { - self::$events = array_merge( self::$events, $async_events ); - delete_transient( self::$deferred_conversion_events_transient_key ); - } - } - - - /** - * Store any events that weren't printed on shutdown. - * - * @return void - */ - public static function save_async_events() { - - if ( ! empty( self::$events ) && self::$deferred_conversion_events_transient_key ) { - set_transient( self::$deferred_conversion_events_transient_key, self::$events, 10 * MINUTE_IN_SECONDS ); + public function handle_page_visit() { + $data = new None(); + if ( is_product() ) { + $product = wc_get_product(); + $data = new Product( + $product->get_id(), + $product->get_name(), + wc_get_product_category_list( $product->get_id() ), + 'brand', + $product->get_price(), + get_woocommerce_currency(), + 1 + ); } + $this->maybe_track_event( static::EVENT_PAGE_VISIT, $data ); } - - /** - * Enqueue JS files necessary to properly track actions such as search. - * - * @return void - */ - public static function enqueue_scripts() { - - $ext = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; - - wp_enqueue_script( - PINTEREST_FOR_WOOCOMMERCE_PREFIX . '-tracking-scripts', - Pinterest_For_Woocommerce()->plugin_url() . '/assets/js/pinterest-for-woocommerce-tracking' . $ext . '.js', - array(), - PINTEREST_FOR_WOOCOMMERCE_VERSION, - true - ); - } - - /** - * Initialize events that need access to conditional tags. + * Used as a callback for the wp_footer hook. * * @return void */ - public static function late_events_handling() { - - // Product page visit. - self::page_visit_event(); - - // Product category page visit. - self::category_visit_event(); - } - - - /** - * Retunrs the hashed e-mails from the logged in user or Session data, - * to be used when Enchanced match is enabled. - * See https://help.pinterest.com/en/business/article/enhanced-match - * - * @return string|false - */ - public static function get_hashed_customer_email() { - - $user_email = false; - - if ( is_user_logged_in() ) { - - $user = wp_get_current_user(); - $user_email = $user->user_email; - } - - if ( empty( $user_email ) ) { - $session_customer = function_exists( 'WC' ) && isset( WC()->session ) ? WC()->session->get( 'customer' ) : false; - $user_email = $session_customer ? $session_customer['email'] : false; + public function handle_view_category() { + if ( ! is_product_category() ) { + return; } - - return $user_email ? md5( $user_email ) : false; + $queried_object = get_queried_object(); + $data = new Category( $queried_object->term_id, $queried_object->name ); + $this->maybe_track_event( static::EVENT_VIEW_CATEGORY, $data ); } - /** - * Use woocommerce_add_to_cart to enqueue our AddToCart event. - * - * @param string $cart_item_key The cart item's key. - * @param integer $product_id The product ID. - * @param integer $quantity The quantity. - * @param integer $variation_id The Variation ID. + * Used as a callback for the woocommerce_add_to_cart hook. * * @return void */ - public static function hook_add_to_cart_event( $cart_item_key, $product_id, $quantity, $variation_id ) { - $redirect_to_cart = 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ); - - if ( wp_doing_ajax() && ! $redirect_to_cart ) { - return; - } - + public function handle_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id ) { $object_id = empty( $variation_id ) ? $product_id : $variation_id; $product = wc_get_product( $object_id ); - - $product_price = self::get_product_display_price( $product ); - - self::add_event( - 'AddToCart', - array( - 'product_id' => $product->get_id(), - 'product_name' => $product->get_name(), - 'value' => ( $product_price * $quantity ), - 'order_quantity' => $quantity, - 'currency' => get_woocommerce_currency(), - ) + $data = new Product( + $product->get_id(), + $product->get_name(), + wc_get_product_category_list( $product->get_id() ), + 'brand', + $product->get_price(), + get_woocommerce_currency(), + $quantity ); + $this->maybe_track_event( static::EVENT_ADD_TO_CART, $data ); } - /** - * Use woocommerce_before_thankyou to enqueue our Checkout event. - * - * @param integer $order_id The Order's ID. + * Used as a callback for the woocommerce_checkout_order_created hook. * * @return void */ - public static function hook_checkout_event( $order_id ) { - + public function handle_checkout( $order_id ) { $order = wc_get_order( $order_id ); - if ( ! $order ) { return; } - $order_items = array(); + $items = array(); $total_quantity = 0; - foreach ( $order->get_items() as $order_item ) { - if ( ! method_exists( $order_item, 'get_product' ) ) { continue; } - $product = $order_item->get_product(); - - $product_price = self::get_product_display_price( $product ); - - $terms = wc_get_object_terms( $product->get_id(), 'product_cat' ); - $categories = ! empty( $terms ) ? wp_list_pluck( $terms, 'name' ) : array(); - - $order_items[] = array( + $product = $order_item->get_product(); + $product_price = $product->get_price(); + $terms = wc_get_object_terms( $product->get_id(), 'product_cat' ); + $categories = ! empty( $terms ) ? wp_list_pluck( $terms, 'name' ) : array(); + + $items[] = new Product( + $product->get_id(), + $order_item->get_name(), + $categories, + 'brand', + $product_price, + get_woocommerce_currency(), + $order_item->get_quantity() + ); + /*$items[] = array( 'product_id' => $product->get_id(), 'product_name' => $order_item->get_name(), 'product_price' => $product_price, 'product_quantity' => $order_item->get_quantity(), 'product_category' => $categories, - ); + );*/ $total_quantity += $order_item->get_quantity(); } - self::add_event( - 'checkout', - array( - 'order_id' => $order_id, - 'value' => $order->get_total(), - 'order_quantity' => $total_quantity, - 'currency' => $order->get_currency(), - 'line_items' => $order_items, - ) + $data = new Checkout( + $order_id, + $order->get_total(), + $total_quantity, + $order->get_currency(), + $items ); - - } - - - /** - * Attaches a piece of JS to wc-add-to-cart script, which binds to the - * added_to_cart event, in order to trigger our AddToCart event - * when the item is added via AJAX. - * - * @return void - */ - public static function ajax_tracking_snippet() { - - if ( is_product() ) { - $tracking = self::get_add_to_cart_snippet_product(); - } else { - $tracking = self::get_add_to_cart_snippet_archive(); - } - - wp_add_inline_script( 'wc-add-to-cart', $tracking ); - - } - - - /** - * Get the add to cart tracking snippet for archives. - * - * @return string - */ - protected static function get_add_to_cart_snippet_archive() { - $wc_currency = esc_js( get_woocommerce_currency() ); - $tracking = <<< JS -jQuery( function( $ ) { - $( document.body ).on( 'added_to_cart', function( e, fragments, cart_hash, thisbutton ) { - var quantity = thisbutton.data( 'quantity' ); - pintrk( 'track', 'AddToCart', { - 'product_id': thisbutton.data( 'product_id' ), - 'product_name': thisbutton.data( 'product_name' ), - 'value': thisbutton.data( 'price' ) * quantity, - 'order_quantity': quantity, - 'currency': '{$wc_currency}' - } ); - } ); -} ); -JS; - - return $tracking; - } - - - /** - * Get the add to cart tracking snippet for single product page. - * - * @return string - */ - protected static function get_add_to_cart_snippet_product() { - $product = wc_get_product( get_the_ID() ); - - if ( ! $product ) { - return ''; - } - - $product_id = $product->get_id(); - $product_name = esc_js( $product->get_name() ); - $product_price = floatval( self::get_product_display_price( $product ) ); - - $wc_currency = esc_js( get_woocommerce_currency() ); - $tracking = <<< JS -jQuery( function( $ ) { - $( document.body ).on( 'added_to_cart', function( e, fragments, cart_hash, thisbutton ) { - var quantity = document.querySelector( 'input.qty[name="quantity"]' ).value; - pintrk( 'track', 'AddToCart', { - 'product_id': '{$product_id}', - 'product_name': '{$product_name}', - 'value': {$product_price} * quantity, - 'order_quantity': quantity, - 'currency': '{$wc_currency}' - } ); - } ); -} ); -JS; - - return $tracking; - } - - - /** - * Checks and returns if tracking is enabled and we got an active tag. - * - * @return boolean - */ - private static function tracking_enabled() { - - /** - * Allow third party plugins to disable the tracking pixel. - * - * This filter is not guaranteed to be here in the future. It may be removed at any time. Use at your own risk. - * - * @since 1.2.7 - * - * @param bool $is_disable Tracking is enabled if false, and disabled if true. - */ - if ( apply_filters( 'woocommerce_pinterest_disable_tracking', false ) ) { - return false; - } - - if ( ! Pinterest_For_Woocommerce()::get_setting( 'track_conversions' ) || ! self::get_active_tag() ) { - return false; - } - - return true; - } - - - /** - * Enqueues the base tag for printing. - * - * @return void - */ - private static function base_tag() { - - $active_tag = self::get_active_tag(); - $email = ''; - - if ( ! $active_tag ) { - return; - } - - if ( Pinterest_For_Woocommerce()::get_setting( 'enhanced_match_support' ) ) { - $email = self::get_hashed_customer_email(); - } - - $snippet = empty( $email ) ? self::$base_tag : self::$base_tag_em; - $snippet = str_replace( array( self::TAG_ID_SLUG, self::HASHED_EMAIL_SLUG ), array( sanitize_key( $active_tag ), $email ), $snippet ); - - self::$script .= $snippet; + $this->maybe_track_event( static::EVENT_CHECKOUT, $data ); } - - /** - * Enqueues the page visit event code for printing. - * - * @return void - */ - private static function page_visit_event() { - - $data = array(); - - if ( is_product() ) { - - $product = wc_get_product(); - - $data = array( - 'product_id' => $product->get_id(), - 'product_name' => $product->get_name(), - 'product_price' => wc_get_price_to_display( $product ), - 'currency' => get_woocommerce_currency(), - ); - } - - self::add_event( 'pagevisit', $data ); - } - - - /** - * Enqueues the Category visit event code for printing. - * - * @return void - */ - private static function category_visit_event() { - - if ( ! is_product_category() ) { + public function handle_search() { + if ( ! is_search() ) { return; } - $queried_object = get_queried_object(); - - $data = array( - 'product_category' => $queried_object->term_id, - 'category_name' => $queried_object->name, - ); - - self::add_event( 'ViewCategory', $data ); + $data = new Search( get_search_query() ); + $this->maybe_track_event( static::EVENT_SEARCH, $data ); } + public function maybe_track_event( string $event_name, Data $data ) { + $is_tracking_enabled = apply_filters( 'woocommerce_pinterest_disable_tracking', false ); + $is_tracking_conversions_enabled = Pinterest_For_Woocommerce()::get_setting( 'track_conversions' ); + $is_tracked_site = ! wp_doing_cron() && ! is_admin(); - /** - * Enqueues or prints the given event, depending on if - * we have already run wp_head or not. - * - * @param string $event The event's type. - * @param array $data The data to be passed to the JS function. - * - * @return void - */ - private static function add_event( $event, $data = array() ) { - $action = did_action( 'wp_head' ) ? 'print_event' : 'enqueue_event'; - - // Adding event_id to data array as part of Pinterest API for Conversions even deduplication program. - $data['event_id'] = PinterestConversionsEventIdProvider::get_event_id( $event ); - - call_user_func_array( array( __CLASS__, $action ), array( $event, $data ) ); - - // Firing Pinterest Conversions event as well. - $user_data = new Conversions\UserData( WC_Geolocation::get_ip_address(), wc_get_user_agent() ); - $custom_data = new Conversions\NoData(); - - $conversions_event_name = PinterestConversionsEventIdProvider::get_event_name_by_pinterest_tag_event_name( $event ); - if ( in_array( $conversions_event_name, array( 'add_to_cart', 'checkout', 'purchase' ), true ) ) { - $custom_data = new Conversions\CartData( '', '', array(), array(), 0, '' ); - } - - if ( in_array( $conversions_event_name, array( 'search', 'view_search_results' ), true ) ) { - $custom_data = new Conversions\SearchData( '' ); + if ( $is_tracking_enabled && $is_tracking_conversions_enabled && $is_tracked_site ) { + foreach ( $this->get_trackers() as $tracker ) { + // Skip Pinterest tag tracking if tag is not active. + if ( $tracker instanceof Tag && ! Tag::get_active_tag() ) { + continue; + } + $tracker->track_event( $event_name, $data ); + } + return true; } - - ( new Conversions( $user_data, $custom_data ) )->add_event( $event, $data ); + return false; } - /** - * Enqueues the given event. + * Returns an array of registered trackers. * - * @param string $event The event's type. - * @param array $data The data to be passed to the JS function. + * @since x.x.x * - * @return void + * @return Tracker[] */ - private static function enqueue_event( $event, $data = array() ) { - self::$events[] = self::prepare_event_code( $event, $data ); + public function get_trackers() { + $this->trackers[] = new Tag(); + // $this->trackers[] = new PinterestConversions( new Conversions\UserData( WC_Geolocation::get_ip_address(), wc_get_user_agent() ), new Conversions\NoData() ); + return $this->trackers; } - /** - * Prints the given event. + * Adds a tracker to the array of trackers. * - * @param string $event The event's type. - * @param array $data The data to be passed to the JS function. + * @param Tracker $tracker * * @return void */ - private static function print_event( $event, $data = array() ) { - echo ''; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - - - /** - * Gets the event's JS code to be enqueued or printed. - * - * @param string $event The event's type. - * @param array $data The data to be passed to the JS function. - * - * @return string - */ - private static function prepare_event_code( $event, $data = array() ) { - $data_string = empty( $data ) ? null : wp_json_encode( $data ); - - return sprintf( - 'pintrk( \'track\', \'%s\' %s);', - $event, - empty( $data_string ) ? '' : ', ' . $data_string - ); - } - - - /** - * Get the actual JS & markup for the base tag as configured in the settings. - * - * @return object|boolean - */ - public static function get_active_tag() { - return Pinterest_For_Woocommerce()::get_setting( 'tracking_tag' ); + public function add_tracker( Tracker $tracker ) { + $this->trackers[] = $tracker; } - /** - * Prints the enqueued base code and events snippets. - * Meant to be used in wp_head. + * Removes a tracker. * + * @param string $tracker Tracker class name to be removed. e.g. PinterestTag::class * @return void */ - public static function print_script() { - - if ( ! empty( self::$script ) ) { - - echo self::$script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped --- Printing hardcoded JS tracking code. - - if ( ! empty( self::$events ) ) { - echo ''; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped --- Printing hardcoded JS tracking code. - self::$events = array(); + public function remove_tracker( string $tracker ) { + $this->trackers = array_filter( + $this->trackers, + function( $item ) use ( $tracker ) { + return get_class( $item ) !== $tracker; } - } - } - - - /** - * Prints the noscript code. - * - * @return void - */ - public static function print_noscript() { - - $active_tag = self::get_active_tag(); - - if ( ! $active_tag ) { - return; - } - - $noscript = str_replace( self::TAG_ID_SLUG, sanitize_key( $active_tag ), self::$noscript_base_tag ); - - echo $noscript; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped --- Printing hardcoded JS tracking code. - } - - - /** - * Verify if the advertiser is properly connected to the platform. - */ - public function verify_advertiser_connection() { - // Verify if advertiser and tag need to be connected due to a plugin upgrade. - try { - self::maybe_connect_advertiser_tag(); - - /* Translators: The error description */ - Logger::log( esc_html__( 'Advertiser connected successfully', 'pinterest-for-woocommerce' ) ); - - } catch ( \Exception $e ) { - - /* Translators: The error description */ - Logger::log( sprintf( esc_html__( 'Could not connect the advertiser. Try to connect from the connection tab. [%s]', 'pinterest-for-woocommerce' ), $e->getMessage() ), 'error' ); - } - } - - /** - * Call connect advertiser method if needed after plugin upgrade. - * - * @throws \Exception PHP Exception. - */ - private static function maybe_connect_advertiser_tag() { - - $is_connected = Pinterest_For_Woocommerce()::get_data( 'is_advertiser_connected' ); - $connected_advertiser = Pinterest_For_Woocommerce()::get_setting( 'tracking_advertiser' ); - $connected_tag = Pinterest_For_Woocommerce()::get_setting( 'tracking_tag' ); - - // Check if advertiser is already connected. - if ( ! $is_connected && $connected_advertiser && $connected_tag ) { - AdvertiserConnect::connect_advertiser_and_tag( $connected_advertiser, $connected_tag ); - } - } - - /** - * Filter the "Add to cart" button attributes to include more data. - * - * @see woocommerce_template_loop_add_to_cart() - * - * @since 1.0.11 - * - * @param array $args The arguments used for the Add to cart button. - * @param WC_Product $product The product object. - * - * @return array The filtered arguments for the Add to cart button. - */ - public static function filter_add_to_cart_attributes( array $args, WC_Product $product ) { - $attributes = array( - 'data-product_name' => $product->get_name(), - 'data-price' => self::get_product_display_price( $product ), ); - - $args['attributes'] = array_merge( $args['attributes'], $attributes ); - - return $args; } - - - /** - * Get the formatted warning message for the potential conflicting tags. - * - * @since 1.2.3 - * - * @return string The warning message. - */ - public static function get_third_party_tags_warning_message() { - - $third_party_tags = self::get_third_party_installed_tags(); - - if ( empty( $third_party_tags ) ) { - return ''; - } - - return sprintf( - /* Translators: 1: Conflicting plugins, 2: Plugins Admin page opening tag, 3: Pinterest settings opening tag, 4: Closing anchor tag */ - esc_html__( 'The following installed plugin(s) can potentially cause problems with tracking: %1$s. %2$sRemove conflicting plugins%4$s or %3$smanage tracking settings%4$s.', 'pinterest-for-woocommerce' ), - implode( ', ', $third_party_tags ), - sprintf( '', esc_url( admin_url( 'plugins.php' ) ) ), - sprintf( '', esc_url( wc_admin_url( '&path=/pinterest/settings' ) ) ), - '', - ); - - } - - - /** - * Detect if there are other tags installed on the site. - * - * @since 1.2.3 - * - * @return array The list of installed tags. - */ - public static function get_third_party_installed_tags() { - - $third_party_tags = array(); - - if ( defined( 'GTM4WP_VERSION' ) ) { - $third_party_tags['gtm'] = 'Google Tag Manager'; - } - - if ( defined( 'PYS_PINTEREST_VERSION' ) ) { - $third_party_tags['pys'] = 'Pixel Your Site - Pinterest Addon'; - } - - if ( class_exists( PinterestPlugin::class ) ) { - $third_party_tags['softblues'] = 'Pinterest for WooCommerce by Softblues'; - } - - return $third_party_tags; - } - - /** - * Get product's price including/excluding tax. - * - * @param WC_Product $product The product object. - * - * @return string - */ - protected static function get_product_display_price( $product ) { - return self::price_includes_tax() ? wc_get_price_including_tax( $product ) : NumberUtil::round( wc_get_price_excluding_tax( $product ), wc_get_price_decimals() ); - } - - /** - * Get if prices should include tax. - * - * @since 1.2.18 - * @return bool - */ - protected static function price_includes_tax() { - if ( isset( WC()->cart ) && method_exists( WC()->cart, 'display_prices_including_tax' ) ) { - return WC()->cart->display_prices_including_tax(); - } - - if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) { - return false; - } - - return 'incl' === get_option( 'woocommerce_tax_display_cart' ); - } - } diff --git a/src/Tracking/Conversions.php b/src/Tracking/Conversions.php new file mode 100644 index 000000000..82ce43ec2 --- /dev/null +++ b/src/Tracking/Conversions.php @@ -0,0 +1,144 @@ +user_data = $user_data; + } + + public function track_event( string $event_name, Data $data ) { + if ( Tracking::EVENT_SEARCH === $event_name ) { + /** @var Search $data */ + $data = array( + 'custom_data' => array( + 'search_string' => $data->get_search_query(), + ), + ); + } + + if ( Tracking::EVENT_PAGE_VISIT === $event_name ) { + /** @var Product $data */ + $data = array( + 'custom_data' => array( + 'currency' => $data->get_currency(), + 'value' => $data->get_price() * $data->get_quantity(), + 'content_ids' => array( $data->get_id() ), + 'contents' => array( + array( + 'id' => $data->get_id(), + 'item_price' => $data->get_price(), + 'quantity' => $data->get_quantity(), + ), + ), + 'num_items' => $data->get_quantity(), + ), + ); + } + + if ( Tracking::EVENT_VIEW_CATEGORY === $event_name ) { + /** @var Category $data */ + $data = array( + 'custom_data' => array( + 'category_name' => $data->getCategoryName(), + ), + ); + } + + if ( Tracking::EVENT_ADD_TO_CART === $event_name ) { + /** @var Product $data */ + $data = array( + 'custom_data' => array( + 'currency' => $data->get_currency(), + 'value' => $data->get_price() * $data->get_quantity(), + 'content_ids' => array( $data->get_id() ), + 'contents' => array( + array( + 'id' => $data->get_id(), + 'item_price' => $data->get_price(), + 'quantity' => $data->get_quantity(), + ), + ), + 'num_items' => $data->get_quantity(), + ), + ); + } + + if ( Tracking::EVENT_CHECKOUT === $event_name ) { + /** @var Checkout $data */ + $data = array( + 'custom_data' => array( + 'currency' => $data->get_currency(), + 'value' => $data->get_price() * $data->get_quantity(), + 'content_ids' => array_map( + function ( Product $product ) { + return $product->get_id(); + }, + $data->get_items() + ), + 'contents' => array_map( + function ( Product $product ) { + return array( + 'id' => $product->get_id(), + 'item_price' => $product->get_price(), + 'quantity' => $product->get_quantity(), + ); + }, + $data->get_items() + ), + 'num_items' => $data->get_quantity(), + ), + ); + } + + $ad_account_id = Pinterest_For_WooCommerce()::get_setting( 'tracking_advertiser' ); + $event_name = 'page_visit'; + + /** @var array $data */ + $data = array_merge( + $data, + array( + 'ad_account_id' => $ad_account_id, + 'event_name' => $event_name, + 'action_source' => 'website', + 'event_time' => time(), + 'event_id' => EventIdProvider::get_event_id( $event_name ), + 'event_source_url' => '', + 'partner_name' => 'ss-woocommerce', + 'user_data' => array( + 'client_ip_address' => $this->user_data->get_client_ip_address(), + 'client_user_agent' => $this->user_data->get_client_user_agent(), + ), + 'language' => 'en', + ) + ); + + try { + APIV5::make_request( + "ad_accounts/{$ad_account_id}/events", + 'POST', + $data + ); + } catch ( Throwable $e ) { + // Do nothing. + } + } + + /** + * @return bool + */ + public static function is_conversions_api_enabled() { + return true; + } +} diff --git a/src/Tracking/Conversions/CartData.php b/src/Tracking/Conversions/CartData.php new file mode 100644 index 000000000..c19fa8d37 --- /dev/null +++ b/src/Tracking/Conversions/CartData.php @@ -0,0 +1,100 @@ +currency = $currency; + $this->value = $value; + $this->content_ids = $content_ids; + $this->contents = $contents; + $this->num_items = $num_items; + $this->order_id = $order_id; + } + + /** + * @return string + */ + public function get_currency(): string { + return $this->currency; + } + + /** + * @return string + */ + public function get_value(): string { + return $this->value; + } + + /** + * @return array + */ + public function get_content_ids(): array { + return $this->content_ids; + } + + /** + * @return array + */ + public function get_contents(): array { + return $this->contents; + } + + /** + * @return int + */ + public function get_num_items(): int { + return $this->num_items; + } + + /** + * @return string + */ + public function get_order_id(): string { + return $this->order_id; + } +} diff --git a/src/Tracking/Conversions/CustomData.php b/src/Tracking/Conversions/CustomData.php new file mode 100644 index 000000000..088562990 --- /dev/null +++ b/src/Tracking/Conversions/CustomData.php @@ -0,0 +1,14 @@ +search_string = $search_string; + } + + /** + * @return string + */ + public function get_search_string(): string { + return $this->search_string; + } +} diff --git a/src/Tracking/Conversions/UserData.php b/src/Tracking/Conversions/UserData.php new file mode 100644 index 000000000..d93851ffb --- /dev/null +++ b/src/Tracking/Conversions/UserData.php @@ -0,0 +1,42 @@ +client_ip_address = $client_ip_address; + $this->client_user_agent = $client_user_agent; + } + + /** + * @return string + */ + public function get_client_ip_address(): string { + return $this->client_ip_address; + } + + /** + * @return string + */ + public function get_client_user_agent(): string { + return $this->client_user_agent; + } +} diff --git a/src/Tracking/Data.php b/src/Tracking/Data.php new file mode 100644 index 000000000..e87d972cc --- /dev/null +++ b/src/Tracking/Data.php @@ -0,0 +1,5 @@ +product_category = $product_category; + $this->category_name = $category_name; + } + + /** + * @return mixed + */ + public function getProductCategory() { + return $this->product_category; + } + + /** + * @return mixed + */ + public function getCategoryName() { + return $this->category_name; + } +} diff --git a/src/Tracking/Data/Checkout.php b/src/Tracking/Data/Checkout.php new file mode 100644 index 000000000..f7fbeaa8d --- /dev/null +++ b/src/Tracking/Data/Checkout.php @@ -0,0 +1,61 @@ +order_id = $order_id; + $this->price = $price; + $this->quantity = $quantity; + $this->currency = $currency; + $this->items = $items; + } + + /** + * @return mixed + */ + public function get_order_id() { + return $this->order_id; + } + + /** + * @return mixed + */ + public function get_price() { + return $this->price; + } + + /** + * @return mixed + */ + public function get_quantity() { + return $this->quantity; + } + + /** + * @return mixed + */ + public function get_currency() { + return $this->currency; + } + + /** + * @return mixed + */ + public function get_items() { + return $this->items; + } +} diff --git a/src/Tracking/Data/Event.php b/src/Tracking/Data/Event.php new file mode 100644 index 000000000..4b47f9feb --- /dev/null +++ b/src/Tracking/Data/Event.php @@ -0,0 +1,15 @@ +id = $id; + $this->time = $time; + $this->name = $name; + } +} diff --git a/src/Tracking/Data/None.php b/src/Tracking/Data/None.php new file mode 100644 index 000000000..e874d17cd --- /dev/null +++ b/src/Tracking/Data/None.php @@ -0,0 +1,7 @@ +id = $id; + $this->name = $name; + $this->category = $category; + $this->brand = $brand; + $this->price = $price; + $this->currency = $currency; + $this->quantity = $quantity; + } + + /** + * @return mixed + */ + public function get_id() { + return $this->id; + } + + /** + * @return mixed + */ + public function get_name() { + return $this->name; + } + + public function get_category() { + return $this->category; + } + + public function get_brand() { + return $this->brand; + } + + /** + * @return mixed + */ + public function get_price() { + return $this->price; + } + + /** + * @return mixed + */ + public function get_currency() { + return $this->currency; + } + + public function get_quantity() { + return $this->quantity; + } +} diff --git a/src/Tracking/Data/Search.php b/src/Tracking/Data/Search.php new file mode 100644 index 000000000..8f07e7d74 --- /dev/null +++ b/src/Tracking/Data/Search.php @@ -0,0 +1,21 @@ +search_query = $search_query; + } + + /** + * @return string + */ + public function get_search_query() { + return $this->search_query; + } +} diff --git a/src/Tracking/EventIdProvider.php b/src/Tracking/EventIdProvider.php new file mode 100644 index 000000000..cb9945231 --- /dev/null +++ b/src/Tracking/EventIdProvider.php @@ -0,0 +1,85 @@ + 'page_view', + 'addtocart' => 'add_to_cart', + 'checkout' => 'checkout', + 'lead' => 'lead', + 'purchase' => 'purchase', + 'search' => 'search', + 'viewcategory' => 'view_category', + 'viewitem' => 'view_item', + 'viewsearchresults' => 'view_search_results', + ]; + + /** + * Returns a persisted event ID for the given event name. + * + * @since x.x.x + * + * @param string $event_name + * @return string + */ + public static function get_event_id( string $event_name ): string { + $event_name = static::$tag_api_name_map[ strtolower( $event_name ) ] ?? $event_name; + return static::$event_ids[ $event_name ] ?? static::generate_event_id( $event_name ); + } + + /** + * Generates a new event ID for the given event name and stores is in-memory. + * + * @since x.x.x + * + * @param string $event_name + * @return string + */ + private static function generate_event_id( string $event_name ): string { + $id = uniqid( 'pinterest-for-woocommerce-conversions-event-id-for-' . $event_name ); + + static::$event_ids[ $event_name ] = $id; + + return $id; + } + + /** + * Returns the Pinterest Conversions API event name for the given Pinterest Tag API event name. + * + * @since x.x.x + * + * @param string $pinterest_tag_event_name + * @return string + */ + public static function get_event_name_by_pinterest_tag_event_name( string $pinterest_tag_event_name ): string { + return static::$tag_api_name_map[ strtolower( $pinterest_tag_event_name ) ] ?? $pinterest_tag_event_name; + } +} diff --git a/src/Tracking/Tag.php b/src/Tracking/Tag.php new file mode 100644 index 000000000..8279260a5 --- /dev/null +++ b/src/Tracking/Tag.php @@ -0,0 +1,279 @@ +\n\n\n"; + + /** + * The base tracking snippet with Enchanced match support. + * Documentation: https://help.pinterest.com/en/business/article/enhanced-match + * + * @var string + */ + private static $base_tag_em = "\n\n\n"; + + /** + * The noscript base tracking snippet. + * Documentation: https://help.pinterest.com/en/business/article/install-the-pinterest-tag + * + * @var string + */ + private static $noscript_base_tag = ''; + + /** + * A list of events that are to be stored or printed out depending on the circumstances. + * + * @var array + */ + private static $deferred_events = array(); + + public function __construct() { + add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) ); + add_action( 'wp_head', array( __CLASS__, 'print_script' ) ); + add_action( 'wp_body_open', array( __CLASS__, 'print_noscript' ), 0 ); + add_action( 'shutdown', array( __CLASS__, 'save_deferred_events' ) ); + } + + /** + * Enqueue JS files necessary to properly track actions such as search. + * + * @return void + */ + public static function enqueue_scripts() { + $ext = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + wp_enqueue_script( + PINTEREST_FOR_WOOCOMMERCE_PREFIX . '-tracking-scripts', + Pinterest_For_Woocommerce()->plugin_url() . '/assets/js/pinterest-for-woocommerce-tracking' . $ext . '.js', + array(), + PINTEREST_FOR_WOOCOMMERCE_VERSION, + true + ); + } + + /** + * Renders Pinterest Tag script part. + * + * @return void + */ + public static function print_script() { + $active_tag = Pinterest_For_Woocommerce()::get_setting( 'tracking_tag' ); + $email = Pinterest_For_Woocommerce()::get_setting( 'enhanced_match_support' ) ? static::maybe_get_hashed_customer_email() : ''; + $script = ! empty( $email ) ? self::$base_tag_em : self::$base_tag; + $script = str_replace( + array( self::TAG_ID_SLUG, self::HASHED_EMAIL_SLUG ), + array( sanitize_key( $active_tag ), $email ), + $script + ); + echo $script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped --- Printing hardcoded JS tracking code. + $deferred_events = static::load_deferred_events(); + if ( ! empty( $deferred_events ) ) { + echo ''; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped --- Printing hardcoded JS tracking code. + } + } + + /** + * Renders Pinterest Tag