HEX
Server: Apache/2.4.37 (CentOS Stream) OpenSSL/1.1.1k
System: Linux ysnet.com.tw 4.18.0-553.5.1.el8.x86_64 #1 SMP Tue May 21 05:46:01 UTC 2024 x86_64
User: test (521)
PHP: 7.4.33
Disabled: NONE
Upload Files
File: /var/www/test/wp-content/plugins/pinterest-for-woocommerce/src/Tracking.php
<?php
/**
 * Pinterest tracking main class.
 *
 * @package Pinterest_For_WooCommerce/Classes/
 * @version 1.0.0
 */

namespace Automattic\WooCommerce\Pinterest;

use Automattic\WooCommerce\Pinterest\Tracking\Conversions;
use Automattic\WooCommerce\Pinterest\Tracking\Data;
use Automattic\WooCommerce\Pinterest\Tracking\Data\Category;
use Automattic\WooCommerce\Pinterest\Tracking\Data\Checkout;
use Automattic\WooCommerce\Pinterest\Tracking\Data\None;
use Automattic\WooCommerce\Pinterest\Tracking\Data\Product;
use Automattic\WooCommerce\Pinterest\Tracking\Data\Search;
use Automattic\WooCommerce\Pinterest\Tracking\Tag;
use Automattic\WooCommerce\Pinterest\Tracking\Tracker;
use Automattic\WooCommerce\Pinterest\Utilities\CrawlerDetector;
use Throwable;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class Tracker responsible for hooking into system events.
 */
class Tracking {

	const EVENT_CHECKOUT      = 'Checkout';

	const EVENT_ADD_TO_CART   = 'AddToCart';

	const EVENT_PAGE_VISIT    = 'PageVisit';

	const EVENT_SEARCH        = 'Search';

	const EVENT_VIEW_CATEGORY = 'ViewCategory';

	/**
	 * @var Tracker[] $trackers A list of available trackers.
	 */
	private $trackers = array();

	/**
	 * Attaches all the required tracking events to corresponding WP/WC hooks.
	 *
	 * @since 1.4.0
	 *
	 * @param array $trackers A list of trackers to track events with.
	 */
	public function __construct( array $trackers = array() ) {
		$this->trackers = $trackers;

		// Tracks page visit events.
		add_action( 'wp_footer', array( $this, 'handle_page_visit' ) );

		// Tracks category visit events.
		add_action( 'wp_footer', array( $this, 'handle_view_category' ) );

		// Tracks search events.
		add_action( 'wp_footer', array( $this, 'handle_search' ) );

		// Tracks add to cart events.
		add_action( 'woocommerce_add_to_cart', array( $this, 'handle_add_to_cart' ), 10, 6 );

		// Tracks checkout events.
		add_action( 'woocommerce_before_thankyou', array( $this, 'handle_checkout' ), 10, 2 );

		array_walk(
			$this->trackers,
			fn ( $tracker ) => call_user_func( array( $tracker, 'init_hooks' ) )
		);
	}

	/**
	 * Used as a callback for the wp_footer hook.
	 *
	 * @since 1.4.0
	 * @since 1.4.8 Added check for product page.
	 *
	 * @return void
	 */
	public function handle_page_visit() {
		if ( is_404() ) {
			// Do not track 404 pages.
			return;
		}

		// Dumy data for when we can't get product data.
		$data = new None( uniqid( 'page' ) );

		// Not a product page.
		if ( ! is_product() ) {
			$this->track_event( static::EVENT_PAGE_VISIT, $data );
			return;
		}

		$product = wc_get_product();
		if ( ! $product instanceof \WC_Product ) {
			$this->track_event( static::EVENT_PAGE_VISIT, $data );
			return;
		}

		$data = new Product(
			uniqid( 'page' ),
			$product->get_id(),
			$product->get_name(),
			wc_get_product_category_list( $product->get_id() ),
			'brand',
			wc_get_price_to_display( $product ),
			get_woocommerce_currency(),
			1
		);
		$this->track_event( static::EVENT_PAGE_VISIT, $data );
	}

	/**
	 * Used as a callback for the wp_footer hook.
	 *
	 * @since 1.4.0
	 *
	 * @return void
	 */
	public function handle_view_category() {
		if ( ! is_product_category() ) {
			return;
		}
		$queried_object = get_queried_object();
		$data           = new Category(
			uniqid( 'category' ),
			$queried_object->term_id,
			$queried_object->name
		);
		$this->track_event( static::EVENT_VIEW_CATEGORY, $data );
	}

	/**
	 * Used as a callback for the woocommerce_add_to_cart hook.
	 *
	 * @since 1.4.0
	 *
	 * @param string $cart_item_key - WooCommerce cart item key.
	 * @param string $product_id           - WooCommerce product id.
	 * @param string $quantity             - Number of products.
	 * @param string $variation_id         - Product variation id if any.
	 *
	 * @return void
	 */
	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 );
		$data      = new Product(
			uniqid( 'cart' ),
			$product->get_id(),
			$product->get_name(),
			wc_get_product_category_list( $product->get_id() ),
			'brand',
			wc_get_price_to_display( $product ),
			get_woocommerce_currency(),
			$quantity
		);
		$this->track_event( static::EVENT_ADD_TO_CART, $data );
	}

	/**
	 * Used as a callback for the woocommerce_before_thankyou hook.
	 *
	 * @since 1.4.0
	 *
	 * @param string $order_id WooCommerce order id.
	 *
	 * @return void
	 */
	public function handle_checkout( $order_id ) {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return;
		}

		$items          = array();
		$total_quantity = 0;
		$checkout_value = 0;
		foreach ( $order->get_items() as $order_item ) {
			if ( ! method_exists( $order_item, 'get_product' ) ) {
				continue;
			}

			$product = $order_item->get_product();

			$items[] = new Product(
				uniqid( 'product' ),
				$product->get_id(),
				$order_item->get_name(),
				wc_get_product_category_list( $product->get_id() ),
				'brand',
				$order->get_item_total( $order_item, false, true ),
				get_woocommerce_currency(),
				$order_item->get_quantity()
			);

			$total_quantity += $order_item->get_quantity();
			$checkout_value += (float) $order_item->get_total();
		}

		// Deterministic event id so Pinterest can deduplicate Tag/CAPI Checkout events when
		// the thank-you page is re-rendered (woocommerce_before_thankyou fires on every render).
		$data = new Checkout(
			'checkout_' . $order->get_id(),
			(string) $order->get_id(),
			wc_format_decimal( $checkout_value, wc_get_price_decimals() ),
			$total_quantity,
			$order->get_currency(),
			$items
		);
		$this->track_event( static::EVENT_CHECKOUT, $data );
	}

	/**
	 * Search event handler.
	 *
	 * @since 1.4.0
	 *
	 * @return void
	 */
	public function handle_search() {
		if ( ! is_search() ) {
			return;
		}

		$data = new Search(
			uniqid( 'pinterest-for-woocommerce-tag-and-conversions-event-id' ),
			get_search_query()
		);
		$this->track_event( static::EVENT_SEARCH, $data );
	}

	/**
	 * Method which iterates over all the attached trackers and delegates the event to them.
	 *
	 * Server-side trackers (Conversions API) are skipped for crawler/bot requests.
	 * Browser-side rendering (Tag JS) is intentionally NOT skipped, so full-page
	 * caches that omit `Vary: User-Agent` do not serve bot-rendered HTML missing
	 * Tag JS to real users on a subsequent cache hit. See CrawlerDetector.
	 *
	 * @since 1.4.0
	 *
	 * @param string $event_name Tracking event name.
	 * @param Data   $data       Event Data object.
	 *
	 * @return void
	 */
	public function track_event( string $event_name, Data $data ) {
		$is_crawler = CrawlerDetector::is_crawler_request();

		foreach ( $this->get_trackers() as $tracker ) {
			// Skip Pinterest tag tracking if tag is not active.
			if ( $tracker instanceof Tag && ! Tag::get_active_tag() ) {
				continue;
			}

			// Skip server-side CAPI dispatch for crawler requests so bot
			// traffic does not inflate CAPI counts vs Tag counts.
			if ( $is_crawler && $tracker instanceof Conversions ) {
				continue;
			}

			try {
				$tracker->track_event( $event_name, $data );
			} catch ( Throwable $e ) {
				/* translators: %1$s - event name, %2$s - tracker class name, %3$s - error message */
				$message = sprintf(
					'Error while tracking event %1$s with tracker %2$s. Error: %3$s',
					$event_name,
					get_class( $tracker ),
					$e->getMessage()
				);
				Logger::log( $message, 'error' );
			}
		}
	}

	/**
	 * Returns an array of registered trackers.
	 *
	 * @since 1.4.0
	 *
	 * @return Tracker[]
	 */
	public function get_trackers() {
		return $this->trackers;
	}

	/**
	 * Adds a tracker to the array of trackers.
	 *
	 * @since 1.4.0
	 *
	 * @param Tracker $tracker - One of objects implementing Tracker interface.
	 *
	 * @return void
	 */
	public function add_tracker( Tracker $tracker ) {
		$tracker->init_hooks();
		$this->trackers[ get_class( $tracker ) ] = $tracker;
	}

	/**
	 * Removes a tracker.
	 *
	 * @since 1.4.0
	 *
	 * @param string $tracker Tracker class name to be removed. e.g. Tag::class, Conversions::class.
	 *
	 * @return void
	 */
	public function remove_tracker( string $tracker ) {
		$this->trackers = array_filter(
			$this->trackers,
			function ( $item ) use ( $tracker ) {
				if ( get_class( $item ) === $tracker ) {
					$item->disable_hooks();
					return false;
				}
				return true;
			}
		);
	}
}