PNG %k25u25%fgd5n!
/home/mkuwqnjx/palaknaturals.com/wp-content/plugins/funnel-builder/includes/class-wffn-public.php
<?php
defined( 'ABSPATH' ) || exit; // Exit if accessed directly

/**
 * Funnel Public facing functionality
 * Class WFFN_Public
 */
if ( ! class_exists( 'WFFN_Public' ) ) {
	class WFFN_Public {

		private static $ins         = null;
		public $environment         = null;
		public $funnel_setup_result = null;

		/**
		 * WFFN_Public constructor..
		 *
		 * @since  1.0.0
		 */
		public function __construct() {
			/**
			 * Maybe try and setup the funnel
			 */
			add_action( 'template_redirect', array( $this, 'maybe_initialize_setup' ), 999 );

			/**
			 * Request actors for teh setup funnel ajax request
			 */
			add_action( 'wp_ajax_wffn_maybe_setup_funnel', array( $this, 'setup_funnel_ajax' ) );
			add_action( 'wp_ajax_nopriv_wffn_maybe_setup_funnel', array( $this, 'setup_funnel_ajax' ) );

			/**
			 * handle analytics requests
			 */
			add_action( 'wp_ajax_wffn_frontend_analytics', array( $this, 'frontend_analytics' ) );
			add_action( 'wp_ajax_nopriv_wffn_frontend_analytics', array( $this, 'frontend_analytics' ) );

			add_action( 'wp_ajax_wffn_tracking_events', array( $this, 'tracking_events' ) );
			add_action( 'wp_ajax_nopriv_wffn_tracking_events', array( $this, 'tracking_events' ) );

			add_action( 'wp', array( $this, 'maybe_register_assets_on_load' ), 10 );
			add_action( 'wffn_mark_pending_conversions', array( $this, 'wffn_record_unique_funnel_session' ), 5, 3 );
			add_action( 'wffn_mark_pending_conversions', array( $this, 'mark_pending_conversions' ), 10, 2 );
			add_action( 'wffn_mark_step_viewed', array( $this, 'mark_funnel_step_viewed' ), 10, 2 );
			add_action( 'woocommerce_thankyou', array( $this, 'maybe_log_thankyou_visited' ), 999, 1 );
			add_action( 'wp_enqueue_scripts', array( $this, 'maybe_setup_tracking_script' ), 11 );
			add_action( 'woocommerce_add_to_cart', array( $this, 'maybe_track_add_to_cart' ), 10, 4 );
			add_filter( 'woocommerce_add_to_cart_fragments', array( $this, 'send_pending_events' ), 99 );
			add_filter( 'fkcart_fragments', array( $this, 'send_pending_events' ), 10 );
			add_filter( 'wc_add_to_cart_message_html', array( $this, 'send_pending_events_on_cart' ), 100, 1 );
			add_action( 'woocommerce_ajax_added_to_cart', array( $this, 'clear_pending_events_data_from_session' ), 10 );
			add_action( 'woocommerce_thankyou', array( $this, 'maybe_destroyed_funnel_session' ), 999, 1 );
			add_action( 'rest_api_init', array( $this, 'register_routes' ) );
			add_action( 'wp_footer', array( $this, 'send_pending_events_on_footer' ), 9999 );
		}

		/**
		 * @return WFFN_Public|null
		 */
		public static function get_instance() {
			if ( null === self::$ins ) {
				self::$ins = new self();
			}

			return self::$ins;
		}


		public function print_custom_js_in_footer() {
			$environment = $this->environment;
			$step_id     = isset( $environment['id'] ) ? $environment['id'] : 0;

			if ( $step_id > 0 ) {
				$custom_script = get_post_meta( $step_id, 'wffn_step_custom_settings', true );
				$custom_js     = isset( $custom_script['custom_js'] ) ? $custom_script['custom_js'] : '';

				if ( ! empty( $custom_js ) ) {
					echo html_entity_decode( $custom_js ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				}
			}
		}

		/**
		 * @hooked over 'template_redirect'
		 * Try to initialize the funnel based on the current environment
		 */
		public function maybe_initialize_setup() {
			$is_preview_mode = WFFN_Common::is_page_builder_preview();
			if ( $is_preview_mode ) {
				return;
			}

			global $post;

			if ( is_null( $post ) || ! ( $post instanceof WP_Post ) ) {
				return;
			}

			$id                = $post->ID;
			$post_type         = $post->post_type;
			$this->environment = apply_filters(
				'wffn_funnel_environment',
				array(
					'id'         => $id,
					'post_type'  => $post_type,
					'setup_time' => strtotime( gmdate( 'c' ) ),
				)
			);

			/**
			 * Pass environment to controller function to get the funnel setup result
			 */
			$this->funnel_setup_result = $this->maybe_setup_funnel( $this->environment );
			/**
			 * Do nothing if funnel setup fails,which means its not our step which is request right now and we do not have to move further
			 */
			if ( empty( $this->funnel_setup_result ) || false === $this->funnel_setup_result['success'] ) {
				return;
			}

			/**
			 * Go ahead and enqueue the scripts
			 */
			$this->funnel_setup_result['setup_time'] = strtotime( gmdate( 'c' ) );
			$this->funnel_setup_result['is_preview'] = $is_preview_mode;
			add_action( 'wp_enqueue_scripts', array( $this, 'maybe_add_script' ) );
		}

		/**
		 * Maybe setup funnel based upon the environment
		 * It checks for the correct environment and finds the running funnel based on that
		 *
		 * @param bool|array $environment environment to set funnel against
		 *
		 * @return array
		 */
		public function maybe_setup_funnel( $environment = false ) {
			/**
			 * Loop over all the supported steps to check if step supports open links and claiming the environment, only then we can initiate the funnel
			 */
			try {

				$get_all_steps = WFFN_Core()->steps->get_supported_steps();
				foreach ( $get_all_steps as $step ) {

					/**
					 * Skip all the other steps which cannot initiate funnel, like upsell and thank you
					 */
					if ( ! $step->supports( 'open_link' ) || false === $step->claim_environment( $environment ) ) {
						continue;
					}

					/**
					 * Ask step to find the funnel based on environment
					 */
					$funnel = $step->get_funnel_to_run( $environment );
					/**
					 * bail if no funnel found
					 */
					if ( ! wffn_is_valid_funnel( $funnel ) ) {
						return ( array( 'success' => false ) );
					}

					do_action( 'wffn_before_setup_funnel', $funnel );
					/**
					 * Setup funnel information for future use
					 */

					if ( isset( $environment['id'] ) && $environment['id'] !== '' ) {
						$environment['id'] = absint( $environment['id'] );
					}

					WFFN_Core()->data->set( 'funnel', $funnel );
					WFFN_Core()->data->set(
						'current_step',
						array(
							'id'   => $environment['id'],
							'type' => $step->slug,
						)
					);

					WFFN_Core()->data->save();

					do_action( 'wffn_after_setup_funnel', $funnel );

					/**
					 * Return the block of info
					 */
					return ( array(
						'success'       => true,
						'current_step'  => array(
							'id'   => $environment['id'],
							'type' => $step->slug,
						),
						'hash'          => WFFN_Core()->data->get_transient_key(),
						'next_link'     => WFFN_Core()->data->get_next_url( $environment['id'] ),
						'support_track' => $step->supports( 'track_views' ),
						'fid'           => $funnel->get_id(),
					) );
				}
			} catch ( Exception | Error $e ) {
				WFFN_Core()->logger->log( __FUNCTION__ . ' error during setup funnel : ' . $e->getMessage(), 'wffn', true );
			}

			return ( array( 'success' => false ) );
		}

		public function maybe_add_script() {
			$live_or_dev = 'live';
			$suffix      = '.min';

			if ( defined( 'WFFN_IS_DEV' ) && true === WFFN_IS_DEV ) {
				$live_or_dev = 'dev';
				$suffix      = '';
			}

			/**
			 * register cookie script for funnel steps for handle blocking script plugins issues
			 */ global $post;

			// Use new WooCommerce handle for WC >= 10.3.0, fallback to legacy handle for older versions
			$cookie_handle = ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '10.3.0', '>=' ) ) ? 'wc-js-cookie' : 'js-cookie';

			if ( ! is_null( $post ) && $post instanceof WP_Post ) {
				if ( in_array( $post->post_type, array( 'wffn_landing', 'wffn_optin', 'wffn_oty', 'wffn_ty' ), true ) ) {

					wp_deregister_script( $cookie_handle );
					wp_register_script(
						$cookie_handle,
						plugin_dir_url( WFFN_PLUGIN_FILE ) . 'assets/' . $live_or_dev . '/js/js.cookie.min.js',
						array( 'jquery' ),
						WFFN_VERSION,
						array(
							'is_footer' => false,
							'strategy'  => 'defer',
						)
					);
				}
			}

			wp_enqueue_script( $cookie_handle );
			wp_enqueue_script( 'jquery' );
			wp_enqueue_script(
				'wffn-public',
				plugin_dir_url( WFFN_PLUGIN_FILE ) . 'assets/' . $live_or_dev . '/js/public' . $suffix . '.js',
				array(
					$cookie_handle,
					'jquery',
				),
				WFFN_VERSION,
				array(
					'is_footer' => true,
					'strategy'  => 'defer',
				)
			);

			wp_localize_script( 'wffn-public', 'wffnfunnelData', $this->funnel_setup_result );
			wp_localize_script( 'wffn-public', 'wffnfunnelEnvironment', $this->environment );

			wp_localize_script(
				'wffn-public',
				'wffnfunnelVars',
				apply_filters(
					'wffn_localized_data',
					array(
						'ajaxUrl'      => admin_url( 'admin-ajax.php' ),
						'restUrl'      => rest_url() . 'wffn/front',
						'is_ajax_mode' => true,
					)
				)
			);
		}


		public function setup_funnel_ajax() {
			$get_data = isset( $_POST['data'] ) ? wffn_clean( wp_unslash( $_POST['data'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing,FunnelBuilder.CodeAnalysis.FunnelBuilderSpecific.MissingCapabilityCheck -- Public AJAX endpoint for frontend funnel setup
			$result   = ( array( 'success' => false ) );
			if ( ! empty( $get_data ) ) {
				try {

					$get_data = json_decode( stripslashes( $get_data ), true );
					if ( is_array( $get_data ) ) {
						$result = $this->maybe_record_data_with_funnel_setup( array( 'data' => $get_data ) );
					}
				} catch ( Exception | Error $e ) {
					WFFN_Core()->logger->log( 'Error in send data : ' . __FUNCTION__ . $e->getMessage(), 'wffn', true );

				}
			}
			wp_send_json( $result );
		}

		/**
		 * Handle Views to be marked during ajax call
		 * This function allows individual step classes to take care of their specific step viewed
		 */
		public function frontend_analytics() {
			$get_data = isset( $_POST['data'] ) ? wffn_clean( wp_unslash( $_POST['data'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing,FunnelBuilder.CodeAnalysis.FunnelBuilderSpecific.MissingCapabilityCheck -- Public AJAX endpoint for frontend analytics
			$result   = $this->send_frontend_analytics( $get_data );
			if ( is_array( $result ) ) {
				$result['funnel_setup'] = false;
			}
			wp_send_json( $result );
		}

		public function send_frontend_analytics( $args = array() ) {
			$current_step = WFFN_Core()->data->get_current_step();
			$response     = array(
				'track_views' => false,
				'ecom_event'  => false,
			);

			/**
			 * Check if we have valid session to proceed
			 */

			if ( WFFN_Core()->data->has_valid_session() && ! empty( $current_step ) ) {

				/**
				 * Start Marking Impressions
				 */
				$get_data = isset( $args ) ? wffn_clean( $args ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Missing , FunnelBuilder.CodeAnalysis.FunnelBuilderSpecific.MissingCapabilityCheck
				if ( ! empty( $get_data ) ) {
					$get_data = json_decode( wp_kses_stripslashes( $get_data ), true );
					$get_data = $this->maybe_setup_step_in_cache( $get_data, $current_step );
					/**
					 * maybe change current step after cache environment check
					 */
					$current_step = WFFN_Core()->data->get_current_step();
				}

				$get_step_object = WFFN_Core()->steps->get_integration_object( $current_step['type'] );

				if ( ! empty( $get_data ) ) {
					if ( is_array( $get_data ) && ! empty( $get_data['events'] ) ) {
						$get_step_object->maybe_ecomm_events( $get_data );
						$response['ecom_event'] = true;
					}
				}
				$funnel = WFFN_Core()->data->get_session_funnel();
				do_action( 'wffn_mark_pending_conversions', $current_step, $get_step_object, $funnel );

				/**
				 *  only track views for the steps that supports
				 */
				if ( $get_step_object->supports( 'track_views' ) ) {
					do_action( 'wffn_mark_step_viewed', $current_step, $get_step_object );
					/**
					 * Now that we have recorded the analytics, we can check if we can mark the funnel ended and clean up the session data
					 */
					$this->maybe_end_funnel_and_clear_data();
					$response['track_views'] = true;
				}
			}

			return $response;
		}

		/**
		 * Handle Views to be marked during ajax call
		 * This function allows individual step classes to take care of their specific step viewed
		 */
		public function tracking_events() {
			$get_data = isset( $_POST ) ? wffn_clean( wp_unslash( $_POST ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing,FunnelBuilder.CodeAnalysis.FunnelBuilderSpecific.MissingCapabilityCheck -- Public AJAX endpoint for tracking events
			$this->send_tracking_events( $get_data );
		}

		public function send_tracking_events( $args = array() ) {
			/**
			 * handel case for sitewide events come from api
			 */
			if ( isset( $args['data'] ) && isset( $args['data']['is_sitewide'] ) ) {
				$args = $args['data'];
			}
			$is_sitewide = isset( $args['is_sitewide'] ) ? true : false;
			if ( true === $is_sitewide ) {
				$get_data = isset( $args['events'] ) ? wffn_clean( $args['events'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Missing
				if ( ! empty( $get_data ) ) {
					WFFN_Tracking_SiteWide::get_instance()->maybe_ecomm_events( $get_data );
				}
				return;
			}

			$post_data = isset( $args['data'] ) ? wffn_clean( $args['data'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Missing
			$post_data = ! is_array( $post_data ) ? json_decode( wp_kses_stripslashes( $post_data ), true ) : $post_data;

			$current_step = WFFN_Core()->data->get_current_step();
			if ( empty( $current_step ) && isset( $post_data['step_data'] ) && isset( $post_data['step_data']['post_type'] ) ) {
				$current_step = array(
					'type' => WFFN_Common::get_step_type( $post_data['step_data']['post_type'] ),
				);
			}

			/**
			 * Check if we have valid session to proceed
			 */

			if ( ! empty( $current_step ) ) {

				/**
				 * Start Running ecomm events
				 */
				$get_step_object = WFFN_Core()->steps->get_integration_object( $current_step['type'] );
				$get_data        = isset( $post_data['events_data'] ) ? $post_data['events_data'] : ''; //phpcs:ignore WordPress.Security.NonceVerification.Missing
				if ( ! empty( $get_data ) && ! empty( $get_step_object ) ) {
					if ( is_array( $get_data ) && count( $get_data ) > 0 ) {
						$get_step_object->maybe_ecomm_events( $get_data );
					}
				}
			}
		}

		/**
		 * Maybe terminate funnel and clear the session data
		 * Checks if the step is the last step we have for the funnel, if yes then terminate
		 */
		public function maybe_end_funnel_and_clear_data() {
			$current_step = WFFN_Core()->data->get_current_step();

			$funnel     = WFFN_Core()->data->get_session_funnel();
			$found_step = 0;
			foreach ( $funnel->steps as $step ) {
				$get_object = WFFN_Core()->steps->get_integration_object( $step['type'] );

				/**
				 * continue till we found the current step
				 */
				if ( absint( $current_step['id'] ) === absint( $step['id'] ) && true === $get_object->supports( 'close_funnel' ) ) {
					$found_step = $current_step['id'];
					continue;
				}

				/**
				 * Continue if we have not found the current step yet
				 */
				if ( 0 === $found_step ) {
					continue;
				}

				if ( empty( $get_object ) ) {
					continue;
				}
				$properties = $get_object->populate_data_properties( $step, $funnel->get_id() );

				if ( false === $get_object->is_disabled( $get_object->get_enitity_data( $properties['_data'], 'status' ) ) ) {
					$found_step = $step['id'];
					break;
				}
			}

			if ( absint( $found_step ) === absint( $current_step['id'] ) ) {
				WFFN_Core()->logger->log( "Funnel id: #{$funnel->get_id()} Closing Funnel" );
				do_action( 'wffn_funnel_ended_event', $current_step, $funnel );
				WFFN_Core()->data->destroy_session();
			}
		}

		public function maybe_register_assets_on_load() {
			$should_register = apply_filters( 'wffn_should_register_assets', true );

			if ( true === $should_register ) {
				$this->maybe_register_assets( array(), '', true );
			}
		}

		public function maybe_register_assets( $handles = array(), $environment = '', $force_environment = false ) {
			$this->maybe_register_styles( $handles, $environment, $force_environment );
			$this->maybe_register_scripts( $handles, $environment, $force_environment );
		}

		public function maybe_register_styles( $handles = array(), $environment = '', $force_environment = false ) {

			$styles = $this->get_styles();

			foreach ( $styles as $handle => $style ) {

				if ( ! empty( $handles ) && false === in_array( $handle, $handles, true ) ) {
					continue;
				}

				if ( false === $force_environment && ! empty( $environment ) && false === in_array( $environment, $style['supports'], true ) ) {
					continue;
				}

				wp_register_style( $handle, $style['path'], array(), $style['version'] );
			}
		}

		public function maybe_register_scripts( $handles = array(), $environment = '', $force_environment = false ) {
			$scripts = $this->get_scripts();

			foreach ( $scripts as $handle => $script ) {
				if ( ! empty( $handles ) && false === in_array( $handle, $handles, true ) ) {
					continue;
				}

				if ( false === $force_environment && ! empty( $environment ) && false === in_array( $environment, $script['supports'], true ) ) {
					continue;
				}
				wp_register_script( $handle, $script['path'], array(), $script['version'], $script['in_footer'] );
			}
		}

		public function get_styles() {
			$live_or_dev = 'live';
			$suffix      = '.min';

			if ( defined( 'WFFN_IS_DEV' ) && true === WFFN_IS_DEV ) {
				$live_or_dev = 'dev';
				$suffix      = '';
			}

			return apply_filters(
				'wffn_assets_styles',
				array(
					'wffn-frontend-style' => array(
						'path'      => WFFN_Core()->get_plugin_url() . '/assets/' . $live_or_dev . '/css/wffn-frontend' . $suffix . '.css',
						'version'   => WFFN_VERSION_DEV,
						'in_footer' => false,
						'supports'  => array(),
					),
					'wffn-template-style' => array(
						'path'      => WFFN_Core()->get_plugin_url() . '/assets/' . $live_or_dev . '/css/wffn-template' . $suffix . '.css',
						'version'   => WFFN_VERSION_DEV,
						'in_footer' => false,
						'supports'  => array(),
					),
				)
			);
		}

		public function get_scripts() {
			return apply_filters(
				'wffn_assets_scripts',
				array(
					'jquery' => array(
						'path'      => includes_url() . 'js/jquery/jquery.js',
						'version'   => null,
						'in_footer' => false,
						'supports'  => array(
							'customizer',
							'customizer-preview',
							'offer',
							'offer-page',
							'offer-single',
						),
					),
				)
			);
		}


		public function wffn_record_unique_funnel_session( $current_step, $get_step_object, $funnel ) {

			$funnel_id          = $funnel->get_id();
			$recorded_funnel_id = WFFN_Core()->data->get( 'recorded_funnel_id_' . $funnel_id );

			if ( ( absint( $funnel_id ) ) !== absint( $recorded_funnel_id ) ) {

				$this->increase_funnel_visit_session_view( $funnel_id );
				WFFN_Core()->data->set( 'recorded_funnel_id_' . $funnel_id, $funnel_id )->save();
				WFFN_Core()->logger->log( __FUNCTION__ . ':: ' . $funnel_id );
			}
		}


		public function increase_funnel_visit_session_view( $funnel_id ) {
			WFCO_Model_Report_views::update_data( gmdate( 'Y-m-d', current_time( 'timestamp' ) ), $funnel_id, 7 );
		}

		/**
		 * Mark Pending Conversions
		 *
		 * This method now includes validation to prevent marking conversions
		 * when users navigate backwards in the funnel flow. It ensures that
		 * conversions are only marked when the user is actually progressing
		 * forward through the funnel steps.
		 *
		 * @param array  $current_step Current step data
		 * @param object $get_step_object Step object instance
		 */
		public function mark_pending_conversions( $current_step, $get_step_object ) {
			/**
			 * Mark Pending Conversions
			 */
			$get_step_to_convert = WFFN_Core()->data->get( 'to_convert' );

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

			if ( absint( $get_step_to_convert['id'] ) === absint( $current_step['id'] ) ) {
				return;
			}

			/**
			 * Validate that current step represents forward navigation
			 * This prevents marking conversions when users navigate backwards
			 */
			if ( ! $this->is_valid_next_step( $get_step_to_convert, $current_step ) ) {
				WFFN_Core()->logger->log( 'Conversion skipped: Invalid next step navigation. Step to convert: ' . $get_step_to_convert['id'] . ', Current step: ' . $current_step['id'], 'wffn' );
				return;
			}

			$get_step_object = WFFN_Core()->steps->get_integration_object( $get_step_to_convert['type'] );

			/**
			 *  only track conversion for the steps that supports
			 */
			if ( $get_step_object instanceof WFFN_Step && $get_step_object->supports( 'track_conversions' ) ) {
				$get_step_object->mark_step_converted( $get_step_to_convert );
				WFFN_Core()->data->set( 'to_convert', '0' )->save();
			}
		}

		/**
		 * Validate if the current step represents forward navigation
		 * This prevents marking conversions when users navigate backwards
		 *
		 * @param array $step_to_convert The step that should be converted
		 * @param array $current_step The current step user is on
		 * @return bool True if current step represents forward navigation, false otherwise
		 */
		private function is_valid_next_step( $step_to_convert, $current_step ) {
			$current_step_id    = absint( $current_step['id'] );
			$step_to_convert_id = absint( $step_to_convert['id'] );

			/**
			 * Don't convert if we're on the same step
			 */
			if ( $current_step_id === $step_to_convert_id ) {
				return false;
			}

			/**
			 * Check if user is navigating backwards by checking if current step
			 * was visited before the step to convert in this session
			 */
			if ( $this->is_backward_navigation( $current_step_id, $step_to_convert_id ) ) {
				return false;
			}

			/**
			 * Allow conversion for any forward navigation
			 * This is more permissive and focuses on preventing backward navigation
			 */
			return true;
		}

		/**
		 * Check if the current step represents backward navigation
		 * by comparing step positions in the funnel sequence and visit history
		 *
		 * @param int $current_step_id The current step ID
		 * @param int $step_to_convert_id The step that should be converted
		 * @return bool True if this is backward navigation, false otherwise
		 */
		private function is_backward_navigation( $current_step_id, $step_to_convert_id ) {
			try {
				/**
				 * Primary check: Compare step positions in funnel sequence
				 * This is more reliable than visit history alone
				 */
				$funnel = WFFN_Core()->data->get_session_funnel();
				if ( wffn_is_valid_funnel( $funnel ) ) {
					$steps = $funnel->get_steps();
					if ( is_array( $steps ) && ! empty( $steps ) ) {
						$current_position = false;
						$convert_position = false;

						/**
						 * Find positions of both steps in the funnel sequence
						 * Break early once both positions are found for better performance
						 */
						foreach ( $steps as $index => $step ) {
							if ( ! isset( $step['id'] ) ) {
								continue;
							}

							$step_id = absint( $step['id'] );

							if ( false === $current_position && $step_id === $current_step_id ) {
								$current_position = $index;
							}
							if ( false === $convert_position && $step_id === $step_to_convert_id ) {
								$convert_position = $index;
							}

							/**
							 * If we found both positions, we can make a decision immediately
							 */
							if ( false !== $current_position && false !== $convert_position ) {
								/**
								 * If current step comes before step to convert in funnel sequence,
								 * it's backward navigation (user went back)
								 */
								if ( $current_position < $convert_position ) {
									return true;
								}

								/**
								 * If current step comes after step to convert, it's forward navigation
								 * Allow conversion in this case
								 */
								return false;
							}
						}
					}
				}

				/**
				 * Fallback check: Use visit history if funnel structure check didn't work
				 * This handles edge cases where steps might not be in the funnel structure
				 */
				$get_all_visit_data = WFFN_Core()->data->get( 'step_analytics' );

				if ( false === $get_all_visit_data || ! is_array( $get_all_visit_data ) ) {
					return false;
				}

				/**
				 * Check if current step was visited before step to convert
				 * This indicates backward navigation
				 * Cache array access to avoid multiple lookups
				 */
				$current_step_data   = isset( $get_all_visit_data[ $current_step_id ] ) ? $get_all_visit_data[ $current_step_id ] : null;
				$current_was_visited = ( null !== $current_step_data &&
										isset( $current_step_data['visit'] ) &&
										'1' === $current_step_data['visit'] );

				$convert_step_data           = isset( $get_all_visit_data[ $step_to_convert_id ] ) ? $get_all_visit_data[ $step_to_convert_id ] : null;
				$step_to_convert_was_visited = ( null !== $convert_step_data &&
													isset( $convert_step_data['visit'] ) &&
													'1' === $convert_step_data['visit'] );

				/**
				 * If both steps were visited, it's backward navigation (user went back to a previous step)
				 * This prevents marking conversion when user navigates backwards
				 */
				return $current_was_visited && $step_to_convert_was_visited;
			} catch ( Throwable $e ) {
				WFFN_Core()->logger->log( __FUNCTION__ . ' error during backward navigation check: ' . $e->getMessage(), 'wffn', true );
				return false;
			}
		}

		public function mark_funnel_step_viewed( $current_step, $get_step_object ) {
			/**
			 * return if we found that this very step is already visited
			 */
			if ( empty( $current_step ) ) {
				return;
			}

			/**
			 * Check if we already tracked view of this step in the current session
			 */
			$get_all_visit_data = WFFN_Core()->data->get( 'step_analytics' );
			if ( false === $get_all_visit_data ) {
				$get_all_visit_data = array();
			}

			/**
			 * return if we found that this step is already visited
			 */
			if ( isset( $get_all_visit_data[ $current_step['id'] ] ) && isset( $get_all_visit_data[ $current_step['id'] ]['visit'] ) && '1' === $get_all_visit_data[ $current_step['id'] ]['visit'] ) {
				return;
			}

			if ( ! is_array( $get_all_visit_data ) ) {
				$get_all_visit_data = array();
			}
			/**
			 * GO ahead & track view
			 */
			if ( ! isset( $get_all_visit_data[ $current_step['id'] ] ) ) {
				$get_all_visit_data[ $current_step['id'] ] = array();
			}

			$get_step_object = WFFN_Core()->steps->get_integration_object( $current_step['type'] );
			/**
			 * Tell step to mark step viewed
			 */
			$get_step_object->mark_step_viewed();

			/**
			 * sets up flag that this step is visited
			 */
			$get_all_visit_data[ $current_step['id'] ]['visit'] = '1';
			WFFN_Core()->data->set( 'step_analytics', $get_all_visit_data )->save();

			/**
			 * if the current step supports tracking conversions then set the flag that this step needs to be converted later in the funnel
			 */
			if ( $get_step_object->supports( 'track_conversions' ) ) {
				WFFN_Core()->data->set( 'to_convert', $current_step )->save();
			}
		}

		public function maybe_log_thankyou_visited( $order_id ) {

			global $post;

			if ( ! is_null( $post ) ) {
				WFFN_Core()->logger->log( 'Order #' . $order_id . ': Thankyou page #' . $post->ID . ' viewed successfully', 'wffn', true );
				if ( isset( $_GET['wfty_source'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
					WFFN_Core()->logger->log( 'Order #' . $order_id . ': wfty thankyou page id #' . $_GET['wfty_source'], 'wffn', true ); // phpcs:ignore

					if ( isset( $_COOKIE[ 'wfty_native_' . $order_id ] ) && 'yes' === $_COOKIE[ 'wfty_native_' . $order_id ] ) {
						return;
					}

					/**
					 * increase store checkout funnel thankyou page views when native checkout set
					 */
					if ( 0 === WFFN_Common::get_store_checkout_id() ) {
						return;
					}

					$funnel = new WFFN_Funnel( WFFN_Common::get_store_checkout_id() );

					/**
					 * Check if this is a valid funnel and has native checkout
					 */
					if ( ! wffn_is_valid_funnel( $funnel ) || false === $funnel->is_funnel_has_native_checkout() ) {
						return;
					}
					/**
					 * Record thankyou page views for native store checkout
					 */
					$order = wc_get_order( $order_id );
					if ( $order instanceof WC_Order ) {
						if ( empty( $order->get_meta( '_wfacp_post_id' ) ) ) {
							WFCO_Model_Report_views::update_data( gmdate( 'Y-m-d', current_time( 'timestamp' ) ), $post->ID, 5 );
							WFFN_Core()->data->set_cookie( 'wfty_native_' . $order_id, 'yes', time() + ( DAY_IN_SECONDS * 1 ) );
							WFFN_Core()->logger->log( 'Order #' . $order_id . ': record view thankyou page #' . $_GET['wfty_source'], 'wffn', true ); // phpcs:ignore

						}
					}
				}
			}
		}


		public function maybe_setup_tracking_script() {
			$is_preview_mode = WFFN_Common::is_page_builder_preview();
			if ( $is_preview_mode ) {
				return;
			}
			WFFN_Tracking_SiteWide::get_instance()->tracking_script();
		}

		public function maybe_track_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id ) {
			WFFN_Tracking_SiteWide::get_instance()->add_to_cart_process( $cart_item_key, $product_id, $quantity, $variation_id );

			$events = WFFN_Tracking_SiteWide::get_instance()->get_pending_events();
			if ( ! is_null( $events ) && is_array( $events ) && count( $events ) > 0 ) {
				if ( function_exists( 'WC' ) && ! is_null( WC()->session ) && WC()->session->has_session() ) {
					$final_events = array();
					if ( ! empty( WC()->session->get( 'wffn_pending_data' ) ) ) {
						$final_events = array_merge( WC()->session->get( 'wffn_pending_data' ), array( $events ) );
					} else {
						$final_events[] = $events;
					}
					WC()->session->set( 'wffn_pending_data', $final_events );
				}
			}
		}

		public function send_pending_events( $fragments ) {

			$events                    = WFFN_Tracking_SiteWide::get_instance()->get_pending_events();
			$fragments['wffnTracking'] = array( 'pending_events' => $events );

			/**
			 * Session events not clear on fkcart fragment and refreshed fragments
			 * Some theme not run wc ajax but run refreshed
			 */
			if ( function_exists( 'did_filter' ) && ( ( 0 === did_filter( 'fkcart_fragments' ) ) && ( 0 === did_action( 'wc_ajax_get_refreshed_fragments' ) ) ) ) {
				$this->clear_pending_events_data_from_session();
			}
			return $fragments;
		}

		/*
		fire events on cart page if product 'Redirect to the cart page after successful addition' setting enabled
		 * @param $message
		 *
		 * @return mixed|string
		 */
		public static function send_pending_events_on_cart( $message ) {
			if ( 'yes' !== get_option( 'woocommerce_cart_redirect_after_add' ) ) {
				return $message;
			}

			$events = WFFN_Tracking_SiteWide::get_instance()->get_pending_events();

			if ( ! is_null( $events ) && is_array( $events ) && count( $events ) > 0 ) {
				$message .= "<div id='wffn_late_event' dir='" . json_encode( $events ) . "'></div>"; //phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
				WFFN_Core()->public->clear_pending_events_data_from_session();
			}

			return $message;
		}

		/**
		 * handle pending events data and fire data which theme not run wc ajax
		 * pending event data fire on next reload or next page
		 *
		 * @return void
		 */
		public static function send_pending_events_on_footer() {
			if ( function_exists( 'WC' ) && ! is_null( WC()->session ) && WC()->session->has_session() ) {
				$events = WC()->session->get( 'wffn_pending_data' );
				if ( ! is_null( $events ) && is_array( $events ) && count( $events ) > 0 ) {
					echo "<div id='wffn_late_event' style='display:none' dir='" . json_encode( $events ) . "'></div>"; //phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
					WFFN_Core()->public->clear_pending_events_data_from_session();
				}
			}
		}

		public function clear_pending_events_data_from_session() {
			if ( function_exists( 'WC' ) && ! is_null( WC()->session ) && WC()->session->has_session() ) {

				if ( function_exists( 'is_checkout' ) && is_checkout() ) {
					return;
				}
				$events = WC()->session->get( 'wffn_pending_data' );
				if ( ! is_null( $events ) && is_array( $events ) && count( $events ) > 0 ) {
					WC()->session->set( 'wffn_pending_data', '' );
				}
			}
		}


		/**
		 * @param $args
		 * @param $current_step
		 *
		 * handle case when optin_ty and wc_thankyou page open in cache environment
		 * in this sometimes both step not set in funnel session current step due to cache
		 * so we manually set here
		 *
		 * @return mixed
		 */
		public function maybe_setup_step_in_cache( $args, $current_step ) {

			if ( ! is_array( $args ) || count( $args ) === 0 ) {
				return $args;
			}

			foreach ( $args as $key => &$data ) {
				if ( isset( $data['current_step'] ) && is_array( $data['current_step'] ) ) {

					/**
					 * If data is incomplete to process, then unset and break loop
					 */
					if ( ! isset( $data['current_step']['post_type'] ) || empty( $data['current_step']['post_type'] ) ) {
						unset( $args[ $key ] );
						break;
					}

					/*
					 * Check if we have correct post types to process
					 */
					if ( is_array( $current_step ) && ! in_array( $current_step['type'], array( 'optin_ty', 'wc_thankyou' ), true ) && in_array(
						$data['current_step']['post_type'],
						array(
							'wffn_oty',
							'wffn_ty',
						),
						true
					) ) {
						WFFN_Core()->data->set(
							'current_step',
							array(
								'id'   => $data['current_step']['id'],
								'type' => ( $data['current_step']['post_type'] === 'wffn_oty' ) ? 'optin_ty' : 'wc_thankyou',
							)
						);
						WFFN_Core()->data->save();
					}

					/**
					 * Making sure its unset from the array of tracking events
					 */
					unset( $args[ $key ] );
				}
			}

			return $args;
		}

		/*
		* Destroyed funnel session in case order created by funnel checkout and
		* funnel not have thankyou step and user land on native thankyou page
		* @param $order_id
		*
		* @return void
		*/
		public function maybe_destroyed_funnel_session( $order_id ) {
			if ( isset( $_GET['wfty_source'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
				return;
			}

			$order = wc_get_order( $order_id );
			if ( $order instanceof WC_Order ) {
				if ( ! empty( $order->get_meta( '_wfacp_post_id' ) ) ) {

					$funnel_id = get_post_meta( absint( $order->get_meta( '_wfacp_post_id' ) ), '_bwf_in_funnel', true );
					if ( empty( $funnel_id ) ) {
						return;
					}
					$funnel = new WFFN_Funnel( $funnel_id );
					if ( ! $funnel instanceof WFFN_Funnel ) {
						return;
					}
					WFFN_Core()->logger->log( "Funnel id: #{ $funnel->id} Closing Funnel on native thankyou page Order #{$order_id}", 'wffn', true );
					do_action( 'wffn_ty_funnel_ended_event', $funnel, $order_id );
					WFFN_Core()->data->destroy_session();
				}
			}
		}

		public function register_routes() {
			register_rest_route(
				'wffn',
				'/' . 'front/',
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'handle_api_request' ),
					'permission_callback' => '__return_true',
				)
			);
		}

		public function handle_api_request( WP_REST_Request $request ) {
			$params = $request->get_params();

			$resp = array(
				'status'       => true,
				'funnel_setup' => false,
				'track_views'  => false,
				'ecom_event'   => false,
			);

			try {
				if ( empty( $params['action'] ) ) {
					return rest_ensure_response( $resp );
				}
				$action = sanitize_text_field( $params['action'] );
				switch ( $action ) {
					case 'wffn_maybe_setup_funnel':
						$resp = $this->maybe_record_data_with_funnel_setup( $params );
						break;

					case 'wffn_frontend_analytics':
						$analytics_result    = WFFN_Core()->public->send_frontend_analytics( wp_json_encode( $params['data'] ) );
						$resp['track_views'] = $analytics_result['track_views'] ?? false;
						$resp['ecom_event']  = $analytics_result['ecom_event'] ?? false;
						break;

					case 'wffn_tracking_events':
						WFFN_Core()->public->send_tracking_events( $params );
						$resp['ecom_event'] = true;
						break;
				}
			} catch ( Exception | Error $e ) {
				WFFN_Core()->logger->log( 'Error in send data : ' . __FUNCTION__ . $e->getMessage(), 'wffn', true );

			}

			return rest_ensure_response( $resp );
		}

		/**
		 * @param $args
		 * @param $track_data
		 *
		 * @return false[]
		 */
		public function maybe_record_frontend_analytics( $args, $track_data ) {
			$response = array(
				'track_views' => false,
				'ecom_event'  => false,
			);

			if ( empty( $args ) || ! is_array( $args ) || empty( $track_data ) ) {
				return $response;
			}

			if ( isset( $args['is_preview'] ) && true === wffn_string_to_bool( $args['is_preview'] ) ) {
				return $response;
			}

			if ( ! isset( $args['hash'] ) || empty( $args['current_step']['id'] ) ) {
				return $response;
			}

			return WFFN_Core()->public->send_frontend_analytics( wp_json_encode( $track_data, true ) );
		}

		/**
		 * Try to setup funnel and record analytics and fire ecom events in single call
		 *
		 * @param $data
		 *
		 * @return void
		 */
		public function maybe_record_data_with_funnel_setup( $args ) {
			$resp   = array(
				'status'       => true,
				'funnel_setup' => false,
				'track_views'  => false,
				'ecom_event'   => false,
			);
			$result = WFFN_Core()->public->maybe_setup_funnel( $args['data'] );
			if ( is_array( $result ) && ! empty( $result['success'] ) ) {
				if ( ! empty( $args['data']['hash'] ) && ! empty( $result['hash'] ) && $args['data']['hash'] === $result['hash'] ) {
					$resp['funnel_setup'] = false;
				} else {
					$resp['funnel_setup'] = true;
				}
				$resp = array_merge( $result, $resp );
			}

			if ( is_array( $resp ) && ! empty( $args['data']['track_data'] ) ) {
				$tracking_result = $this->maybe_record_frontend_analytics( $resp, $args['data']['track_data'] );
				if ( ! empty( $tracking_result ) ) {
					$resp['track_views'] = $tracking_result['track_views'] ?? false;
					$resp['ecom_event']  = $tracking_result['ecom_event'] ?? false;
				}
			}

			return $resp;
		}
	}

	if ( class_exists( 'WFFN_Core' ) ) {
		WFFN_Core::register( 'public', 'WFFN_Public' );
	}
}