<?php

/**
 * The public-facing functionality of the plugin.
 *
 * Defines the plugin name, version, and hooks to
 * enqueue the admin-specific stylesheet and JavaScript.
 *
 * @package    CartBounty Pro - Save and recover abandoned carts for WooCommerce
 * @subpackage CartBounty Pro - Save and recover abandoned carts for WooCommerce/public
 * @author     Streamline.lv
 */
class CartBounty_Pro_Public{
	
	/**
	 * The ID of this plugin.
	 *
	 * @since    1.0
	 * @access   private
	 * @var      string    $plugin_name    The ID of this plugin.
	 */
	private $plugin_name;

	/**
	 * The version of this plugin.
	 *
	 * @since    1.0
	 * @access   private
	 * @var      string    $version    The current version of this plugin.
	 */
	private $version;

	/**
	 * The admin handler that manages the plugin's settings, options, and backend functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Admin    $admin    Provides methods to control and extend the plugin's admin area.
	 */
	protected $admin = null;

	/**
	 * The automation handler that manages the plugin's abandoned cart reminder automation functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Automation    $automation    Provides methods to control and extend the plugin's automations.
	 */
	protected $automation = null;

	/**
	 * Get the admin handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Admin
	 */
	protected function admin(){
		
		if( $this->admin === null ){
			$this->admin = new CartBounty_Pro_Admin( CARTBOUNTY_PRO_PLUGIN_NAME_SLUG, CARTBOUNTY_PRO_VERSION_NUMBER );
		}

		return $this->admin;
	}

	/**
	 * Get the automation handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Automation
	 */
	protected function automation(){
		
		if( $this->automation === null ){
			$this->automation = new CartBounty_Pro_Automation();
		}

		return $this->automation;
	}

	/**
	 * Initialize the class and set its properties.
	 *
	 * @since    1.0
	 * @param    string    $plugin_name       The name of the plugin.
	 * @param    string    $version    The version of this plugin.
	 */
	public function __construct( $plugin_name, $version ) {
		$this->plugin_name = $plugin_name;
		$this->version = $version;
	}

	/**
	 * Register the stylesheets for the public area.
	 *
	 * @since    4.0
	 */
	public function enqueue_styles(){
		$admin = $this->admin();
		$automation = $this->automation();

		if( $this->tool_enabled( 'exit_intent' ) || $this->tool_enabled( 'early_capture' ) || $admin->international_phone_enabled() || $automation->automation_enabled( 'push_notification' ) ){ //If Exit Intent or Early capture enabled
			wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/cartbounty-pro-public.css', array(), $this->version, 'all' );
			wp_style_add_data( $this->plugin_name, 'rtl', 'replace' );
		}
	}

	/**
	 * Register the javascripts for the public area.
	 *
	 * @since    4.0
	 */
	public function enqueue_scripts(){

		if( !class_exists( 'WooCommerce' ) ) return; //If WooCommerce does not exist

		$admin = $this->admin();
		$automation = $this->automation();
		$recaptcha_enabled = false;
		$recaptcha_site_key = false;
		$user_logged_in = false;
		$language = $admin->detect_languege();
		$admin_ajax = admin_url( 'admin-ajax.php' );
		$email_validation = apply_filters( 'cartbounty_pro_email_validation', '^[^\s@]+@[^\s@]+\.[^\s@]{2,}$');
		$phone_validation = apply_filters( 'cartbounty_pro_phone_validation', '^[+0-9\s]\s?\d[0-9\s-.]{6,30}$');

		if( !$admin->check_license() ) return; //Exit if license key not valid

		if( is_user_logged_in() ){ //Logged in users do not have to go through reCAPTCHA validation
			$user_logged_in = true;

		}else{
			$recaptcha = $admin->get_recaptcha();

			if( $recaptcha['enabled'] ){
				$recaptcha_enabled = $recaptcha['enabled'];
				$recaptcha_site_key = $recaptcha['site_key'];
				wp_enqueue_script( $this->plugin_name . '-recaptchav3', 'https://www.google.com/recaptcha/api.js?render=' . $recaptcha_site_key, false, $this->version, false ); //Add reCAPTCHA v3 script on checkout form
			}
		}

		if( $admin->international_phone_enabled() ){ //If international phone number input enabled
			$data_ip = array(
				'preferredCountries'    => $admin->get_international_phone_preferred_countries(),
				'initialCountry' 		=> $admin->get_default_country(),
				'separateDialCode' 		=> false,
				'utilsScript'           => plugin_dir_url( __FILE__ ) . 'js/utils.js',
			);

			wp_enqueue_script( $this->plugin_name . '-international-phone-input', plugin_dir_url( __FILE__ ) . 'js/cartbounty-pro-public-phone-input.js', array( 'jquery' ), $this->version, true );
			wp_localize_script( $this->plugin_name . '-international-phone-input', 'cartbounty_ip', $data_ip ); //Sending variable over to JS file
		}

		if( $this->tool_enabled( 'exit_intent' ) ){ //If Exit Intent Enabled
			$ei_settings = $admin->get_settings( 'exit_intent' );
			$hours = esc_html(apply_filters( 'cartbounty_pro_exit_intent_reset_hours', 1));
			$mobile_exit_intent_enabled = $ei_settings['mobile_status'];

			if( $ei_settings['test_mode'] ){ //If test mode is enabled
				$hours = 0;
				$mobile_exit_intent_enabled = true;
			}

			$data_ei = array(
				'hours' 						=> $hours,
				'product_count' 				=> $this->get_cart_product_count(),
				'mobile_exit_intent_enabled'	=> $mobile_exit_intent_enabled,
			);
			wp_enqueue_script( $this->plugin_name . '-exit-intent', plugin_dir_url( __FILE__ ) . 'js/cartbounty-pro-public-exit-intent.js', array( 'jquery' ), $this->version, false );
			wp_localize_script( $this->plugin_name . '-exit-intent', 'cartbounty_ei', $data_ei ); //Sending variable over to JS file
		}
		
		if( $this->tool_enabled( 'early_capture' ) ){ //If Early capture is enabled
			$ec_settings = $admin->get_settings( 'early_capture' );
			$cart_recoverable = $this->cart_recoverable();
			if( !$cart_recoverable || $ec_settings['test_mode'] ){ //Continue only if cart has not been already saved (it is not recoverable) or if test mode enabled
				$hours = esc_html(apply_filters( 'cartbounty_pro_early_capture_reset_hours', 1));
				$mandatory_input = false;
				if( $ec_settings['test_mode'] ){ //If test mode is enabled
					$hours = 0;
				}
				if( $ec_settings['mandatory'] ){ //If mandatory input enabled
					$mandatory_input = true;
				}
				$style = esc_attr( $ec_settings['style'] );

				$data_ec = array(
				    'hours' => $hours,
				    'mandatory_input' 		=> $mandatory_input,
				    'style' 				=> $style,
				);
				if($style !=2){ //In case we must display Early capture as a popup, we do not require tooltipster
					wp_enqueue_script( $this->plugin_name . '-tooltipster', plugin_dir_url( __FILE__ ) . 'js/tooltipster.bundle.min.js', array( 'jquery' ), $this->version, false );
				}

				wp_enqueue_script( $this->plugin_name . '-early-capture', plugin_dir_url( __FILE__ ) . 'js/cartbounty-pro-public-early-capture.js', array( 'jquery' ), $this->version, false );
				wp_localize_script( $this->plugin_name . '-early-capture', 'cartbounty_ec', $data_ec ); //Sending variable over to JS file
			}
		}

		if( $this->tool_enabled( 'tab_notification', $ignore_logged_users = true ) ){ //If Tab notification is enabled
			$tn_settings = $admin->get_settings( 'tab_notification' );
			$message = $admin->get_tab_notification_message();
			$check_cart = false;
			$interval = $admin->get_tools_defaults( 'interval', 'tab_notification' );

			if( $tn_settings['check_cart'] ){ //If test mode is enabled
				$check_cart = true;
			}

			if( $tn_settings['interval'] ){
				$interval = $tn_settings['interval'];
			}

			//Prepare Favicon
			$image_id = $tn_settings['favicon_image'];
			$image_url = $this->get_plugin_url() . '/public/assets/tab-notification-favicon.png';
			
			if( $image_id ){
				$image = wp_get_attachment_image_src( $image_id, 'full' );
				if( is_array( $image ) ){
					$image_url = $image[0];
				}
			}

			$data_tn = array(
				'product_count' 		=> $this->get_cart_product_count(),
				'message' 				=> $message['value'],
				'check_cart' 			=> $check_cart,
				'interval' 				=> apply_filters( 'cartbounty_pro_tab_notification_interval', $interval ),
				'favicon_enabled' 		=> $tn_settings['favicon'],
				'favicon' 				=> apply_filters( 'cartbounty_pro_tab_notification_favicon', $image_url ),
				'favicon_relationship' 	=> apply_filters( 'cartbounty_pro_tab_notification_favicon_relationship', 'icon, shortcut icon' ),
			);

			wp_enqueue_script( $this->plugin_name . '-tab-notification', plugin_dir_url( __FILE__ ) . 'js/cartbounty-pro-tab-notification.js', array( 'jquery' ), $this->version, true );
			wp_localize_script( $this->plugin_name . '-tab-notification', 'cartbounty_tn', $data_tn );
		}

		if( $automation->automation_enabled( 'push_notification' ) ){
			$push_notification = new CartBounty_Pro_Push_Notification();

			if( $push_notification->display_permission_request() ){ //Continue in case test mode enabled and user not Admin
				$backdrop_status = true;
				$permission_style = $push_notification->get_settings( 'permission_style' );
				$vapid_public_key = $push_notification->get_vapid_keys();
				$hours = esc_html( apply_filters( 'cartbounty_pro_push_notification_soft_ask_reset_hours', 48 ) );

				if( $push_notification->get_settings( 'test_mode' ) ){ //If test mode is enabled
					$hours = 0;
				}

				if( $permission_style == 3 ){
					$backdrop_status = false;
				}

				$data_pn = array(
					'hours'				=> $hours,
					'product_count' 	=> $this->get_cart_product_count(),
					'js_location' 		=> plugin_dir_url( __FILE__ ) . 'js/',
					'scope' 			=> apply_filters( 'cartbounty_pro_push_notification_scope', '/' . CARTBOUNTY_PRO_PLUGIN_NAME_SLUG . '/push' ),
					'soft_ask_enabled' 	=> $push_notification->soft_ask_enabled(),
					'backdrop' 			=> apply_filters( 'cartbounty_pro_push_notification_soft_ask_backdrop', $backdrop_status ),
					'nonce'				=> wp_create_nonce( 'update_notification_subscription' ),
					'vapid_public_key'	=> $vapid_public_key['publicKey'],
				);

				wp_enqueue_script( $this->plugin_name . '-push-notification', plugin_dir_url( __FILE__ ) . 'js/cartbounty-pro-push-notification.js', array( 'jquery' ), $this->version, true );
				wp_localize_script( $this->plugin_name . '-push-notification', 'cartbounty_pn', $data_pn );
			}
		}

		//General CartBounty javascript functions
		$data_co = array(
			'save_custom_fields' 		=> apply_filters( 'cartbounty_pro_save_custom_fields', true ),
			'checkout_fields' 			=> $this->get_checkout_fields(),
			'custom_email_selectors' 	=> $this->get_custom_email_selectors(),
			'custom_phone_selectors' 	=> $this->get_custom_phone_selectors(),
			'custom_button_selectors' 	=> $this->get_custom_add_to_cart_button_selectors(),
			'consent_field' 			=> $admin->get_consent_field_data( 'field_name' ),
			'email_validation' 			=> $email_validation,
			'phone_validation' 			=> $phone_validation,
			'is_user_logged_in' 		=> $user_logged_in,
		    'recaptcha_enabled' 		=> $recaptcha_enabled,
		    'recaptcha_site_key' 		=> $recaptcha_site_key,
		    'language' 					=> $language,
		    'nonce' 					=> wp_create_nonce( 'user_data' ),
		    'ajaxurl' 					=> $admin_ajax
		);

		wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/cartbounty-pro-public.js', array( 'jquery' ), $this->version, false );
		wp_localize_script( $this->plugin_name, 'cartbounty_co', $data_co );
	}

	/**
	 * Method that takes care of saving and updating carts
	 *
	 * @since    8.1
	 */
	function save_cart(){
		$admin = $this->admin();
		$anonymous = true;
		$user_ip = $this->get_user_ip();

		if( !$admin->check_license() ) return; //Exit if license key not valid

		if ( isset( $_GET['cartbounty-pro'] ) || isset( $_GET['cb'] ) ) return; //Do not create or update cart in case we have a user who returns from abandoned cart reminder message

		if( !WC()->cart ) return; //Exit if WooCommerce cart has not been initialized

		if( $this->is_bot() ) return; //Checking against bots

		if( !$this->check_recaptcha() ) return; //Checking against bots using Google reCaptcha

		if( WC()->session->get( 'cartbounty_pro_ignore_cart' ) ) return; //Exit and do not save cart if the cart is created for coupon code validation prior to sending it out

		if( $this->cart_recoverable() ){ //If cart is recoverable
			$anonymous = false;
			$this->update_logged_customer_id(); //If current user had an abandoned cart before - restore session ID (in case of user switching)
		}

		if( $this->exclude_anonymous_cart() && $anonymous ) return; //If Anonymous carts are disabled and current cart is anonymous cart and current country should be excluded - do not save it

		if( $this->exclude_ip_address( $user_ip ) ){ //Check if a cart should be excluded via its IP address

			$admin->log( 'info', sprintf( 'Not saving abandoned cart: User IP address %s excluded.', $user_ip ) );
			return;
		}

		if( $this->tool_enabled( 'early_capture' ) ){ //If Early capture enabled

			if( $admin->get_settings( 'early_capture', 'mandatory' ) ){ //If mandatory input enabled
				
				if( $anonymous ){ //In case of anonymous cart

					if( apply_filters( 'cartbounty_pro_early_capture_mandatory_input_hard_validation', true ) ){ //If hard validation is enabled - to prevent from saving carts that are added via link ?add-to-cart=[product_id]
						return; //Prevent from saving carts that are not recoverable
					}
				}
			}
		}

		$cart = $this->read_cart();
		$cart_saved = $this->cart_saved( $cart, $user_ip );
		$user_data = $this->get_user_data();

		if( $cart_saved ){ //If cart has already been saved
			$result = $this->update_cart( $cart, $user_data, $anonymous, $user_ip );

		}else{
			$result = $this->create_new_cart( $cart, $user_data, $anonymous, $user_ip );
		}

		$user_excluded = $this->exclude_recoverable_cart( $cart, $user_data );

		if( isset( $_POST["action"] ) ){ //In case we are saving cart data via Ajax coming from CartBounty tools - try and return the result
			if( $_POST["action"] == 'cartbounty_pro_save' ){
				if( $result ){
					
					$coupon_code_result = array();
					
					if( !$user_excluded ){ //Provide coupon code for users who are not excluded from CartBounty
						$coupon_code_result = $admin->try_provide_coupon_code_for_tool( $cart['session_id'], $_POST );
					}
					wp_send_json_success( $coupon_code_result );

				}else{
					wp_send_json_error();
				}
			}
		}
	}

	/**
	 * Method creates a new cart
	 *
	 * @since    8.1
	 * @return   boolean
	 * @param    array      $cart     		Cart contents
	 * @param    array      $user_data      User's data
	 * @param    boolean    $anonymous    	If the cart is anonymous or not
	 * @param    string     $user_ip    	User's IP address
	 */
	function create_new_cart( $cart = array(), $user_data = array(), $anonymous = false, $user_ip = '' ){
		global $wpdb;
		$admin = $this->admin();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;

		//In case if the cart has no items in it, we must delete the cart
		if( empty( $cart['cart_contents'] ) ){
			$admin->clear_cart_data();
			return;
		}

		if( $anonymous ){ //If dealing with anonymous cart
			$wpdb->query(
				$wpdb->prepare(
					"INSERT INTO $cart_table
					( location, cart_contents, cart_hash, cart_meta, cart_total, currency, time, session_id, ip_address )
					VALUES ( %s, %s, %s, %s, %0.2f, %s, %s, %s, %s )",
					array(
						'location'		=> sanitize_text_field( maybe_serialize( $user_data['location'] ) ),
						'cart_contents'	=> maybe_serialize( $cart['cart_contents'] ),
						'cart_hash'		=> sanitize_text_field( $cart['cart_hash'] ),
						'cart_meta'		=> maybe_serialize( $cart['cart_meta'] ),
						'total'			=> sanitize_text_field( $cart['cart_total'] ),
						'currency'		=> sanitize_text_field( $cart['cart_currency'] ),
						'time'			=> sanitize_text_field( $cart['current_time'] ),
						'session_id'	=> sanitize_text_field( $cart['session_id'] ),
						'ip_address'	=> sanitize_text_field( $user_ip )
					)
				)
			);
			$this->increase_anonymous_cart_count();

		}else{ //If dealing with a logged in user
			$other_fields = NULL;
			$cart_source = $this->get_cart_source();
			$source = $cart_source['source'];
			$email_consent = false;
			$phone_consent = false;

			if( empty( $source ) ){ //If source not coming from Tools
				$source = 'NULL';
			}

			if( !empty( $user_data['other_fields'] ) ){
				$other_fields = sanitize_text_field( maybe_serialize( $user_data['other_fields'] ) );
			}

			$wpdb->query(
				$wpdb->prepare(
					"INSERT INTO $cart_table
					( name, surname, email, phone, email_consent, phone_consent, location, cart_contents, cart_hash, cart_meta, cart_total, currency, time, session_id, language, other_fields, ip_address, saved_via )
					VALUES ( %s, %s, %s, %s, %d, %d, %s, %s, %s, %s, %0.2f, %s, %s, %s, %s, %s, %s, %s )",
					array(
						'name'			=> sanitize_text_field( $user_data['name'] ),
						'surname'		=> sanitize_text_field( $user_data['surname'] ),
						'email'			=> sanitize_email( $user_data['email'] ),
						'phone'			=> filter_var( $user_data['phone'], FILTER_SANITIZE_NUMBER_INT),
						'email_consent'	=> sanitize_text_field( $email_consent ),
						'phone_consent'	=> sanitize_text_field( $phone_consent ),
						'location'		=> maybe_serialize( $user_data['location'] ),
						'cart_contents'	=> maybe_serialize( $cart['cart_contents'] ),
						'cart_hash'		=> sanitize_text_field( $cart['cart_hash'] ),
						'cart_meta'		=> maybe_serialize( $cart['cart_meta'] ),
						'total'			=> sanitize_text_field( $cart['cart_total'] ),
						'currency'		=> sanitize_text_field( $cart['cart_currency'] ),
						'time'			=> sanitize_text_field( $cart['current_time'] ),
						'session_id'	=> sanitize_text_field( $cart['session_id'] ),
						'language'		=> sanitize_text_field( $user_data['language'] ),
						'other_fields'	=> $other_fields,
						'ip_address'	=> sanitize_text_field( $user_ip ),
						'saved_via'		=> $source
					)
				)
			);
			$this->increase_recoverable_cart_count();
		}
		$this->set_cartbounty_session( $cart['session_id'] );
		
		return true;
	}

	/**
	 * Method updates a cart
	 *
	 * @since    8.1
	 * @return   boolean
	 * @param    array      $cart     		Cart contents
	 * @param    array      $user_data      User's data
	 * @param    boolean    $anonymous    	If the cart is anonymous or not
	 * @param    string     $user_ip    	User's IP address
	 */
	function update_cart( $cart = array(), $user_data = array(), $anonymous = false, $user_ip = '' ){
		
		//In case if the cart has no items in it, we must delete the cart
		if( empty( $cart['cart_contents'] ) ){
			$admin = $this->admin();
			$admin->clear_cart_data();
			return;
		}

		if( $anonymous ){ //In case of anonymous cart
			$this->update_cart_data( $cart, $user_ip );

		}else{
			$mailchimp = new CartBounty_Pro_Mailchimp();
			$mailchimp->delete_updated_cart( $cart ); //Sending a Delete request of the current cart over to MailChimp
			$save_user_data = false;

			if( isset( $_POST["action"] ) ){

				if( $_POST["action"] == 'cartbounty_pro_save' ){
					$save_user_data = true;
				}
			}

			if( is_user_logged_in() || $save_user_data ){
				$this->update_cart_and_user_data( $cart, $user_data, $user_ip );

			}else{
				$this->update_cart_data( $cart, $user_ip );
			}
		}
		
		$this->set_cartbounty_session( $cart['session_id'] );

		return true;
	}

	/**
	 * Method updates only cart related data excluding customer details
	 *
	 * @since    8.1
	 * @param    Array    	$cart    		Cart contents including cart session ID
	 * @param    string     $user_ip    	User's IP address
	 */
	function update_cart_data( $cart, $user_ip = '' ){
		global $wpdb;
		$admin = $this->admin();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;

		$updated_rows = $wpdb->query(
			$wpdb->prepare(
				"UPDATE $cart_table
				SET cart_contents = %s,
				cart_hash = %s,
				cart_meta = %s,
				cart_total = %0.2f,
				currency = %s,
				time = %s,
				ip_address = %s
				WHERE session_id = %s AND
				(type = %d OR type = %d OR type = %d)",
				maybe_serialize( $cart['cart_contents'] ),
				sanitize_text_field( $cart['cart_hash'] ),
				maybe_serialize( $cart['cart_meta'] ),
				sanitize_text_field( $cart['cart_total'] ),
				sanitize_text_field( $cart['cart_currency'] ),
				sanitize_text_field( $cart['current_time'] ),
				sanitize_text_field( $user_ip ),
				$cart['session_id'],
				$admin->get_cart_type('abandoned'),
				$admin->get_cart_type('recovered_pending'),
				$admin->get_cart_type('excluded')
			)
		);

		$this->delete_duplicate_carts( $cart['session_id'], $updated_rows );
	}

	/**
	 * Method updates both customer data and contents of the cart
	 *
	 * @since    8.1
	 * @param    Array    	$cart    		Cart contents including cart session ID
	 * @param    Array    	$user_data    	User's data
	 * @param    string     $user_ip    	User's IP address
	 */
	function update_cart_and_user_data( $cart, $user_data, $user_ip = '' ){
		global $wpdb;
		$admin = $this->admin();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$other_fields = NULL;
		$cart_source = $this->get_cart_source();
		$source = $cart_source['source'];
		$email_consent = $user_data['email_consent'];
		$phone_consent = $user_data['phone_consent'];

		if( empty( $source ) ){ //If source not coming from Tools
			$source = 'NULL';
		}

		if( !empty( $user_data['other_fields'] ) ){
			$other_fields = sanitize_text_field( maybe_serialize( $user_data['other_fields'] ) );
		}

		$updated_rows = $wpdb->query(
			$wpdb->prepare(
				"UPDATE $cart_table
				SET name = %s,
				surname = %s,
				email = %s,
				phone = %s,
				email_consent = %d,
				phone_consent = %d,
				location = %s,
				cart_contents = %s,
				cart_hash = %s,
				cart_meta = %s,
				cart_total = %0.2f,
				currency = %s,
				time = %s,
				language = %s,
				other_fields = %s,
				ip_address = %s,
				saved_via = $source,
				sms_excluded = 0
				WHERE session_id = %s AND
				(type = %d OR type = %d OR type = %d)",
				sanitize_text_field( $user_data['name'] ),
				sanitize_text_field( $user_data['surname'] ),
				sanitize_email( $user_data['email'] ),
				filter_var( $user_data['phone'], FILTER_SANITIZE_NUMBER_INT),
				sanitize_text_field( $email_consent ),
				sanitize_text_field( $phone_consent ),
				sanitize_text_field( maybe_serialize( $user_data['location'] ) ),
				maybe_serialize( $cart['cart_contents'] ),
				sanitize_text_field( $cart['cart_hash'] ),
				maybe_serialize( $cart['cart_meta'] ),
				sanitize_text_field( $cart['cart_total'] ),
				sanitize_text_field( $cart['cart_currency'] ),
				sanitize_text_field( $cart['current_time'] ),
				sanitize_text_field( $user_data['language'] ),
				$other_fields,
				sanitize_text_field( $user_ip ),
				$cart['session_id'],
				$admin->get_cart_type( 'abandoned' ),
				$admin->get_cart_type( 'recovered_pending' ),
				$admin->get_cart_type( 'excluded' )
			)
		);

		$this->delete_duplicate_carts( $cart['session_id'], $updated_rows );
		$this->increase_recoverable_cart_count();
		$this->update_woocommerce_database_session( (object)$user_data ); //This added here in order to restore email and phone in the checkout form in case it was left using CartBounty Tools

		//Saving default coupons if customer has applied any
		if( isset( WC()->cart ) ){
			$applied_coupons = WC()->cart->get_applied_coupons();
			
			if( !empty( $applied_coupons ) ){ //If customer has applied any coupons, save them
				$coupons = new CartBounty_Pro_Coupons();
				$coupons->update_cart_coupon( $code = $applied_coupons, $recovery = 'manual', $step_nr = false, $cart['session_id'] );
			}
		}
	}

	/**
	 * Method shows if the current user is identifiable and the cart can be recovered later
	 *
	 * @since    8.1
	 * @return   Boolean
	 */
	function cart_recoverable(){
		$recoverable = false;
		
		if( is_user_logged_in() || isset( $_POST["action"] ) || $this->cart_identifiable() ){
			$recoverable = true;
		}

		if( isset( $_POST["source"] ) ){

			if( $_POST["source"] == 'cartbounty_pro_anonymous_bot_test' ){
				$recoverable = false;
			}
		}

		return $recoverable;
	}

	/**
	 * Method returns True in case the current user has got a cart that contains a phone or email
	 *
	 * @since    5.0
	 * @return   Boolean
	 */
	function cart_identifiable(){
		global $wpdb;
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$identifiable = false;
		$admin = $this->admin();
		$where_sentence = $admin->get_where_sentence('recoverable');
		$cart = $this->read_cart();

		//Checking if we have this abandoned cart in our database already
		$result = $wpdb->get_var($wpdb->prepare(
			"SELECT id
			FROM $cart_table
			WHERE session_id = %s
			$where_sentence",
			$cart['session_id']
		));

		if($result){
			$identifiable = true;
		}

		return $identifiable;
	}

	/**
	 * Method returns available user's data
	 *
	 * @since    5.0
	 * @return   Array
	 */
	function get_user_data(){
		$admin = $this->admin();
		$user_data = array(
			'name'			=> '',
			'surname'		=> '',
			'email'			=> '',
			'phone'			=> '',
			'email_consent'	=> '',
			'phone_consent'	=> '',
			'location'		=> '',
			'other_fields'	=> '',
			'language'		=> '',
		);
		$location = array(
			'country' 	=> '',
			'city' 		=> '',
			'postcode' 	=> ''
		);
		$other_fields = array();
		$email_consent_field_name = $admin->get_consent_field_name( 'email' );
		$phone_consent_field_name = $admin->get_consent_field_name( 'phone' );
		$phone_email_consent_field_name = $admin->get_consent_field_name( 'phone-and-email' );

		if( is_user_logged_in() && !isset( $_POST["action"] ) ){ //If user has signed in and the request is not triggered by checkout fields or Exit Intent
			$current_user = wp_get_current_user();

			if( $current_user->billing_first_name ){
				$user_data['name'] = sanitize_text_field( $current_user->billing_first_name );

			}else{
				$user_data['name'] = sanitize_text_field( $current_user->user_firstname );
			}

			if( $current_user->billing_last_name ){
				$user_data['surname'] = sanitize_text_field( $current_user->billing_last_name );
			
			}else{
				$user_data['surname'] = sanitize_text_field( $current_user->user_lastname );
			}

			if( $current_user->billing_email ){
				$user_data['email'] = sanitize_email( $current_user->billing_email );
			
			}else{
				$user_data['email'] = sanitize_email( $current_user->user_email );
			}

			if( $current_user->billing_phone ){
				$user_data['phone'] = sanitize_text_field( $current_user->billing_phone );
			}

			if( $current_user->billing_country ){
				$location['country'] = sanitize_text_field( $current_user->country );
			}

			if( $current_user->billing_city ){
				$location['city'] = sanitize_text_field( $current_user->billing_city );
			}

			if( $current_user->billing_postcode ){
				$location['postcode'] = sanitize_text_field( $current_user->billing_postcode );
			}

			//Try to check if registered user has consent data saved
			$customer = new WC_Customer( get_current_user_id() );

			if( $customer->get_meta( $email_consent_field_name ) ){
				$user_data['email_consent'] = 1;
			
			}elseif( $customer->get_meta( $phone_consent_field_name ) ){
				$user_data['phone_consent'] = 1;

			}elseif( $customer->get_meta( $phone_email_consent_field_name ) ){
				$user_data['email_consent'] = 1;
				$user_data['phone_consent'] = 1;
			}

			$user_data['language'] = get_locale();

		}else{

			if( check_ajax_referer( 'user_data', 'nonce', false ) ){ //If security check passed or user has logged in

				if( isset( $_POST['customer'] ) ){
					$customer = $_POST['customer'];

					//In case of data coming from block checkout and user has only provided Shipping details - change shipping fields to billing fields
					if( !preg_grep( '/^billing-/', array_keys( $customer ) ) && preg_grep( '/^shipping-/', array_keys( $customer ) ) ){
						
						foreach( $customer as $key => $value ){
							
							if( strpos( $key, 'shipping-' ) === 0 ){
								$new_key = str_replace( 'shipping-', 'billing-', $key );
								$customer[$new_key] = $value;
								unset( $customer[$key] );
							}
						}

						$other_fields['converted-shipping-to-billing'] = true;
					}

					foreach( $customer as $key => $value ){

						if( $key == 'billing-first_name' || $key == 'billing_first_name' ){
							$user_data['name'] = sanitize_text_field( $value );

						}elseif( $key == 'billing-last_name' || $key == 'billing_last_name' ){
							$user_data['surname'] = sanitize_text_field( $value );
						
						}elseif( $key == 'email' || $key == 'billing_email' ){
							$user_data['email'] = sanitize_email( $value );

						}elseif( $key == 'billing-phone' || $key == 'billing_phone' || $key == 'phone' ){
							$user_data['phone'] = sanitize_text_field( $value );

						}elseif( $key == $email_consent_field_name ){
							$user_data['email_consent'] = sanitize_text_field( $value );

						}elseif( $key == $phone_consent_field_name ){
							$user_data['phone_consent'] = sanitize_text_field( $value );

						}elseif( $key == $phone_email_consent_field_name ){
							$user_data['email_consent'] = sanitize_text_field( $value );
							$user_data['phone_consent'] = sanitize_text_field( $value );

						}elseif( $key == 'billing-country' || $key == 'billing_country' ){
							$location['country'] = sanitize_text_field( $value );

						}elseif( $key == 'billing-city' || $key == 'billing_city' ){
							$location['city'] = sanitize_text_field( $value );

						}elseif( $key == 'billing-postcode' || $key == 'billing_postcode' ){
							$location['postcode'] = sanitize_text_field( $value );

						}else{
							$other_fields[$key] = sanitize_text_field( $value );
						}
					}

					if( isset( $_POST['language'] ) ){
						$user_data['language'] = sanitize_text_field( $_POST['language'] );
					}
				}
			}
		}

		if( empty( $location['country'] ) ){ //Try to locate country in case unknown
			$country = WC_Geolocation::geolocate_ip();
			$location['country'] = $country['country'];
		}

		$user_data['location'] = $location;
		$user_data['other_fields'] = $other_fields;
		return $user_data;
	}

	/**
	 * Method checks if recaptcha has been enabled and determines if the cart should be saved or not
	 * Returns true if all is ok and false in case there was an issue
	 *
	 * @since    8.1
	 * @return   boolean
	 */
	function check_recaptcha(){
		$result = true;
		$admin = $this->admin();
		$recaptcha = $admin->get_recaptcha();

		//Checking if we have reCAPTCHA token coming from the form
		if(isset($_POST['cartbounty_pro_recaptcha_token'])){
			//Send request to the reCAPTCHA validation service
			$recaptcha_params = array(
				'secret' => $recaptcha['secret_key'],
				'response' => sanitize_text_field($_POST["cartbounty_pro_recaptcha_token"])
			);
			$query = esc_url_raw(add_query_arg($recaptcha_params, RECAPTCHA_SITE_API));
			$response = wp_remote_get($query, array('timeout' => 10, 'sslverify' => false));
			$response_body = json_decode(wp_remote_retrieve_body($response));

			//If this request was not a valid reCAPTCHA token for this site - not saving abandoned cart and cleaning any previously saved cart for this user
			if(!$response_body->success){
				$admin->log( 'info', 'Not saving abandoned cart: Request did not include a valid reCAPTCHA token for this site.' );
				$admin->clear_cart_data();
				$result = false;
			}

			//Setting the default minimum required reCAPTCHA score to be 0.2
			$recaptcha_minimum_allowed_score = esc_html(apply_filters( 'cartbounty_pro_recaptcha_minimum_allowed_score', 0.2));

			if(empty($response_body->action)){ //In case no data received from response about action, we make sure to set it so we would not get an error notice
				$response_body->action = '';
			}

			//If this request does not return our action defined in token or if the score is lower than what is set - not saving abandoned cart
			if(($response_body->action != 'cartbounty_pro_abandoned_cart_checkout' && $response_body->action != 'cartbounty_pro_abandoned_cart_tool') || $response_body->score < $recaptcha_minimum_allowed_score ){
				if(empty($response_body->score)){
					$response_body->score = 'null';
				}
				$admin->clear_cart_data();
				$admin->log( 'notice', sprintf( "Not saving abandoned cart: Minimum allowed score is %s, but visitor's score was %s.", esc_html( $recaptcha_minimum_allowed_score ), esc_html( $response_body->score ) ) );
				$result = false;
			}else{
				$admin->log( 'info', sprintf( "Saving abandoned cart: Successful validation by reCAPTCHA. Visitor's score was %s.", esc_html( $response_body->score ) ) );
			}

		}else{ //In case no token received, meaning that function is not triggered by any of the forms CartBounty is listening to
			if($this->cart_recoverable()){
				if($recaptcha['enabled'] && !is_user_logged_in()){
					//In case if the token was not received, output an error message
					$admin->log( 'info', 'Saving cart, but reCAPTCHA token was not received.' );
					$result = true;
				}
			}
		}

		return $result;
	}

	/**
	 * Check if visitor is a known bot or pretends to be one
	 * Checking only "Add to cart" actions in case of guest users
	 *
	 * @since    9.9.2
	 * @return   boolean
	 */
	function is_bot(){

		if( is_user_logged_in() ) return;

		if( current_filter() != 'woocommerce_add_to_cart' ) return;

		$admin = $this->admin();
		$bot = false;
		$bot_string = 'human';

		//Additional security step - if hidden input value is missing or is false
		if( !apply_filters( 'cartbounty_pro_disable_input_bot_test', false ) ){
		
			if( ( !isset( $_POST['cartbounty_pro_bot_test'] ) || sanitize_text_field( $_POST['cartbounty_pro_bot_test'] ) != '1' ) ){ 
				$bot = true;
				$admin->log( 'info', 'Not saving abandoned cart: Bot detected. Cart creation does not include a valid hidden input value' );
				return $bot;
			}
		}

		//To increase speed, we are storing information about bots in a session variable
		if( isset( WC()->session ) ){ //If WooCommerce session is set

			$session_is_bot = WC()->session->get( 'cartbounty_pro_is_bot' );

			if( isset( $session_is_bot ) ){ //If session variable exists
				
				if( $session_is_bot == 'bot' ){ //If user is a bot
					$bot = true;
				}
				
				return $bot;
			}
		}

		$user_agent = $_SERVER['HTTP_USER_AGENT'];
		$bot_list = apply_filters(
			'cartbounty_pro_bot_list',
			array(
				'A6-Indexer',
				'Aboundex',
				'AboutUsBot',
				'ADmantX Platform',
				'AdsBot-Google',
				'AdvBot',
				'AhrefsBot',
				'Apache-HttpClient',
				'ApacheBench',
				'AppEngine-Google',
				'Apple-PubSub',
				'Applebot',
				'ArchitextSpider',
				'archive.org_bot',
				'Baiduspider',
				'Bingbot',
				'bitlybot',
				'BLEXBot',
				'BUbiNG',
				'Butterfly',
				'CC Metadata Scaper',
				'CCBot',
				'Cliqzbot',
				'coccoc',
				'Covario IDS',
				'Curious George',
				'Curl',
				'DataparkSearch',
				'Daumoa',
				'DiamondBot',
				'Diffbot',
				'DigExt; ',
				'Discordbot',
				'Domain Re-Animator Bot',
				'domaincrawler',
				'DotBot',
				'DuckDuckBot',
				'EasouSpider',
				'evrinid',
				'Exabot',
				'Ezooms',
				'facebookexternalhit',
				'Facebot',
				'Feedfetcher-Google',
				'Feedly',
				'findlinks',
				'Fish search',
				'Genieo Web filter',
				'Gigabot',
				'Googlebot',
				'GrapeshotCrawler',
				'Hatena',
				'heritrix',
				'HTTP Client',
				'httpunit',
				'ia_archiver',
				'ICC-Crawler',
				'ichiro',
				'InAGist',
				'InfoPath.2',
				'jaunty',
				'Java VM',
				'KomodiaBot',
				'kraken',
				'larbin',
				'lightDeckReports Bot',
				'Linguee Bot',
				'linkdexbot',
				'LinkWalker',
				'lipperhey',
				'LongURL API',
				'ltx71',
				'lwp-trivial',
				'lycos',
				'Mail.RU_Bot',
				'Mediapartners-Google',
				'MegaIndex.ru',
				'MegaIndex',
				'memorybot',
				'MetaURI',
				'Microsoft URL Control',
				'Miniflux',
				'MJ12bot',
				'mlbot',
				'MojeekBot',
				'mon.itor.us',
				'msnbot-media ',
				'nagios-plugins',
				'nerdybot',
				'NetcraftSurveyAgent',
				'netEstate NE Crawler',
				'newspaper',
				'NING',
				'Nutch',
				'oBot',
				'Ocarinabot',
				'okhttp',
				'OpenHoseBot',
				'OrangeBot',
				'panscient.com',
				'PaperLiBot',
				'Pingdom.com bot',
				'Pinterest',
				'Plukkie',
				'PostRank',
				'proximic',
				'PycURL',
				'Python-httplib2',
				'Python-urllib',
				'Qseero',
				'Qwantify',
				'RealPlayer%20Downloader',
				'robot',
				'rogerbot',
				'SafeDNSBot',
				'Scrapy',
				'semanticdiscovery',
				'SemrushBot',
				'Seobility',
				'SeznamBot',
				'ShowyouBot',
				'SISTRIX Crawler',
				'SiteExplorer',
				'SkypeUriPreview',
				'Sogou',
				'spbot',
				'Speedy Spider',
				'Spinn3r',
				'SputnikBot',
				'Sqworm',
				'Storebot-Google',
				'SurdotlyBot',
				'Swiftbot',
				'TencentTraveler',
				'test_crawler',
				'Traackr',
				'trendictionbot',
				'TurnitinBot',
				'TweetmemeBot',
				'TwengaBot',
				'Twitterbot',
				'UniversalFeedParser',
				'UnwindFetchor',
				'UptimeRobot',
				'VoilaBot',
				'WBSearchBot',
				'webcrawler',
				'WebThumbnail',
				'Wget',
				'WhatsApp',
				'woriobot',
				'xovibot',
				'Y!J-BRW',
				'yacybot',
				'Yahoo Link Preview',
				'Yahoo! Slurp',
				'YandexBot',
				'YisouSpider',
				'YYSpider',
				'ZmEu',
				'ZumBot'
			)
		);

		foreach( $bot_list as $item ) {
			
			if( stripos( strtolower( $user_agent ), strtolower( $item ) ) !== false ){ //If user agent is a bot
				$bot = true;
				$bot_string = 'bot';
				$admin->log( 'info', sprintf( 'Not saving abandoned cart: Bot detected. User Agent string - %s', $user_agent ) );
			}
		}

		WC()->session->set( 'cartbounty_pro_is_bot', $bot_string ); //Saving string inside session since false or null values are not saved inside session

		return $bot;
	}

	/**
	 * Method sets CartBounty session id value
	 *
	 * @since    5.0
	 */
	function set_cartbounty_session($session_id){
		if(!WC()->session->get('cartbounty_pro_session_id')){ //In case browser session is not set, we make sure it gets set
			WC()->session->set('cartbounty_pro_session_id', $session_id); //Storing session_id in WooCommerce session
		}
	}

	/**
	 * Method checks if current shopping cart has been saved in the past 2 hours (by default) basing upon session ID
	 * or if the IP address and cart content hash in the last 10 minutes have been saved
	 * Cooldown period introduced to prevent creating new abandoned carts during the same session after user has already placed a new order or
	 * in case the session_id value gets changed quickly for each page reload and we would end up with multiple identical abandoned carts for the same user
	 * New cart for the same user in the same session will be created once the cooldown period has ended
	 *
	 * @since    4.0
	 * @return   boolean
	 * @param    array         $cart    			Cart data
	 * @param    boolean       $user_ip    			Visitor's IP address
	 * @param    boolean       $ignore_cooldown    	If cooldown should be igonerd or not. Used when resetting abandoned cart data for registered users
	 */
	function cart_saved( $cart, $user_ip = '', $ignore_cooldown = false ){
		$saved = false;

		if( $cart['session_id'] !== NULL ){
			global $wpdb;
			$admin = $this->admin();
			$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
			$time = $admin->get_time_intervals();
			$cooldown_period = 0;
			$ip_cooldown = apply_filters( 'cartbounty_pro_cart_ip_cooldown_period', $time['ten_minutes'] );
			$cart_hash = md5( maybe_serialize( $cart['cart_contents'] ) );

			if( !$ignore_cooldown ){
				$cooldown_period = apply_filters( 'cartbounty_pro_cart_cooldown_period', $time['two_hours'] );
			}

			//First part of the select that looks for the same session ID in the given time period
			$sql = "SELECT id FROM $cart_table WHERE ( session_id = %s AND time > %s )";
			$params = [ $cart['session_id'], $cooldown_period ];

			//Second part of the select if we know the IP address which looks for the same IP address and cart contents in the given time period
			if( !empty( $user_ip ) ){
				$sql .= " OR ( ip_address = %s AND cart_hash = %s AND time > %s )";
				$params = array_merge( $params, [ $user_ip, $cart_hash, $ip_cooldown ] );
			}

			$sql .= " LIMIT 1";
			$result = $wpdb->get_var( $wpdb->prepare( $sql, $params ) );

			if( $result ){
				$saved = true;
			}
		}

		return $saved;
	}

	/**
	 * Method updates abandoned cart session from unknown session customer_id to known one in case if the user logs in
	 *
	 * @since    6.5
	 */
	function update_logged_customer_id(){
		if(is_user_logged_in()){ //If a user is logged in
			if(!WC()->session){ //If session does not exist, exit function
				return;
			}
			$customer_id = WC()->session->get_customer_id();

			if( WC()->session->get('cartbounty_pro_session_id') !== NULL && WC()->session->get('cartbounty_pro_session_id') !== $customer_id){ //If session is set and it is different from the one that currently is assigned to the customer
				global $wpdb;
				$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;

				//Updating session ID to match the one of a logged in user
				$wpdb->update(
					$cart_table,
					array('session_id' => $customer_id),
					array('session_id' => WC()->session->get('cartbounty_pro_session_id')),
					array('%s'),
					array('%s')
				);

				WC()->session->set('cartbounty_pro_session_id', $customer_id);
				$cart = $this->read_cart();
				$user_data = $this->get_user_data();
				$user_ip = $this->get_user_ip();
				$this->update_cart_and_user_data( $cart, $user_data, $user_ip ); //Updating logged in user cart data so we do not have anything that was entered in the checkout form prior the user signed in

			}else{
				return;
			}

		}else{
			return;
		}
	}

	/**
	 * Method deletes duplicate abandoned carts from the database
	 *
	 * @since    6.5
	 * @param    $session_id			Session ID
	 * @param    $duplicate_count		Number of duplicate carts
	 */
	private function delete_duplicate_carts( $session_id, $duplicate_count ){
		global $wpdb;
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;

		if($duplicate_count){ //If we have updated at least one row
			if($duplicate_count > 1){ //Checking if we have updated more than a single row to know if there were duplicates
				$admin = $this->admin();
				$where_sentence = $admin->get_where_sentence('anonymous');
				//First delete all duplicate anonymous carts
				$deleted_duplicate_anonymous_carts = $wpdb->query(
					$wpdb->prepare(
						"DELETE FROM $cart_table
						WHERE session_id = %s
						$where_sentence",
						$session_id
					)
				);

				$limit = $duplicate_count - $deleted_duplicate_anonymous_carts - 1;
				if($limit < 1){
					$limit = 0;
				}

				$deleted_duplicate = $wpdb->query( //Leaving one cart remaining that can be identified
					$wpdb->prepare(
						"DELETE FROM $cart_table
						WHERE session_id = %s AND
						type != %d
						ORDER BY id DESC
						LIMIT %d",
						$session_id,
						$admin->get_cart_type('recovered'),
						$limit
					)
				);
			}
		}
	}

	/**
	 * Method builds and returns an array of cart products and their quantities, car total value, currency, time, session id, mail status
	 *
	 * @since    4.0
	 * @return   Array
	 */
	function read_cart(){

		if( !WC()->cart ) return; //Exit if WooCommerce cart has not been initialized

		//Retrieving cart total value and currency
		$admin = $this->admin();
		$translatepress = new CartBounty_Pro_TranslatePress();
		$cart_total = WC()->cart->total;
		$cart_currency = $this->get_selected_currency();
		$current_time = current_time( 'mysql', true ); //Retrieving current time
		$session_id = WC()->session->get( 'cartbounty_pro_session_id' ); //Check if the session is already set

		if( empty( $session_id ) ){ //If session value does not exist - set one now
			$session_id = WC()->session->get_customer_id(); //Retrieving customer ID from WooCommerce sessions variable
		}

		if( WC()->session->get( 'cartbounty_pro_from_link' ) && WC()->session->get( 'cartbounty_pro_session_id' ) ){ //In case users returns from abandoned cart email link
			$session_id = WC()->session->get( 'cartbounty_pro_session_id' );
		}

		//Retrieving cart
		$products = WC()->cart->get_cart_contents();
		$product_array = array();
				
		foreach( $products as $key => $product ){
			$item = wc_get_product( $product['data']->get_id() );
			$product_title = strip_tags( $item->get_title() );
			$product_title = $translatepress->maybe_translate_trp( $product_title );
			$product_quantity = $product['quantity'];
			$product_variation_price = '';
			$product_tax = '';
			$variation_attributes = array();
			$product_variation_id = '';
			$product_attributes = '';

			if( isset( $product['line_total'] ) ){
				$product_variation_price = $product['line_total'];
			}

			if( isset( $product['line_tax'] ) ){ //If we have taxes, add them to the price
				$product_tax = $product['line_tax'];
			}

			//Handling product variations
			if( isset( $product['variation'] ) ){
				
				if( is_array( $product['variation'] ) ){
					foreach( $product['variation'] as $key => $variation ){
						$variation_attributes[$key] = $variation;
					}
				}
			}

			if( isset( $product['variation_id'] ) ){ //If user has chosen a variation
				$product_variation_id = $product['variation_id'];
				$product_attributes = $this->get_attribute_names( $variation_attributes, $product['product_id'] );
			}

			$product_data = array(
				'product_title' 				=> $product_title . $product_attributes,
				'quantity' 						=> $product_quantity,
				'product_id'					=> $product['product_id'],
				'product_variation_id' 			=> $product_variation_id,
				'product_variation_price' 		=> $product_variation_price,
				'product_variation_attributes' 	=> $variation_attributes,
				'product_tax' 					=> $product_tax
			);

			//Handle WooCommerce Product Bundles plugin data
			if( class_exists( 'WC_Bundles' ) ){ //If WooCommerce Product Bundles plugin active
				
				if( !wc_pb_is_bundled_cart_item( $product ) ){ //If cart item is not part of a bundle (looking to find only bundle parent item)
					
					if( isset( $product['stamp'] ) ){ //If bundle parent has data about bundled items
						$bundled_item_data = $product['stamp'];
						$product_data['bundle_data'] = $bundled_item_data;
						$bundle_price = 0;
						$bundle_tax = 0;
						$bundled_cart_items = wc_pb_get_bundled_cart_items( $product );

						//Looping through bundle sub-items to get price of the main bundle item
						foreach ( $bundled_cart_items as $key => $bundled_cart_item ) {

							if( isset( $bundled_cart_item['line_total'] ) ){
								$bundled_cart_item_price = $bundled_cart_item['line_total'];
							}

							if( isset( $bundled_cart_item['line_tax'] ) ){ //If we have taxes, add them to the price
								$bundled_cart_item_tax = $bundled_cart_item['line_tax'];
							}

							$bundle_price = $bundle_price + $bundled_cart_item_price;
							$bundle_tax = $bundle_tax + $bundled_cart_item_tax;
						}

						$product_data['product_variation_price'] = $product_variation_price + $bundle_price;
						$product_data['product_tax'] = $product_tax + $bundle_tax;
					}

				}else{ //Skip adding individual bundle sub-items to the cart
					continue;
				}
			}

			//Skip adding bundled sub-items.
			//Added for YITH Product Bundles because cart restoring creates incorrect pricing
			if( isset( $product['bundled_by'] ) ){ 
				continue;
			}

			$product_array[] = $product_data;
		}

		return $results_array = array(
			'cart_total' 	=> $cart_total,
			'cart_currency' => $cart_currency,
			'current_time' 	=> $current_time,
			'session_id' 	=> $session_id,
			'cart_contents' => $product_array,
			'cart_hash' 	=> md5( maybe_serialize( $product_array ) ),
			'cart_meta' 	=> WC()->cart->get_cart_contents()
		);
	}

	/**
	 * Returns currently selected currency
	 * First going through session variable to check if currency has been set here since some currency switcher plugins may store this here
	 * After that look inside cart data for currency information since some plugins store it there
	 * Supporting these plugins by default:
	 * - WooPayments: Integrated WooCommerce Payments (built-in currency switcher)				[wcpay_currency]
	 * - YayCurrency – WooCommerce Multi-Currency Switcher 										[yay_currency_code]
	 *
	 * @since    10.7
	 * @return   string
	 */
	function get_selected_currency(){
		$currency_keys = array(
			'wcpay_currency',
		);

		foreach( $currency_keys as $key ){

			if( is_user_logged_in() ){ //For signed-in customers
				$currency = get_user_meta( get_current_user_id(), $key, true );
			
			}else{
				$currency = WC()->session->get( $key );
			}

			if( $currency ){
				return $currency;
			}
		}

		if( did_action( 'wp_loaded' ) ){
			//In case of YayCurrency, we must look inside cart data for stored currency code
			foreach( WC()->cart->get_cart() as $cart_item ){
				
				if( isset( $cart_item['yay_currency_code'] ) ){
					return $cart_item['yay_currency_code'];
				}
			}
		}

		return get_woocommerce_currency();
	}

	/**
	 * Method returns product attributes
	 *
	 * @since    1.4.1
	 * @return   String
	 * @param    $product_variations    Product variations - array
	 */
	public function attribute_slug_to_title( $product_variations ){
		$attribute_array = array();
		
		if($product_variations){
			foreach($product_variations as $product_variation_key => $product_variation_name){
				$value = '';
				if ( taxonomy_exists( esc_attr( str_replace( 'attribute_', '', $product_variation_key )))){
					$term = get_term_by( 'slug', $product_variation_name, esc_attr( str_replace( 'attribute_', '', $product_variation_key )));
					if (!is_wp_error($term) && !empty($term->name)){
						$value = $term->name;
						if(!empty($value)){
							$attribute_array[] = $value;
						}
					}
				}else{
					$value = apply_filters( 'woocommerce_variation_option_name', $product_variation_name );
					if(!empty($value)){
						$attribute_array[] = $value;
					}
				}
			}
			
			//Generating attribute output			
			$total_variations = count($attribute_array);
			$increment = 0;
			$product_attribute = '';
			foreach($attribute_array as $attribute){
				if($increment === 0 && $increment != $total_variations - 1){ //If this is first variation and we have multiple variations
					$colon = ': ';
					$comma = ', ';
				}
				elseif($increment === 0 && $increment === $total_variations - 1){ //If we have only one variation
					$colon = ': ';
					$comma = false;
				}
				elseif($increment === $total_variations - 1) { //If this is the last variation
					$comma = '';
					$colon = false;
				}else{
					$comma = ', ';
					$colon = false;
				}
				$product_attribute .= $colon . $attribute . $comma;
				$increment++;
			}
			return $product_attribute;
		}
		else{
			return;
		}
	}

	/**
	 * Retrieve product attribute names as comma sepparated string
	 *
	 * @since    10.1.1
	 * @return   string
	 * @param    array    	$variation_attributes   Product variation attributes
	 * @param    integer    $product_id   			Product number
	 */
	public function get_attribute_names( $variation_attributes, $product_id = '' ){
		$translatepress = new CartBounty_Pro_TranslatePress();
		$attribute_names = '';
		$labels = array();
		
		if( is_array( $variation_attributes ) ){
			foreach( $variation_attributes as $attribute_name => $attribute_value ){

				$attribute_name = str_replace( 'attribute_', '', $attribute_name );
				$product_terms = wc_get_product_terms( $product_id, $attribute_name );

				if( is_array( $product_terms ) ){
					foreach( $product_terms as $key => $term ){

						if( $term->slug == $attribute_value ){
							$labels[] = $translatepress->maybe_translate_trp( $term->name );
							break;
						}
					}
				}
			}
		}

		if( !empty( $labels ) ){
			$attribute_names = ': ' . implode( ', ', $labels );
		}

		return $attribute_names;
	}
	
	/**
	 * Method restores previous Checkout form data for users who are not registered
	 *
	 * @since    3.0
	 */
	public function restore_classic_checkout_fields(){

		if( !is_checkout() ) return; //Exit if not on checkout page

		if( has_block( 'woocommerce/checkout' ) ) return; //Stop block checkout detected

		if( !apply_filters( 'cartbounty_pro_restore_classic_checkout', true ) ) return;

		$saved_cart = $this->get_saved_cart();

		if( !$saved_cart ) return;

		$admin = $this->admin();
		$get_consent_field_data = $admin->get_consent_field_data( 'field_name' );
		$other_fields = maybe_unserialize( $saved_cart->other_fields );
		$location_data = $admin->get_cart_location( $saved_cart->location );

		if( empty( $_POST['billing_first_name'] ) ){
			$_POST['billing_first_name'] = esc_html( $saved_cart->name );
		}

		if( empty( $_POST['billing_last_name'] ) ){
			$_POST['billing_last_name'] = esc_html( $saved_cart->surname );
		}

		if( empty( $_POST['billing_email'] ) ){
			$_POST['billing_email'] = esc_html( $saved_cart->email );
		}

		if( empty( $_POST['billing_phone'] ) ){
			$_POST['billing_phone'] = esc_html( $saved_cart->phone );
		}

		if( empty( $_POST[$get_consent_field_data] ) ){
			$_POST[$get_consent_field_data] = $admin->get_customers_consent( $saved_cart );
		}

		if( empty( $_POST['billing_country'] ) ){
			$_POST['billing_country'] = esc_html( $location_data['country'] );
		}

		if( empty( $_POST['billing_city'] ) ){
			$_POST['billing_city'] = esc_html( $location_data['city'] );
		}

		if( empty( $_POST['billing_postcode'] ) ){
			$_POST['billing_postcode'] = esc_html( $location_data['postcode'] );
		}

		if( is_array( $other_fields ) ){
			
			foreach( $other_fields as $key => $value ){
				
				if( empty( $_POST[$key] ) ){

					if( $key == 'ship-to-different-address-checkbox' ){
					
						if( $value ){
							add_filter( 'woocommerce_ship_to_different_address_checked', '__return_true' );
						}

					}elseif( $key == 'createaccount' ){

						if( $value ){
							add_filter( 'woocommerce_create_account_default_checked', '__return_true' );
						}
					}

					$_POST[$key] = esc_html( $value );
				}
			}
		}
	}
	
	/**
	 * Method restores WooCommerce block checkout input fields
	 * Since WooCommerce currently describes block checkout as being in development stage, 
	 * it does not have a lot of hooks and filters available as with Classic checkout
	 * Therefore currently block checkout fields are restored by directly updating session in database
	 * Not restoring checkout fields while in administration mode or if the user has signed in
	 * Using Transient system to limit how many times requesting database
	 * Not using block checkout or is_checkout detection since then we are unable to detect these early enough during the use of "shutdown" action
	 *
	 * @since    10.4
	 */
	public function restore_block_checkout_fields(){

		if( !apply_filters( 'cartbounty_pro_restore_block_checkout', true ) ) return;

		if( !function_exists( 'is_admin' ) || !function_exists( 'is_user_logged_in' ) ) return;

		if( is_admin() || is_user_logged_in() ) return;

		if( WC()->session ){
			$admin = $this->admin();
			$counter = $admin->get_cartbounty_transient( 'counter', $unique = true );

			if ( !$counter ) {
				$counter = 0;
			}

			$counter++;
			$admin->set_cartbounty_transient( 'counter', $counter, 30, $unique = true ); //Expires in 30 seconds

			if( $counter < 5 ){
				$this->update_woocommerce_database_session();
			}
		}
	}

	/**
	 * Method adds customer data to WooCommerce sessions database table
	 * At this stage if a user enters his contact details in the Early capture checkout and immediately opens checkout page,
	 * the email or phone number do not get restored on the first page load. Need to resolve it once blok checkout
	 * gets additional features
	 * If customer left abandoned cart in the Classic checkout and returns to the cart in a Block checkout - the cart will not be restored
	  * If customer left abandoned cart in the Block checkout and returns to the cart in a Classic checkout - the cart will be restored
	 *
	 * @since    10.4
	 */
	public function update_woocommerce_database_session( $saved_cart = false ){
		global $wpdb;
		$admin = $this->admin();
		$session_table_name = $wpdb->prefix . 'woocommerce_sessions';
		$is_block_checkout = false;
		$use_same_address_for_billing = false;
		$new_session_data = array();

	    if( WC()->session ){

	    	if( !$saved_cart ){
				$saved_cart = $this->get_saved_cart();
			}

			if( !$saved_cart ) return; //Stop if abandoned cart missing

			if( empty( $saved_cart->email ) && empty( $saved_cart->phone ) && empty( $saved_cart->pn_subscription ) ) return; //Stop if contact details are missing

			$necessary_fields = array(
				'name' 			=> '',
				'surname' 		=> '',
				'email' 		=> '',
				'phone' 		=> '',
				'location' 		=> '',
				'other_fields' 	=> '',
			);
			$saved_cart = array_intersect_key( (array)$saved_cart, $necessary_fields ); //Leaving just keys that are needed for session
			$session_id = WC()->session->get_customer_id();
			$session_customer = WC()->session->get('customer');
			$other_fields = maybe_unserialize( $saved_cart['other_fields'] );

			if( !is_array( $other_fields ) ){
				$other_fields = array();
			}

			$saved_cart['location'] = $admin->get_cart_location( $saved_cart['location'] );
			$saved_cart['other_fields'] = $other_fields;
			
			if( empty( $other_fields ) ){
				$is_block_checkout = true;

			}elseif( is_array( $other_fields ) ){
				$is_block_checkout = preg_grep( '/^(shipping|billing)-/', array_keys( $other_fields ) );
			}

			if( empty( $is_block_checkout ) ) return; //Stop if block checkout is not detected

			$session_data = $wpdb->get_var( $wpdb->prepare( "SELECT session_value FROM $session_table_name WHERE session_key = %s", $session_id ) );

			if( !$session_data ) return; //Stop if session data missing

			$session_data = maybe_unserialize( $session_data );

			if( isset( $session_data['customer'] ) ){
				$customer = maybe_unserialize( $session_data['customer'] );

				if( ( isset( $customer['email'] ) && !empty( $customer['email'] ) ) || ( isset( $customer['phone'] ) && !empty( $customer['phone'] ) ) ) return; //Stop in case session already has email or phone number stored				

				//In case of data coming from block checkout - replace billing details with shipping unless both shipping and billing fields are present
				if( isset( $other_fields['converted-shipping-to-billing'] ) ){ //If during abandoned cart saving process we converted shipping fields to billing - must use shipping fields as billing fields
					$use_same_address_for_billing = true;
				}

				//Mapping data
				foreach( $saved_cart as $key => $value ){
					
					if( $key == 'name' ){

						if( $use_same_address_for_billing ){
							$new_session_data['shipping_first_' . $key] = $value;

						}else{
							$new_session_data['first_' . $key] = $value;
						}
					
					}elseif( $key == 'surname' ){

						if( $use_same_address_for_billing ){
							$new_session_data['shipping_last_name'] = $value;

						}else{
							$new_session_data['last_name'] = $value;
						}

					}elseif( $key == 'phone' && $use_same_address_for_billing ){
						$new_session_data['shipping_' . $key] = $value;

					}elseif( $key == 'location' ){
						$location_data = $value;
						
						foreach( $location_data as $key => $value){

							if( $use_same_address_for_billing && in_array( $key, ['country', 'city', 'postcode'] ) ){
								$new_session_data['shipping_' . $key] = $value;

							}else{
								$new_session_data[$key] = $value;
							}
						}

					}elseif( $key == 'other_fields' ){
						$other_fields_data = $value;
						
						foreach( $other_fields_data as $key => $value ){

							if( strpos( $key, 'billing-' ) === 0 && $use_same_address_for_billing ){
								$new_key = str_replace( 'billing-', 'shipping_', $key );
								$new_session_data[$new_key] = $value;

							}elseif( strpos( $key, 'billing-' ) === 0 ){

								if( $key == 'billing-company' || $key == 'billing-phone' || $key == 'billing-address_1' || $key == 'billing-address_2' || 'billing-state' ){
									$new_key = str_replace( 'billing-', '', $key );

								}else{
									$new_key = str_replace( 'billing-', 'billing_', $key );
								}
								
								$new_session_data[$new_key] = $value;
							
							}elseif( strpos( $key, 'shipping-' ) === 0 ){
								$new_key = str_replace( 'shipping-', 'shipping_', $key );
								$new_session_data[$new_key] = $value;
							}
						}

					}else{
						$new_session_data[$key] = $value;
					}
				}

				$customer = array_merge( $customer, $new_session_data );
				$session_data['customer'] = maybe_serialize( $customer );
				$new_session_data = maybe_serialize( $session_data );

				$result = $wpdb->update(
					$session_table_name,
					[ 'session_value' => $new_session_data ],
					[ 'session_key' => $session_id ],
					[ '%s' ],
					[ '%s' ]
				);
			}
		}
	}

	/**
	 * Retrieving current user's saved abandoned cart
	 *
	 * @since    10.1.1
	 * @return   array
	 */
	function get_saved_cart(){
		global $wpdb;
		$admin = $this->admin();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$cart = $this->read_cart();

		if( !isset( $cart['session_id'] ) ) return;

		$this->update_logged_customer_id(); //If current user had an abandoned cart before - restore session ID (in case of user switching)

		//Retrieve a single, latest edited abandoned cart with current customer ID
		$saved_cart = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT *
				FROM $cart_table
				WHERE session_id = %s
				ORDER BY time DESC",
				$cart['session_id']
			)
		);

		return $saved_cart;
	}

	/**
	 * Method saves and updates total count of captured recoverable abandoned carts
	 *
	 * @since    8.1
	 */
	function increase_recoverable_cart_count(){

		if( !WC()->session ) return; //If session does not exist, exit

		if( WC()->session->get( 'cartbounty_pro_recoverable_count_increased' ) ) return; //Exit in case we already have run this once

		WC()->session->set( 'cartbounty_pro_recoverable_count_increased', true );

		if( WC()->session->get( 'cartbounty_pro_from_link' ) ) return; //Exit if user returned from link - it means we have increased this before

		$admin = $this->admin();
		$misc_settings = $admin->get_settings( 'misc_settings' );
		$misc_settings['recoverable_carts'] = $misc_settings['recoverable_carts'] + 1;
		update_option( 'cartbounty_pro_misc_settings', $misc_settings );

		if( WC()->session->get( 'cartbounty_pro_anonymous_count_increased' ) ){ //In case we previously increased anonymous cart count, we must now reduce it as it has been turned to recoverable
			$this->decrease_anonymous_cart_count( 1 );
		}
	}

	/**
	 * Method saves and updates total count of captured anonymous carts
	 *
	 * @since    8.1
	 */
	function increase_anonymous_cart_count(){
		
		if( !WC()->session ) return; //If session does not exist, exit
		
		if( WC()->session->get( 'cartbounty_pro_anonymous_count_increased' ) ) return; //Exit in case we already have run this once

		$admin = $this->admin();
		$misc_settings = $admin->get_settings( 'misc_settings' );
		$misc_settings['anonymous_carts'] = $misc_settings['anonymous_carts'] + 1;
		update_option( 'cartbounty_pro_misc_settings', $misc_settings );
		WC()->session->set( 'cartbounty_pro_anonymous_count_increased', 1 );
	}

	/**
	 * Method saves and updates total count of recovered carts
	 *
	 * @since    9.2.3
	 * @param    boolean    $order_status_changed            If function is triggered from changing order status or not
	 */
	function increase_recovered_cart_count( $order_status_changed ){
		
		if( ! $order_status_changed){ //If order status has not changed - check if session Exists
			if( !WC()->session ) return; //If session does not exist, exit function

			if( WC()->session->get( 'cartbounty_pro_recovered_count_increased' ) ) return; //Exit function in case we already have run this once

			WC()->session->set( 'cartbounty_pro_recovered_count_increased', 1 );
		}

		$admin = $this->admin();
		$misc_settings = $admin->get_settings( 'misc_settings' );
		$misc_settings['recovered_carts'] = $misc_settings['recovered_carts'] + 1;
		update_option( 'cartbounty_pro_misc_settings', $misc_settings );
	}

	/**
	 * Method decreases the total count of captured abandoned carts
	 *
	 * @since    8.1
	 * @param    integer 	$count    			Cart count 
	 * @param    integer 	$only_decrease    	If just recoverable cart decrease must be performed without increasing anonymous carts (used in case if the user excluded from saving abandoned cart)
	 */
	function decrease_recoverable_cart_count( $count = 1, $only_decrease = false ){
		if( !class_exists( 'WooCommerce' ) ) return; //If WooCommerce does not exist

		$admin = $this->admin();
		$misc_settings = $admin->get_settings( 'misc_settings' );
		$misc_settings['recoverable_carts'] = $misc_settings['recoverable_carts'] - $count;

		update_option( 'cartbounty_pro_misc_settings', $misc_settings ); //Decreasing the count by one abandoned cart

		if( !$only_decrease ){

			if( WC()->session ) {
				WC()->session->__unset( 'cartbounty_pro_recoverable_count_increased' );
				$this->increase_anonymous_cart_count(); //Since this is no longer a recoverable cart - must make sure we update anonymous cart count
			}
		}
	}

	/**
	 * Method decreases the total count of captured anonymous carts
	 *
	 * @since    8.1
	 * @param    $count    Cart number - integer 
	 */
	function decrease_anonymous_cart_count( $count ){
		if( !class_exists( 'WooCommerce' ) ) return; //If WooCommerce does not exist

		$admin = $this->admin();
		$misc_settings = $admin->get_settings( 'misc_settings' );
		$misc_settings['anonymous_carts'] = $misc_settings['anonymous_carts'] - $count;

		update_option( 'cartbounty_pro_misc_settings', $misc_settings );
		
		if( WC()->session ){
			WC()->session->__unset( 'cartbounty_pro_anonymous_count_increased' );
		}
	}

	/**
	 * Outputting email form if a user who is not logged in wants to leave with a full shopping cart
	 *
	 * @since    4.0
	 */
	function display_exit_intent_form(){
		if( !class_exists( 'WooCommerce' ) ) return; //If WooCommerce does not exist

		if( !$this->tool_enabled( 'exit_intent' ) || !WC()->cart ) return; //If Exit Intent disabled or WooCommerce cart does not exist

		$admin = $this->admin();
		$user_is_admin = $admin->user_is_admin();
		$output = $this->build_exit_intent_output( $user_is_admin ); //Creating the Exit Intent output
		echo $output;
		echo "<script>localStorage.setItem( 'cartbounty_pro_product_count', " . $this->get_cart_product_count() . " )</script>";
	}

	/**
	 * Adding Early capture request to the page
	 *
	 * @since    9.1
	 */
	function display_early_capture_form(){
		if (!class_exists('WooCommerce')) { //If WooCommerce does not exist
			return;
		}

		if(!$this->tool_enabled( 'early_capture' )){ //If Early capture disabled
			return;
		}

		$output = $this->build_early_capture_output(); //Creating the Early capture output
		if (isset( $_POST["cartbounty_pro_show_early_capture"])) { //When function triggered using Ajax Add to Cart
			return wp_send_json_success($output); //Sending Output to Javascript function
		}		
	}

	/**
	 * Checking if cart is empty and sending result to Ajax function
	 *
	 * @since    4.0
	 * @return   boolean
	 */
	function check_empty_cart(){
		if(!WC()->cart) return;

		if( WC()->cart->get_cart_contents_count() == 0 ){ //If the cart is empty
			return wp_send_json_success('true'); //Sending successful output to Javascript function

		}else{
			return wp_send_json_success('false');
		}
	}

	/**
	 * Building the Exit Intent output
	 *
	 * @since    4.0
	 * @return   string
	 * @param    $current_user_is_admin    If the current user has Admin rights or not - boolean
	 */
	function build_exit_intent_output( $current_user_is_admin ){
		global $wpdb;
		$admin = $this->admin();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$cart = $this->read_cart();
		$exit_intent_heading = $admin->get_exit_intent_heading();
		$exit_intent_content = $admin->get_exit_intent_content();
		$ei_settings = $admin->get_settings( 'exit_intent' );
		$heading = apply_filters( 'wpml_translate_single_string', $exit_intent_heading['value'], 'woo-save-abandoned-carts', $exit_intent_heading['name'] );
		$content = apply_filters( 'wpml_translate_single_string', $exit_intent_content['value'], 'woo-save-abandoned-carts', $exit_intent_content['name'] );
		$main_color = $ei_settings['main_color'];
		$inverse_color = $ei_settings['inverse_color'];
		$where_sentence = $admin->get_where_sentence('recoverable');
		$tools_consent = $admin->get_tools_consent();
		$tools_consent = apply_filters( 'wpml_translate_single_string', $tools_consent['value'], 'woo-save-abandoned-carts', $tools_consent['name'] );
		$consent_settings = $admin->get_consent_settings();
		$email_consent_enabled = $consent_settings['email'];
		$phone_consent_enabled = $consent_settings['phone'];
		$consent_enabled = false;

		if(!$main_color){
			$main_color = '#e3e3e3';
		}
		if(!$inverse_color){
			$inverse_color = $this->invert_color($main_color);
		}

		//Retrieve a single row with current customer ID
		$row = $wpdb->get_row($wpdb->prepare(
			"SELECT *
			FROM  $cart_table
			WHERE session_id = %s
			$where_sentence",
			$cart['session_id'])
		);

		if($row && !$current_user_is_admin){ //Exit if cart already saved and the current user is not admin
			return;
		}

		//Prepare Exit Intent image
		$image_id = $ei_settings['image'];
		$image_url = $this->get_plugin_url() . '/public/assets/abandoned-shopping-cart.gif';
		if($image_id){
			$image = wp_get_attachment_image_src( $image_id, 'full' );
			if(is_array($image)){
				$image_url = $image[0];
			}
		}

		$type = $ei_settings['field_type'];
		
		if( ( $email_consent_enabled && $phone_consent_enabled ) || ( $email_consent_enabled && $type == 'email' ) || ( $phone_consent_enabled && $type == 'phone' ) || ( ( $email_consent_enabled || $phone_consent_enabled ) && $type == 'phone-and-email' ) ){
			$consent_enabled = true;
		}

		$args = apply_filters(
			'cartbounty_pro_exit_intent_args',
			array(
				'image_url' 		=> $image_url,
				'heading' 			=> $heading,
				'content' 			=> $content,
				'type' 				=> $type,
				'main_color' 		=> $main_color,
				'inverse_color' 	=> $inverse_color,
				'consent_enabled' 	=> $consent_enabled,
				'tools_consent' 	=> $tools_consent,
				'title' 			=> esc_html__( 'You were not leaving your cart just like that, right?', 'woo-save-abandoned-carts' ),
				'alt' 				=> esc_html__( 'You were not leaving your cart just like that, right?', 'woo-save-abandoned-carts' ),
			)
		);

		return $this->get_template( 'cartbounty-pro-exit-intent.php', $args );
	}

	/**
	 * Building Early capture output
	 *
	 * @since    9.1
	 * @return   string
	 */
	function build_early_capture_output(){
		$admin = $this->admin();
		$ec_settings = $admin->get_settings( 'early_capture' );
		$type = $ec_settings['field_type'];
		$early_capture_heading = $admin->get_early_capture_heading();
		$heading = apply_filters( 'wpml_translate_single_string', $early_capture_heading['value'], 'woo-save-abandoned-carts', $early_capture_heading['name'] );
		$main_color = $ec_settings['main_color'];
		$inverse_color = $ec_settings['inverse_color'];
		$style = $ec_settings['style'];
		$tools_consent = $admin->get_tools_consent();
		$tools_consent = apply_filters( 'wpml_translate_single_string', $tools_consent['value'], 'woo-save-abandoned-carts', $tools_consent['name'] );
		$consent_settings = $admin->get_consent_settings();
		$email_consent_enabled = $consent_settings['email'];
		$phone_consent_enabled = $consent_settings['phone'];
		$consent_enabled = false;

		if(!$main_color && $style != 2){
			$main_color = '#ffffff';
		}elseif(!$main_color){
			$main_color = '#e3e3e3';
		}
		
		if(!$main_color){
			$main_color = '#e3e3e3';
		}
		if(!$inverse_color){
			$inverse_color = $this->invert_color($main_color);
		}

		
		$style = $this->early_capture_style();

		if( ( $email_consent_enabled && $phone_consent_enabled ) || ( $email_consent_enabled && $type == 'email' ) || ( $phone_consent_enabled && $type == 'phone' ) ){
			$consent_enabled = true;
		}

		$args = apply_filters(
			'cartbounty_pro_early_capture_args',
			array(
				'heading' => $heading,
				'type' => $type,
				'style' => $style,
				'main_color' => $main_color,
				'inverse_color' => $inverse_color,
				'consent_enabled' => $consent_enabled,
				'tools_consent' => $tools_consent
			)
		);

		//In case the function is called via Ajax Add to Cart button
		//We must add wp_die() or otherwise the function does not return anything
		if (isset( $_POST["cartbounty_pro_show_early_capture"])){ 
			$output = $this->get_template( 'cartbounty-pro-early-capture.php', $args );
			die();
		}else{
			return $this->get_template( 'cartbounty-pro-early-capture.php', $args );
		}
	}

	/**
	 * Checking if a tool is enabled or not
	 *
	 * @since    9.1
	 * @return   boolean
	 * @param    string    $tool 					Tool that is being checked
	 * @param    boolean   $ignore_logged_users 	Weather logged in users should be ignored
	 */
	function tool_enabled( $tool, $ignore_logged_users = false ){
		$admin = $this->admin();
		if(!$admin->check_license()){//Exit if license key not valid
			return false;
		}else{
			switch ( $tool ) {
				case 'exit_intent':
					$ei_settings = $admin->get_settings( 'exit_intent' );
					$tool_enabled = $ei_settings['status'];
					$test_mode_on = $ei_settings['test_mode'];
					break;

				case 'early_capture':
					$ec_settings = $admin->get_settings( 'early_capture' );
					$tool_enabled = $ec_settings['status'];
					$test_mode_on = $ec_settings['test_mode'];
					break;

				case 'tab_notification':
					$tn_settings = $admin->get_settings( 'tab_notification' );
					$tool_enabled = $tn_settings['status'];
					$test_mode_on = $tn_settings['test_mode'];
					break;
			}

			$user_is_admin = $admin->user_is_admin();

			if( $test_mode_on && $user_is_admin ){
				//Outputting tool for Testing purposes to Administrators
				return true;

			}elseif( ( $tool_enabled && !$test_mode_on && !is_user_logged_in() ) || ( $tool_enabled && !$test_mode_on && $ignore_logged_users ) ){
				//Outputting tool for all users who are not logged in
				return true;
				
			}else{
				//Tool is not enabled
				return false;
			}
		}
	}

	/**
	 * Getting inverse color from the given one
	 *
	 * @since    4.0
	 * @return   string
	 * @param    $color    Color code - string
	 */
	function invert_color( $color ){
	    $color = str_replace('#', '', $color);
	    if (strlen($color) != 6){ return '000000'; }
	    $rgb = '';
	    for ($x=0;$x<3;$x++){
	        $c = 255 - hexdec(substr($color,(2*$x),2));
	        $c = ($c < 0) ? 0 : dechex($c);
	        $rgb .= (strlen($c) < 2) ? '0'.$c : $c;
	    }
	    return '#'.$rgb;
	}

	/**
	 * Method returns a value that informs about which Exit Intent style is used
	 *
	 * @since    4.0
	 */
	function exit_intent_style(){
		$admin = $this->admin();
		$exit_intent_style_value = 'cartbounty-pro-ei-center'; //Setting default class
		$exit_intent_style = $admin->get_settings( 'exit_intent', 'style' );

		if( $exit_intent_style == 2 ){
			$exit_intent_style_value = 'cartbounty-pro-ei-left';

		}elseif( $exit_intent_style == 3 ){
			$exit_intent_style_value = 'cartbounty-pro-ei-fullscreen';
		}

		return $exit_intent_style_value;
	}

	/**
	 * Method returns a value that informs about which Early capture style is used
	 *
	 * @since    9.1
	 */
	function early_capture_style(){
		$admin = $this->admin();
		$early_capture_style_value = 'cartbounty-pro-ec-center-near-button'; //Setting default class
		$early_capture_style = $admin->get_settings( 'early_capture', 'style' );

		if( $early_capture_style == 2 ){
			$early_capture_style_value = 'cartbounty-pro-ec-center';
		}

		return $early_capture_style_value;
	}

	/**
	 * Method returns a value that informs about which Push notification permission style is used
	 *
	 * @since    9.10
	 */
	function notification_permission_style(){
		$push_notification = new CartBounty_Pro_Push_Notification();
		$notification_permission_type_value = 'cartbounty-pro-np-center'; //Setting default class
		$notification_permission_type = $push_notification->get_settings( 'permission_style' );

		if( $notification_permission_type == 2 ){
			$notification_permission_type_value = 'cartbounty-pro-np-left';

		}elseif( $notification_permission_type == 3 ){
			$notification_permission_type_value = 'cartbounty-pro-np-top-bar';
		}

		return $notification_permission_type_value;
	}

	/**
	 * Get the plugin url.
	 *
	 * @since    4.0
	 * @return   string
	 */
	public function get_plugin_url() {
		return plugins_url( '/', dirname(__FILE__) );
	}

	/**
	 * Locating template file.
	 * Method returns the path to the template
	 *
	 * Search Order:
	 * 1. /themes/theme/templates/emails/$template_name
	 * 2. /themes/theme/templates/$template_name
	 * 3. /themes/theme/$template_name
	 * 4. /plugins/woo-save-abandoned-carts-pro/templates/$template_name
	 *
	 * @since    4.0
	 * @return   string
	 * @param 	 string    $template_name - template to load.
	 * @param 	 string    $string $template_path - path to templates.
	 * @param    string    $default_path - default path to template files.
	 */
	function get_template_path( $template_name, $template_path = '', $default_path = '' ){
		$search_array = array();

		// Set variable to search folder of theme.
		if( !$template_path ){
			$template_path = 'templates/';
		}

		//Add paths to look for template files
		$search_array[] = $template_path . 'emails/' . $template_name; 
		$search_array[] = $template_path . $template_name;
		$search_array[] = $template_name;

		// Search template file in theme folder.
		$template = locate_template( $search_array );

		// Set default plugin templates path.
		if( !$default_path ){
			$default_path = plugin_dir_path( __FILE__ ) . '../templates/'; // Path to the template folder
		}

		// Get plugins template file.
		if( !$template ){
			$template = $default_path . $template_name;
		}

		return apply_filters( 'cartbounty_pro_get_template_path', $template, $template_name, $template_path, $default_path );
	}

	/**
	 * Get the template.
	 *
	 * @since    4.0
	 *
	 * @param    string    $template_name - template to load.
	 * @param    array     $args - args passed for the template file.
	 * @param    string    $string $template_path - path to templates.
	 * @param    string    $default_path - default path to template files.
	 */
	function get_template( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
		
		if ( is_array( $args ) && isset( $args ) ){
			extract( $args );
		}

		$template_file = $this->get_template_path($template_name, $template_path, $default_path);
		
		if ( !file_exists( $template_file ) ){ //Handling error output in case template file does not exist
			_doing_it_wrong( __FUNCTION__, sprintf( 'Template file %s does not exist.', esc_html( $template_file ) ), '9.1' );
			return;
		}

		include $template_file;
	}
	
	/**
	 * Check weather we must exclude anonymous cart from being saved. By default all countries are excluded if anonymous cart exclusion is enabled.
	 *
	 * @since    9.2
	 * @return 	 Boolean
	 */
	function exclude_anonymous_cart(){
		$admin = $this->admin();
		$exclude = false;
		$settings = $admin->get_settings( 'settings' );

		if( $settings['exclude_anonymous_carts'] ){ //If anonymous cart exclusion enabled
			$exclude = true;

			if( $settings['allowed_countries'] ){ //In case at least one country is selected in the exclusions list

				if( $this->is_country_allowed() ){ //In case the country is in the exception list of countries - we should allow to save this anonymous cart
					$exclude = false;
				}
			}
		}

		return $exclude;
	}

	/**
	 * Return whether we must exclude recoverable shopping cart or not
	 *
	 * @since    10.1
	 * @return 	 Boolean
	 * @param    array      $cart     		Cart contents
	 * @param    array      $user_data      User's data
	 */
	function exclude_recoverable_cart( $cart, $user_data ){
		$admin = $this->admin();
		$exclude = false;
		$contact = '';

		if( empty( $admin->get_settings( 'settings', 'excluded_emails_phones' ) ) ) return; //Stop in case no exclusions have been set

		if( WC()->session->get( 'cartbounty_pro_cart_ignored' ) ) {
			$exclude = true;

		}else{

			$user_data = (object)$user_data; //Changing array to object

			if( !empty( $user_data->email ) || !empty( $user_data->phone ) ){

				if( $admin->email_phone_excluded( $user_data, $recovery = false, $option = false, $step_nr = '', $coupon = false, $settings = true ) ){

					if( !empty( $cart['session_id'] ) ){ //Updating cart type
						$type = $admin->get_cart_type( 'excluded' );
						$updated_rows = $admin->update_cart_type( $cart['session_id'], $type );
						$this->decrease_recoverable_cart_count( $updated_rows, $only_decrease = true ); //Decreasing recoverable cart count by updated row count
						WC()->session->set( 'cartbounty_pro_cart_ignored', true ); //Setting a marker that current cart should be ignored from being saved

						if( !empty( $user_data->email ) ){
							$contact = $user_data->email;

						}elseif( !empty( $user_data->phone ) ){
							$contact = $user_data->phone;
						}

						$admin->log( 'info', sprintf( 'Not saving abandoned cart: Contact %s excluded.', $contact ) );
						$exclude = true;
					}
				}
			}
		}

		return $exclude;
	}

	/**
	 * Check weather we must exclude shopping carts coming from certain IP addresses
	 *
	 * @since    10.0
	 * @return 	 Boolean
	 * @param    string 	$user_ip    		User's IP address 
	 */
	function exclude_ip_address( $user_ip ){
		$exclude = false;
		$excluded_ip_addresses = apply_filters( 'cartbounty_pro_excluded_ip_addresses', array() );

		//Check IP exclusions
		if( !empty( $excluded_ip_addresses ) ){

			if( !empty( $user_ip ) ){

				if( is_array( $excluded_ip_addresses ) ){

					if( in_array( $user_ip, $excluded_ip_addresses ) ){ //If IP address is in the excluded IP array
						$exclude = true;
					}
				}
			}
		}

		return $exclude;
	}

	/**
	 * Check weather current user is from an allowed country or not
	 *
	 * @since    9.2
	 * @return 	 Boolean
	 */
	function is_country_allowed(){
		$admin = $this->admin();
		$allowed = false;
		$allowed_countries = $admin->get_settings( 'settings', 'allowed_countries' ); //Retrieving array of allowed countries
		$user_data = $this->get_user_data();
		if(isset($user_data['location'])){
			$location = $user_data['location'];
			if(isset($location['country'])){
				$country = $location['country'];
				if(in_array($country, $allowed_countries)){ //If country is in the list of allowed countries
					$allowed = true;
				}
			}
		}
		return $allowed;
	}

	/**
	 * Check what is the source of the saved abandoned cart and return it
	 * Source types NULL = checkout, 1 = Exit Intent, 2 = Early capture, 3 = Custom email or phone field
	 * Variable $first - holds information weather this is the first time we are running this function or not (necessary because we need to know if the consent has to be saved now or it has already been saved before)
	 *
	 * @since    9.2
	 * @return 	 Array
	 */
	function get_cart_source(){
		$source = '';
		$first = true;

		if(WC()->session){ //If session exists

			if(!WC()->session->get('cartbounty_pro_saved_via')){ //If we do not know how the cart was initially saved
				
				if(isset($_POST['source'])){ //Check if data coming from a source or not
					switch ( $_POST['source'] ) {
						case 'cartbounty_pro_exit_intent':
							$source = 1;
							break;

						case 'cartbounty_pro_early_capture':
							$source = 2;
							break;

						case 'cartbounty_pro_custom_field':
							$source = 3;
							break;
					}

					if(!empty($source)){
						WC()->session->set('cartbounty_pro_saved_via', $source);
					}
				}
				
			}else{ //In case we already know how the cart was initially saved - retrieve the information from session
				$source = WC()->session->get('cartbounty_pro_saved_via');
				$first = false;
			}
		}
		return array(
			'source' 	=> $source,
			'first'		=> $first
		);
	}

	/**
	 * Get product count isnide shopping cart
	 *
	 * @since    9.8
	 * @return   integer
	 */
	function get_cart_product_count(){
		$count = 0;

		if( WC()->cart ){
			$count = WC()->cart->get_cart_contents_count();
		}

		return $count;
	}

	/**
	 * Get a list of WooCommerce checkout fields that should be saved
	 *
	 * @since    10.4
	 * @return   string
	 */
	function get_checkout_fields(){
		$admin = $this->admin();
		$consent_field = $admin->get_consent_field_data( 'field_name' );
		$selectors = array(
			"email",
			"billing_email",
			"billing-country",
			"billing_country",
			"billing-first_name",
			"billing_first_name",
			"billing-last_name",
			"billing_last_name",
			"billing-company",
			"billing_company",
			"billing-address_1",
			"billing_address_1",
			"billing-address_2",
			"billing_address_2",
			"billing-city",
			"billing_city",
			"billing-state",
			"billing_state",
			"billing-postcode",
			"billing_postcode",
			"billing-phone",
			"billing_phone",
			"shipping-country",
			"shipping_country",
			"shipping-first_name",
			"shipping_first_name",
			"shipping-last_name",
			"shipping_last_name",
			"shipping-company",
			"shipping_company",
			"shipping-address_1",
			"shipping_address_1",
			"shipping-address_2",
			"shipping_address_2",
			"shipping-city",
			"shipping_city",
			"shipping-state",
			"shipping_state",
			"shipping-postcode",
			"shipping_postcode",
			"shipping-phone",
			"checkbox-control-1",
			"ship-to-different-address-checkbox",
			"checkbox-control-0",
			"createaccount",
			"checkbox-control-2",
			"order-notes textarea",
			"order_comments",
		);

		if( $consent_field ){
			$selectors[] = $consent_field;
		}

		return '#' . implode(', #', apply_filters( 'cartbounty_pro_checkout_fields', $selectors ) );
	}

	/**
	 * Get custom email field selectors (required for adding email to abandoned cart data input field data from 3rd party plugins)
	 * Ability to use a filter to edit this list
	 *
	 * Supporting these plugins by default:
	 * - CartBounty custom email field option									[cartbounty]
	 * - WooCommerce MyAccount login form										[woocommerce]
	 * - WPForms Lite by by WPForms												[wpforms]
	 * - Popup Builder by Looking Forward Software Incorporated					[sgpb]
	 * - Popup Maker by Popup Maker												[pum]
	 * - Ninja Forms by Saturday Drive											[ninja]
	 * - Contact Form 7 by Takayuki Miyoshi										[wpcf7]
	 * - Fluent Forms by Contact Form - WPManageNinja LLC						[fluentform]
	 * - Newsletter, SMTP, Email marketing and Subscribe forms by Brevo			[sib]
	 * - MailPoet by MailPoet													[mailpoet]
	 * - Newsletter by Stefano Lissa & The Newsletter Team						[tnp]
	 * - OptinMonster by OptinMonster Popup Builder Team						[optinmonster]
	 * - OptiMonk: Popups, Personalization & A/B Testing by OptiMonk			[optimonk]
	 * - Poptin by Poptin														[poptin]
	 * - Gravity Forms by Gravity Forms											[gform]
	 * - Popup Anything by WP OnlineSupport, Essential Plugin					[paoc]
	 * - Popup Box by Popup Box Team											[ays]
	 * - Hustle by WPMU DEV														[hustle]
	 * - Popups for Divi by divimode.com										[popdivi]
	 * - Brave Conversion Engine by Brave										[brave]
	 * - Popup by Supsystic by supsystic.com									[ppspopup]
	 * - Login/Signup Popup by XootiX											[xoologin]
	 *
	 * @since    9.12
	 * @return   string
	 */
	function get_custom_email_selectors(){
		$selectors = apply_filters( 'cartbounty_pro_custom_email_selectors',
			array(
				'cartbounty' 	=> '.cartbounty-pro-custom-email-field',
				'woocommerce' 	=> '.login #username',
				'wpforms' 		=> '.wpforms-container input[type="email"]',
				'sgpb' 			=> '.sgpb-form input[type="email"]',
				'pum' 			=> '.pum-container input[type="email"]',
				'ninja'			=> '.nf-form-cont input[type="email"]',
				'wpcf7'			=> '.wpcf7 input[type="email"]',
				'fluentform'	=> '.fluentform input[type="email"]',
				'sib'			=> '.sib_signup_form input[type="email"]',
				'mailpoet'		=> '.mailpoet_form input[type="email"]',
				'tnp'			=> '.tnp input[type="email"]',
				'optinmonster'	=> '.om-element input[type="email"]',
				'optimonk'		=> '.om-holder input[type="email"]',
				'poptin'		=> '.poptin-popup input[type="email"]',
				'gform'			=> '.gform_wrapper input[type="email"]',
				'paoc'			=> '.paoc-popup input[type="email"]',
				'ays'			=> '.ays-pb-form input[type="email"]',
				'hustle'		=> '.hustle-form input[type="email"]',
				'popdivi'		=> '.et_pb_section input[type="email"]',
				'brave'			=> '.brave_form_form input[type="email"]',
				'ppspopup'		=> '.ppsPopupShell input[type="email"]',
				'xoologin'		=> '.xoo-el-container input[type="email"], .xoo-el-container input[name="xoo-el-username"]',
			)
		);

		return implode( ', ', $selectors );
	}

	/**
	 * Get custom phone field selectors (required for adding phone to abandoned cart data input field data from 3rd party plugins)
	 * Ability to use a filter to edit this list
	 *
	 * Supporting these plugins by default:
	 * - CartBounty custom email field option									[cartbounty]
	 * - WPForms Lite by by WPForms												[wpforms]
	 * - Popup Builder by Looking Forward Software Incorporated					[sgpb]
	 * - Ninja Forms by Saturday Drive											[ninja]
	 * - Contact Form 7 by Takayuki Miyoshi										[wpcf7]
	 * - Fluent Forms by Contact Form - WPManageNinja LLC						[fluentform]
	 * - OptinMonster by OptinMonster Popup Builder Team						[optinmonster]
	 * - OptiMonk: Popups, Personalization & A/B Testing by OptiMonk			[optimonk]
	 * - Poptin by Poptin														[poptin]
	 * - Gravity Forms by Gravity Forms											[gform]
	 * - Popup Anything by WP OnlineSupport, Essential Plugin					[paoc]
	 * - Popup Box by Popup Box Team											[ays]
	 * - Hustle by WPMU DEV														[hustle]
	 * - Popups for Divi by divimode.com										[popdivi]
	 * - Login/Signup Popup by XootiX											[xoologin]
	 *
	 * @since    10.3
	 * @return   string
	 */
	function get_custom_phone_selectors(){
		$selectors = apply_filters( 'cartbounty_pro_custom_phone_selectors',
			array(
				'cartbounty' 	=> '.cartbounty-pro-custom-phone-field',
				'wpforms' 		=> '.wpforms-container input[type="tel"]',
				'sgpb' 			=> '.sgpb-form input[type="tel"]',
				'ninja'			=> '.nf-form-cont input[type="tel"]',
				'wpcf7'			=> '.wpcf7 input[type="tel"]',
				'fluentform'	=> '.fluentform input[type="tel"]',
				'optinmonster'	=> '.om-element input[type="tel"]',
				'optimonk'		=> '.om-holder input[type="tel"]',
				'poptin'		=> '.poptin-popup input[type="tel"]',
				'gform'			=> '.gform_wrapper input[type="tel"]',
				'paoc'			=> '.paoc-popup input[type="tel"]',
				'ays'			=> '.ays-pb-form input[type="tel"]',
				'hustle'		=> '.hustle-form input[name="phone"]',
				'popdivi'		=> '.et_pb_section input[type="tel"]',
				'xoologin'		=> '.xoo-el-container input[type="tel"]',
			)
		);

		return implode( ', ', $selectors );
	}

	/**
	 * Get custom "Add to cart" button selectors
	 * Ability to use a filter to edit this list
	 *
	 * Supporting these plugins by default:
	 * - CartBounty custom add to cart button class								[cartbounty]
	 * - WooCommerce 															[woocommerce]
	 * - YITH WooCommerce Frequently Bought Together by YITH					[yith-wfbt]
	 *
	 * @since    10.7
	 * @return   string
	 */
	function get_custom_add_to_cart_button_selectors(){
		$selectors = apply_filters( 'cartbounty_pro_custom_add_to_cart_button_selectors',
			array(
				'cartbounty' 	=> '.cartbounty-pro-add-to-cart',
				'woocommerce' 	=> '.add_to_cart_button, .ajax_add_to_cart, .single_add_to_cart_button',
				'yith-wfbt' 	=> '.yith-wfbt-submit-button',
			)
		);

		return implode( ', ', $selectors );
	}

	/**
	 * Get user IP address
	 * HTTP_X_FORWARDED_FOR can contain a chain of comma-separated
	 * addresses. The first one usually is the original client, but it can't be trusted for authenticity
	 *
	 * @since    10.0
	 * @return   string
	 */
	function get_user_ip(){
		$ip = '';

		$headers = array(
			'HTTP_CLIENT_IP',
			'HTTP_X_FORWARDED_FOR',
			'HTTP_X_FORWARDED',
			'HTTP_X_CLUSTER_CLIENT_IP',
			'HTTP_FORWARDED_FOR',
			'HTTP_FORWARDED',
			'REMOTE_ADDR',
		);

		foreach( $headers as $header ) {
			
			if ( array_key_exists( $header, $_SERVER ) ){
				$address_chain = explode( ',', $_SERVER[$header] );
				$ip = trim( $address_chain[0] );

				break;
			}
		}

		if( !filter_var( $ip, FILTER_VALIDATE_IP ) ) { //Check if a valid IP provided
			$ip = '';
		}

		return $ip;
	}
}