\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 printed out.
*
* @var array
*/
private static $events = array();
/**
* A list of events that are to be stored.
*
* @var array
*/
private static $deferred_events = array();
/**
* Initialises hooks a tracker need to operate.
*
* @since 1.4.0
*
* @return void
*/
public function init_hooks() {
add_action( 'wp_footer', array( $this, 'print_script' ) );
add_action( 'wp_footer', array( $this, 'print_noscript' ) );
add_action( 'shutdown', array( $this, 'save_deferred_events' ) );
}
/**
* Disables hooks a tracker could set.
*
* @since 1.4.0
*
* @return void
*/
public function disable_hooks() {
remove_action( 'wp_footer', array( $this, 'print_script' ) );
remove_action( 'wp_footer', array( $this, 'print_noscript' ) );
remove_action( 'shutdown', array( $this, 'save_deferred_events' ) );
}
/**
* Renders Pinterest Tag script part.
*
* @since 1.4.0
*
* @return void
*/
public 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
);
//phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $script;
$events = array_merge(
static::$events,
static::load_deferred_events()
);
if ( ! empty( $events ) ) {
//phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '';
}
//phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '';
}
/**
* Renders Pinterest Tag part.
*
* @since 1.4.0
*
* @return void
*/
public function print_noscript() {
$active_tag = Pinterest_For_Woocommerce()::get_setting( 'tracking_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.
}
/**
* Generates Pinterest Tag event call.
*
* @since 1.4.0
*
* @param string $event_name Event name. e.g. Checkout, AddToCart, etc.
* @param array $data Corresponding event data object.
*
* @return string A generated event call.
*/
private static function get_event_code( string $event_name, array $data ) {
$data_string = empty( $data ) ? null : wp_json_encode( $data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
return sprintf(
'pintrk( \'track\', \'%s\' %s);',
$event_name,
empty( $data_string ) ? '' : ', ' . $data_string
);
}
/**
* Loads deferred event from the storage.
*
* @since 1.4.0
*
* @return array
*/
public static function load_deferred_events() {
$transient_key = static::get_deferred_events_transient_key();
if ( ! $transient_key ) {
return array();
}
$async_events = get_transient( $transient_key );
if ( ! $async_events ) {
return array();
}
delete_transient( $transient_key );
return $async_events;
}
/**
* Adds event into the list of events to be delayed.
*
* @since 1.4.0
*
* @param string $event_name Event name. e.g. Checkout, AddToCart, etc.
* @param array $data Corresponding event data object.
*
* @return true
*/
public static function add_deferred_event( string $event_name, array $data ) {
static::$deferred_events[] = static::get_event_code( $event_name, $data );
return true;
}
/**
* Adds event into the list of events to be rendered.
*
* @since 1.4.0
*
* @param string $event_name Event name. e.g. Checkout, AddToCart, etc.
* @param array $data Corresponding event data object.
*
* @return true
*/
public static function add_event( string $event_name, array $data ) {
static::$events[] = static::get_event_code( $event_name, $data );
return true;
}
/**
* Saves events to be rendered the next page load.
*
* @since 1.4.0
*
* @return void
*/
public static function save_deferred_events() {
$transient_key = static::get_deferred_events_transient_key();
if ( ! $transient_key ) {
return;
}
$existing_events = static::load_deferred_events();
static::$deferred_events = array_merge( $existing_events, static::$deferred_events );
if ( ! empty( static::$deferred_events ) ) {
set_transient( $transient_key, static::$deferred_events, DAY_IN_SECONDS );
}
}
/**
* Tracks event.
*
* @since 1.4.0
*
* @param string $event_name Event name. e.g. Checkout, AddToCart, etc.
* @param Data $data Corresponding event data object.
*
* @return true
*/
public function track_event( string $event_name, Data $data ) {
$data = $this->prepare_request_data( $event_name, $data );
if ( wp_doing_ajax() ) {
return static::maybe_add_fragment( $event_name, $data );
}
/**
* If the cart redirect is enabled, force add the event to the deferred events list
* because if redirect is enabled, the cart page will be reloaded and the event will get lost.
*/
$is_redirect = 'yes' === get_option( 'woocommerce_cart_redirect_after_add' );
$is_add_to_cart = Tracking::EVENT_ADD_TO_CART === $event_name;
if ( $is_redirect && $is_add_to_cart ) {
return static::add_deferred_event( $event_name, $data );
}
return static::add_event( $event_name, $data );
}
/**
* Prepares event data for the request.
*
* @since 1.4.0
*
* @param string $event_name Event name. e.g. Checkout, AddToCart, etc.
* @param Data $data Corresponding event data object.
*
* @return array Event data.
*/
public function prepare_request_data( string $event_name, Data $data ) {
$event_name = static::EVENT_MAP[ $event_name ] ?? '';
$method = "get_{$event_name}_data";
if ( method_exists( $this, $method ) ) {
$prepared_data = call_user_func( array( $this, $method ), $data );
} else {
$prepared_data = array(
'event_id' => $data->get_event_id(),
);
}
return $prepared_data;
}
/**
* Prepares data for search event.
*
* @see Tag::prepare_request_data()
* @since 1.4.0
*
* @param Search $data Search tracking data.
*
* @return array Prepared request data.
*/
private function get_search_data( Search $data ) {
return array(
'event_id' => $data->get_event_id(),
'search_query' => $data->get_search_query(),
);
}
/**
* Prepares data for page visit event.
*
* @see Tag::prepare_request_data()
* @since 1.4.0
*
* @param Product|None $data Product tracking data.
*
* @return array Prepared request data.
*/
private function get_page_visit_data( Data $data ) {
if ( $data instanceof None ) {
return array(
'event_id' => $data->get_event_id(),
);
}
return array(
'event_id' => $data->get_event_id(),
'product_id' => $data->get_id(),
'product_name' => $data->get_name(),
'product_price' => $data->get_price(),
'currency' => $data->get_currency(),
);
}
/**
* Prepares data for view category event.
*
* @see Tag::prepare_request_data()
* @since 1.4.0
*
* @param Category $data Category tracking data.
*
* @return array Prepared request data.
*/
private function get_view_category_data( Category $data ) {
return array(
'event_id' => $data->get_event_id(),
'product_category' => $data->get_id(),
'category_name' => $data->get_name(),
);
}
/**
* Prepares data for checkout event.
*
* @see Tag::prepare_request_data()
* @since 1.4.0
*
* @param Checkout $data Checkout tracking data.
*
* @return array Prepared request data.
*/
private function get_checkout_data( Checkout $data ) {
return array(
'event_id' => $data->get_event_id(),
'order_id' => $data->get_order_id(),
'value' => $data->get_price(),
'order_quantity' => $data->get_quantity(),
'currency' => $data->get_currency(),
'line_items' => array_map(
function ( $item ) {
return array(
'product_id' => $item->get_id(),
'product_name' => $item->get_name(),
'product_price' => $item->get_price(),
'product_quantity' => $item->get_quantity(),
'product_category' => $item->get_category(),
);
},
$data->get_items()
),
);
}
/**
* Prepares data for add to cart event.
*
* @see Tag::prepare_request_data()
* @since 1.4.0
*
* @param Product $data Product tracking data.
*
* @return array Prepared request data.
*/
private function get_add_to_cart_data( Product $data ) {
return array(
'event_id' => $data->get_event_id(),
'product_id' => $data->get_id(),
'product_name' => $data->get_name(),
'value' => $data->get_price() * $data->get_quantity(),
'order_quantity' => $data->get_quantity(),
'currency' => $data->get_currency(),
);
}
/**
* @return mixed
*/
public static function get_active_tag() {
return Pinterest_For_Woocommerce()::get_setting( 'tracking_tag' );
}
/**
* Get the formatted warning message for the potential conflicting tags.
*
* @since 1.4.0
*
* @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.4.0
*
* @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;
}
/**
* Returns the hashed email of the current user if any.
*
* @return string
*/
private static function maybe_get_hashed_customer_email() {
$user_email = '';
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'] : '';
}
return $user_email ? md5( $user_email ) : '';
}
/**
* Returns the transient key for deferred events based on user session.
*
* @return string
*/
private static function get_deferred_events_transient_key() {
if ( is_object( WC()->session ) ) {
return 'pinterest_for_woocommerce_async_events_' . md5( WC()->session->get_customer_id() );
}
return '';
}
/**
* Adds a fragment to trigger Pinterest Tag event on add to cart event.
*
* @since 1.4.0
*
* @param string $event_name Event name.
* @param array $data Event data.
* @return bool
*/
private static function maybe_add_fragment( string $event_name, array $data ) {
if ( Tracking::EVENT_ADD_TO_CART === $event_name ) {
$event = static::get_event_code( $event_name, $data );
add_filter(
'woocommerce_add_to_cart_fragments',
function ( $fragments ) use ( $event ) {
$fragments['script#pinterest-tag-placeholder'] = <<
{$event}
JS;
return $fragments;
}
);
}
return true;
}
}