<?php
/**
 * Batch Processing
 *
 * @package CartFlows
 * @since 1.0.0
 */

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

	/**
	 * CartFlows_Batch_Process
	 *
	 * @since 1.0.0
	 */
	class CartFlows_Batch_Process {

		/**
		 * Instance
		 *
		 * @since 1.0.0
		 * @var object Class object.
		 * @access private
		 */
		private static $instance;

		/**
		 * Elementor Batch Instance
		 *
		 * @since 1.1.1 Updated instance name with elementor specific.
		 *
		 * @since 1.0.0
		 * @var object Class object.
		 * @access public
		 */
		public static $batch_instance_elementor;

		/**
		 * Beaver Builder Batch Instance
		 *
		 * @since 1.1.1
		 * @var object Class object.
		 * @access public
		 */
		public static $batch_instance_bb;

		/**
		 * Divi Batch Instance
		 *
		 * @since 1.1.1
		 * @var object Class object.
		 * @access public
		 */
		public static $batch_instance_divi;

		/**
		 * Gutenberg Batch Instance
		 *
		 * @since 1.5.9
		 * @var object Class object.
		 * @access public
		 */
		public static $batch_instance_gb;

		/**
		 * Sites Importer
		 *
		 * @since 1.6.15
		 * @var object Class object.
		 * @access public
		 */
		public static $process_site_importer;

		/**
		 * Last Export Checksums
		 *
		 * @since 1.6.15
		 * @var object Class object.
		 * @access public
		 */
		public $last_export_checksums;

		/**
		 * Set the
		 *
		 * @since 2.0.8
		 * @var bool True/False flag to get the status of import process.
		 * @access public
		 */
		public static $is_wcf_template_import;

		/**
		 * Initiator
		 *
		 * @since 1.0.0
		 * @return object initialized object of class.
		 */
		public static function get_instance() {
			if ( ! isset( self::$instance ) ) {
				self::$instance = new self();
			}
			return self::$instance;
		}

		/**
		 * Constructor
		 *
		 * @since 1.0.0
		 */
		public function __construct() {

			// Not BB or Elementor then avoid importer.
			// if ( ! class_exists( '\Elementor\Plugin' ) && ! class_exists( 'FLBuilder' ) ) {
			// return;
			// }
			// Core Helpers - Image.
			require_once ABSPATH . 'wp-admin/includes/image.php';

			// Core Helpers - Batch Processing.
			require_once CARTFLOWS_DIR . 'classes/importer/batch-process/helpers/class-cartflows-importer-image.php';
			require_once CARTFLOWS_DIR . 'classes/importer/batch-process/helpers/class-wp-async-request.php';
			require_once CARTFLOWS_DIR . 'classes/importer/batch-process/helpers/class-wp-background-process.php';

			$default_page_builder = Cartflows_Helper::get_common_setting( 'default_page_builder' );

			// Elementor.
			if ( ( 'elementor' === $default_page_builder ) && class_exists( '\Elementor\Plugin' ) ) {
				// Add "elementor" in import [queue].
				// @todo Remove required `allow_url_fopen` support.
				if ( ini_get( 'allow_url_fopen' ) ) {
					require_once CARTFLOWS_DIR . 'classes/importer/batch-process/class-cartflows-importer-elementor.php';
					require_once CARTFLOWS_DIR . 'classes/importer/batch-process/class-cartflows-importer-elementor-batch.php';
					self::$batch_instance_elementor = new Cartflows_Importer_Elementor_Batch();
				}
			}

			// Beaver Builder.
			if ( ( 'beaver-builder' === $default_page_builder ) && class_exists( 'FLBuilder' ) ) {
				require_once CARTFLOWS_DIR . 'classes/importer/batch-process/class-cartflows-importer-beaver-builder.php';
				require_once CARTFLOWS_DIR . 'classes/importer/batch-process/class-cartflows-importer-beaver-builder-batch.php';
				self::$batch_instance_bb = new Cartflows_Importer_Beaver_Builder_Batch();
			}

			// Divi.
			if ( ( 'other' === $default_page_builder ) && ( class_exists( 'ET_Builder_Plugin' ) || Cartflows_Compatibility::get_instance()->is_divi_enabled() ) ) {
				require_once CARTFLOWS_DIR . 'classes/importer/batch-process/class-cartflows-importer-divi.php';
				require_once CARTFLOWS_DIR . 'classes/importer/batch-process/class-cartflows-importer-divi-batch.php';
				self::$batch_instance_divi = new Cartflows_Importer_Divi_Batch();
			}

			// Gutenberg.
			if ( ( 'gutenberg' === $default_page_builder ) ) {
				require_once CARTFLOWS_DIR . 'classes/importer/batch-process/class-cartflows-importer-gutenberg.php';
				require_once CARTFLOWS_DIR . 'classes/importer/batch-process/class-cartflows-importer-gutenberg-batch.php';
				self::$batch_instance_gb = new Cartflows_Importer_Gutenberg_Batch();
			}

			require_once CARTFLOWS_DIR . 'classes/importer/batch-process/class-cartflows-batch-processing-sync-library.php';
			require_once CARTFLOWS_DIR . 'classes/importer/batch-process/helpers/class-wp-background-process-cartflows-sync-library.php';
			self::$process_site_importer = new WP_Background_Process_Cartflows_Sync_Library();

			// Start image importing after site import complete.
			add_action( 'cartflows_after_template_import', array( $this, 'start_batch_process' ) );
			add_action( 'cartflows_import_complete', array( $this, 'complete_batch_import' ) );
			add_filter( 'upload_mimes', array( $this, 'wcf_custom_upload_mimes' ) ); //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.upload_mimes
			add_filter( 'wp_prepare_attachment_for_js', array( $this, 'add_svg_image_support' ), 10, 3 );
			add_action( 'cartflows_before_elementor_import_single_template', array( $this, 'enable_unfiltered_upload_elementor' ) );
			add_action( 'admin_head', array( $this, 'start_importer' ) );
		}

		/**
		 * Start Importer
		 *
		 * @since 1.6.15
		 * @return void
		 */
		public function start_importer() {

			$is_fresh_site = get_site_option( 'cartflows-fresh-site', '' );

			// Process initially for the fresh user.
			if ( isset( $_GET['reset'] ) && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'cartflows_batch_reset' ) ) {

				// Process import.
				$this->process_batch();

			} elseif ( empty( $is_fresh_site ) ) {

				// First time user save the data of sites, pages, categories etc from the JSON file.
				$dir        = CARTFLOWS_DIR . 'admin-core/assets/importer-data';
				$list_files = list_files( $dir );
				if ( ! empty( $list_files ) ) {
					$list_files = array_map( 'basename', $list_files );

					foreach ( $list_files as $key => $file_name ) {
						$data = file_get_contents( $dir . '/' . $file_name );
						if ( ! empty( $data ) ) {
							$option_name = str_replace( '.json', '', $file_name );
							update_site_option( $option_name, json_decode( $data, true ) );
						}
					}
				}

				// Also, Trigger the batch to get latest data.
				// If batch failed then user have at least the data from the JSON file.
				$this->process_batch();

				update_site_option( 'cartflows-fresh-site', 'yes', 'no' );

				// If not fresh user then trigger batch import on the transient and option
				// Only on the Astra Sites page.
			} else {

				$current_screen = get_current_screen();

				// Bail if not on Astra Sites screen.
				if ( ! is_object( $current_screen ) && null === $current_screen ) {
					return;
				}

				if ( 'toplevel_page_cartflows' === $current_screen->id || 'cartflows_page_cartflows_settings' === $current_screen->id ) {

					// Process import.
					$this->process_batch();
				}
			}
		}

		/**
		 * Update Latest Checksums
		 *
		 * Store latest checksum after batch complete.
		 *
		 * @param string $templates templates category to fetch.
		 * @since 1.6.15
		 * @return void
		 */
		public function update_latest_checksums( $templates = '' ) {
			$suffix = 'store-checkout' === $templates ? 'store-checkout-' : '';
			wcf()->logger->sync_log( 'Checkusms updated' );
			$latest_checksums = get_site_option( 'cartflows-' . $suffix . 'last-export-checksums-latest', '' );
			update_site_option( 'cartflows-' . $suffix . 'last-export-checksums', $latest_checksums, 'no' );
		}

		/**
		 * Added .svg files as supported format in the uploader.
		 *
		 * @since 1.1.4
		 *
		 * @param array $mimes Already supported mime types.
		 */
		public function wcf_custom_upload_mimes( $mimes ) {
			// Return if the current user don't have the access to upload the files.
			if ( ! current_user_can( 'cartflows_manage_flows_steps' ) && ! current_user_can( 'unfiltered_upload' ) ) {
				return $mimes;
			}
			// Only add the SVG support if and only if the CartFlows import is complete OR in progress.
			if ( false === get_transient( 'cartflows_is_wcf_template_import' ) ) {
				return $mimes;
			}
			// Allow SVG files.
			$mimes['svg']  = 'image/svg+xml';
			$mimes['svgz'] = 'image/svg+xml';

			// Allow XML files.
			$mimes['xml'] = 'text/xml';

			return $mimes;
		}

		/**
		 * Add SVG image support
		 *
		 * @since 1.1.4
		 *
		 * @param array  $response    Attachment response.
		 * @param object $attachment Attachment object.
		 * @param array  $meta        Attachment meta data.
		 */
		public function add_svg_image_support( $response, $attachment, $meta ) {
			if ( ! function_exists( 'simplexml_load_file' ) ) {
				return $response;
			}

			if ( ! empty( $response['sizes'] ) ) {
				return $response;
			}

			if ( 'image/svg+xml' !== $response['mime'] ) {
				return $response;
			}

			$svg_path = get_attached_file( $attachment->ID );

			$dimensions = self::get_svg_dimensions( $svg_path );

			$response['sizes'] = array(
				'full' => array(
					'url'         => $response['url'],
					'width'       => $dimensions->width,
					'height'      => $dimensions->height,
					'orientation' => $dimensions->width > $dimensions->height ? 'landscape' : 'portrait',
				),
			);

			return $response;
		}

		/**
		 * Get SVG Dimensions
		 *
		 * @since 1.1.4.
		 *
		 * @param  string $svg SVG file path.
		 * @return array      Return SVG file height & width for valid SVG file.
		 */
		public static function get_svg_dimensions( $svg ) {

			// Security: Disable external entity loading to prevent XXE attacks.
			$previous_value = libxml_disable_entity_loader( true ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated
			$svg            = simplexml_load_file( $svg, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOENT );
			libxml_disable_entity_loader( $previous_value ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated

			if ( false === $svg ) {
				$width  = '0';
				$height = '0';
			} else {
				$attributes = $svg->attributes();
				$width      = (string) $attributes->width;
				$height     = (string) $attributes->height;
			}

			return (object) array(
				'width'  => $width,
				'height' => $height,
			);
		}

		/**
		 * Batch Process Complete.
		 *
		 * @return void
		 */
		public function complete_batch_import() {
			wcf()->logger->import_log( '(✓) BATCH Process Complete!' );
		}

		/**
		 * Start Image Import
		 *
		 * @param integer $post_id Post Id.
		 *
		 * @return void
		 */
		public function start_batch_process( $post_id = '' ) {

			$default_page_builder = Cartflows_Helper::get_common_setting( 'default_page_builder' );

			wcf()->logger->import_log( '(✓) BATCH Started!' );
			wcf()->logger->import_log( '(✓) Step ID ' . $post_id );

			// Add "elementor" in import [queue].
			if ( 'beaver-builder' === $default_page_builder && self::$batch_instance_bb ) {

				// Add to queue.
				self::$batch_instance_bb->push_to_queue( $post_id );

				// Dispatch Queue.
				self::$batch_instance_bb->save()->dispatch();

				wcf()->logger->import_log( '(✓) Dispatch "Beaver Builder" Request..' );

			} elseif ( 'elementor' === $default_page_builder && self::$batch_instance_elementor ) {

				// Add to queue.
				self::$batch_instance_elementor->push_to_queue( $post_id );

				// Dispatch Queue.
				self::$batch_instance_elementor->save()->dispatch();

				wcf()->logger->import_log( '(✓) Dispatch "Elementor" Request..' );
			} elseif ( 'divi' === $default_page_builder && self::$batch_instance_divi ) {

				// Add to queue.
				self::$batch_instance_divi->push_to_queue( $post_id );

				// Dispatch Queue.
				self::$batch_instance_divi->save()->dispatch();

				wcf()->logger->import_log( '(✓) Dispatch "Divi" Request..' );
			} elseif ( 'gutenberg' === $default_page_builder && self::$batch_instance_gb ) {

				// Add to queue.
				self::$batch_instance_gb->push_to_queue( $post_id );

				// Dispatch Queue.
				self::$batch_instance_gb->save()->dispatch();

				wcf()->logger->import_log( '(✓) Dispatch "Gutenberg" Request..' );
			} else {
				wcf()->logger->import_log( '(✕) Could not import image due to allow_url_fopen() is disabled!' );
			}
		}

		/**
		 * Set Last Exported Checksum
		 *
		 * @param string $templates templates category to fetch.
		 * @since 1.6.15
		 * @return string Checksums Status.
		 */
		public function set_last_export_checksums( $templates = '' ) {

			if ( ! empty( $this->last_export_checksums ) ) {
				return $this->last_export_checksums;
			}

			$api_args = array(
				'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
			);

			wcf()->logger->sync_log( wcf()->get_site_url() . 'wp-json/cartflows-server/v1/get-last-export-checksums' );

			$response = wp_remote_get( wcf()->get_site_url() . 'wp-json/cartflows-server/v1/get-last-export-checksums', $api_args );
			if ( ! is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) === 200 ) {
				$result = json_decode( wp_remote_retrieve_body( $response ), true );

				// Set last export checksums.
				if ( ! empty( $result['last_export_checksums'] ) ) {
					$suffix = 'store-checkout' === $templates ? 'store-checkout-' : '';
					update_site_option( 'cartflows-' . $suffix . 'last-export-checksums-latest', $result['last_export_checksums'], 'no' );

					$this->last_export_checksums = $result['last_export_checksums'];
				}
			}

			return $this->last_export_checksums;
		}

		/**
		 * Get Last Exported Checksum Status
		 *
		 * @param string $templates templates category to fetch.
		 * @since 1.6.15
		 * @return string Checksums Status.
		 */
		public function get_last_export_checksums( $templates = '' ) {
			$suffix = 'store-checkout' === $templates ? 'store-checkout-' : '';

			$old_last_export_checksums = get_site_option( 'cartflows-' . $suffix . 'last-export-checksums', '' );

			$new_last_export_checksums = $this->set_last_export_checksums( $templates );

			$checksums_status = 'no';

			if ( empty( $old_last_export_checksums ) ) {
				$checksums_status = 'yes';
			}

			if ( $new_last_export_checksums != $old_last_export_checksums ) {
				$checksums_status = 'yes';
			}

			return apply_filters( 'cartflows_checksums_status', $checksums_status );
		}

		/**
		 * Check Cron Status
		 *
		 * Gets the current cron status by performing a test spawn. Cached for one hour when all is well.
		 *
		 * @since 1.6.15
		 *
		 * @param bool $cache Whether to use the cached result from previous calls.
		 * @return true|WP_Error Boolean true if the cron spawner is working as expected, or a WP_Error object if not.
		 */
		public function test_cron( $cache = true ) {
			global $wp_version;

			if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
				return new WP_Error( 'wp_portfolio_cron_error', esc_html__( 'ERROR! Cron schedules are disabled by setting constant DISABLE_WP_CRON to true.<br/>To start the import process please enable the cron by setting the constant to false. E.g. define( \'DISABLE_WP_CRON\', false );', 'cartflows' ) );
			}

			if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
				return new WP_Error( 'wp_portfolio_cron_error', esc_html__( 'ERROR! Cron schedules are disabled by setting constant ALTERNATE_WP_CRON to true.<br/>To start the import process please enable the cron by setting the constant to false. E.g. define( \'ALTERNATE_WP_CRON\', false );', 'cartflows' ) );
			}

			$cached_status = get_transient( 'cartflows-cron-test-ok' );

			if ( $cache && $cached_status ) {
				return true;
			}

			$sslverify     = version_compare( $wp_version, 4.0, '<' );
			$doing_wp_cron = sprintf( '%.22F', microtime( true ) );

			$cron_request = apply_filters(
				'cron_request',
				array(
					'url'  => site_url( 'wp-cron.php?doing_wp_cron=' . $doing_wp_cron ),
					'key'  => $doing_wp_cron,
					'args' => array(
						'timeout'   => 3,
						'blocking'  => true,
						'sslverify' => apply_filters( 'https_local_ssl_verify', $sslverify ),
					),
				)
			);

			$cron_request['args']['blocking'] = true;

			$result = wp_remote_post( $cron_request['url'], $cron_request['args'] );

			if ( is_wp_error( $result ) ) {
				return $result;
			} elseif ( wp_remote_retrieve_response_code( $result ) >= 300 ) {
				return new WP_Error(
					'unexpected_http_response_code',
					sprintf(
						/* translators: 1: The HTTP response code. */
						__( 'Unexpected HTTP response code: %s', 'cartflows' ),
						intval( wp_remote_retrieve_response_code( $result ) )
					)
				);
			} else {
				set_transient( 'cartflows-cron-test-ok', 1, 3600 );
				return true;
			}
		}

		/**
		 * Get Blocks Total Requests
		 *
		 * @since 1.6.15
		 * @param  string $page_builder_slug  Page builder slug.
		 * @param string $templates templates category to fetch.
		 * @return integer
		 */
		public function get_total_requests( $page_builder_slug = '', $templates = '' ) {
			$page_builder_slug = ( ! empty( $page_builder_slug ) ) ? $page_builder_slug : wcf()->get_site_slug();

			update_site_option( 'cartflows-batch-status-string', 'Getting Total Blocks', 'no' );

			$api_args = array(
				'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
			);

			$url = wcf()->get_site_url( $page_builder_slug ) . 'wp-json/cartflows-server/v1/get-total-flows/';
			wcf()->logger->sync_log( $url );

			$response = wp_remote_get( $url, $api_args );

			/* Check one more time */
			if ( is_wp_error( $response ) ) {
				$response = wp_remote_get( $url, $api_args );
			}

			if ( ! is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) === 200 ) {
				$total_requests = json_decode( wp_remote_retrieve_body( $response ), true );

				$suffix = 'store-checkout' === $templates ? 'store-checkout-' : '';

				if ( isset( $total_requests['pages'] ) ) {
					update_site_option( 'cartflows-batch-status-string', 'Updated requests ' . $total_requests['pages'], 'no' );

					update_site_option( 'cartflows-' . $suffix . $page_builder_slug . '-requests', $total_requests['pages'], 'no' );

					return $total_requests['pages'];
				}
			} else {

				wcf()->logger->sync_log( 'Client\'s server is blocking requests. May be client\'s server firewall is blocking request. Make sure a firewall or any kind of secuirty pluging is blocking requests or not?' );

				/* Fallback default data if client server is blocking requests */
				$total_requests = array(
					'no_of_items' => 15,
					'pages'       => 1,
					'per_page'    => 100,
				);

				update_site_option( 'cartflows-batch-status-string', 'Updated requests ' . $total_requests['pages'], 'no' );

				update_site_option( 'cartflows-' . $page_builder_slug . '-requests', $total_requests['pages'], 'no' );

				return $total_requests['pages'];
			}
		}

		/**
		 * Process Batch
		 *
		 * @since 1.6.15
		 * @param string $templates templates category to fetch.
		 * @return mixed
		 */
		public function process_batch( $templates = '' ) {

			if ( 'other' === wcf()->get_site_slug() ) {
				if ( defined( 'WP_CLI' ) ) {
					WP_CLI::error( 'Invalid page builder! Please select the page builder to start sync process.' );
				} else {
					return;
				}
			}

			if ( 'no' === $this->get_last_export_checksums( $templates ) ) {
				if ( defined( 'WP_CLI' ) ) {
					WP_CLI::line( 'Library is up to date!' );
				}
				return;
			}

			$status = $this->test_cron();
			if ( is_wp_error( $status ) ) {
				if ( defined( 'WP_CLI' ) ) {
					WP_CLI::line( 'Error! Batch Not Start due to disabled cron events!' );
				}
				update_site_option( 'cartflows-batch-status-string', 'Error! Batch Not Start due to disabled cron events!', 'no' );

				if ( defined( 'WP_CLI' ) ) {
					WP_CLI::line( 'Error! Batch Not Start due to disabled cron events!' );
				} else {
					// For non- WP CLI request return to prevent the request.
					return;
				}
			}

			// Get count.
			$total_requests = $this->get_total_requests();
			if ( $total_requests ) {
				if ( defined( 'WP_CLI' ) ) {
					WP_CLI::line( 'Total Requests ' . $total_requests );
				}

				for ( $page = 1; $page <= $total_requests; $page++ ) {

					if ( defined( 'WP_CLI' ) ) {
						WP_CLI::line( 'Added page ' . $page . ' in queue.' );
					}

					if ( defined( 'WP_CLI' ) ) {
						Cartflows_Batch_Processing_Sync_Library::get_instance()->import_sites( $page, $templates );
					} else {
						self::$process_site_importer->push_to_queue(
							array(
								'page'     => $page,
								'template' => $templates,
								'instance' => Cartflows_Batch_Processing_Sync_Library::get_instance(),
								'method'   => 'import_sites',
							)
						);
					}
				}
			}

			if ( defined( 'WP_CLI' ) ) {
				WP_CLI::line( 'Sync Process Complete.' );
			} else {
				// Dispatch Queue.
				self::$process_site_importer->save()->dispatch();
			}
		}

		/**
		 * Enable the unfiltered upload of the elementor to upload the SVG files.
		 *
		 * @since 1.10.0
		 */
		public function enable_unfiltered_upload_elementor() {

			add_filter( 'elementor/files/allow_unfiltered_upload', '__return_true' );
		}

		/**
		 * Set the flat for import process is in progress or not.
		 * This flag will be used to add a support of extra files for uploading to the website directory while importing the ready-made templates.
		 *
		 * @param bool $bool The default state is false.
		 * @return void
		 */
		public static function set_is_wcf_template_import( $bool = false ) {
			if ( $bool ) {
				set_transient( 'cartflows_is_wcf_template_import', $bool, HOUR_IN_SECONDS );
			} else {
				delete_transient( 'cartflows_is_wcf_template_import' ); // Delete the option if $bool is false.
			}
		}
	}

	/**
	 * Kicking this off by calling 'get_instance()' method
	 */
	CartFlows_Batch_Process::get_instance();

endif;
