<?php
/**
 * Utils.
 *
 * @package CARTFLOWS
 */

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

/**
 * Class Cartflows_Utils.
 */
class Cartflows_Utils {

	/**
	 * Member Variable
	 *
	 * @var instance
	 */
	private static $instance;

	/**
	 * Member Variable
	 *
	 * @var checkout_products
	 */
	public $checkout_products = array();


	/**
	 *  Initiator
	 */
	public static function get_instance() {
		if ( ! isset( self::$instance ) ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 *  Constructor
	 */
	public function __construct() {
	}

	/**
	 *  Get current post type
	 *
	 * @param string $post_type post type.
	 * @return string
	 */
	public function current_post_type( $post_type = '' ) {

		global $post;

		if ( '' === $post_type && is_object( $post ) ) {
			$post_type = $post->post_type;
		}

		return $post_type;
	}

	/**
	 * Check if post type is of step.
	 *
	 * @param string $post_type post type.
	 * @return bool
	 */
	public function is_step_post_type( $post_type = '' ) {

		if ( $this->get_step_post_type() === $this->current_post_type( $post_type ) ) {

			return true;
		}

		return false;
	}

	/**
	 * Check if post type is of flow.
	 *
	 * @param string $post_type post type.
	 * @return bool
	 */
	public function is_flow_post_type( $post_type = '' ) {

		if ( $this->get_flow_post_type() === $this->current_post_type( $post_type ) ) {

			return true;
		}

		return false;
	}

	/**
	 * Get post type of step.
	 *
	 * @return string
	 */
	public function get_step_post_type() {

		return CARTFLOWS_STEP_POST_TYPE;
	}

	/**
	 * Get post type of flow.
	 *
	 * @return string
	 */
	public function get_flow_post_type() {

		return CARTFLOWS_FLOW_POST_TYPE;
	}

	/**
	 * Get flow id
	 *
	 * @return int
	 */
	public function get_flow_id() {

		global $post;

		$post_meta = false;

		if ( $post ) {
			$post_meta = get_post_meta( $post->ID, 'wcf-flow-id', true );
		}

		return $post_meta;
	}

	/**
	 * Get flow id by step
	 *
	 * @param int $step_id step ID.
	 * @return int
	 */
	public function get_flow_id_from_step_id( $step_id ) {

		return get_post_meta( $step_id, 'wcf-flow-id', true );
	}

	/**
	 * Get flow steps by id
	 *
	 * @param int $flow_id flow ID.
	 * @return int
	 */
	public function get_flow_steps( $flow_id ) {

		$steps = get_post_meta( $flow_id, 'wcf-steps', true );

		if ( is_array( $steps ) && ! empty( $steps ) ) {
			return $steps;
		}

		return false;
	}

	/**
	 * Get template type of step
	 *
	 * @param int $step_id step ID.
	 * @return int
	 */
	public function get_step_type( $step_id ) {

		return get_post_meta( $step_id, 'wcf-step-type', true );
	}

	/**
	 * Get next id for step
	 *
	 * @param int $flow_id flow ID.
	 * @param int $step_id step ID.
	 * @return bool
	 */
	public function get_next_step_id( $flow_id, $step_id ) {

		$wcf_step_obj = wcf_get_step( $step_id );
		$next_step_id = $wcf_step_obj->get_direct_next_step_id();

		return $next_step_id;
	}

	/**
	 * Get next id for step
	 *
	 * @param object $order order object.
	 * @return int
	 */
	public function get_flow_id_from_order( $order ) {

		if ( ! is_object( $order ) ) {
			$order = wc_get_order( $order );
		}

		$flow_id = is_object( $order ) ? $order->get_meta( '_wcf_flow_id' ) : 0;

		return intval( $flow_id );
	}

	/**
	 * Get checkout id for order
	 *
	 * @param object $order order object.
	 * @return int
	 */
	public function get_checkout_id_from_order( $order ) {

		if ( ! is_object( $order ) ) {
			$order = wc_get_order( $order );
		}

		$checkout_id = is_object( $order ) ? $order->get_meta( '_wcf_checkout_id' ) : 0;

		return intval( $checkout_id );
	}

	/**
	 * We are using this function mostly in ajax on checkout page
	 *
	 * @return mixed
	 */
	public function get_checkout_id_from_post_data() {

		$checkout_id = false;

		//phpcs:disable WordPress.Security.NonceVerification

		if ( isset( $_POST['_wcf_checkout_id'] ) ) {

			$checkout_id = intval( $_POST['_wcf_checkout_id'] );

		} elseif ( isset( $_GET['wcf_checkout_id'] ) ) {

			$checkout_id = intval( $_GET['wcf_checkout_id'] );

		}
		//phpcs:enable WordPress.Security.NonceVerification

		return $checkout_id;
	}

	/**
	 * We are using this function mostly in ajax on checkout page
	 *
	 * @return bool
	 */
	public function get_flow_id_from_post_data() {

		$flow_id = false;

		//phpcs:disable WordPress.Security.NonceVerification

		if ( isset( $_POST['_wcf_flow_id'] ) ) {

			$flow_id = intval( $_POST['_wcf_flow_id'] );

		} elseif ( isset( $_GET['wcf_checkout_id'] ) ) {

			$flow_id = wcf()->utils->get_flow_id_from_step_id( intval( $_GET['wcf_checkout_id'] ) );

		}

		//phpcs:enable WordPress.Security.NonceVerification

		return $flow_id;
	}

	/**
	 * Get optin id for order
	 *
	 * @param object $order order object.
	 * @return int
	 */
	public function get_optin_id_from_order( $order ) {

		if ( ! is_object( $order ) ) {
			$order = wc_get_order( $order );
		}

		$optin_id = $order ? $order->get_meta( '_wcf_optin_id' ) : 0;

		return intval( $optin_id );
	}

	/**
	 * We are using this function mostly in ajax on checkout page
	 *
	 * @return bool
	 */
	public function get_optin_id_from_post_data() {

		if ( isset( $_POST['_wcf_optin_id'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing

			$optin_id = intval( $_POST['_wcf_optin_id'] ); //phpcs:ignore WordPress.Security.NonceVerification.Missing

			return $optin_id;
		}

		return false;
	}


	/**
	 * Check for checkout page
	 *
	 * @param int $step_id step ID.
	 * @return bool
	 */
	public function check_is_checkout_page( $step_id ) {

		$step_type = $this->get_step_type( $step_id );

		if ( 'checkout' === $step_type ) {

			return true;
		}

		return false;
	}

	/**
	 * Check for thank you page
	 *
	 * @param int $step_id step ID.
	 * @return bool
	 */
	public function check_is_thankyou_page( $step_id ) {

		$step_type = $this->get_step_type( $step_id );

		if ( 'thankyou' === $step_type ) {

			return true;
		}

		return false;
	}

	/**
	 * Check for offer page
	 *
	 * @param int $step_id step ID.
	 * @return bool
	 */
	public function check_is_offer_page( $step_id ) {

		$step_type = $this->get_step_type( $step_id );

		if ( 'upsell' === $step_type || 'downsell' === $step_type ) {

			return true;
		}

		return false;
	}

	/**
	 * Check if a step is disabled.
	 *
	 * @since 2.1.20
	 * @param int $step_id step ID.
	 * @return bool
	 */
	public function is_step_disabled( $step_id ) {

		if ( empty( $step_id ) ) {
			return false;
		}

		$is_disabled = get_post_meta( $step_id, 'wcf-disable-step', true );

		// Step is enabled by default, so if disabled step meta is set to 'yes', the step is disabled.
		return ( 'yes' === $is_disabled );
	}

	/**
	 *  Check if loaded page requires woo.
	 *
	 * @return bool
	 */
	public function check_is_woo_required_page() {

		global $post;
		$step_id               = $post->ID;
		$woo_not_required_type = array( 'landing' );
		$step_type             = $this->get_step_type( $step_id );
		return ( ! in_array( $step_type, $woo_not_required_type, true ) );
	}

	/**
	 * Define constant for cache
	 *
	 * @return void
	 */
	public function do_not_cache() {

		global $post;

		if ( ! apply_filters( 'cartflows_do_not_cache_step', true, $post->ID ) ) {
			return;
		}

		$this->get_cache_headers();
	}

	/**
	 * Function to get/set the do not cache page constants.
	 *
	 * @return void
	 */
	public function get_cache_headers() {

		wcf_maybe_define_constant( 'DONOTCACHEPAGE', true );
		wcf_maybe_define_constant( 'DONOTCACHEOBJECT', true );
		wcf_maybe_define_constant( 'DONOTCACHEDB', true );

		nocache_headers();
	}

	/**
	 * Get linking url
	 *
	 * @param array $args query args.
	 * @return string
	 */
	public function get_linking_url( $args = array() ) {

		$url = get_home_url();

		$url = add_query_arg( $args, $url );

		return $url;
	}

	/**
	 * Get assets urls
	 *
	 * @return array
	 * @since 1.1.6
	 */
	public function get_assets_path() {

		$rtl = '';

		if ( is_rtl() ) {
			$rtl = '-rtl';
		}

		$file_prefix = '';
		$dir_name    = '';

		$is_min = apply_filters( 'cartflows_load_min_assets', false );

		if ( $is_min ) {
			$file_prefix = '.min';
			$dir_name    = 'min-';
		}

		$js_gen_path  = CARTFLOWS_URL . 'assets/' . $dir_name . 'js/';
		$css_gen_path = CARTFLOWS_URL . 'assets/' . $dir_name . 'css/';

		return array(
			'css'         => $css_gen_path,
			'js'          => $js_gen_path,
			'file_prefix' => $file_prefix,
			'rtl'         => $rtl,
		);
	}

	/**
	 * Get assets css url
	 *
	 * @param string $file file name.
	 * @return string
	 * @since 1.1.6
	 */
	public function get_css_url( $file ) {

		$assets_vars = wcf()->assets_vars;

		$url = $assets_vars['css'] . $file . $assets_vars['rtl'] . $assets_vars['file_prefix'] . '.css';

		return $url;
	}

	/**
	 * Get assets js url
	 *
	 * @param string $file file name.
	 * @return string
	 * @since 1.1.6
	 */
	public function get_js_url( $file ) {

		$assets_vars = wcf()->assets_vars;

		$url = $assets_vars['js'] . $file . $assets_vars['file_prefix'] . '.js';

		return $url;
	}

	/**
	 * Get unique id.
	 *
	 * @param int $length    Length.
	 *
	 * @return string|false
	 */
	public function get_unique_id( $length = 8 ) {

		return substr( wp_generate_password( $length * 2, false ), 0, $length );
	}

	/**
	 * Get selected checkout products and data
	 *
	 * @param int   $checkout_id    Checkout id..
	 * @param array $saved_products Saved product.
	 *
	 * @return array
	 */
	public function get_selected_checkout_products( $checkout_id = '', $saved_products = array() ) {

		if ( empty( $checkout_id ) ) {

			global $post;

			$checkout_id = $post->ID;
		}

		if ( ! isset( $this->checkout_products[ $checkout_id ] ) ) {

			if ( ! empty( $saved_products ) ) {

				$products = $saved_products;
			} else {

				$products = wcf()->options->get_checkout_meta_value( $checkout_id, 'wcf-checkout-products' );
			}

			$default_add_to_cart = true;

			if ( is_array( $products ) ) {

				foreach ( $products as $in => $data ) {

					$default_data = array(
						'quantity'       => 1,
						'discount_type'  => '',
						'discount_value' => '',
						'unique_id'      => $this->get_unique_id(),
						'add_to_cart'    => $default_add_to_cart,
					);

					$products[ $in ] = wp_parse_args( $products[ $in ], $default_data );
				}
			}

			$products = apply_filters( 'cartflows_selected_checkout_products', $products, $checkout_id );

			// This is frontend. Ignoring nonce rule.
			if ( isset( $_GET['wcf-default'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
				$products = $this->update_the_add_to_cart_param( $products );
			}

			$this->checkout_products[ $checkout_id ] = $products;
		}

		return $this->checkout_products[ $checkout_id ];
	}

	/**
	 * Update product add to cart option.
	 *
	 * @param array $products product data.
	 */
	public function update_the_add_to_cart_param( $products ) {

		$default_sequence = isset( $_GET['wcf-default'] ) ? sanitize_text_field( wp_unslash( $_GET['wcf-default'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$default_ids      = array_map( 'intval', explode( ',', $default_sequence ) );

		if ( is_array( $products ) ) {

			foreach ( $products as $in => $data ) {

				$default_add_to_cart = 'no';
				$sequence            = $in + 1;

				if ( in_array( $sequence, $default_ids, true ) ) {
					$default_add_to_cart = 'yes';

				}

				$products[ $in ]['add_to_cart'] = $default_add_to_cart;
			}
		}

		return $products;
	}

	/**
	 * Get selected checkout products and data
	 *
	 * @param int   $checkout_id    Checkout id..
	 * @param array $products_data  Saved product.
	 *
	 * @return array
	 */
	public function set_selcted_checkout_products( $checkout_id = '', $products_data = array() ) {

		if ( empty( $checkout_id ) ) {

			global $post;

			$checkout_id = $post->ID;
		}

		if ( isset( $this->checkout_products[ $checkout_id ] ) ) {

			$products = $this->checkout_products[ $checkout_id ];
		} else {
			$products = $this->get_selected_checkout_products( $checkout_id );
		}

		if ( is_array( $products ) && ! empty( $products_data ) ) {

			foreach ( $products as $in => $data ) {

				if ( isset( $products_data[ $in ] ) ) {
					$products[ $in ] = wp_parse_args( $products_data[ $in ], $products[ $in ] );
				}
			}
		}

		$this->checkout_products[ $checkout_id ] = $products;

		return $this->checkout_products[ $checkout_id ];
	}

	/**
	 * Clear Installed Page Builder Cache
	 */
	public function clear_cache() {

		// Clear 'Elementor' file cache.
		if ( class_exists( '\Elementor\Plugin' ) ) {
			Elementor\Plugin::$instance->files_manager->clear_cache();
		}
	}

	/**
	 * Append the query strings present in the URL to pass it to the next step URL.
	 * This will carry-forward the passed query strings to the next step URL.
	 *
	 * @param array $original_query_strings Default query strings of CartFlows.
	 * @return array $original_query_strings Modified query strings.
	 */
	public function may_be_append_query_string( $original_query_strings ) {

		// Return if the feature is not enabled. Default is disabled.
		if ( ! apply_filters( 'cartflows_enable_append_query_string', false ) ) {
			return $original_query_strings;
		}

		// Check if HTTP_REFERER is set and fetch its query strings.
		if ( empty( $_SERVER['HTTP_REFERER'] ) ) {
			return $original_query_strings;
		}

		// Get the current page URL and parse it to explode the URL in different URL components.
		$url_params_components = wp_parse_url( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) );

		// Process only if the URL components is not empty and query i:e query strings are not empty.
		if ( is_array( $url_params_components ) && ! empty( $url_params_components['query'] ) ) {

			$forwarded_params = $url_params_components['query'];

			// Convert the string query from string to array format.
			parse_str( $forwarded_params, $parsed_query_string );

			// Remove the already present wcf-key and wcf-order params from the URl and append the rest of.
			if ( $parsed_query_string['wcf-key'] && $parsed_query_string['wcf-order'] ) {
				unset( $parsed_query_string['wcf-key'] );
				unset( $parsed_query_string['wcf-order'] );
			}

			// Merge the new and already existing query strings.
			$original_query_strings = array_merge( $original_query_strings, $parsed_query_string );
		}

		// Return the query strings.
		return apply_filters( 'cartflows_may_be_append_query_strings_args', $original_query_strings );
	}
	
	/**
	 * Checks if the WooCommerce cart is empty.
	 *
	 * This function checks if the WooCommerce cart is empty and if the session has not expired.
	 * It returns true if the cart is empty and the session is valid, otherwise it returns false.
	 *
	 * @return bool True if the cart is empty and the session is valid, otherwise false.
	 */
	public function is_woo_cart_empty() {

		$wc            = function_exists( 'WC' ) ? WC() : null;
		$cart          = ( $wc && $wc->cart instanceof WC_Cart ) ? $wc->cart : null;
		$cart_is_empty = ! $cart || $cart->is_empty();

		return $cart_is_empty
			&& ! is_customize_preview()
			&& apply_filters( 'woocommerce_checkout_update_order_review_expired', true );
	}

	/**
	 * Checks if WooCommerce HPOS (High-Performance Order Storage) is enabled.
	 *
	 * @since 2.2.2
	 *
	 * @return bool True if HPOS is enabled, false otherwise.
	 */
	public function is_hpos_enabled() {

		if (
			class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' )
			&& method_exists( '\Automattic\WooCommerce\Utilities\OrderUtil', 'custom_orders_table_usage_is_enabled' )
		) {
			return \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
		}
		return false;
	}
}

/**
 * Get a specific property of an array without needing to check if that property exists.
 *
 * Provide a default value if you want to return a specific value if the property is not set.
 *
 * @param array  $array   Array from which the property's value should be retrieved.
 * @param string $prop    Name of the property to be retrieved.
 * @param string $default Optional. Value that should be returned if the property is not set or empty. Defaults to null.
 *
 * @return null|string|mixed The value
 */
function wcf_get_prop( $array, $prop, $default = null ) { // phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed

	if ( ! is_array( $array ) && ! ( is_object( $array ) && $array instanceof ArrayAccess ) ) {
		return $default;
	}

	if ( isset( $array[ $prop ] ) ) {
		$value = $array[ $prop ];
	} else {
		$value = '';
	}

	return empty( $value ) && null !== $default ? $default : $value;
}
