PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / classes / class-cartflows-tracking.php

class-cartflows-tracking.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.3.0, at classes/class-cartflows-tracking.php

1,804 lines 57.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Cartflows_Tracking
4 *
5 * @package CartFlows
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12 /**
13 * Flow Markup
14 *
15 * @since 1.0.0
16 */
17 class Cartflows_Tracking {
18
19
20 /**
21 * Member Variable
22 *
23 * @var object instance
24 */
25 private static $instance;
26
27 /**
28 * Member Variable
29 *
30 * @var object fb_pixel_settings
31 */
32 private static $fb_pixel_settings;
33
34 /**
35 * Member Variable
36 *
37 * @var object tik_pixel_settings
38 */
39 private static $tik_pixel_settings;
40
41 /**
42 * Member Variable
43 *
44 * @var object pin_tag_settings
45 */
46 private static $pin_tag_settings;
47
48 /**
49 * Member Variable
50 *
51 * @var object ga_settings
52 */
53 private static $ga_settings;
54
55 /**
56 * Member Variable
57 *
58 * @since 2.1.0
59 * @var array gads_settings
60 */
61 private static $gads_settings;
62
63 /**
64 * Member Variable
65 *
66 * @since 2.1.0
67 * @var array snapchat_settings
68 */
69 private static $snapchat_settings;
70
71 /**
72 * Initiator
73 */
74 public static function get_instance() {
75 if ( ! isset( self::$instance ) ) {
76 self::$instance = new self();
77 }
78 return self::$instance;
79 }
80
81 /**
82 * Constructor
83 */
84 public function __construct() {
85
86 self::$fb_pixel_settings = Cartflows_Helper::get_facebook_settings();
87 self::$tik_pixel_settings = Cartflows_Helper::get_tiktok_settings();
88 self::$ga_settings = Cartflows_Helper::get_google_analytics_settings();
89 self::$pin_tag_settings = Cartflows_Helper::get_pinterest_settings();
90 self::$gads_settings = Cartflows_Helper::get_google_ads_settings();
91 self::$snapchat_settings = Cartflows_Helper::get_snapchat_settings();
92
93 add_action( 'wp_head', array( $this, 'add_tracking_code' ) );
94
95 add_filter( 'global_cartflows_js_localize', array( $this, 'add_localize_vars' ) );
96
97 add_action( 'wp_footer', array( $this, 'render_pinterest_consent_popup' ) );
98 }
99
100 /**
101 * Validate order access by checking wcf-key against order key.
102 *
103 * Prevents order data leakage via order ID enumeration by ensuring
104 * the request includes a valid order key that matches the order.
105 *
106 * @param int $order_id The order ID to validate.
107 * @return bool True if order access is valid, false otherwise.
108 */
109 private function is_valid_order_access( $order_id ) {
110 //phpcs:ignore WordPress.Security.NonceVerification.Recommended
111 if ( ! isset( $_GET['wcf-key'] ) ) {
112 return false;
113 }
114
115 $order = wc_get_order( $order_id );
116 if ( ! $order ) {
117 return false;
118 }
119
120 //phpcs:ignore WordPress.Security.NonceVerification.Recommended
121 $order_key = sanitize_text_field( wp_unslash( $_GET['wcf-key'] ) );
122
123 return $order->get_order_key() === $order_key;
124 }
125
126 /**
127 * Add the required nonce for tracking.
128 *
129 * @param array $vars localised vars.
130 */
131 public function add_localize_vars( $vars ) {
132
133 // Add the dynamic cookie name in the localize vars for frontend use.
134 $vars['pinterest_consent_cookie'] = CARTFLOWS_PINTEREST_CONSENT;
135
136 if ( 'enable' === self::$fb_pixel_settings['facebook_pixel_add_payment_info'] ) {
137 $vars['fb_add_payment_info_data'] = wp_json_encode( $this->prepare_cart_data_fb_response( 'add_payment_info' ) );
138 }
139
140 if ( 'enable' === self::$ga_settings['enable_add_payment_info'] ) {
141 $vars['add_payment_info_data'] = wp_json_encode( $this->prepare_cart_data_ga_response() );
142 }
143
144 if ( 'enable' === self::$tik_pixel_settings['enable_tiktok_add_payment_info'] ) {
145 $vars['tiktok_add_payment_info_data'] = wp_json_encode( $this->prepare_cart_data_tiktok_response() );
146 }
147
148 if ( 'enable' === self::$pin_tag_settings['enable_pinterest_add_payment_info'] ) {
149 $vars['pinterest_add_payment_info_data'] = wp_json_encode( $this->prepare_cart_data_pinterest_response() );
150 }
151
152 if ( 'enable' === self::$pin_tag_settings['enable_pinterest_signup'] ) {
153 $vars['pinterest_signup_info_data'] = wp_json_encode( $this->prepare_cart_data_pinterest_response( 'signup' ) );
154 }
155
156 if ( 'enable' === self::$gads_settings['enable_google_ads_add_payment_info'] ) {
157 $vars['gads_add_payment_info_data'] = wp_json_encode( $this->prepare_cart_data_gads_response() );
158 }
159
160 return $vars;
161 }
162
163 /**
164 * Add the facebook pixel and google analytics code.
165 */
166 public function add_tracking_code() {
167
168 $compatibility = Cartflows_Compatibility::get_instance();
169
170 if ( $compatibility->is_page_builder_preview() ) {
171 return;
172 }
173
174 $this->add_facebook_pixel_tracking_code();
175 $this->add_google_analytics_tracking_code();
176 $this->add_tiktok_pixel_tracking_code();
177 $this->add_pinterest_tag_tracking_code();
178 $this->add_google_ads_tracking_code();
179 $this->add_snapchat_pixel_tracking_code();
180 }
181
182 /**
183 * Function for facebook pixel.
184 */
185 public function add_facebook_pixel_tracking_code() {
186
187 if ( 'enable' === self::$fb_pixel_settings['facebook_pixel_tracking'] ) {
188
189 $facebook_id = esc_attr( self::$fb_pixel_settings['facebook_pixel_id'] );
190 $fb_script = "
191 <!-- Facebook Pixel Script By CartFlows -->
192
193 <script type='text/javascript'>
194 !function(f,b,e,v,n,t,s)
195 {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
196 n.callMethod.apply(n,arguments):n.queue.push(arguments)};
197 if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
198 n.queue=[];t=b.createElement(e);t.async=!0;
199 t.src=v;s=b.getElementsByTagName(e)[0];
200 s.parentNode.insertBefore(t,s)}(window, document,'script',
201 'https://connect.facebook.net/en_US/fbevents.js');
202 </script>
203
204 <noscript>
205 <img height='1' width='1' style='display:none' src='https://www.facebook.com/tr?id=" . esc_js( $facebook_id ) . "&ev=PageView&noscript=1'/>
206 </noscript>
207
208 <script type='text/javascript'>
209 fbq('init', " . esc_js( $facebook_id ) . ");
210 fbq('track', 'PageView', {'plugin': 'CartFlows'});
211 </script>
212
213 <!-- End Facebook Pixel Script By CartFlows -->";
214
215 if ( 'enable' === self::$fb_pixel_settings['facebook_pixel_tracking_for_site'] ) {
216 echo $fb_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
217 $this->trigger_viewcontent_events();
218 } elseif ( wcf()->utils->is_step_post_type() ) {
219 echo $fb_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
220 $this->trigger_viewcontent_events();
221 }
222
223 // Trigger other events on CartFlows pages only.
224 if ( wcf()->is_woo_active && wcf()->utils->is_step_post_type() ) {
225 $this->trigger_other_fb_events();
226 }
227 }
228 }
229
230 /**
231 * Trigger the View Content events for facebook pixel.
232 */
233 public function trigger_viewcontent_events() {
234
235 $event_script = '';
236
237 // Check if ViewContent is enable or disable.
238 if ( 'enable' === self::$fb_pixel_settings['facebook_pixel_view_content'] ) {
239 $view_content = wp_json_encode( $this->prepare_viewcontent_data_fb_response() );
240 $event_script .= "
241 <script type='text/javascript'>
242 fbq( 'track', 'ViewContent', $view_content );
243 </script>";
244 }
245
246 echo $event_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
247 }
248
249 /**
250 * Trigger the other events for facebook pixel.
251 */
252 public function trigger_other_fb_events() {
253
254 $event_script = '';
255
256 if ( _is_wcf_checkout_type() && 'enable' === self::$fb_pixel_settings['facebook_pixel_initiate_checkout'] ) {
257
258 $cart_data = wp_json_encode( $this->prepare_cart_data_fb_response( 'add_to_cart' ) );
259 $initiate_checkout_data = wp_json_encode( $this->prepare_cart_data_fb_response( 'initiate_checkout' ) );
260
261 $event_script .= "
262 <script type='text/javascript'>
263 fbq( 'track', 'AddToCart', $cart_data );
264 fbq( 'track', 'InitiateCheckout', $initiate_checkout_data );
265 </script>";
266 }
267
268 if ( isset( $_GET['wcf-order'] ) && 'enable' === self::$fb_pixel_settings['facebook_pixel_purchase_complete'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
269
270 $order_id = intval( $_GET['wcf-order'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
271
272 if ( $this->is_valid_order_access( $order_id ) ) {
273 $purchase_details = $this->prepare_purchase_data_fb_response( $order_id );
274 if ( ! empty( $purchase_details ) ) {
275 $purchase_details = wp_json_encode( $purchase_details );
276 $event_script .= "
277 <script type='text/javascript'>
278 fbq( 'track', 'Purchase', $purchase_details );
279 </script>";
280 }
281 }
282 }
283
284 do_action( 'cartflows_facebook_pixel_events' );
285
286 echo $event_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
287 }
288
289 /**
290 * Prepare checkout purchase response for facebook purchase event.
291 *
292 * @param integer $order_id order id.
293 */
294 public function prepare_purchase_data_fb_response( $order_id ) {
295
296 $purchase_data = array();
297 $order = wc_get_order( $order_id );
298
299 if ( ! $order ) {
300 return $purchase_data;
301 }
302
303 $is_checkout_tracked = $order->get_meta( '_wcf_fbp_checkout_tracked' );
304 if ( $is_checkout_tracked ) {
305 return $purchase_data;
306 }
307
308 // Do not trigger purchase event if it is optin.
309 $is_optin = $order->get_meta( '_wcf_optin_id' );
310
311 if ( $is_optin ) {
312 return $purchase_data;
313 }
314
315 $order_currency = wcf()->options->get_checkout_meta_value( $order_id, '_order_currency' );
316
317 if ( empty( $order_currency ) ) {
318 $order_currency = $order->get_currency();
319 }
320
321 $purchase_data['transaction_id'] = $order_id;
322 $purchase_data['content_type'] = 'product';
323 $purchase_data['currency'] = $order_currency;
324 $purchase_data['userAgent'] = wcf()->options->get_checkout_meta_value( $order_id, '_customer_user_agent' );
325 $purchase_data['plugin'] = 'CartFlows';
326
327 // Iterating through each WC_Order_Item_Product objects.
328 foreach ( $order->get_items() as $item_key => $item ) {
329 $product = $item->get_product(); // Get the WC_Product object.
330 $purchase_data['content_ids'][] = (string) $product->get_id();
331 $purchase_data['content_names'][] = $product->get_name();
332 $purchase_data['content_category'][] = wp_strip_all_tags( wc_get_product_category_list( $product->get_id() ) );
333 }
334
335 $order_value = wcf()->options->get_checkout_meta_value( $order_id, '_order_total' );
336
337 if ( empty( $order_value ) ) {
338 $order_value = $order->get_total();
339 }
340
341 $purchase_data['value'] = $order_value;
342 $order->update_meta_data( '_wcf_fbp_checkout_tracked', true );
343 $order->save();
344
345 return $purchase_data;
346 }
347
348 /**
349 * Prepare cart data for fb response.
350 *
351 * @param string $event event type.
352 *
353 * @return array
354 */
355 public function prepare_cart_data_fb_response( $event = '' ) {
356
357 $params = array();
358
359 if ( ! wcf()->is_woo_active ) {
360 return $params;
361 }
362
363 $cart_total = self::format_number( WC()->cart->cart_contents_total + WC()->cart->tax_total );
364 $cart_items_count = WC()->cart->get_cart_contents_count();
365 $items = WC()->cart->get_cart();
366
367 $product_data = $this->get_required_product_data_for_fb( $items );
368
369 $params['content_ids'] = $product_data['content_ids'];
370 $params['content_type'] = 'product';
371 $params['plugin'] = 'CartFlows-Checkout';
372 $params['value'] = $cart_total;
373 $params['content_name'] = substr( $product_data['product_names'], 2 );
374 $params['content_category'] = substr( $product_data['category_names'], 2 );
375 $params['contents'] = wp_json_encode( $product_data['cart_contents'] );
376 $params['currency'] = get_woocommerce_currency();
377
378 if ( 'add_to_cart' !== $event ) {
379 $params['num_items'] = $cart_items_count;
380 $params['domain'] = get_site_url();
381 $params['language'] = get_bloginfo( 'language' );
382 $params['userAgent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? wc_clean( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; //phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__HTTP_USER_AGENT__
383 }
384
385 return $params;
386 }
387
388 /**
389 * Prepare view content data for fb response.
390 *
391 * @return array
392 */
393 public function prepare_viewcontent_data_fb_response() {
394 global $post, $wcf_step;
395
396 $params = array();
397
398 // Page Title.
399 $step_id = ( $wcf_step ) ? ( $wcf_step->get_current_step() ) : ( get_the_ID() );
400 $params['content_name'] = get_post_field( 'post_title', $step_id );
401
402 // Checkout Page View Content Data.
403 if ( wcf()->is_woo_active ) {
404
405 if ( _is_wcf_checkout_type() ) {
406 $cart_total = self::format_number( WC()->cart->cart_contents_total + WC()->cart->tax_total );
407 $items = WC()->cart->get_cart();
408 $product_data = $this->get_required_product_data_for_fb( $items );
409
410 $params['content_ids'] = $product_data['content_ids'];
411 $params['currency'] = get_woocommerce_currency();
412 $params['value'] = $cart_total;
413 $params['content_type'] = 'product';
414 $params['contents'] = wp_json_encode( $product_data['cart_contents'] );
415 }
416
417 // Added filter for offer pages view content event compatibility.
418 $params = apply_filters( 'cartflows_view_content_offer', $params, $step_id );
419 }
420
421 return $params;
422 }
423
424 /**
425 * Get product data for FB.
426 *
427 * @param object $items products data.
428 */
429 public function get_required_product_data_for_fb( $items ) {
430
431 $product_data = array();
432 $content_ids = array();
433 $category_names = '';
434 $product_names = '';
435
436 foreach ( $items as $item => $value ) {
437
438 $_product = wc_get_product( $value['product_id'] );
439
440 if ( $_product ) {
441
442 $product_obj = $_product;
443
444 if ( $_product->is_type( 'variable' ) && isset( $value['variation_id'] ) ) {
445 $product_obj = wc_get_product( $value['variation_id'] );
446 }
447
448 if ( $product_obj ) {
449 $content_ids[] = (string) $product_obj->get_id();
450 $product_names = $product_names . ', ' . $product_obj->get_name();
451 $category_names = $category_names . ', ' . wp_strip_all_tags( wc_get_product_category_list( $product_obj->get_id() ) );
452
453 $data = array(
454 'id' => $product_obj->get_id(),
455 'name' => $product_obj->get_name(),
456 'price' => self::format_number( $value['line_subtotal'] + $value['line_subtotal_tax'] ),
457 'quantity' => $value['quantity'],
458 );
459
460 array_push( $product_data, $data );
461 }
462 }
463 }
464
465 return array(
466 'cart_contents' => $product_data,
467 'content_ids' => $content_ids,
468 'product_names' => $product_names,
469 'category_names' => $category_names,
470 );
471 }
472 /**
473 * Render google tag framework.
474 */
475 public function add_google_analytics_tracking_code() {
476
477 $ga_tracking_id = esc_attr( self::$ga_settings['google_analytics_id'] );
478
479 if ( 'enable' === self::$ga_settings['enable_google_analytics'] ) {
480 // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript
481 $ga_script =
482 '<!-- Google Analytics Script By CartFlows start-->
483 <script async src="https://www.googletagmanager.com/gtag/js?id=' . esc_js( $ga_tracking_id ) . '"></script>
484
485 <script>
486 window.dataLayer = window.dataLayer || [];
487 function gtag(){dataLayer.push(arguments);}
488 gtag( "js", new Date() );
489 gtag("config","' . esc_js( $ga_tracking_id ) . '");
490 </script>
491
492 <!-- Google Analytics Script By CartFlows -->
493 ';
494
495 //phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript
496
497 if ( 'enable' === self::$ga_settings['enable_google_analytics_for_site'] ) {
498 echo $ga_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
499 } elseif ( wcf()->utils->is_step_post_type() ) {
500 echo $ga_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
501 }
502
503 // Trigger other events on CartFlows pages only.
504 if ( wcf()->is_woo_active && wcf()->utils->is_step_post_type() ) {
505 $this->trigger_other_ga_events();
506 }
507 }
508 }
509
510 /**
511 * Trigger the other events for facebook pixel.
512 */
513 public function trigger_other_ga_events() {
514
515 $event_script = '';
516
517 if ( _is_wcf_checkout_type() ) {
518
519 $cart_data = $this->prepare_cart_data_ga_response();
520
521 $event_data = wp_json_encode( $cart_data );
522
523 if ( 'enable' === self::$ga_settings['enable_add_to_cart'] ) {
524 $event_script .= "
525 <script type='text/javascript'>
526 gtag( 'event', 'add_to_cart', $event_data );
527 </script>";
528 }
529 if ( 'enable' === self::$ga_settings['enable_begin_checkout'] ) {
530 $event_script .= "
531 <script type='text/javascript'>
532 gtag( 'event', 'begin_checkout', $event_data );
533 </script>";
534 }
535 }
536
537 if ( isset( $_GET['wcf-order'] ) && 'enable' === self::$ga_settings['enable_purchase_event'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
538
539 $order_id = intval( $_GET['wcf-order'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
540
541 if ( $this->is_valid_order_access( $order_id ) ) {
542 $purchase_details = $this->get_ga_purchase_transactions_data( $order_id );
543
544 if ( ! empty( $purchase_details ) ) {
545
546 $purchase_data = wp_json_encode( $purchase_details );
547
548 $event_script .= "
549 <script type='text/javascript'>
550 gtag( 'event', 'purchase', $purchase_data );
551 </script>";
552 }
553 }
554 }
555
556 do_action( 'cartflows_google_analytics_events' );
557
558 echo $event_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
559 }
560
561 /**
562 * Prepare cart data for GA response.
563 *
564 * @param int $order_id order id.
565 * @return array
566 */
567 public function get_ga_purchase_transactions_data( $order_id ) {
568
569 $purchase_data = array();
570 $order = wc_get_order( $order_id );
571
572 if ( ! $order ) {
573 return $purchase_data;
574 }
575
576 $is_checkout_tracked = $order->get_meta( '_wcf_ga_checkout_tracked' );
577 if ( $is_checkout_tracked ) {
578 return $purchase_data;
579 }
580
581 $purchase_data['items'] = array();
582 $cart_contents = array();
583
584 $purchase_data = array(
585 'send_to' => self::$ga_settings['google_analytics_id'],
586 'event_category' => 'Enhanced-Ecommerce',
587 'transaction_id' => $order_id,
588 'affiliation' => get_bloginfo( 'name' ),
589 'value' => self::format_number( $order->get_total() ),
590 'currency' => $order->get_currency(),
591 'tax' => self::format_number( $order->get_total_tax() ),
592 'shipping' => self::format_number( $order->get_shipping_total() + $order->get_shipping_tax() ),
593 'coupon' => $order->get_coupon_codes(),
594 'non_interaction' => true,
595 );
596
597 $items = $order->get_items();
598 $items_data = $this->get_required_product_data_for_ga( $items );
599 $purchase_data['items'] = $items_data;
600
601 $order->update_meta_data( '_wcf_ga_checkout_tracked', true );
602 $order->save();
603
604 return $purchase_data;
605 }
606
607 /**
608 * Prepare cart data for GA response.
609 *
610 * @since 2.1.0
611 * @param int $order_id order id.
612 * @return array $purchase_data The Purchase data of the Google Ads Event
613 */
614 public function get_gads_purchase_transactions_data( $order_id ) {
615
616 $purchase_data = array();
617 $order = wc_get_order( $order_id );
618
619 if ( ! $order ) {
620 return $purchase_data;
621 }
622
623 $is_checkout_tracked = $order->get_meta( '_wcf_gads_checkout_tracked' );
624 if ( $is_checkout_tracked ) {
625 return $purchase_data;
626 }
627
628 $purchase_data['items'] = array();
629 $gads_tracking_id = sanitize_text_field( self::$gads_settings['google_ads_id'] );
630 $gads_conversion_label = sanitize_text_field( self::$gads_settings['google_ads_label'] );
631 $purchase_data = array(
632 'send_to' => $gads_tracking_id . '/' . $gads_conversion_label,
633 'event_category' => 'Enhanced-Ecommerce',
634 'transaction_id' => $order_id,
635 'affiliation' => get_bloginfo( 'name' ),
636 'value' => self::format_number( $order->get_total() ),
637 'currency' => $order->get_currency(),
638 'tax' => self::format_number( $order->get_total_tax() ),
639 'shipping' => self::format_number( $order->get_shipping_total() + $order->get_shipping_tax() ),
640 'coupon' => $order->get_coupon_codes(),
641 'non_interaction' => true,
642 );
643
644 $items = $order->get_items();
645 $items_data = $this->get_required_product_data_for_ga( $items );
646 $purchase_data['items'] = $items_data;
647
648 $order->update_meta_data( '_wcf_gads_checkout_tracked', true );
649 $order->save();
650
651 return $purchase_data;
652 }
653
654 /**
655 * Prepare Ecommerce data for GA response.
656 *
657 * @return array
658 */
659 public function prepare_cart_data_ga_response() {
660
661 $items_data = array();
662 $cart_data = array();
663
664 if ( ! wcf()->is_woo_active ) {
665 return $cart_data;
666 }
667
668 $items = WC()->cart->get_cart();
669
670 $items_data = $this->get_required_product_data_for_ga( $items );
671
672 $cart_data = array(
673 'send_to' => self::$ga_settings['google_analytics_id'],
674 'event_category' => 'Enhanced-Ecommerce',
675 'currency' => get_woocommerce_currency(),
676 'coupon' => WC()->cart->get_applied_coupons(),
677 'value' => self::format_number( WC()->cart->cart_contents_total + WC()->cart->tax_total ),
678 'items' => $items_data,
679 'non_interaction' => true,
680 );
681
682 return $cart_data;
683 }
684
685
686 /**
687 * Prepare Ecommerce data for GA response.
688 *
689 * @since 2.1.0
690 * @return array
691 */
692 public function prepare_cart_data_gads_response() {
693
694 $items_data = array();
695 $cart_data = array();
696
697 if ( ! wcf()->is_woo_active ) {
698 return $cart_data;
699 }
700
701 $items = WC()->cart->get_cart();
702
703 $items_data = $this->get_required_product_data_for_ga( $items );
704 $gads_tracking_id = sanitize_text_field( self::$gads_settings['google_ads_id'] );
705 $gads_conversion_label = sanitize_text_field( self::$gads_settings['google_ads_label'] );
706 $cart_data = array(
707 'send_to' => $gads_tracking_id . '/' . $gads_conversion_label,
708 'event_category' => 'Enhanced-Ecommerce',
709 'currency' => get_woocommerce_currency(),
710 'coupon' => WC()->cart->get_applied_coupons(),
711 'value' => self::format_number( WC()->cart->cart_contents_total + WC()->cart->tax_total ),
712 'items' => $items_data,
713 'non_interaction' => true,
714 );
715
716 return $cart_data;
717 }
718
719
720 /**
721 * Get product data.
722 *
723 * @param object $items products data.
724 */
725 public function get_required_product_data_for_ga( $items ) {
726
727 $product_data = array();
728
729 foreach ( $items as $item => $value ) {
730
731 $_product = wc_get_product( $value['product_id'] );
732
733 if ( $_product ) {
734
735 $product_obj = $_product;
736
737 if ( $_product->is_type( 'variable' ) && isset( $value['variation_id'] ) ) {
738 $product_obj = wc_get_product( $value['variation_id'] );
739 }
740
741 if ( $product_obj ) {
742
743 $data = array(
744 'id' => $product_obj->get_id(),
745 'name' => $product_obj->get_name(),
746 'sku' => $product_obj->get_sku(),
747 'category' => wp_strip_all_tags( wc_get_product_category_list( $product_obj->get_id() ) ),
748 'price' => self::format_number( $value['line_subtotal'] + $value['line_subtotal_tax'] ),
749 'quantity' => $value['quantity'],
750 );
751
752 array_push( $product_data, $data );
753 }
754 }
755 }
756
757 return $product_data;
758 }
759
760 /**
761 * Function for tiktok pixel.
762 */
763 public function add_tiktok_pixel_tracking_code() {
764
765 if ( 'enable' === self::$tik_pixel_settings['tiktok_pixel_tracking'] ) {
766
767 $tiktok_id = isset( self::$tik_pixel_settings['tiktok_pixel_id'] ) ? trim( (string) sanitize_text_field( self::$tik_pixel_settings['tiktok_pixel_id'] ) ) : '';
768 $identify_data = wp_json_encode( $this->prepare_identity_data_for_pixel_events() );
769
770 $tik_script = "
771 <!-- TikTok Pixel Script By CartFlows -->
772
773 <script type='text/javascript'>
774 !function (w, d, t) {
775 w.TiktokAnalyticsObject=t;var ttq=w[t]=w[t]||[];ttq.methods=['page','track','identify','instances','debug','on','off','once','ready','alias','group','enableCookie','disableCookie','holdConsent','revokeConsent','grantConsent'],ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}};for(var i=0;i<ttq.methods.length;i++)ttq.setAndDefer(ttq,ttq.methods[i]);ttq.instance=function(t){for(
776 var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e},ttq.load=function(e,n){var r='https://analytics.tiktok.com/i18n/pixel/events.js',o=n&&n.partner;ttq._i=ttq._i||{},ttq._i[e]=[],ttq._i[e]._u=r,ttq._t=ttq._t||{},ttq._t[e]=+new Date,ttq._o=ttq._o||{},ttq._o[e]=n||{};n=document.createElement('script')
777 ;n.type='text/javascript',n.async=!0,n.src=r+'?sdkid='+e+'&lib='+t;e=document.getElementsByTagName('script')[0];e.parentNode.insertBefore(n,e)};
778
779
780 ttq.load('" . esc_js( $tiktok_id ) . "');
781 ttq.instance('" . esc_js( $tiktok_id ) . "').identify($identify_data);
782 ttq.page();
783 }(window, document, 'ttq');
784 </script>
785
786 <!-- End TikTok Pixel Script By CartFlows -->";
787
788 if ( 'enable' === self::$tik_pixel_settings['tiktok_pixel_tracking_for_site'] ) {
789 echo $tik_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
790 $this->trigger_tiktok_viewcontent_events();
791 } elseif ( wcf()->utils->is_step_post_type() ) {
792 echo $tik_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
793 $this->trigger_tiktok_viewcontent_events();
794 }
795
796 // Trigger other events on CartFlows pages only.
797 if ( wcf()->is_woo_active && wcf()->utils->is_step_post_type() && $tiktok_id ) {
798 $this->trigger_other_tiktok_events( $tiktok_id );
799 }
800 }
801 }
802
803 /**
804 * Trigger the View Content events for tiktok pixel.
805 */
806 public function trigger_tiktok_viewcontent_events() {
807
808 $event_script = '';
809
810 // Check if ViewContent is enable or disable.
811 if ( 'enable' === self::$tik_pixel_settings['enable_tiktok_view_content'] ) {
812 $view_content = $this->prepare_viewcontent_data_tiktok_response();
813
814 if ( ! empty( $view_content ) ) {
815 $view_content_data = wp_json_encode( $this->prepare_viewcontent_data_tiktok_response() );
816 $event_script .= "
817 <script type='text/javascript'>
818 setTimeout(function () {
819 ttq.track('ViewContent', " . $view_content_data . ');
820 }, 1200);
821 </script>';
822 }
823 }
824
825 echo $event_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
826 }
827
828 /**
829 * Prepare view content data for tiktok response.
830 *
831 * @return array
832 */
833 public function prepare_viewcontent_data_tiktok_response() {
834 global $post, $wcf_step;
835
836 $params = array();
837
838 $step_id = ( $wcf_step ) ? ( $wcf_step->get_current_step() ) : ( get_the_ID() );
839 $content_name = get_post_field( 'post_title', $step_id );
840 $contents = array();
841 $total_value = 0;
842
843 // Checkout Page View Content Data.
844 if ( wcf()->is_woo_active ) {
845 if ( _is_wcf_thankyou_type() ) {
846 if ( isset( $_GET['wcf-order'] ) && ! empty( $_GET['wcf-order'] ) ) {//phpcs:ignore WordPress.Security.NonceVerification.Recommended
847 $order_id = intval( $_GET['wcf-order'] );//phpcs:ignore WordPress.Security.NonceVerification.Recommended
848 $order = wc_get_order( $order_id );
849
850 // Check if the order exists.
851 if ( $order ) {
852 $total_value = $order->get_total();
853
854 foreach ( $order->get_items() as $item_id => $item ) {
855 $product = $item->get_product();
856
857 $contents[] = array(
858 'content_id' => $product->get_id(),
859 'content_name' => $product->get_name(),
860 'content_type' => 'product',
861 );
862 }
863 }
864 }
865 } elseif ( _is_wcf_checkout_type() || _is_wcf_landing_type() ) {
866 $total_value = self::format_number( WC()->cart->cart_contents_total + WC()->cart->tax_total );
867 $items = WC()->cart->get_cart();
868
869 if ( $items ) {
870 foreach ( $items as $item ) {
871 $product_id = $item['product_id'];
872 $product_name = get_the_title( $product_id );
873
874 $contents[] = array(
875 'content_id' => (string) $product_id,
876 'content_name' => $product_name,
877 'content_type' => 'product',
878 );
879 }
880 }
881 }
882
883 // Set TikTok parameters.
884 $params['contents'] = $contents;
885 $params['value'] = $total_value;
886 $params['currency'] = get_woocommerce_currency();
887
888 // Added filter for offer pages view content event compatibility.
889 $params = apply_filters( 'cartflows_tiktok_view_content_offer', $params, $step_id );
890
891 if ( empty( $params['contents'] ) ) {
892 return array();
893 }
894 }
895
896 return $params;
897 }
898
899 /**
900 * Prepare identity data for pixel events.
901 *
902 * @return array
903 */
904 public function prepare_identity_data_for_pixel_events() {
905 $user = wp_get_current_user();
906 $identify_data = array();
907
908 if ( ! empty( $user ) && 0 !== $user->ID ) {
909
910 // Check if user has an email, and hash it using SHA-256.
911 $user_email = ! empty( $user->user_email ) ? $user->user_email : $user->get( 'billing_email' );
912
913 // Check if user has an phone number, and hash it using SHA-256.
914 $user_contact = get_user_meta( $user->ID, 'user_phone', true );
915 $phone_number = ! empty( $user_contact ) ? $user_contact : $user->get( 'billing_phone' );
916
917 $identify_data['email'] = ! empty( $user_email ) ? hash( 'sha256', $user_email ) : '';
918 $identify_data['phone_number'] = ! empty( $phone_number ) ? hash( 'sha256', $phone_number ) : '';
919
920 }
921
922 return $identify_data;
923 }
924
925 /**
926 * Prepare cart data for tiktok response.
927 *
928 * @param string $event event type.
929 *
930 * @return array
931 */
932 public function prepare_cart_data_tiktok_response( $event = '' ) {
933
934 $params = array();
935
936 if ( ! wcf()->is_woo_active ) {
937 return $params;
938 }
939
940 // Calculate cart total and get cart items.
941 $cart_total = self::format_number( WC()->cart->cart_contents_total + WC()->cart->tax_total );
942 $cart_items = WC()->cart->get_cart();
943 $contents = array();
944
945 // Loop through each cart item to get the required data.
946 foreach ( $cart_items as $cart_item ) {
947 $product = $cart_item['data'];
948 $contents[] = array(
949 'content_id' => $cart_item['product_id'],
950 'content_name' => $product->get_name(),
951 'quantity' => $cart_item['quantity'],
952 'price' => wc_get_price_to_display( $product ),
953 'content_type' => 'product',
954 );
955 }
956
957 // Prepare params.
958 $params['contents'] = $contents;
959 $params['value'] = $cart_total;
960 $params['currency'] = get_woocommerce_currency();
961
962 if ( 'add_to_cart' !== $event ) {
963 $params['num_items'] = WC()->cart->get_cart_contents_count();
964 }
965
966 return $params;
967 }
968
969 /**
970 * Prepare purchase data for tiktok response.
971 *
972 * @param integer $order_id order id.
973 *
974 * @return array
975 */
976 public function prepare_purchase_data_tiktok_response( $order_id ) {
977
978 $purchase_data = array();
979 $order = wc_get_order( $order_id );
980
981 if ( ! $order ) {
982 return $purchase_data;
983 }
984
985 // Check if the checkout has already been tracked.
986 $is_checkout_tracked = $order->get_meta( '_wcf_tiktok_checkout_tracked' );
987 if ( $is_checkout_tracked ) {
988 return $purchase_data;
989 }
990
991 // Do not trigger purchase event if it is an opt-in.
992 $is_optin = $order->get_meta( '_wcf_optin_id' );
993 if ( $is_optin ) {
994 return $purchase_data;
995 }
996
997 // Prepare data for TikTok CompletePayment event.
998 $purchase_data['transaction_id'] = $order_id;
999 $purchase_data['currency'] = $order->get_currency();
1000 $purchase_data['value'] = $order->get_total();
1001 $purchase_data['content_type'] = 'product'; // Set content type to 'product'.
1002 $purchase_data['plugin'] = 'CartFlows';
1003
1004 // Initialize contents array.
1005 $contents = array();
1006
1007 // Iterating through each WC_Order_Item_Product objects.
1008 foreach ( $order->get_items() as $item_key => $item ) {
1009 $product = $item->get_product(); // Get the WC_Product object.
1010
1011 // Append each product details to contents array.
1012 $contents[] = array(
1013 'content_id' => (string) $product->get_id(),
1014 'content_name' => $product->get_name(),
1015 'quantity' => $item->get_quantity(),
1016 'price' => wc_get_price_to_display( $product ),
1017 'content_type' => 'product',
1018 );
1019 }
1020
1021 // Add contents to the purchase data.
1022 $purchase_data['contents'] = $contents;
1023
1024 // Mark the order as tracked for this event.
1025 $order->update_meta_data( '_wcf_tiktok_checkout_tracked', true );
1026 $order->save();
1027
1028 return $purchase_data;
1029 }
1030
1031 /**
1032 * Trigger the other events for tiktok pixel.
1033 *
1034 * @param string $tiktok_id TikTok pixel ID.
1035 */
1036 public function trigger_other_tiktok_events( $tiktok_id ) {
1037
1038 $event_script = '';
1039
1040 if ( _is_wcf_checkout_type() && 'enable' === self::$tik_pixel_settings['enable_tiktok_add_to_cart'] ) {
1041 $cart_data = $this->prepare_cart_data_tiktok_response( 'add_to_cart' );
1042 if ( ! empty( $cart_data ) ) {
1043 $cart_data = wp_json_encode( $cart_data );
1044
1045 $event_script .= "
1046 <script type='text/javascript'>
1047 setTimeout(function () {
1048 ttq.instance('" . esc_js( $tiktok_id ) . "').track( 'AddToCart', $cart_data );
1049 }, 1300);
1050 </script>";
1051 }
1052 }
1053
1054 if ( _is_wcf_checkout_type() && 'enable' === self::$tik_pixel_settings['enable_tiktok_begin_checkout'] ) {
1055 $checkout_data = $this->prepare_cart_data_tiktok_response();
1056 if ( ! empty( $checkout_data ) ) {
1057 $checkout_data = wp_json_encode( $checkout_data );
1058
1059 $event_script .= "
1060 <script type='text/javascript'>
1061 ttq.instance('" . esc_js( $tiktok_id ) . "').track( 'InitiateCheckout', $checkout_data );
1062 </script>";
1063 }
1064 }
1065
1066 if ( isset( $_GET['wcf-order'] ) && 'enable' === self::$tik_pixel_settings['enable_tiktok_purchase_event'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1067 $order_id = intval( $_GET['wcf-order'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1068
1069 if ( $this->is_valid_order_access( $order_id ) ) {
1070 $purchase_details = $this->prepare_purchase_data_tiktok_response( $order_id );
1071 if ( ! empty( $purchase_details ) ) {
1072 $purchase_details = wp_json_encode( $purchase_details );
1073 $event_script .= "
1074 <script type='text/javascript'>
1075 ttq.instance('" . esc_js( $tiktok_id ) . "').track( 'CompletePayment', $purchase_details );
1076 </script>";
1077 }
1078 }
1079 }
1080
1081 do_action( 'cartflows_tiktok_pixel_events' );
1082
1083 echo $event_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1084 }
1085
1086 /**
1087 * Render google tag framework.
1088 *
1089 * @since 2.1.0
1090 * @return void
1091 */
1092 public function add_google_ads_tracking_code() {
1093
1094 $gads_tracking_id = esc_attr( self::$gads_settings['google_ads_id'] );
1095
1096 if ( 'enable' === self::$gads_settings['google_ads_tracking'] ) {
1097 // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript
1098 $gads_script =
1099 '<!-- Google Ads Script By CartFlows start-->
1100 <script async src="https://www.googletagmanager.com/gtag/js?id=' . esc_js( $gads_tracking_id ) . '"></script>
1101
1102 <script>
1103 window.dataLayer = window.dataLayer || [];
1104 function gtag(){dataLayer.push(arguments);}
1105 gtag( "js", new Date() );
1106 gtag("config","' . esc_js( $gads_tracking_id ) . '");
1107 </script>
1108
1109 <!-- Google Ads Script By CartFlows -->
1110 ';
1111
1112 //phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript
1113
1114 if ( 'enable' === self::$gads_settings['google_ads_for_site'] ) {
1115 echo $gads_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1116 $this->trigger_gads_viewcontent_events();
1117 } elseif ( wcf()->utils->is_step_post_type() ) {
1118 echo $gads_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1119 $this->trigger_gads_viewcontent_events();
1120 }
1121
1122 // Trigger other events on CartFlows pages only.
1123 if ( wcf()->is_woo_active && wcf()->utils->is_step_post_type() ) {
1124 $this->trigger_other_gads_events();
1125 }
1126 }
1127 }
1128
1129 /**
1130 * Trigger the other events for facebook pixel.
1131 *
1132 * @since 2.1.0
1133 * @return void
1134 */
1135 public function trigger_other_gads_events() {
1136 $event_script = '';
1137
1138 if ( _is_wcf_checkout_type() ) {
1139
1140 $cart_data = $this->prepare_cart_data_gads_response();
1141
1142 $event_data = wp_json_encode( $cart_data );
1143
1144 if ( 'enable' === self::$gads_settings['enable_google_ads_add_to_cart'] ) {
1145 $event_script .= "
1146 <script type='text/javascript'>
1147 gtag( 'event', 'add_to_cart', $event_data );
1148 </script>
1149 ";
1150 }
1151 if ( 'enable' === self::$gads_settings['enable_google_ads_begin_checkout'] ) {
1152 $event_script .= "
1153 <script type='text/javascript'>
1154 gtag( 'event', 'begin_checkout', $event_data );
1155 </script>";
1156 }
1157 }
1158
1159 if ( isset( $_GET['wcf-order'] ) && 'enable' === self::$gads_settings['enable_google_ads_purchase_event'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1160
1161 $order_id = intval( $_GET['wcf-order'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1162
1163 if ( $this->is_valid_order_access( $order_id ) ) {
1164 $purchase_details = $this->get_gads_purchase_transactions_data( $order_id );
1165
1166 if ( ! empty( $purchase_details ) ) {
1167
1168 $purchase_data = wp_json_encode( $purchase_details );
1169
1170 $event_script .= "
1171 <script type='text/javascript'>
1172 gtag( 'event', 'purchase', $purchase_data );
1173 </script>";
1174 }
1175 }
1176 }
1177
1178 do_action( 'cartflows_google_ads_events' );
1179
1180 echo $event_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1181 }
1182
1183 /**
1184 * Function for Pinterest tag.
1185 */
1186 public function add_pinterest_tag_tracking_code() {
1187 if ( 'enable' === self::$pin_tag_settings['pinterest_tag_tracking'] ) {
1188 $identify_data = $this->prepare_identity_data_for_pixel_events();
1189
1190 // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE
1191 $raw_consent = isset( $_COOKIE[ CARTFLOWS_PINTEREST_CONSENT ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ CARTFLOWS_PINTEREST_CONSENT ] ) ) : 'false';
1192 $pinterest_consent_cookie = ( 'true' === $raw_consent ) ? 'true' : 'false';
1193 $pinterest_id = isset( self::$pin_tag_settings['pinterest_tag_id'] ) ? trim( sanitize_text_field( self::$pin_tag_settings['pinterest_tag_id'] ) ) : '';
1194 $pinterest_email = isset( $identify_data['email'] ) ? $identify_data['email'] : '';
1195
1196 $pinterest_consent = 'enable' === self::$pin_tag_settings['enable_pinterest_consent']
1197 ? $pinterest_consent_cookie
1198 : 'true';
1199
1200 $pinterest_script = "
1201 <!-- Pinterest Tag -->
1202 <script>
1203 function loadPinterestTag(consent = '" . esc_js( $pinterest_consent ) . "') {
1204 if (typeof pintrk === 'undefined') {
1205 !function(e){if(!window.pintrk){window.pintrk = function () {
1206 window.pintrk.queue.push(Array.prototype.slice.call(arguments))};var
1207 n=window.pintrk;n.queue=[],n.version='3.0';var
1208 t=document.createElement('script');t.async=!0,t.src=e;var
1209 r=document.getElementsByTagName('script')[0];
1210 r.parentNode.insertBefore(t,r)}}('https://s.pinimg.com/ct/core.js');
1211 pintrk('setconsent', consent);
1212 pintrk('load', '" . esc_js( $pinterest_id ) . "');
1213 pintrk('page');
1214 pintrk('track', 'pagevisit');
1215 }
1216 }
1217
1218 // Load Pinterest Tag immediately if consent is already given.
1219 if ( 'true' === '" . esc_js( $pinterest_consent ) . "' ) {
1220 loadPinterestTag();
1221 }
1222
1223 // Listen for changes in the consent cookie.
1224 document.addEventListener('cartflows_pinterest_consent_changed', function(e) {
1225 if (e.detail === 'true') {
1226 loadPinterestTag(e.detail);
1227 }
1228 });
1229 </script>
1230 <noscript>
1231 <img height='1' width='1' style='display:none;' alt='' src='https://ct.pinterest.com/v3/?event=init&tid=" . esc_js( $pinterest_id ) . '&pd[em]=' . esc_js( $pinterest_email ) . "&noscript=1' />
1232 </noscript>
1233 <!-- end Pinterest Tag -->
1234 ";
1235
1236 if ( 'enable' === self::$pin_tag_settings['pinterest_tag_tracking_for_site'] ) {
1237 echo $pinterest_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1238 } elseif ( wcf()->utils->is_step_post_type() ) {
1239 echo $pinterest_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1240 }
1241
1242 // Trigger other events on CartFlows pages only.
1243 if ( wcf()->is_woo_active && wcf()->utils->is_step_post_type() && $pinterest_id ) {
1244 $this->trigger_other_pinterest_events( $pinterest_id );
1245 }
1246 }
1247 }
1248
1249 /**
1250 * Trigger the other events for Pinterest.
1251 *
1252 * @param string $pinterest_id Pinterest ID.
1253 */
1254 public function trigger_other_pinterest_events( $pinterest_id ) {
1255 $event_script = '';
1256
1257 if ( _is_wcf_checkout_type() && 'enable' === self::$pin_tag_settings['enable_pinterest_add_to_cart'] ) {
1258 $cart_data = $this->prepare_cart_data_pinterest_response();
1259 if ( ! empty( $cart_data ) ) {
1260 $cart_data = wp_json_encode( $cart_data );
1261
1262 $event_script .= "
1263 <script type='text/javascript'>
1264 if (typeof pintrk !== 'undefined') {
1265 pintrk('track', 'AddToCart', $cart_data );
1266 }
1267 </script>
1268 ";
1269 }
1270 }
1271
1272 if ( _is_wcf_checkout_type() && 'enable' === self::$pin_tag_settings['enable_pinterest_begin_checkout'] ) {
1273 $checkout_data = $this->prepare_cart_data_pinterest_response();
1274 if ( ! empty( $checkout_data ) ) {
1275 $checkout_data = wp_json_encode( $checkout_data );
1276
1277 $event_script .= "
1278 <script type='text/javascript'>
1279 if (typeof pintrk !== 'undefined') {
1280 pintrk('track', 'BeginCheckout', $checkout_data );
1281 }
1282 </script>";
1283 }
1284 }
1285
1286 if ( isset( $_GET['wcf-order'] ) && 'enable' === self::$pin_tag_settings['enable_pinterest_purchase_event'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1287 $order_id = intval( $_GET['wcf-order'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1288
1289 if ( $this->is_valid_order_access( $order_id ) ) {
1290 $purchase_details = $this->prepare_purchase_data_pinterest_response( $order_id );
1291 if ( ! empty( $purchase_details ) ) {
1292 $purchase_details = wp_json_encode( $purchase_details );
1293 $event_script .= "
1294 <script type='text/javascript'>
1295 if (typeof pintrk !== 'undefined') {
1296 pintrk('track', 'Checkout', $purchase_details );
1297 }
1298 </script>";
1299 }
1300 }
1301 }
1302
1303 do_action( 'cartflows_pinterest_tag_events' );
1304
1305 echo $event_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1306 }
1307
1308 /**
1309 * Prepare cart data for Pinterest response.
1310 *
1311 * @param string $event Event type.
1312 * @return array
1313 */
1314 public function prepare_cart_data_pinterest_response( $event = '' ) {
1315 // Calculate cart total and get cart items.
1316 $cart_total = self::format_number( WC()->cart->cart_contents_total + WC()->cart->tax_total );
1317 $cart_items = WC()->cart->get_cart();
1318
1319 $params = array(
1320 'event_id' => 'eventId' . gmdate( 'YmdHis' ),
1321 'value' => $cart_total,
1322 'order_quantity' => WC()->cart->get_cart_contents_count(),
1323 'currency' => get_woocommerce_currency(),
1324 'line_items' => array(),
1325 );
1326
1327 if ( 'signup' === $event ) {
1328 $params['lead_type'] = 'CartFlows Optin';
1329 }
1330
1331 // Loop through each cart item to get the required data.
1332 foreach ( $cart_items as $cart_item ) {
1333 $product = $cart_item['data'];
1334 $line_item = array(
1335 'product_id' => $cart_item['product_id'],
1336 'product_name' => $product->get_name(),
1337 'product_category' => $product->get_category_ids() ? wp_get_post_terms( $product->get_id(), 'product_cat', array( 'fields' => 'names' ) )[0] : '',
1338 );
1339
1340 if ( 'signup' !== $event ) {
1341 $line_item['product_variant'] = $product->get_id() !== $cart_item['variation_id'] ? $cart_item['variation_id'] : '';
1342 $line_item['product_price'] = wc_get_price_to_display( $product );
1343 $line_item['product_quantity'] = $cart_item['quantity'];
1344 $line_item['product_brand'] = '';
1345 }
1346
1347 $params['line_items'][] = $line_item;
1348 }
1349
1350 return $params;
1351 }
1352
1353 /**
1354 * Prepare purchase data for Pinterest response.
1355 *
1356 * @param integer $order_id order id.
1357 *
1358 * @return array
1359 */
1360 public function prepare_purchase_data_pinterest_response( $order_id ) {
1361 $order = wc_get_order( $order_id );
1362 if ( ! $order || $order->get_meta( '_wcf_pinterest_checkout_tracked' ) || $order->get_meta( '_wcf_optin_id' ) ) {
1363 return array();
1364 }
1365
1366 $purchase_data = array(
1367 'event_id' => 'eventId' . $order_id,
1368 'event_name' => 'checkout',
1369 'value' => self::format_number( $order->get_total() ),
1370 'order_quantity' => $order->get_item_count(),
1371 'currency' => $order->get_currency(),
1372 'order_id' => $order->get_order_number(),
1373 'line_items' => array(),
1374 );
1375
1376 foreach ( $order->get_items() as $item ) {
1377 $product = $item->get_product();
1378 $purchase_data['line_items'][] = array(
1379 'product_id' => $item->get_product_id(),
1380 'product_name' => $item->get_name(),
1381 'product_variant' => $item->get_variation_id(),
1382 'product_price' => self::format_number( $order->get_item_total( $item, true ) ),
1383 'product_quantity' => $item->get_quantity(),
1384 'product_category' => $product->get_category_ids() ? wp_get_post_terms( $product->get_id(), 'product_cat', array( 'fields' => 'names' ) )[0] : '',
1385 );
1386 }
1387
1388 $order->update_meta_data( '_wcf_pinterest_checkout_tracked', true );
1389 $order->save();
1390
1391 return $purchase_data;
1392 }
1393
1394 /**
1395 * Get decimal of price.
1396 *
1397 * @param integer $price price.
1398 */
1399 public static function format_number( $price ) {
1400
1401 return number_format( floatval( $price ), wc_get_price_decimals(), '.', '' );
1402 }
1403
1404 /**
1405 * Render Pinterest consent popup.
1406 *
1407 * @since 2.1.0
1408 * @return void
1409 */
1410 public function render_pinterest_consent_popup() {
1411
1412 // Return if not on a CartFlows step.
1413 if ( ! wcf()->utils->is_step_post_type() ) {
1414 return;
1415 }
1416
1417 // Return if Pinterest is not enabled.
1418 if ( 'enable' !== self::$pin_tag_settings['pinterest_tag_tracking'] || 'enable' !== self::$pin_tag_settings['enable_pinterest_consent'] ) {
1419 return;
1420 }
1421
1422 // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE
1423 $consent_cookie = isset( $_COOKIE[ CARTFLOWS_PINTEREST_CONSENT ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ CARTFLOWS_PINTEREST_CONSENT ] ) ) : '';
1424
1425 // Return if consent is already given. We don't want to show the popup.
1426 if ( boolval( $consent_cookie ) ) {
1427 return;
1428 }
1429
1430 // Default consent strings.
1431 $consent_strings = apply_filters(
1432 'cartflows_pinterest_tag_consent_strings',
1433 array(
1434 'message' => __( 'We use Pinterest tags to improve your experience. Do you consent to our use of Pinterest tags?', 'cartflows' ),
1435 'accept' => __( 'Accept', 'cartflows' ),
1436 'decline' => __( 'Decline', 'cartflows' ),
1437 )
1438 );
1439
1440 ?>
1441 <div id="cartflows-pinterest-consent-wrapper" class="wcf-pinterest-consent-wrapper" role="dialog" aria-label="<?php echo esc_attr__( 'Pinterest Consent', 'cartflows' ); ?>">
1442 <div class="wcf-pinterest-consent-message">
1443 <p><?php echo esc_html( $consent_strings['message'] ); ?></p>
1444 </div>
1445 <div class="wcf-pinterest-consent-buttons">
1446 <button id="cartflows-pinterest-consent-accept" class="wcf-pinterest-consent-button" data-action="accept"><?php echo esc_html( $consent_strings['accept'] ); ?></button>
1447 <button id="cartflows-pinterest-consent-decline" class="wcf-pinterest-consent-button" data-action="decline"><?php echo esc_html( $consent_strings['decline'] ); ?></button>
1448 </div>
1449 </div>
1450 <?php
1451 }
1452
1453 /**
1454 * Trigger the View Content events for gads tracking.
1455 *
1456 * @since 2.1.0
1457 * @return void
1458 */
1459 public function trigger_gads_viewcontent_events() {
1460 $gads_tracking_id = esc_js( sanitize_text_field( self::$gads_settings['google_ads_id'] ) );
1461 $gads_conversion_label = esc_js( sanitize_text_field( self::$gads_settings['google_ads_label'] ) );
1462 $script = "<script type='text/javascript'>
1463 gtag( 'event', 'page_view',{ 'send_to': '" . $gads_tracking_id . '/' . $gads_conversion_label . "'} );
1464 </script>";
1465
1466 echo $script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1467 }
1468
1469 /**
1470 * Prepare common data for Snapchat response.
1471 *
1472 * @since 2.1.0
1473 * @return array
1474 */
1475 public function prepare_common_data_snapchat_response() {
1476 global $post, $wcf_step;
1477
1478 $identify_data = $this->prepare_identity_data_for_pixel_events();
1479 $step_id = $wcf_step ? $wcf_step->get_current_step() : $post->ID;
1480
1481 $common_data = array(
1482 'price' => 0,
1483 'description' => ! empty( $step_id ) ? sanitize_text_field( get_post_field( 'post_name', $step_id ) ) : '',
1484 );
1485
1486 // Add user's hashed email if available.
1487 if ( ! empty( $identify_data['email'] ) ) {
1488 $common_data['user_hashed_email'] = $identify_data['email'];
1489 }
1490
1491 // Add user's hashed phone number if available.
1492 if ( ! empty( $identify_data['phone_number'] ) ) {
1493 $common_data['user_hashed_phone_number'] = $identify_data['phone_number'];
1494 }
1495
1496 // Add uuid_c1 value only if the Snapchat cookie '_scid' is available and valid UUID.
1497 if ( isset( $_COOKIE['_scid'] ) ) {
1498 // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE
1499 $scid_value = sanitize_text_field( wp_unslash( $_COOKIE['_scid'] ) );
1500 $common_data['uuid_c1'] = $scid_value;
1501 }
1502
1503 // Add currency if available.
1504 if ( ! empty( get_woocommerce_currency() ) ) {
1505 $common_data['currency'] = get_woocommerce_currency();
1506 }
1507
1508 return $common_data;
1509 }
1510
1511 /**
1512 * Function for Snapchat pixel.
1513 *
1514 * @since 2.1.0
1515 * @return void
1516 */
1517 public function add_snapchat_pixel_tracking_code() {
1518
1519 if ( 'enable' === self::$snapchat_settings['snapchat_pixel_tracking'] ) {
1520 $common_data = $this->prepare_common_data_snapchat_response();
1521
1522 $snapchat_id = isset( self::$snapchat_settings['snapchat_pixel_id'] ) ? trim( (string) sanitize_text_field( self::$snapchat_settings['snapchat_pixel_id'] ) ) : '';
1523 $user_hashed_email = isset( $common_data['user_hashed_email'] ) ? $common_data['user_hashed_email'] : '';
1524 $page_description = $common_data['description'];
1525
1526 $snapchat_script = "
1527 <!-- Snapchat Pixel Script By CartFlows -->
1528
1529 <script type='text/javascript'>
1530 (function(e,t,n){if(e.snaptr)return;var a=e.snaptr=function()
1531 {a.handleRequest?a.handleRequest.apply(a,arguments):a.queue.push(arguments)};
1532 a.queue=[];var s='script';r=t.createElement(s);r.async=!0;
1533 r.src=n;var u=t.getElementsByTagName(s)[0];
1534 u.parentNode.insertBefore(r,u);})(window,document,
1535 'https://sc-static.net/scevent.min.js');
1536
1537 snaptr('init', '" . esc_js( $snapchat_id ) . "', {
1538 'integration': 'woocommerce',
1539 'user_hashed_email': '" . esc_js( $user_hashed_email ) . "'
1540 });
1541
1542 snaptr('track', 'PAGE_VIEW', {description: '" . esc_js( $page_description ) . "'});
1543 </script>
1544
1545 <!-- End Snapchat Pixel Script By CartFlows -->";
1546
1547 if ( 'enable' === self::$snapchat_settings['snapchat_pixel_tracking_for_site'] ) {
1548 echo $snapchat_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1549 $this->trigger_snapchat_viewcontent_events();
1550 } elseif ( wcf()->utils->is_step_post_type() ) {
1551 echo $snapchat_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1552 $this->trigger_snapchat_viewcontent_events();
1553 }
1554
1555 // Trigger other events on CartFlows pages only.
1556 if ( wcf()->is_woo_active && wcf()->utils->is_step_post_type() ) {
1557 $this->trigger_other_snapchat_events();
1558 }
1559 }
1560 }
1561
1562 /**
1563 * Trigger the View Content events for Snapchat pixel.
1564 *
1565 * @since 2.1.0
1566 * @return void
1567 */
1568 public function trigger_snapchat_viewcontent_events() {
1569
1570 $event_script = '';
1571
1572 // Check if ViewContent is enable or disable.
1573 if ( 'enable' === self::$snapchat_settings['enable_snapchat_view_content'] ) {
1574 global $post, $wcf_step;
1575 $step_id = ( $wcf_step ) ? ( $wcf_step->get_current_step() ) : ( get_the_ID() );
1576 // Added filter for offer pages view content event compatibility.
1577 $view_content = apply_filters( 'cartflows_snapchat_view_content_offer', $this->prepare_event_data_snapchat_response(), $step_id );
1578
1579 if ( ! empty( $view_content ) ) {
1580 $view_content_data = wp_json_encode( $view_content );
1581 $event_script .= "
1582 <script type='text/javascript'>
1583 snaptr('track', 'VIEW_CONTENT', $view_content_data);
1584 </script>";
1585 }
1586 }
1587
1588 echo $event_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1589 }
1590
1591 /**
1592 * Prepare event data for Snapchat response.
1593 *
1594 * @since 2.1.0
1595 * @return array
1596 */
1597 public function prepare_event_data_snapchat_response() {
1598 // Initialize parameters with default values.
1599 $params = $this->prepare_common_data_snapchat_response();
1600
1601 // Handle data for different types of pages.
1602 if ( _is_wcf_thankyou_type() ) {
1603 // Check if order ID is present in the URL.
1604 if ( isset( $_GET['wcf-order'] ) && ! empty( $_GET['wcf-order'] ) ) {//phpcs:ignore WordPress.Security.NonceVerification.Recommended
1605 $order_id = intval( $_GET['wcf-order'] );//phpcs:ignore WordPress.Security.NonceVerification.Recommended
1606 $order = wc_get_order( $order_id );
1607
1608 // If the order exists, prepare order details.
1609 if ( $order ) {
1610 $items = $order->get_items();
1611 $item_category = implode(
1612 ',',
1613 array_map(
1614 function ( $item ) {
1615 return wp_strip_all_tags( wc_get_product_category_list( $item['product_id'] ) );
1616 },
1617 $items
1618 )
1619 );
1620
1621 $params['item_ids'] = array_column( $items, 'product_id' );
1622 $params['item_category'] = $item_category;
1623 $params['number_items'] = array_sum( array_column( $items, 'qty' ) );
1624 $params['price'] = $order->get_total();
1625 }
1626 }
1627 } elseif ( _is_wcf_checkout_type() || _is_wcf_landing_type() ) {
1628 $items = WC()->cart->get_cart();
1629
1630 // If there are items in the cart, prepare cart details.
1631 if ( $items ) {
1632 $item_category = implode(
1633 ',',
1634 array_map(
1635 function ( $item ) {
1636 return wp_strip_all_tags( wc_get_product_category_list( $item['product_id'] ) );
1637 },
1638 $items
1639 )
1640 );
1641
1642 $params['price'] = self::format_number( WC()->cart->cart_contents_total + WC()->cart->tax_total );
1643 $params['item_ids'] = array_column( $items, 'product_id' );
1644 $params['item_category'] = $item_category;
1645 $params['number_items'] = count( $items );
1646 }
1647 }
1648
1649 return $params;
1650 }
1651
1652 /**
1653 * Prepare purchase data for Snapchat response.
1654 *
1655 * @param integer $order_id order id.
1656 *
1657 * @since 2.1.0
1658 * @return array
1659 */
1660 public function prepare_purchase_data_snapchat_response( $order_id ) {
1661 $order = wc_get_order( $order_id );
1662 $is_checkout_tracked = $order->get_meta( '_wcf_snapchat_checkout_tracked' );
1663 $is_optin = $order->get_meta( '_wcf_optin_id' );
1664
1665 if ( ! $order || $is_checkout_tracked || $is_optin ) {
1666 return array();
1667 }
1668
1669 // Initialize parameters with default values.
1670 $purchase_data = $this->prepare_common_data_snapchat_response();
1671
1672 // Retrieve order items.
1673 $items = $order->get_items();
1674
1675 // Prepare item categories.
1676 $item_category = implode(
1677 ',',
1678 array_map(
1679 function ( $item ) {
1680 return wp_strip_all_tags( wc_get_product_category_list( $item['product_id'] ) );
1681 },
1682 $items
1683 )
1684 );
1685
1686 // Set customer ID if available.
1687 if ( $order->get_customer_id() ) {
1688 $purchase_data['uuid_c1'] = $order->get_customer_id();
1689 }
1690
1691 $purchase_data['transaction_id'] = $order_id;
1692 $purchase_data['currency'] = $order->get_currency();
1693 $purchase_data['item_ids'] = array_column( $items, 'product_id' );
1694 $purchase_data['item_category'] = $item_category;
1695 $purchase_data['number_items'] = array_sum( array_column( $items, 'qty' ) );
1696 $purchase_data['price'] = $order->get_total();
1697
1698 // Mark the order as tracked for this event.
1699 $order->update_meta_data( '_wcf_snapchat_checkout_tracked', true );
1700 $order->save();
1701
1702 return $purchase_data;
1703 }
1704
1705 /**
1706 * Trigger other events for Snapchat pixel.
1707 *
1708 * @since 2.1.0
1709 * @return void
1710 */
1711 public function trigger_other_snapchat_events() {
1712 $event_script = '';
1713
1714 // Check if the current page is a checkout type and if Snapchat add to cart event is enabled.
1715 if ( _is_wcf_checkout_type() && 'enable' === self::$snapchat_settings['enable_snapchat_add_to_cart'] ) {
1716 $cart_data = $this->prepare_event_data_snapchat_response();
1717 if ( ! empty( $cart_data ) ) {
1718 $cart_data = wp_json_encode( $cart_data );
1719
1720 $event_script .= "
1721 <script type='text/javascript'>
1722 snaptr('track', 'ADD_CART', $cart_data);
1723 </script>";
1724 }
1725 }
1726
1727 // Check if the current page is a checkout type and if Snapchat begin checkout event is enabled.
1728 if ( _is_wcf_checkout_type() && 'enable' === self::$snapchat_settings['enable_snapchat_begin_checkout'] ) {
1729 $checkout_data = $this->prepare_event_data_snapchat_response();
1730 if ( ! empty( $checkout_data ) ) {
1731 $checkout_data = wp_json_encode( $checkout_data );
1732
1733 $event_script .= "
1734 <script type='text/javascript'>
1735 snaptr('track', 'START_CHECKOUT', $checkout_data);
1736 </script>";
1737 }
1738 }
1739
1740 // Check if the order ID is present in the URL and if Snapchat purchase event is enabled.
1741 if ( isset( $_GET['wcf-order'] ) && 'enable' === self::$snapchat_settings['enable_snapchat_purchase_event'] ) {//phpcs:ignore WordPress.Security.NonceVerification.Recommended
1742 $order_id = intval( $_GET['wcf-order'] );//phpcs:ignore WordPress.Security.NonceVerification.Recommended
1743
1744 if ( $this->is_valid_order_access( $order_id ) ) {
1745 $purchase_details = $this->prepare_purchase_data_snapchat_response( $order_id );
1746 if ( ! empty( $purchase_details ) ) {
1747 $purchase_details = wp_json_encode( $purchase_details );
1748 $event_script .= "
1749 <script type='text/javascript'>
1750 snaptr('track', 'PURCHASE', $purchase_details);
1751 </script>";
1752 }
1753 }
1754 }
1755
1756 // Check if the order ID is present in the URL and if Snapchat subscribe event is enabled.
1757 if ( isset( $_GET['wcf-order'] ) && 'enable' === self::$snapchat_settings['enable_snapchat_subscribe_event'] ) {//phpcs:ignore WordPress.Security.NonceVerification.Recommended
1758 $order_id = intval( $_GET['wcf-order'] );//phpcs:ignore WordPress.Security.NonceVerification.Recommended
1759
1760 if ( $this->is_valid_order_access( $order_id ) ) {
1761 $order = wc_get_order( $order_id );
1762 $is_subscription_tracked = $order && $order->get_meta( '_wcf_snapchat_is_subscription_tracked' );
1763
1764 if ( ! $is_subscription_tracked && function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) {
1765 // Get all subscriptions related to this order.
1766 $subscriptions = function_exists( 'wcs_get_subscriptions_for_order' ) ? wcs_get_subscriptions_for_order( $order_id ) : null;
1767
1768 if ( ! empty( $subscriptions ) ) {
1769 $subscription_data = $this->prepare_common_data_snapchat_response();
1770
1771 // Set customer ID if available.
1772 if ( $order->get_customer_id() ) {
1773 $subscription_data['uuid_c1'] = $order->get_customer_id();
1774 }
1775
1776 $subscription_data['price'] = $order->get_total();
1777 $subscription_data['transaction_id'] = $order_id;
1778 $subscription_data['currency'] = $order->get_currency();
1779
1780 $subscription_data = wp_json_encode( $subscription_data );
1781
1782 $event_script .= "
1783 <script type='text/javascript'>
1784 snaptr('track', 'SUBSCRIBE', $subscription_data);
1785 </script>";
1786
1787 // Mark the order as tracked for this event.
1788 $order->update_meta_data( '_wcf_snapchat_is_subscription_tracked', true );
1789 $order->save();
1790 }
1791 }
1792 }
1793 }
1794
1795 do_action( 'cartflows_snapchat_pixel_events' );
1796
1797 echo $event_script; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1798 }
1799 }
1800 /**
1801 * Kicking this off by calling 'get_instance()' method
1802 */
1803 Cartflows_Tracking::get_instance();
1804