509318396
­­­­­­­­­­­­­­­­­­
<!DOCTYPE html>
<html>
<?php

namespace PixelYourSite;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}


class EventsManager {
    private static $_instance;
    public $facebookServerEvents = array();
    public $openaiServerEvents = array();
    private $pinterestServerEvents = array();
	public $doingAMP = false;
    private $standardParams = array();
    private $staticEvents = array();
    private $dynamicEvents = array();
    private $triggerEvents = array();
    private $triggerEventTypes = array();
    private $uniqueId = array();

    public function __construct() {

		add_action( 'wp_enqueue_scripts', array( $this, 'enqueueScripts' ),10 );
        add_action( 'wp_enqueue_scripts', array( $this, 'setupEventsParams' ),14 );
        $this->setupEddButtonHooks();
        add_action( 'wp_enqueue_scripts', array( $this, 'outputData' ),15 );
        add_action( 'wp_head', array( $this, 'outputPbidPrefetch' ), 1 );
		add_action( 'wp_footer', array( $this, 'outputNoScriptData' ), 10 );

        // Classic hook for checkout page
        add_filter( 'script_loader_tag', array( $this, 'add_data_attribute_to_script' ), 10, 3 );
	}

    public static function instance() {

        if ( is_null( self::$_instance ) ) {
            self::$_instance = new self();
        }

        return self::$_instance;


    }
    function add_data_attribute_to_script( $tag, $handle, $src ) {
        $array_scripts = array('js-cookie-pys', 'jquery-bind-first', 'pys', 'js-sha256');

        // Add defer attribute to all plugin scripts for better performance
        $defer_scripts = array(
                'jquery-bind-first',
                'js-cookie-pys',
                'js-sha256',
                'pys',
                'vimeo',
                'pys-pinterest',
                'pys-bing',
                'pys_sp_public_js',
                'pys-reddit'
        );

        if ( in_array( $handle, $defer_scripts, true ) ) {
            // Add defer attribute if not already present
            if ( strpos( $tag, 'defer' ) === false && strpos( $tag, 'async' ) === false ) {
                $tag = str_replace( ' src=', ' defer src=', $tag );
            }
        }

        if ( 'js-cookie-pys' === $handle && isCookiebotPluginActivated()) {
            $tag = str_replace( 'src=', 'data-cookieconsent="true" src=', $tag );
        }
        return $tag;
    }
	public function enqueueScripts() {

        // Register core scripts (all in footer for better performance)
        wp_register_script( 'jquery-bind-first', PYS_FREE_URL . '/dist/scripts/jquery.bind-first-0.2.3.min.js', array( 'jquery' ), '0.2.3', true);

        wp_register_script( 'js-cookie-pys', PYS_FREE_URL . '/dist/scripts/js.cookie-2.1.3.min.js', array(), '2.1.3', true );
        wp_register_script( 'js-sha256', PYS_FREE_URL . '/dist/scripts/sha256.js', array(), '0.11.0', true );
        // tld.min.js is gone. Its bundled Public Suffix List predated ~2018 and
        // returned the suffix itself for newer entries (app.vercel.app ->
        // "vercel.app"), so cookies scoped with it were dropped by the browser.
        // probeCookieDomain() in dist/scripts/public.js asks the browser
        // instead, which cannot go stale. Also 141 KB off every page load.

        // Enqueue core scripts
        wp_enqueue_script( 'jquery-bind-first' );
        wp_enqueue_script( 'js-cookie-pys' );
        wp_enqueue_script( 'js-sha256' );

        // Load main plugin script (compressed or uncompressed)
        if ( PYS()->getOption( 'compress_front_js' )){
            wp_enqueue_script( 'pys', PYS_FREE_URL . '/dist/scripts/public.min.js',
                array( 'jquery', 'js-sha256', 'js-cookie-pys', 'jquery-bind-first' ), PYS_FREE_VERSION, true );
        }
        else
        {
            wp_enqueue_script( 'pys', PYS_FREE_URL . '/dist/scripts/public.js',
                array( 'jquery', 'js-sha256', 'js-cookie-pys', 'jquery-bind-first' ), PYS_FREE_VERSION, true );
        }


	}

	/**
	 * Start the pys_get_pbid request from <head>, before public.js is even
	 * downloaded.
	 *
	 * On a first visit the Meta pixel holds its init for that reply
	 * (Utils.holdForIdentity(), ceiling PBID_WAIT_MS). public.js is a footer
	 * script, so a request started there leaves the whole page download inside
	 * the wait, and on a slow host the pixel falls back to the inline value —
	 * on a cached page, somebody else's. Utils.requestPbid() picks up
	 * window.pysPbidPrefetch and makes its own request only when the prefetch is
	 * missing or failed.
	 *
	 * Printed regardless of the cookie: the page may come from a cache generated
	 * by a cookie-holding visitor, so the cookie check lives in the snippet.
	 *
	 * Inline-script rules: no `<`/`&`, and no "fbq"/"pysOptions" substrings,
	 * which Consent Magic's script blocker parks.
	 */
	public function outputPbidPrefetch() {

		if ( ! self::isTrackExternalId() || ! Facebook()->enabled() ) {
			return;
		}

		$url = add_query_arg( 'action', 'pys_get_pbid', admin_url( 'admin-ajax.php' ) );

		echo '<script id="pys-pbid-prefetch">(function(){'
			. 'if(!window.fetch){return;}'
			. 'if(/(^|;\s*)pbid=/.test(document.cookie)){return;}'
			. 'try{window.pysPbidPrefetch=window.fetch(' . wp_json_encode( $url ) . ',{credentials:"same-origin"})'
			. '.then(function(r){return r.json();});}catch(e){}'
			. '})();</script>' . "\r\n";
	}

	public function outputData() {

		// Everything below ends up inside the page HTML, which a full-page
		// cache may serve to other visitors. Visitor-specific reads are gated
		// on this for the duration.
		$previous_render_context = pys_html_render_context( true );

		$data = array(
            'staticEvents'          => $this->staticEvents,
            'dynamicEvents'         => $this->dynamicEvents,
            'triggerEvents'         => $this->triggerEvents,
            'triggerEventTypes'     => $this->triggerEventTypes,
        );
		// collect options for configured pixel
		foreach ( PYS()->getRegisteredPixels() as $pixel ) {
			/** @var Pixel|Settings $pixel */

		    if ( $pixel->configured() ) {
			    $data[ $pixel->getSlug() ] = $pixel->getPixelOptions();
		    }

		}

		$pys_analytics_storage_mode = has_filter( 'pys_analytics_storage_mode' );
		$pys_ad_storage_mode = has_filter( 'pys_ad_storage_mode' );
		$pys_ad_user_data_mode = has_filter( 'pys_ad_user_data_mode' );
		$pys_ad_personalization_mode = has_filter( 'pys_ad_personalization_mode' );
		$google_consent_mode = ( has_filter( 'cm_google_consent_mode' ) || $pys_analytics_storage_mode || $pys_ad_storage_mode || $pys_ad_user_data_mode || $pys_ad_personalization_mode ) ? true : PYS()->getOption( 'google_consent_mode' );

		$options = array(
			'debug'                            => PYS()->getOption( 'debug_enabled' ),
			'siteUrl'                          => site_url(),
			'ajaxUrl'                          => admin_url( 'admin-ajax.php' ),
			'ajax_event'                       => wp_create_nonce( 'ajax-event-nonce' ),
			'enable_remove_download_url_param' => PYS()->getOption( 'enable_remove_download_url_param' ),
			'cookie_duration'                  => PYS()->getOption( 'cookie_duration' ),
			// Meta's cookies do not follow cookie_duration; see
			// pys_fb_cookie_lifetime() in includes/functions-fb-cookies.php.
			'fb_cookie_duration'               => pys_fb_cookie_lifetime() / DAY_IN_SECONDS,
			'last_visit_duration'              => PYS()->getOption( 'last_visit_duration' ),
			'enable_success_send_form'         => PYS()->getOption( 'enable_success_send_form' ),
			'ajaxForServerEvent'               => PYS()->getOption( 'server_event_use_ajax' ),
            "ajaxForServerStaticEvent"         => PYS()->getOption( 'server_static_event_use_ajax' ),
            "useSendBeacon"                     => PYS()->getOption( 'use_send_beacon' ),
			"send_external_id"                 => PYS()->getOption( 'send_external_id' ),
			"external_id_expire"               => PYS()->getOption( 'external_id_expire' ),
            "track_cookie_for_subdomains"      => PYS()->getOption( 'track_cookie_for_subdomains' ),
			"google_consent_mode"              => $google_consent_mode,
			// A consent management plugin populates cm_google_consent_mode and issues
			// its own Consent Mode update from the visitor's live choice. Utils
			// .pushGTMConsentState() checks this and stays out of the way: our update
			// would carry the state as of page render and could overwrite theirs.
			"consent_mode_cmp_active"          => has_filter( 'cm_google_consent_mode' ),
			'data_persistency'                  => PYS()->getOption( 'data_persistency' )
		);

		$options[ 'gdpr' ] = array(
			'ajax_enabled'               => PYS()->getOption( 'gdpr_ajax_enabled' ),
			'all_disabled_by_api'        => apply_filters( 'pys_disable_by_gdpr', false ),
			'facebook_disabled_by_api'   => apply_filters( 'pys_disable_facebook_by_gdpr', false ),
			'analytics_disabled_by_api'  => apply_filters( 'pys_disable_analytics_by_gdpr', false ),
			'google_ads_disabled_by_api' => apply_filters( 'pys_disable_google_ads_by_gdpr', false ),
			'pinterest_disabled_by_api'  => apply_filters( 'pys_disable_pinterest_by_gdpr', false ),
			'bing_disabled_by_api'       => apply_filters( 'pys_disable_bing_by_gdpr', false ),
			'reddit_disabled_by_api'     => apply_filters( 'pys_disable_reddit_by_gdpr', false ),
			'openai_disabled_by_api'     => apply_filters( 'pys_disable_openai_by_gdpr', false ),

			'externalID_disabled_by_api' => apply_filters( 'pys_disable_externalID_by_gdpr', false ),

			'facebook_prior_consent_enabled'   => PYS()->getOption( 'gdpr_facebook_prior_consent_enabled' ),
			'analytics_prior_consent_enabled'  => PYS()->getOption( 'gdpr_analytics_prior_consent_enabled' ),
			'google_ads_prior_consent_enabled' => PYS()->getOption( 'gdpr_google_ads_prior_consent_enabled' ),
			'pinterest_prior_consent_enabled'  => PYS()->getOption( 'gdpr_pinterest_prior_consent_enabled' ),
			'bing_prior_consent_enabled'       => PYS()->getOption( 'gdpr_bing_prior_consent_enabled' ),
			'openai_prior_consent_enabled'     => PYS()->getOption( 'gdpr_openai_prior_consent_enabled' ),


			'cookiebot_integration_enabled'          => isCookiebotPluginActivated() && PYS()->getOption( 'gdpr_cookiebot_integration_enabled' ),
			'cookiebot_facebook_consent_category'    => PYS()->getOption( 'gdpr_cookiebot_facebook_consent_category' ),
			'cookiebot_analytics_consent_category'   => PYS()->getOption( 'gdpr_cookiebot_analytics_consent_category' ),
			'cookiebot_tiktok_consent_category'      => PYS()->getOption( 'gdpr_cookiebot_tiktok_consent_category' ),
			'cookiebot_google_ads_consent_category'  => PYS()->getOption( 'gdpr_cookiebot_google_ads_consent_category' ),
			'cookiebot_pinterest_consent_category'   => PYS()->getOption( 'gdpr_cookiebot_pinterest_consent_category' ),
			'cookiebot_bing_consent_category'        => PYS()->getOption( 'gdpr_cookiebot_bing_consent_category' ),
			'cookiebot_openai_consent_category'      => PYS()->getOption( 'gdpr_cookiebot_openai_consent_category' ),
			'consent_magic_integration_enabled'      => isConsentMagicPluginActivated() && PYS()->getOption( 'consent_magic_integration_enabled' ),
			'real_cookie_banner_integration_enabled' => isRealCookieBannerPluginActivated() && PYS()->getOption( 'gdpr_real_cookie_banner_integration_enabled' ),
			'cookie_notice_integration_enabled'      => isCookieNoticePluginActivated() && PYS()->getOption( 'gdpr_cookie_notice_integration_enabled' ),
			'cookie_law_info_integration_enabled'    => isCookieLawInfoPluginActivated() && PYS()->getOption( 'gdpr_cookie_law_info_integration_enabled' ),
		);

		$options[ 'gdpr' ] = array_merge( $options[ 'gdpr' ], apply_filters( 'cm_google_consent_mode', array(
			'analytics_storage'  => array(
				'enabled' => $google_consent_mode,
				'value'   => 'granted',
			),
			'ad_storage'         => array(
				'enabled' => $google_consent_mode,
				'value'   => 'granted',
			),
			'ad_user_data'       => array(
				'enabled' => $google_consent_mode,
				'value'   => 'granted',
			),
			'ad_personalization' => array(
				'enabled' => $google_consent_mode,
				'value'   => 'granted',
			),
		) ) );

		$options[ 'gdpr' ][ 'analytics_storage' ][ 'filter' ] = $pys_analytics_storage_mode;
		$options[ 'gdpr' ][ 'ad_storage' ][ 'filter' ] = $pys_ad_storage_mode;
		$options[ 'gdpr' ][ 'ad_user_data' ][ 'filter' ] = $pys_ad_user_data_mode;
		$options[ 'gdpr' ][ 'ad_personalization' ][ 'filter' ] = $pys_ad_personalization_mode;
		$options[ 'gdpr' ][ 'analytics_storage' ][ 'value' ] = $pys_analytics_storage_mode ? ( apply_filters( 'pys_analytics_storage_mode', true ) ? 'granted' : 'denied' ) : $options[ 'gdpr' ][ 'analytics_storage' ][ 'value' ];
		$options[ 'gdpr' ][ 'ad_storage' ][ 'value' ] = $pys_ad_storage_mode ? ( apply_filters( 'pys_ad_storage_mode', true ) ? 'granted' : 'denied' ) : $options[ 'gdpr' ][ 'ad_storage' ][ 'value' ];
		$options[ 'gdpr' ][ 'ad_user_data' ][ 'value' ] = $pys_ad_user_data_mode ? ( apply_filters( 'pys_ad_user_data_mode', true ) ? 'granted' : 'denied' ) : $options[ 'gdpr' ][ 'ad_user_data' ][ 'value' ];
		$options[ 'gdpr' ][ 'ad_personalization' ][ 'value' ] = $pys_ad_personalization_mode ? ( apply_filters( 'pys_ad_personalization_mode', true ) ? 'granted' : 'denied' ) : $options[ 'gdpr' ][ 'ad_personalization' ][ 'value' ];

		$options[ 'cookie' ] = array(
			'disabled_all_cookie'                => apply_filters( 'pys_disable_all_cookie', false ),
			'disabled_start_session_cookie'      => apply_filters( 'pys_disabled_start_session_cookie', false ),
			'disabled_advanced_form_data_cookie' => pys_advanced_form_data_cookie_disabled(),
			'disabled_landing_page_cookie'       => apply_filters( 'pys_disable_landing_page_cookie', false ),
			'disabled_first_visit_cookie'        => apply_filters( 'pys_disable_first_visit_cookie', false ),
			'disabled_trafficsource_cookie'      => apply_filters( 'pys_disable_trafficsource_cookie', false ),
			'disabled_utmTerms_cookie'           => apply_filters( 'pys_disable_utmTerms_cookie', false ),
			'disabled_utmId_cookie'              => apply_filters( 'pys_disable_utmId_cookie', false ),
		);

		/*
		 * Deliberately empty of landing page, traffic source and UTMs.
		 *
		 * This array is printed into the page HTML, and a full-page cache stores
		 * that HTML once and serves it to everybody: whichever visitor happened to
		 * generate the cache entry would hand their own landing page and campaign
		 * parameters to every later visitor of that page. The front end derives all
		 * three on its own — from its own cookies, falling back to the current URL —
		 * so nothing is lost by leaving them out. See getLandingPageValue() and
		 * getUTMs() in dist/scripts/public.js.
		 *
		 * The session copies written by PYS::populateSessionVisitData() stay: the
		 * server-side (CAPI) events read them, and those run per request and are
		 * never served from a page cache.
		 *
		 * The keys are emptied, never removed. A visitor can be running an older
		 * public.js than the HTML it receives — from their own browser cache while
		 * ?ver= is unchanged, or from a cache plugin's minified copy, which is not
		 * rebuilt when this file changes. That older script does
		 * options.tracking_analytics.TrafficUtmsId[ name ] with no guard on the inner
		 * object, so a missing key is a TypeError that takes pysNormalInit() down and
		 * with it every event on the page. An empty array reads as "nothing here" and
		 * costs nothing.
		 */
		$options[ 'tracking_analytics' ] = array(
			"TrafficLanding" => '',
			"TrafficUtms"    => array(),
			"TrafficUtmsId"  => array(),
		);

        $options['GATags']["ga_datalayer_type"] = GATags()->getOption('gtag_datalayer_type');
        $options['GATags']["ga_datalayer_name"] = GATags()->getOption('gtag_datalayer_name');
        /**
         * @var EventsFactory[] $eventsFactory
         */
        $eventsFactory = apply_filters("pys_event_factory",[]);
        foreach ($eventsFactory as $factory) {
            $opt =  $factory->getOptions();
            if(!empty($opt)) {
                $options[$factory::getSlug()] = $factory->getOptions();
            }
        }
        $options['cache_bypass'] = time();

        if ( PYS()->getOption( 'fetch_user_data_via_rest' ) ) {
            unset( $options['ajax_event'] );
            unset( $options['cache_bypass'] );
            // tracking_analytics is deliberately left in place. It holds no visitor
            // data any more (see outputData() above), and the front end reads its
            // inner keys unguarded — removing the array turns an unreachable
            // dynamic-options endpoint into a TypeError that kills every event.
            unset( $options['gdpr']['all_disabled_by_api'] );
            unset( $options['gdpr']['facebook_disabled_by_api'] );
            unset( $options['gdpr']['analytics_disabled_by_api'] );
            unset( $options['gdpr']['google_ads_disabled_by_api'] );
            unset( $options['gdpr']['pinterest_disabled_by_api'] );
            unset( $options['gdpr']['bing_disabled_by_api'] );
            unset( $options['gdpr']['reddit_disabled_by_api'] );
            unset( $options['gdpr']['openai_disabled_by_api'] );
            unset( $options['gdpr']['externalID_disabled_by_api'] );
            unset( $options['cookie'] );
            $options['dynamicDataUrl'] = rest_url( 'pys/v1/dynamic-options' );

            /*
             * Strip per-pixel user data (advanced matching) out of the HTML. The
             * endpoint above returns it per visitor instead — see
             * PYS::serve_dynamic_options().
             *
             * Note these keys live in $data, filled by the pixel loop at the top of
             * this method, not in the $options array the rest of this block edits.
             * The two are merged only afterwards.
             *
             * Purchase confirmation pages are left untouched: their data is
             * order-derived and cannot be rebuilt on a REST request, and those pages
             * are never HTML-cached. See isPurchaseConfirmationPage().
             */
            if ( ! isPurchaseConfirmationPage() ) {
                foreach ( getDynamicUserDataKeys() as $slug => $keys ) {
                    foreach ( $keys as $key ) {
                        if ( isset( $data[ $slug ][ $key ] ) ) {
                            // Emptied, not removed: the front-end reads
                            // Object.keys( options.<slug>.<key> ).length and would
                            // throw on undefined. An empty value is a state that
                            // already occurs whenever there is nothing to match on.
                            $data[ $slug ][ $key ] = array();
                        }
                    }
                }
            }
        }

        $data = array_merge( $data, $options );

		wp_localize_script( 'pys', 'pysOptions', $data );

		pys_html_render_context( $previous_render_context );

	}

	public function outputNoScriptData() {
        if(!apply_filters( 'pys_disable_by_gdpr', false)) {
            foreach (PYS()->getRegisteredPixels() as $pixel) {
                /** @var Pixel|Settings $pixel */
                if ($this->noScriptAllowed($pixel->getSlug())) {
                    $pixel->outputNoScriptEvents();
                }
            }
        }
    }

    /**
     * A no-script tag is a plain image request: it carries no Consent Mode
     * state, so for the Google pixels restricted mode is no licence to send it.
     * GA and GTM also obey the shared pys_disable_analytics_by_gdpr filter,
     * which stops them everywhere else; pys_disable_{ga|gtm}_by_gdpr only
     * reach the no-script tags.
     *
     * @param string $slug Pixel slug
     * @return bool
     */
    private function noScriptAllowed( $slug ) {
        $disabled = (bool) apply_filters( 'pys_disable_' . $slug . '_by_gdpr', false );
        if ( in_array( $slug, array( 'ga', 'gtm' ), true ) ) {
            $disabled = $disabled || apply_filters( 'pys_disable_analytics_by_gdpr', false );
        }
        if ( ! $disabled ) {
            return true;
        }

        return ! in_array( $slug, array( 'ga', 'gtm', 'google_ads' ), true )
            && Consent()->firesInRestrictedMode( $slug );
    }






    public function setupEventsParams() {

        $this->standardParams = getStandardParams();
        $this->facebookServerEvents = array();
        $this->openaiServerEvents = array();


        /**
         * @var EventsFactory[] $eventsFactory
         **/
        $eventsFactory = apply_filters("pys_event_factory",[]);

        foreach ($eventsFactory as $factory) {
            if(!$factory->isEnabled())  continue;
            $events = $factory->generateEvents();
            $this->addEvents($events,$factory->getSlug());
        }

		// initial event
		$initEvent = new SingleEvent('init_event',EventTypes::$STATIC,'');

		foreach ( PYS()->getRegisteredPixels() as $pixel ) {

			$events = $pixel->generateEvents( $initEvent );
			foreach ($events as $event) {
				$params = array();
				if( get_post_type() == "post" && !is_archive() ) {
					global $post;
					$params[ 'post_category' ] = implode( ', ', getObjectTerms( 'category', $post->ID ) );
				}

				$slug = $pixel->getSlug();
				if( $slug !== "reddit" ) {
					$event->addParams( $params );
					$event->addParams( $this->standardParams );
				}
				$this->addStaticEvent( $event,$pixel,"" );
			}
		}

        // edd_purchase_link_end is now registered early in setupEddButtonHooks() (called from constructor)

        if(EventsWoo()->isEnabled()){
            // AddToCart on button and Affiliate
            if ( PYS()->getOption('woo_add_to_cart_catch_method') == "add_cart_js"
                    && isEventEnabled( 'woo_add_to_cart_enabled')
                    && PYS()->getOption( 'woo_add_to_cart_on_button_click' )
            ) {
                add_action( 'woocommerce_after_shop_loop_item', array( $this, 'setupWooLoopProductData' ) );
                add_action( 'woocommerce_after_add_to_cart_button', 'PixelYourSite\EventsManager::setupWooSingleProductData' );

                // The net for themes and page builders that never fire the button hook, where
                // the block was simply missing and AddToCart never had data to fire with.
                // setupWooSingleProductData() is idempotent per product id, so when the hook
                // does run this adds nothing.
                if ( is_product() ) {
                    add_action( 'wp_footer', 'PixelYourSite\EventsManager::setupWooSingleProductData', 5 );
                }
                add_filter( 'woocommerce_blocks_product_grid_item_html', array( $this, 'setupWooBlocksProductData' ), 10, 3 );
                add_filter('jet-woo-builder/elementor-views/frontend/archive-item-content', array( $this, 'setupWooBlocksProductData' ),10, 3);
            }
        }

        if(!PYS()->is_user_agent_bot()){
            if( count($this->facebookServerEvents ) > 0
                && Facebook()->enabled()
                && Facebook()->isServerApiEnabled()
                && !PYS()->isCachePreload()
                && Consent()->mayFire( 'facebook' )
            ) {
                FacebookServer()->sendEventsAsync( $this->facebookServerEvents );
                $this->facebookServerEvents = array();
            }

            if ( isPinterestActive()
                && count($this->pinterestServerEvents) > 0
                &&  Pinterest()->enabled()
                && Pinterest()->isServerApiEnabled()
                && !PYS()->isCachePreload()
                && Consent()->mayFire( 'pinterest' )
            ) {
                PinterestServer()->sendEventsAsync( $this->pinterestServerEvents );
                $this->pinterestServerEvents = array();
            }

            if ( count($this->openaiServerEvents) > 0
                && OpenAI()->enabled()
                && OpenAI()->isServerApiEnabled()
                && !PYS()->isCachePreload()
                && Consent()->mayFire( 'openai' )
            ) {
                OpenAIServer()->sendEventsAsync( $this->openaiServerEvents );
                $this->openaiServerEvents = array();
            }
        }


        // remove new user mark
        if($user_id = get_current_user_id()) {
            if ( get_user_meta( $user_id, 'pys_complete_registration', true ) ) {
                delete_user_meta( $user_id, 'pys_complete_registration' );
            }
        }
	}

	public function getStaticEvents( $context ) {
	    return isset( $this->staticEvents[ $context ] ) ? $this->staticEvents[ $context ] : array();
    }


    function addEvents($pixelEvents,$slug) {

        foreach ($pixelEvents as $pixelSlug => $events) {
            $pixel = PYS()->getRegisteredPixels()[$pixelSlug];
            foreach ($events as $event) {
                // add standard params
                if(!isset($this->uniqueId[$event->getId()])) {
                    $this->uniqueId[$event->getId()] = EventIdGenerator::guidv4();
                }
                $event->addPayload(['eventID'=>$this->uniqueId[$event->getId()]]);

	            if( $pixelSlug !== "reddit" ) {
		            $event->addParams( $this->standardParams );
	            }
                //save different types of events
                if($event->getType() == EventTypes::$STATIC) {
                    $this->addStaticEvent( $event,$pixel,$slug );
                } elseif($event->getType() == EventTypes::$TRIGGER) {
                    $this->addTriggerEvent($event,$pixel,$slug);
                } else {
                    $this->addDynamicEvent($event,$pixel,$slug);
                }
            }

        }
    }



    function addDynamicEvent($event,$pixel,$slug) {

        $eventData = $event->getData();
        $eventData = $this::filterEventParams($eventData,$slug,['event_id'=>$event->getId(),'pixel'=>$pixel->getSlug()]);

        if($event->getId() == 'edd_remove_from_cart' || $event->getId() == 'woo_remove_from_cart')  {
            $this->dynamicEvents[ $event->getId() ][ $event->args['key'] ][ $pixel->getSlug() ] = $eventData;
        } else  {
            $this->dynamicEvents[ $event->getId() ][ $pixel->getSlug() ] = $eventData;
        }
    }

    function addTriggerEvent($event,$pixel,$slug) {

        $eventData = $event->getData();
        $eventData = $this->filterEventParams($eventData,$slug,['event_id'=>$event->getId(),'pixel'=>$pixel->getSlug()]);
        //save static event data
        if($event->getId() == "custom_event") {
            $eventId = $event->args->getPostId();
        } else {
            $eventId = $event->getId();
        }
        $this->triggerEvents[ $eventId ][ $pixel->getSlug() ] = $eventData;
        if ( !empty( $event->args ) &&  $event->getCategory() === 'custom') {
            $this->triggerEventTypes = array_replace_recursive( $this->triggerEventTypes, $event->args->__get( 'triggerEventTypes' ) );
        }

        if($event->getCategory() === 'fdp'){
            $this->triggerEventTypes[ $eventData['trigger_type'] ][ $eventId ][] = $eventData['trigger_value'];
        }

    }

    /**
     * Create stack event, they fire when page loaded
     * @param Event $event
     */
    function addStaticEvent($event,$pixel,$slug) {
        $event_getId = $event->getId() == 'custom_event' ? $event->getPayloadValue('custom_event_post_id') : $event->getId();

        if(!isset($this->uniqueId[$event_getId])) {
            $this->uniqueId[$event_getId] = EventIdGenerator::guidv4();
        }

        $event->addPayload(['eventID'=>$this->uniqueId[$event_getId]]);

        $eventData = $event->getData();
        $eventData = $this::filterEventParams($eventData,$slug,['event_id'=>$event->getId(),'pixel'=>$pixel->getSlug()]);

        //save static event data
        $this->staticEvents[ $pixel->getSlug() ][ $event->getId() ][] = $eventData;
        // fire fb server api event
        if( $eventData['delay'] == 0 && (!PYS()->getOption( "server_static_event_use_ajax" ) || !PYS()->getOption('server_event_use_ajax'))) {
            if($pixel->getSlug() === "facebook" && Facebook()->enabled()) {
                $this->facebookServerEvents[] = $event;
            }
            if(isPinterestActive() && $pixel->getSlug() === "pinterest" && Pinterest()->enabled()) {
                $this->pinterestServerEvents[] = $event;
            }
            if($pixel->getSlug() === "openai" && OpenAI()->enabled()) {
                $this->openaiServerEvents[] = $event;
            }
        }

    }

    static function filterEventParams($data,$slug,$context = null)
    {

        if(!PYS()->getOption('enable_content_name_param')) {
            unset($data['params']['content_name']);
        }

        if(!PYS()->getOption('enable_page_title_param')) {
            unset($data['params']['page_title']);
        }
        if(!PYS()->getOption('enable_post_category_param')) {
            unset($data['params']['post_category']);
        }

        if($slug == EventsWoo::getSlug()) {
            if(!PYS()->getOption("enable_woo_category_name_param")) {
                unset($data['params']['category_name']);
            }
            if(!PYS()->getOption("enable_woo_num_items_param")) {
                unset($data['params']['num_items']);
            }

            if(!PYS()->getOption("enable_woo_tags_param")) {
                unset($data['params']['tags']);
            }

            if(!PYS()->getOption("enable_woo_fees_param")) {
                unset($data['params']['fees']);
            }
        }

        if($slug == EventsEdd::getSlug()) {
            if(!PYS()->getOption("enable_edd_category_name_param")) {
                unset($data['params']['category_name']);
            }
            if(!PYS()->getOption("enable_edd_num_items_param")) {
                unset($data['params']['num_items']);
            }

            if(!PYS()->getOption("enable_edd_tags_param")) {
                unset($data['params']['tags']);
            }
        }

        if(isset($context) && isset($context['pixel']) && $context['pixel'] === 'facebook' && Facebook()->getOption('enabled_medical')) {
            foreach (Facebook()->getOption('do_not_track_medical_param') as $param) {
                unset($data['params'][$param]);
            }
        }

        return $data;
    }



    function isGdprPluginEnabled() {
        return apply_filters( 'pys_disable_by_gdpr', false ) ||
            apply_filters( 'pys_disable_facebook_by_gdpr', false ) ||
            (isCookiebotPluginActivated() && PYS()->getOption('gdpr_cookiebot_integration_enabled')) ||
            (isConsentMagicPluginActivated() && PYS()->getOption('consent_magic_integration_enabled')) ||
            (isRealCookieBannerPluginActivated() && PYS()->getOption('gdpr_real_cookie_banner_integration_enabled')) ||
            (isCookieNoticePluginActivated() && PYS()->getOption('gdpr_cookie_notice_integration_enabled')) ||
            (isCookieLawInfoPluginActivated() && PYS()->getOption('gdpr_cookie_law_info_integration_enabled'));
    }


    public function setupWooLoopProductData()
    {
        global $product;

        $this->setupWooProductData($product);
    }

    public function setupWooBlocksProductData($html, $data, $product)
    {

        $this->setupWooProductData($product);
        return $html;
    }

    public function setupWooProductData($product) {

		if (  !is_a($product,"WC_Product")
            || wooProductIsType( $product, 'variable' )
            || wooProductIsType( $product, 'grouped' )
        ) {
			return; // skip variable products
		}

        $product_id = $product->get_id();

		$params = array();
        $event = new SingleEvent('woo_add_to_cart_on_button_click',EventTypes::$STATIC,'woo');
        $event->args = ['productId' => $product_id,'quantity' => 1];
        $eventID = EventIdGenerator::guidv4();
        $event->addPayload(['eventID'=>$eventID]);
		foreach ( PYS()->getRegisteredPixels() as $pixel ) {
			/** @var Pixel|Settings $pixel */

            $events = $pixel->generateEvents( $event );
            foreach ($events as $event) {
                // prepare event data
                $eventData = $event->getData();
                $eventData = EventsManager::filterEventParams($eventData,"woo",['event_id'=>$event->getId(),'pixel'=>$pixel->getSlug()]);

                $params[$pixel->getSlug()] = $eventData; // replace data!!(now use only one event)
            }
        }

		if ( empty( $params ) ) {
			return;
		}

		// JSON_HEX_TAG, or a product or category name containing "</script" ends the
		// element and the rest of the page becomes markup. The title reaches this JSON
		// through content_name in getWooCustomAudiencesOptimizationParams().
		$params = wp_json_encode( $params, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT );

		?>

		<script type="application/javascript" style="display:none">
            /* <![CDATA[ */
            window.pysWooProductData = window.pysWooProductData || {};
            window.pysWooProductData[ <?php echo (int) $product_id; ?> ] = <?php echo $params; ?>;
            /* ]]> */
		</script>

		<?php

	}

	public static function setupWooSingleProductData() {
        global $product;

        if ( ! is_object( $product)) $product = wc_get_product( get_the_ID() );

        // In the footer the global cannot be trusted: a widget or shortcode that ran its
        // own product loop without wp_reset_postdata() leaves it on a foreign product, and
        // the net would then print that product's data while the page's own went missing —
        // the exact case the net exists for. At the button hook the global is right, and
        // deliberately so for a [product_page] shortcode inside a product page, hence the
        // doing_action() scope.
        if ( doing_action( 'wp_footer' ) && function_exists( 'is_product' ) && is_product() ) {
            $queried_id = (int) get_queried_object_id();
            if ( $queried_id && ( !is_object( $product ) || (int) $product->get_id() !== $queried_id ) ) {
                $queried_product = wc_get_product( $queried_id );
                if ( $queried_product ) {
                    $product = $queried_product;
                }
            }
        }

        if(!$product || !is_a($product,"WC_Product") ) return;

        // This runs on the add-to-cart button hook and again on wp_footer, so that a theme
        // or page builder which never fires the button hook still gets the data. Whichever
        // comes first prints; the other finds the id here and leaves.
        static $printed = array();

        $guard_id = $product->get_id();
        if ( isset( $printed[ $guard_id ] ) ) {
            return;
        }

        // The net runs on every product page, including ones that have no add-to-cart
        // form at all — every variation out of stock, catalog mode, "request a quote".
        // No click can come from those, so the prerender (seconds on a large variable
        // product) would be pure cost. Asked after the guard, so a page whose button hook
        // already printed never pays for the check.
        if ( doing_action( 'wp_footer' ) && function_exists( 'is_product' ) && is_product()
             && ! pysWooProductCanBeAdded( $product ) ) {
            return;
        }

        if ( wooProductIsType( $product, 'external' ) ) {
            $eventType = 'woo_affiliate';
        } else {
            $eventType = 'woo_add_to_cart_on_button_click';
        }
        $product_id = $product->get_id();

        // main product id
        $product_ids[] = $product_id;

        $params = array();

        // One build per product id for the whole loop instead of one per pixel per
        // helper; see WooProductScope. Closed in finally, or a throw here would leave
        // every later wc_get_product() in this request served from the cache.
        WooProductScope::enter();
        try {
            // variations ids — only those a shopper can add. For the rest WooCommerce disables
            // the add-to-cart button and the click handler returns on .disabled, so their data
            // could never be used. is_in_stock() is true for backorders, so those stay.
            //
            // Only while WooCommerce embeds availability in the page, though — the same
            // comparison its variation form makes. Above that threshold the form fetches a
            // variation's stock live when it is picked, so a cached page can enable the button
            // for a variation that was out of stock at render time; leaving its data out would
            // then lose that AddToCart silently. There every child is printed, as before.
            if ( wooProductIsType( $product, 'variable' ) ) {
                $children = $product->get_children();
                $embedded = count( $children ) <= apply_filters( 'woocommerce_ajax_variation_threshold', 30, $product );
                foreach ( $children as $child_id ) {
                    if ( ! $embedded ) {
                        $product_ids[] = $child_id;
                        continue;
                    }
                    $child = pysWooGetProduct( (int) $child_id );
                    if ( $child && $child->is_purchasable() && $child->is_in_stock() && pysWooVariationIsListable( $child ) ) {
                        $product_ids[] = $child_id;
                    }
                }
            }

            foreach ( $product_ids as $product_id ) {

                foreach ( PYS()->getRegisteredPixels() as $pixel ) {
                    /** @var Pixel|Settings $pixel */
                    $initEvent = new SingleEvent($eventType,EventTypes::$STATIC,"woo");
                    $initEvent->args = ['productId' => $product_id,'quantity' => 1];
                    $events = [];
                    if(method_exists($pixel,'generateEvents')) {
                        add_filter('pys_conditional_post_id', function($id) use ($product_id) { return $product_id; });
                        $events =  $pixel->generateEvents( $initEvent );
                        remove_all_filters('pys_conditional_post_id',10);
                    } else {
                        if( $pixel->addParamsToEvent( $initEvent )) {
                            $events[] = $initEvent;
                        }
                    }

                    if(count($events) == 0) continue;
                    foreach ($events as $event) {
                        // prepare event data
                        $eventData = $event->getData();
                        $eventData = EventsManager::filterEventParams($eventData,"woo",['event_id'=>$event->getId(),'pixel'=>$pixel->getSlug()]);

                        $params[ $product_id ][ $pixel->getSlug() ] = $eventData; // replace (use only one event for product)
                    }

                }

            }
        } finally {
            WooProductScope::leave();
        }

        if ( empty( $params ) ) {
            return;
        }

        $printed[ $guard_id ] = true;

        // One base plus per-id diffs when there are several ids, unless JSON cannot carry
        // the payload — then the full print, as before compaction existed.
        $compact = count( $params ) > 1 ? pysCompactProductData( $params ) : null;

        ?>

        <script type="application/javascript" style="display:none">
            /* <![CDATA[ */
            <?php if ( null !== $compact ) : ?>
            <?php // One base plus per-id diffs; the expander rebuilds window.pysWooProductData as the full print would. ?>
            <?php echo pysProductDataExpanderJs(); ?>
            window.pysExpandProductData(<?php echo wp_json_encode( $compact, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>);
            <?php else : ?>
            window.pysWooProductData = window.pysWooProductData || {};
            <?php foreach ( $params as $product_id => $product_data ) : ?>
            window.pysWooProductData[<?php echo (int) $product_id; ?>] = <?php echo wp_json_encode( $product_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>;
            <?php endforeach; ?>
            <?php endif; ?>
            /* ]]> */
        </script>

        <?php

	}

    public function setupEddSingleDownloadData($purchase_link) {
        $download = $purchase_link;
        $download_ids = array();

        if ( edd_has_variable_prices( $download ) ) {

            $prices = edd_get_variable_prices( $download );

            foreach ( $prices as $price_index => $price_data ) {
                $download_ids[] = $download . '_' . $price_index;
            }

        } else {

            $download_ids[] = $download;

        }

        $params = array();
        foreach ( $download_ids as $download_id ) {
            $event = EventsEdd()->getEvent('edd_add_to_cart_on_button_click');
            $event->args = $download_id;
            foreach ( PYS()->getRegisteredPixels() as $pixel ) {
                /** @var Pixel|Settings $pixel */
                $events = $pixel->generateEvents( $event );
                foreach ($events as $singleEvent) {
                    $eventData = $singleEvent->getData();
                    $eventData = EventsManager::filterEventParams($eventData,"edd",['event_id'=>$event->getId(),'pixel'=>$pixel->getSlug()]);
                    /**
                     * Format is pysEddProductData[ id ][ id ] or pysEddProductData[ id ] [ id_1, id_2, ... ]
                     */
                    $params[ $download_id ][ $pixel->getSlug() ] = [ // replace data there use only one event
                            'params' => $eventData['params']
                    ];
                }
            }
        }

        ?>

        <script type="application/javascript" style="display:none">
            /* <![CDATA[ */
            window.pysEddProductData = window.pysEddProductData || {};
            window.pysEddProductData[<?php echo (int) $download; ?>] = <?php echo wp_json_encode( $params, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>;
            /* ]]> */
        </script>

        <?php

    }
    /**
     * Register edd_purchase_link_end hook as early as possible (from the
     * constructor, on 'init') so it is added before any content is rendered.
     * Required for block/FSE themes and page builders (e.g. Elementor shortcodes)
     * where the purchase form is generated before wp_enqueue_scripts fires.
     */
    public function setupEddButtonHooks() {
        if ( EventsEdd()->isEnabled() ) {
            if ( isEventEnabled( 'edd_add_to_cart_enabled' ) && PYS()->getOption( 'edd_add_to_cart_on_button_click' ) ) {
                add_action( 'edd_purchase_link_end', array( $this, 'setupEddSingleDownloadData' ) );
            }
        }
    }

    static function isTrackExternalId(){
        return PYS()->getOption("send_external_id") && !apply_filters( 'pys_disable_externalID_by_gdpr', false ) && !apply_filters( 'pys_disable_all_cookie', false );
    }
}