<?php
/**
 * CartFlows Admin
 *
 * @package CartFlows
 * @since 1.0.0
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
use CartflowsAdmin\AdminCore\Inc\AdminHelper;

if ( ! class_exists( 'CartFlows_Importer' ) ) :

	/**
	 * CartFlows Import
	 *
	 * @since 1.0.0
	 */
	class CartFlows_Importer {

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

		/**
		 * 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() {

			add_action( 'cartflows_import_complete', array( $this, 'clear_cache' ) );
		}

		/**
		 * Get flow export data
		 *
		 * @since 1.1.4
		 *
		 * @param  integer $flow_id Flow ID.
		 * @return array
		 */
		public function get_flow_export_data( $flow_id ) {

			$export_all = apply_filters( 'cartflows_export_all', true );

			$new_steps = array();
			$steps     = get_post_meta( $flow_id, 'wcf-steps', true );
			if ( is_array( $steps ) ) {
				foreach ( $steps as $key => $step ) {

					// Add step post meta.
					$all_meta = get_post_meta( $step['id'] );

					$exclude_step_meta_keys = Cartflows_Helper::get_instance()->get_meta_keys_to_exclude_from_import( $step['id'] );

					if ( is_array( $all_meta ) ) {

						foreach ( $exclude_step_meta_keys as $meta_key ) {

							if ( in_array( $meta_key, array_keys( $all_meta ), true ) ) {
								unset( $all_meta[ $meta_key ] );
							}
						}
					}

					// Add single step.
					$step_data_arr = array(
						'title'        => get_the_title( $step['id'] ),
						'type'         => $step['type'],
						'meta'         => $all_meta,
						'post_content' => '',
					);
			
					if ( $export_all ) {
						$step_post_obj                 = get_post( $step['id'] );
						$step_data_arr['post_content'] = $step_post_obj->post_content;
					}

					$new_steps[] = $step_data_arr;
				}
			}

			$store_checkout_id = (string) Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' );
			$flow_type         = ( $store_checkout_id === (string) $flow_id ) ? 'store-checkout' : 'flows';

			// Get all flow settings using AdminHelper.
			$flow_settings = AdminHelper::get_flow_meta_options( $flow_id );

			// Remove steps data as we're already handling it separately.
			if ( isset( $flow_settings['wcf-steps'] ) ) {
				unset( $flow_settings['wcf-steps'] );
			}

			if ( 'store-checkout' === $flow_type ) {
				$common                                    = Cartflows_Helper::get_common_settings();
				$override_global_checkout                  = $common['override_global_checkout'];
				$flow_settings['override_global_checkout'] = $override_global_checkout;
			}

			$flow_data = array(
				'title'     => get_the_title( $flow_id ),
				'flow_type' => $flow_type,
				'flow_meta' => $flow_settings,
				'steps'     => $new_steps,
			);

			return $flow_data;
		}

		/**
		 * Get all flow export data
		 *
		 * @since 1.1.4
		 */
		public function get_all_flow_export_data() {

			$query_args = array(
				'post_type'      => CARTFLOWS_FLOW_POST_TYPE,

				// Query performance optimization.
				'fields'         => 'ids',
				'no_found_rows'  => true,
				'posts_per_page' => -1,
			);

			$query = new WP_Query( $query_args );
			$flows = array();
			if ( $query->posts ) {
				foreach ( $query->posts as $key => $post_id ) {
					$flows[] = $this->get_flow_export_data( $post_id );
				}
			}

			return $flows;
		}


		/**
		 * Import flow from the JSON data
		 *
		 * @since 1.6.15
		 * @param  array $flows JSON array.
		 * @return void
		 */
		public function import_from_json_data( $flows ) {
			if ( $flows ) {

				foreach ( $flows as $key => $flow ) {
					$this->import_single_flow_from_json( $flow );
				}
			}
		}

		/**
		 * Import Single Flow from JSON
		 *
		 * @param array $flow flow_data.
		 * @param bool  $return return value.
		 */
		public function import_single_flow_from_json( $flow = array(), $return = false ) {

			$default_page_builder = Cartflows_Helper::get_common_setting( 'default_page_builder' );
			
			// Check if this is a store checkout flow.
			if ( isset( $flow['flow_type'] ) && 'store-checkout' === $flow['flow_type'] ) {
				// Get the current store checkout ID.
				$store_checkout_id = Cartflows_Helper::get_global_setting( '_cartflows_store_checkout' );
				
				if ( ! empty( $store_checkout_id ) ) {
					wp_delete_post( absint( $store_checkout_id ), true );
				}
			}

			$flow_title = $flow['title'];
			if ( post_exists( $flow['title'] ) ) {
				$flow_title = $flow['title'] . ' Copy';
			}

			// Create post object.
			$new_flow_args = apply_filters(
				'cartflows_flow_importer_args',
				array(
					'post_type'   => CARTFLOWS_FLOW_POST_TYPE,
					'post_title'  => $flow_title,
					'post_status' => 'publish',
				)
			);

			// Insert the post into the database.
			$flow_id = wp_insert_post( $new_flow_args );
			
			// Import flow_meta settings from the imported flow.
			if ( isset( $flow['flow_meta'] ) && is_array( $flow['flow_meta'] ) ) {
				foreach ( $flow['flow_meta'] as $meta_key => $meta_value ) {
					update_post_meta( $flow_id, $meta_key, $meta_value );
					wcf()->logger->import_log( '(✓) Imported flow meta: ' . $meta_key );
				}
			}
			
			// If this is a store checkout flow, update the global setting.
			if ( isset( $flow['flow_type'] ) && 'store-checkout' === $flow['flow_type'] ) {
				update_option( '_cartflows_store_checkout', $flow_id );
				$old_global_checkout = get_option( '_cartflows_old_global_checkout', false );
				$checkout_id         = $old_global_checkout ? absint( $old_global_checkout ) : $flow_id;
				
				// Reset global checkout on store checkout creation.
				$common_settings                    = \Cartflows_Helper::get_common_settings();
				$common_settings['global_checkout'] = $checkout_id;
				
				if ( isset( $flow['flow_meta']['override_global_checkout'] ) ) {
					$common_settings['override_global_checkout'] = $flow['flow_meta']['override_global_checkout'];
				}

				update_option( '_cartflows_common', $common_settings );
			}

			/**
			 * Fire after flow import
			 *
			 * @since 1.6.15
			 * @param int $flow_id Flow ID.
			 * @param array $new_flow_args Flow post args.
			 * @param array $flows Flow JSON data.
			 */
			do_action( 'cartflows_flow_imported', $flow_id, $new_flow_args, $flow );

			if ( $flow['steps'] ) {

				$exclude_meta_keys = Cartflows_Helper::get_instance()->get_meta_keys_to_exclude_from_import();

				foreach ( $flow['steps'] as $key => $step ) {

					$new_all_meta = array();
					if ( is_array( $step['meta'] ) ) {

						foreach ( $step['meta'] as $meta_key => $mvalue ) {

							if ( in_array( $meta_key, $exclude_meta_keys, true ) ) {
								continue;
							}

							// Security: Only allow meta keys matching known prefixes to prevent arbitrary DB writes.
							if ( ! Cartflows_Helper::get_instance()->is_meta_key_allowed_for_import( $meta_key ) ) {
								continue;
							}

							if ( is_serialized( $mvalue[0], true ) ) {
								// Security: Using unserialize with allowed_classes=>false to prevent object injection.
								$meta_value = unserialize( stripslashes( $mvalue[0] ), array( 'allowed_classes' => false ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize, PHPCompatibility.FunctionUse.NewFunctionParameters.unserialize_optionsFound
								// Drop malicious payloads completely to prevent fatal errors.
								if ( false === $meta_value || is_object( $meta_value ) ) {
									$meta_value = '';
								}
							} else {
								$meta_value = $mvalue[0];
							}

							if ( '_elementor_data' === $meta_key ) {

								if ( is_array( $meta_value ) ) {
									$meta_value = wp_slash( wp_json_encode( $meta_value ) );
								} else {
									$meta_value = wp_slash( $meta_value );
								}
							}

							/**
							 * Commented the below code and added the above code check and convert the elementor data into the proper format.
							 * Kept this code for future code reference. Remove it after two updates.
							 * $meta_value = maybe_unserialize( $mvalue[0] );
							 * */

							$new_all_meta[ $meta_key ] = $meta_value;

						}
					}
					$post_content = isset( $step['post_content'] ) ? ( 'gutenberg' === $default_page_builder ? $step['post_content'] : wp_slash( wp_json_encode( $step['post_content'] ) ) ) : '';
					

					$new_step_args = apply_filters(
						'cartflows_step_importer_args',
						array(
							'post_type'    => CARTFLOWS_STEP_POST_TYPE,
							'post_title'   => $step['title'],
							'post_status'  => 'publish',
							'meta_input'   => $new_all_meta,
							'post_content' => $post_content,
						)
					);
					$new_step_id   = wp_insert_post( $new_step_args );
				
					/**
					 * Fire after step import
					 *
					 * @since 1.6.15
					 * @param int $new_step_id step ID.
					 * @param int $flow_id flow ID.
					 * @param array $new_step_args Step post args.
					 * @param array $flow_steps Flow steps.
					 * @param array $flows All flows JSON data.
					 */
					do_action( 'cartflows_step_imported', $new_step_id, $flow_id, $new_step_args, $flow['steps'] );

					// Insert post meta.
					update_post_meta( $new_step_id, 'wcf-flow-id', $flow_id );

					$step_taxonomy = CARTFLOWS_TAXONOMY_STEP_TYPE;
					$current_term  = term_exists( $step['type'], $step_taxonomy );

					$step_slug = $step['type'];

					if ( isset( $current_term['term_id'] ) ) {
						// Set type object.
						$data      = get_term( $current_term['term_id'], $step_taxonomy );
						$step_slug = $data->slug;
					}

					// Set term step type.
					wp_set_object_terms( $new_step_id, $step_slug, $step_taxonomy );

					// Set type.
					update_post_meta( $new_step_id, 'wcf-step-type', $step_slug );

					// Set flow.
					wp_set_object_terms( $new_step_id, 'flow-' . $flow_id, CARTFLOWS_TAXONOMY_STEP_FLOW );

					self::get_instance()->set_step_to_flow( $flow_id, $new_step_id, $step['title'], $step_slug );

					if ( apply_filters( 'cartflows_enable_imported_content_processing', true ) ) {
						if ( isset( $step['post_content'] ) && ! empty( $step['post_content'] ) ) {

							// Download and replace images.
							$content = $this->get_content( $step['post_content'] );

							$is_divi = ( 'other' === $default_page_builder ) && ( class_exists( 'ET_Builder_Plugin' ) || Cartflows_Compatibility::get_instance()->is_divi_enabled() );

							// Encode content if the page builder is not DIVI and Gutenberg.
							$encode_content = ! $is_divi && 'gutenberg' !== $default_page_builder;
							$post_content   = $encode_content ? wp_slash( wp_json_encode( $content ) ) : $content;

							// Update post content.
							wp_update_post(
								array(
									'ID'           => intval( $new_step_id ),
									// If the page builder is DIVI then pass the content as it is but for rest of the page builders, Encrypt it.
									'post_content' => $post_content,
								)
							);
						}

						// Elementor Data.
						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' ) && isset( $step['meta']['_elementor_data'] ) ) {
								$obj = new \Elementor\TemplateLibrary\CartFlows_Importer_Elementor();
								$obj->import_single_template( $new_step_id );
							}
						}

						// Beaver Builder.
						if ( ( 'beaver-builder' === $default_page_builder ) && class_exists( 'FLBuilder' ) ) {
							if ( isset( $step['meta']['_fl_builder_data'] ) ) {
								CartFlows_Importer_Beaver_Builder::get_instance()->import_single_post( $new_step_id );
							}
						}
					}
				}
			}

			if ( $return ) {
				return array(
					'flow_id'   => $flow_id,
					'edit_link' => admin_url( 'post.php?action=edit&post=' . $flow_id ),
				);
			}
		}

		/**
		 * Download and Replace hotlink images
		 *
		 * @since 1.6.15
		 *
		 * @param  string $content Mixed post content.
		 * @return array           Hotlink image array.
		 */
		public function get_content( $content = '' ) {

			$content = stripslashes( $content );

			// Extract all links.
			$all_links = wp_extract_urls( $content );

			// Not have any link.
			if ( empty( $all_links ) ) {
				return $content;
			}

			$link_mapping = array();
			$image_links  = array();
			$other_links  = array();

			// Extract normal and image links.
			foreach ( $all_links as $key => $link ) {
				if ( preg_match( '/^((https?:\/\/)|(www\.))([a-z0-9-].?)+(:[0-9]+)?\/[\w\-]+\.(jpg|png|gif|jpeg)\/?$/i', $link ) ) {

					// Get all image links.
					// Avoid *-150x, *-300x and *-1024x images.
					if (
						false === strpos( $link, '-150x' ) &&
						false === strpos( $link, '-300x' ) &&
						false === strpos( $link, '-1024x' )
					) {
						$image_links[] = $link;
					}
				} else {

					// Collect other links.
					$other_links[] = $link;
				}
			}

			// Step 1: Download images.
			if ( ! empty( $image_links ) ) {
				foreach ( $image_links as $key => $image_url ) {
					// Download remote image.
					$image            = array(
						'url' => $image_url,
						'id'  => 0,
					);
					$downloaded_image = CartFlows_Import_Image::get_instance()->import( $image );

					// Old and New image mapping links.
					$link_mapping[ $image_url ] = $downloaded_image['url'];
				}
			}

			// Step 3: Replace mapping links.
			foreach ( $link_mapping as $old_url => $new_url ) {
				$content = str_replace( $old_url, $new_url, $content );

				// Replace the slashed URLs if any exist.
				$old_url = str_replace( '/', '/\\', $old_url );
				$new_url = str_replace( '/', '/\\', $new_url );
				$content = str_replace( $old_url, $new_url, $content );
			}

			return $content;
		}

		/**
		 * Clear Cache.
		 *
		 * @since 1.0.0
		 */
		public function clear_cache() {
			// Clear 'Elementor' file cache.
			if ( class_exists( '\Elementor\Plugin' ) ) {
				\Elementor\Plugin::$instance->files_manager->clear_cache();
			}
		}

		/**
		 * Set steps to the flow
		 *
		 * @param integer $flow_id     Flow ID.
		 * @param integer $new_step_id New step ID.
		 * @param string  $step_title    Flow Type.
		 * @param string  $step_slug Flow Type.
		 */
		public function set_step_to_flow( $flow_id, $new_step_id, $step_title, $step_slug ) {
			// Update steps for the current flow.
			$flow_steps = get_post_meta( $flow_id, 'wcf-steps', true );

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

			$flow_steps[] = array(
				'id'    => $new_step_id,
				'title' => $step_title,
				'type'  => $step_slug,
			);
			update_post_meta( $flow_id, 'wcf-steps', $flow_steps );
			wcf()->logger->import_log( '(✓) Updated flow steps post meta key \'wcf-steps\' ' . wp_json_encode( $flow_steps ) );
		}

		/**
		 * Create step for given flow.
		 *
		 * @param int $flow_id flow ID.
		 * @param int $step_type step type.
		 * @param int $step_title step title.
		 * @since 1.0.0
		 *
		 * @return mixed
		 */
		public function create_step( $flow_id, $step_type, $step_title ) {

			$args = array(
				'post_type'   => CARTFLOWS_STEP_POST_TYPE,
				'post_title'  => $step_title,
				'post_status' => 'publish',
			);

			$new_step_id = wp_insert_post( $args );

			if ( $new_step_id ) {

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

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

				$flow_steps[] = array(
					'id'    => $new_step_id,
					'title' => $step_title,
					'type'  => $step_type,
				);

				$flow_steps = Cartflows_Helper::get_instance()->maybe_update_flow_steps( $flow_id, $flow_steps );

				// insert post meta.
				update_post_meta( $new_step_id, 'wcf-flow-id', $flow_id );
				update_post_meta( $new_step_id, 'wcf-step-type', $step_type );
				update_post_meta( $new_step_id, '_wp_page_template', 'cartflows-default' );

				wp_set_object_terms( $new_step_id, $step_type, CARTFLOWS_TAXONOMY_STEP_TYPE );
				wp_set_object_terms( $new_step_id, 'flow-' . $flow_id, CARTFLOWS_TAXONOMY_STEP_FLOW );
			}

			update_post_meta( $flow_id, 'wcf-steps', $flow_steps );
			do_action( 'cartflows_after_create_step', $new_step_id, $step_type );

			return $new_step_id;
		}
	}

	/**
	 * Initialize class object with 'get_instance()' method
	 */
	CartFlows_Importer::get_instance();

endif;
