?AlkantarClanX12

Your IP : 216.73.217.134


Current Path : /home/rankinh/reunionmag/wp-content/plugins/coming-soon/admin/includes/
Upload File :
Current File : /home/rankinh/reunionmag/wp-content/plugins/coming-soon/admin/includes/import-export-functions.php

<?php
/**
 * V2 Import/Export Functions
 *
 * These are wrapper functions for the existing import/export functionality
 * to work with the new WordPress-native admin system
 *
 * @package    SeedProd_Lite
 * @subpackage SeedProd_Lite/admin/includes
 */

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

// Include V2 utility functions
require_once __DIR__ . '/import-export-utilities.php';

/*
==============================================
	HELPER FUNCTIONS FOR IMPORT/EXPORT OPERATIONS
	============================================== */

/**
 * Validate AJAX request with nonce and permissions.
 *
 * @param string $capability The capability to check.
 */
function seedprod_lite_v2_validate_ajax_request( $capability = 'export' ) {
	if ( ! check_ajax_referer( 'seedprod_v2_nonce', 'nonce', false ) ) {
		wp_send_json_error( __( 'Invalid security token', 'coming-soon' ) );
	}

	if ( ! current_user_can( apply_filters( 'seedprod_import_export', $capability ) ) ) {
		wp_send_json_error( __( 'Insufficient permissions', 'coming-soon' ) );
	}
}

/**
 * Initialize WordPress filesystem.
 *
 * @return object WP_Filesystem instance.
 */
function seedprod_lite_v2_init_filesystem() {
	if ( ! function_exists( 'WP_Filesystem' ) ) {
		require_once ABSPATH . 'wp-admin/includes/file.php';
	}

	// Force direct filesystem method for AJAX requests
	add_filter(
		'filesystem_method',
		function () {
			return 'direct';
		}
	);

	global $wp_filesystem;
	WP_Filesystem();

	return $wp_filesystem;
}

/**
 * Get upload error message.
 *
 * @param int $error_code The PHP upload error code.
 * @return string The error message.
 */
function seedprod_lite_v2_get_upload_error_message( $error_code ) {
	switch ( $error_code ) {
		case UPLOAD_ERR_INI_SIZE:
		case UPLOAD_ERR_FORM_SIZE:
			return __( 'File is too large', 'coming-soon' );
		case UPLOAD_ERR_PARTIAL:
			return __( 'File was only partially uploaded', 'coming-soon' );
		case UPLOAD_ERR_NO_FILE:
			return __( 'No file was uploaded', 'coming-soon' );
		default:
			return __( 'File upload failed', 'coming-soon' );
	}
}

/**
 * Validate that a URL is safe for theme/template import.
 *
 * Enforces HTTPS and rejects path traversal. Does not restrict domains
 * because admins may import from any URL (Dropbox, Google Drive, etc.).
 * Private-IP SSRF is handled by wp_safe_remote_get() at the call site.
 *
 * @param string $url The URL to validate.
 * @return boolean True if the URL passes safety checks.
 */
function seedprod_lite_v2_is_allowed_import_url( $url ) {
	$parsed = wp_parse_url( $url );

	if ( empty( $parsed['host'] ) || empty( $parsed['scheme'] ) ) {
		return false;
	}

	// Enforce HTTPS (allow HTTP only in local dev).
	if ( 'https' !== strtolower( $parsed['scheme'] ) && ! defined( 'SEEDPROD_LOCAL_JS' ) ) {
		return false;
	}

	// Block path traversal sequences.
	if ( ! empty( $parsed['path'] ) && false !== strpos( $parsed['path'], '..' ) ) {
		return false;
	}

	return true;
}

/**
 * Validate ZIP file upload.
 *
 * @param string $file_key The $_FILES array key.
 * @return array File information or sends JSON error.
 */
function seedprod_lite_v2_validate_zip_upload( $file_key ) {
	// Check if file was uploaded.
	if ( ! isset( $_FILES[ $file_key ] ) ) {
		wp_send_json_error( __( 'No file uploaded', 'coming-soon' ) );
	}

	// Check for upload errors.
	if ( UPLOAD_ERR_OK !== $_FILES[ $file_key ]['error'] ) {
		$error_message = seedprod_lite_v2_get_upload_error_message( $_FILES[ $file_key ]['error'] );
		wp_send_json_error( $error_message );
	}

	$filename = wp_unslash( $_FILES[ $file_key ]['name'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
	$source = $_FILES[ $file_key ]['tmp_name']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
	$type = $_FILES[ $file_key ]['type']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash

	// Validate ZIP file type.
	$accepted_types = array(
		'application/zip',
		'application/x-zip-compressed',
		'multipart/x-zip',
		'application/x-compressed',
	);

	$is_valid_type     = in_array( $type, $accepted_types, true );
	$has_zip_extension = preg_match( '/\.zip$/i', $filename );

	if ( ! $is_valid_type && ! $has_zip_extension ) {
		wp_send_json_error( __( 'The file you are trying to upload is not a .zip file. Please try again.', 'coming-soon' ) );
	}

	return array(
		'filename' => $filename,
		'source'   => $source,
		'type'     => $type,
	);
}

/**
 * Setup import/export directories.
 *
 * @param string $folder_name The folder name to create.
 * @return array Directory paths.
 */
function seedprod_lite_v2_setup_directories( $folder_name ) {
	$upload_dir = wp_upload_dir();
	$path       = trailingslashit( $upload_dir['basedir'] );
	$url        = trailingslashit( $upload_dir['baseurl'] );

	$target_dir = $path . $folder_name;
	$target_url = $url . $folder_name;

	// Clean up existing directory.
	if ( is_dir( $target_dir ) ) {
		// Directory cleanup handled by V2 function.
		seedprod_lite_v2_recursive_rmdir( $target_dir );
	}

	// Create fresh directory.
	wp_mkdir_p( $target_dir );

	return array(
		'path'       => $path,
		'url'        => $url,
		'target_dir' => $target_dir,
		'target_url' => $target_url,
	);
}

/**
 * Extract ZIP file with validation.
 *
 * @param string $zip_path   Path to ZIP file.
 * @param string $extract_to Directory to extract to.
 * @param bool   $is_theme   Whether this is a theme import.
 * @return bool|WP_Error True on success, WP_Error on failure.
 */
function seedprod_lite_v2_extract_zip( $zip_path, $extract_to, $is_theme = true ) {
	// Validate ZIP contents.
	$validation = seedprod_lite_v2_validate_import_zip( $zip_path, $is_theme );
	if ( is_wp_error( $validation ) ) {
		return $validation;
	}

	if ( ! class_exists( 'ZipArchive' ) ) {
		return new WP_Error(
			'zip_not_available',
			__( 'ZipArchive class not available. Please contact your host to enable it.', 'coming-soon' )
		);
	}

	$zip = new ZipArchive();
	if ( true === $zip->open( $zip_path ) ) {
		$zip->extractTo( $extract_to );
		$zip->close();
		return true;
	}

	return new WP_Error( 'extract_failed', __( 'Failed to extract ZIP file.', 'coming-soon' ) );
}

/**
 * Read JSON file with HTTP/filesystem fallback.
 *
 * @param string $file_path Local file path.
 * @param string $url_path  Optional URL for HTTP attempt.
 * @return object|WP_Error Decoded JSON data or WP_Error.
 */
function seedprod_lite_v2_read_json_file( $file_path, $url_path = null ) {
	global $wp_filesystem;
	$json_data = null;

	// Try HTTP first if URL provided.
	if ( $url_path ) {
		$response = wp_remote_get( $url_path, array( 'sslverify' => false ) );
		if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
			$json_data = wp_remote_retrieve_body( $response );
		}
	}

	// Fallback to direct filesystem reading.
	if ( ! $json_data && $wp_filesystem && $wp_filesystem->exists( $file_path ) ) {
		$json_data = $wp_filesystem->get_contents( $file_path );
	}

	if ( ! $json_data ) {
		return new WP_Error( 'read_failed', __( 'Unable to read data file.', 'coming-soon' ) );
	}

	// Decode and validate JSON.
	$data = json_decode( $json_data );
	if ( null === $data && JSON_ERROR_NONE !== json_last_error() ) {
		return new WP_Error( 'invalid_json', __( 'Invalid data format.', 'coming-soon' ) );
	}

	return $data;
}

/**
 * Process and prepare export data for themes/pages.
 *
 * @param array  $results Database results.
 * @param string $type    Export type ('theme' or 'page').
 * @param mixed  $ptype   Page type for landing pages.
 * @return array Processed export data.
 */
function seedprod_lite_v2_process_export_data( $results, $type = 'theme', $ptype = null ) {
	$processed_data    = array();
	$export            = array();
	$shortcode_exports = array();

	// Set export type
	$export['type']             = ( 'theme' === $type ) ? 'theme-builder' : 'landing-page';
	$export['current_home_url'] = home_url();
	$export['theme']            = array();
	$export['mapped']           = array();

	// Check if this is a template (contains placehold.co)
	$is_template = false;
	if ( 'theme' === $type ) {
		foreach ( $results as $v ) {
			if ( strpos( $v->post_content_filtered, 'placehold.co' ) !== false ) {
				$is_template = true;
				break;
			}
		}
	}

	// Process each result
	foreach ( $results as $k => $v ) {
		// Skip if essential data is missing
		if ( empty( $v->ID ) ) {
			continue;
		}

		$meta = ( 'theme' === $type ) ? get_post_meta( $v->ID ) : wp_json_encode( get_post_meta( $v->ID ) );

		if ( 'theme' === $type && $is_template ) {
			unset( $meta['_seedprod_css'] );
			unset( $meta['_seedprod_html'] );
		}

		if ( 'theme' === $type ) {
			$meta = json_encode( $meta ); // phpcs:ignore
		}

		// Handle null/empty content safely
		$content          = ! empty( $v->post_content ) ? $v->post_content : '';
		$content_filtered = ! empty( $v->post_content_filtered ) ? $v->post_content_filtered : '';

		// Process image filenames

		if ( 'theme' === $type && $is_template ) {
			$processed_data[ $k ] = array(
				'data'   => $content_filtered,
				'html'   => $content,
				'images' => array(),
			);
		} else {
			$processed_result = seedprod_lite_v2_process_image_filenames( $content_filtered, $content );

			// Ensure the result has the expected structure
			$processed_data[ $k ] = array(
				'data'   => isset( $processed_result['data'] ) ? $processed_result['data'] : $content_filtered,
				'html'   => isset( $processed_result['html'] ) ? $processed_result['html'] : $content,
				'images' => isset( $processed_result['images'] ) && is_array( $processed_result['images'] ) ? $processed_result['images'] : array(),
			);
		}

		// Safely get processed data with fallbacks
		$processed_html         = isset( $processed_data[ $k ]['html'] ) ? $processed_data[ $k ]['html'] : '';
		$processed_data_content = isset( $processed_data[ $k ]['data'] ) ? $processed_data[ $k ]['data'] : '';

		// Build export entry
		$entry = array(
			'order'                 => $v->menu_order,
			'post_content'          => base64_encode( $processed_html ), // phpcs:ignore
			'post_content_filtered' => base64_encode( $processed_data_content ), // phpcs:ignore
			'post_title'            => base64_encode( $v->post_title ), // phpcs:ignore
			'meta'                  => base64_encode( $meta ), // phpcs:ignore
		);

		// Add page-specific fields
		if ( 'page' === $type ) {
			$entry['post_type'] = base64_encode( $v->post_type ); // phpcs:ignore
			$entry['post_status'] = base64_encode( $v->post_status ); // phpcs:ignore

			// Determine page type: check both meta field AND WordPress options
			// Special pages are identified by options, not meta
			$actual_ptype = get_post_meta( $v->ID, '_seedprod_page_type', true );

			// If no meta, check if this is a special page by checking WordPress options
			if ( empty( $actual_ptype ) ) {
				$coming_soon_id = get_option( 'seedprod_coming_soon_page_id' );
				$maintenance_id = get_option( 'seedprod_maintenance_mode_page_id' );
				$login_id       = get_option( 'seedprod_login_page_id' );
				$fourohfour_id  = get_option( 'seedprod_404_page_id' );

				if ( $v->ID == $coming_soon_id ) {
					$actual_ptype = 'cs';
				} elseif ( $v->ID == $maintenance_id ) {
					$actual_ptype = 'mm';
				} elseif ( $v->ID == $login_id ) {
					$actual_ptype = 'loginp';
				} elseif ( $v->ID == $fourohfour_id ) {
					$actual_ptype = 'p404';
				}
			}

			$entry['ptype'] = base64_encode( $actual_ptype ?? '' ); // phpcs:ignore
		}

		$export['theme'][] = $entry;

		// Find shortcodes in content (for both themes and landing pages)
		if ( ! empty( $processed_html ) ) {
			$post_content_shortcode = base64_decode( base64_encode( $processed_html ) ); // phpcs:ignore
			$re                     = '/((\[)(sp_template_part id="){1}[0-9]*["](\]))/m';
			preg_match_all( $re, $post_content_shortcode, $matches, PREG_SET_ORDER, 0 );

			if ( $matches ) {
				foreach ( $matches as $t => $val ) {
					$shortcode_content                       = $val[0];
					$shortcode_page_sc                       = str_replace( '[sp_template_part id="', '', $shortcode_content );
					$shortcode_page_sc                       = str_replace( '"]', '', $shortcode_page_sc );
					$shortcode_exports[ $shortcode_page_sc ] = array(
						'id'        => $shortcode_page_sc,
						'shortcode' => $shortcode_content,
					);
				}
			}
		}
	}

	return array(
		'export'            => $export,
		'processed_data'    => $processed_data,
		'shortcode_exports' => $shortcode_exports,
	);
}

/*
==============================================
	MAIN EXPORT/IMPORT FUNCTIONS
	============================================== */

/**
 * Export theme files (V2 implementation)
 * Exports all theme templates as a downloadable ZIP file
 */
function seedprod_lite_v2_export_theme_files() {
	// Validate request
	seedprod_lite_v2_validate_ajax_request( 'export' );

	// Initialize filesystem
	$wp_filesystem = seedprod_lite_v2_init_filesystem();

	global $wpdb;
	$tablename      = $wpdb->prefix . 'posts';
	$meta_tablename = $wpdb->prefix . 'postmeta';

	// Get list of theme templates AND pages edited with SeedProd.
	// phpcs:disable Generic.Formatting.MultipleStatementAlignment.NotSameWarning
	$sql      = 'SELECT DISTINCT p.* FROM ' . $tablename . ' p ';
	$sql     .= "LEFT JOIN $meta_tablename pm ON (pm.post_id = p.ID AND pm.meta_key = '_seedprod_is_theme_template') ";
	$sql     .= "LEFT JOIN $meta_tablename pm2 ON (pm2.post_id = p.ID AND pm2.meta_key = '_seedprod_edited_with_seedprod') ";
	$sql     .= "WHERE p.post_status = 'publish' ";
	$sql     .= 'AND ( ';
	$sql     .= "  (p.post_type = 'seedprod' AND pm.meta_value IS NOT NULL) ";  // Theme templates.
	$sql     .= "  OR (p.post_type = 'page' AND pm2.meta_value = '1') ";        // Pages edited with SeedProd.
	$sql     .= ') ';
	// phpcs:enable Generic.Formatting.MultipleStatementAlignment.NotSameWarning
	$results  = $wpdb->get_results( $sql ); // phpcs:ignore

	$processed_data             = array();
	$export                     = array();
	$export['type']             = 'theme-builder';
	$export['current_home_url'] = home_url();
	$export['theme']            = array();
	$export['mapped']           = array();
	$shortcode_exports          = array();

	// Check if this is a template (contains placehold.co)
	$is_template = false;
	foreach ( $results as $k => $v ) {
		if ( strpos( $v->post_content_filtered, 'placehold.co' ) !== false ) {
			$is_template = true;
			break;
		}
	}

	// Process each template
	foreach ( $results as $k => $v ) {
		// Get post meta
		$meta = get_post_meta( $v->ID );
		if ( $is_template === true ) {
			unset( $meta['_seedprod_css'] );
			unset( $meta['_seedprod_html'] );
		}
		$meta = json_encode( $meta ); // phpcs:ignore
		$content          = $v->post_content;
		$content_filtered = $v->post_content_filtered;

		// Process image filenames if not a template
		if ( $is_template === false ) {
			// Include helper functions if needed
			// Image processing handled by V2 function
			$processed_data[ $k ] = seedprod_lite_v2_process_image_filenames( $content_filtered, $content );
		} else {
			$processed_data[ $k ] = array(
				'data'   => $content_filtered,
				'html'   => $content,
				'images' => array(),
			);
		}

		// Determine page category - theme template vs edited page.
		$page_category      = 'theme_template';
		$export_post_type   = 'seedprod';
		$export_post_status = 'publish';

		if ( 'page' === $v->post_type ) {
			$page_category      = 'edited_page';
			$export_post_type   = 'page';
			$export_post_status = $v->post_status;
		}

		$export['theme'][] = array(
			'order'                 => $v->menu_order,
			'post_content'          => base64_encode( $processed_data[ $k ]['html'] ), // phpcs:ignore
			'post_content_filtered' => base64_encode( $processed_data[ $k ]['data'] ), // phpcs:ignore
			'post_title'            => base64_encode( $v->post_title ), // phpcs:ignore
			'meta'                  => base64_encode( $meta ), // phpcs:ignore
			'page_category'         => base64_encode( $page_category ), // phpcs:ignore
			'post_type'             => base64_encode( $export_post_type ), // phpcs:ignore
			'post_status'           => base64_encode( $export_post_status ), // phpcs:ignore
		);

		// Find shortcodes in content
		$post_content_shortcode = base64_decode( base64_encode( $processed_data[ $k ]['html'] ) ); // phpcs:ignore
		$re                     = '/((\[)(sp_template_part id="){1}[0-9]*["](\]))/m';
		preg_match_all( $re, $post_content_shortcode, $matches, PREG_SET_ORDER, 0 );
		if ( $matches ) {
			foreach ( $matches as $t => $val ) {
				$shortcode_content                       = $val[0];
				$shortcode_page_sc                       = str_replace( '[sp_template_part id="', '', $shortcode_content );
				$shortcode_page_sc                       = str_replace( '"]', '', $shortcode_page_sc );
				$shortcode_exports[ $shortcode_page_sc ] = array(
					'id'        => $shortcode_page_sc,
					'shortcode' => $shortcode_content,
				);
			}
		}
	}

	// Map shortcodes
	foreach ( $shortcode_exports as $n => $t ) {
		$page_id            = $t['id'];
		$sql                = "SELECT post_title FROM $tablename WHERE ID = %d";
		$fetch_page_title   = $wpdb->get_var( $wpdb->prepare( $sql, absint( $page_id ) ) ); // phpcs:ignore
		$export['mapped'][] = array(
			'shortcode'  => base64_encode( $t['shortcode'] ), // phpcs:ignore
			'page_title' => $fetch_page_title,
		);
	}

	// Process images for export
	$export_data_images = array();
	foreach ( $processed_data as $p => $v ) {
		foreach ( $v['images'] as $d => $x ) {
			$export_data_images[] = $x;
		}
	}

	// Create export directory
	$upload_dir    = wp_upload_dir();
	$path          = trailingslashit( $upload_dir['basedir'] );
	$webpath       = trailingslashit( $upload_dir['baseurl'] );
	$export_folder = 'seedprod-themes-exports';
	$export_path   = $path . $export_folder;

	// Create export folder if it doesn't exist (don't delete - prevents race condition with concurrent exports).
	if ( ! is_dir( $export_path ) ) {
		wp_mkdir_p( $export_path );
	}

	// Clean up old files (older than 1 hour) to prevent disk bloat.
	// This preserves recent exports from other users while still managing disk space.
	$old_files    = glob( $export_path . '/*' );
	$one_hour_ago = time() - HOUR_IN_SECONDS;
	if ( $old_files ) {
		foreach ( $old_files as $old_file ) {
			if ( filemtime( $old_file ) < $one_hour_ago ) {
				wp_delete_file( $old_file );
			}
		}
	}

	// Save JSON export file
	$json_name   = 'export_theme.json';
	$json_file   = $export_path . '/' . $json_name;
	$export_json = json_encode( $export ); // phpcs:ignore
	$wp_filesystem->put_contents( $json_file, $export_json, FS_CHMOD_FILE );

	// Copy images to export folder
	// Save images locally and track failed ones
	$all_failed_images = array();
	if ( count( $export_data_images ) > 0 ) {
		$failed_images     = seedprod_lite_v2_save_images_locally( $export_data_images );
		$all_failed_images = array_merge( $all_failed_images, $failed_images );
	}

	// Create ZIP file
	if ( ! class_exists( 'ZipArchive' ) ) {
		wp_send_json_error( __( 'ZipArchive class not available. Please contact your host to enable it.', 'coming-soon' ) );
	}

	$zip      = new ZipArchive();
	$zip_name = 'seedprod-theme-export-' . gmdate( 'Y-m-d-H-i-s' ) . '.zip';
	$zip_path = $export_path . '/' . $zip_name;

	if ( $zip->open( $zip_path, ZipArchive::CREATE ) === true ) {
		// Add all files from export folder to zip
		seedprod_lite_v2_zipdir( $export_path, $zip, strlen( "$export_path/" ) );
		$zip->close();

		// Clean up temp files (JSON, images) but keep ZIP for download.
		$json_file_path = $export_path . '/' . $json_name;
		if ( file_exists( $json_file_path ) ) {
			wp_delete_file( $json_file_path );
		}
		// Delete any image files that were copied for export.
		if ( defined( 'GLOB_BRACE' ) ) {
			$image_files = glob( $export_path . '/*.{jpg,jpeg,png,gif,webp,svg}', GLOB_BRACE );
			if ( $image_files ) {
				foreach ( $image_files as $image_file ) {
					wp_delete_file( $image_file );
				}
			}
		} else {
			// Fallback for environments without GLOB_BRACE (e.g. WordPress Playground).
			foreach ( $export_data_images as $image_data ) {
				if ( ! empty( $image_data['filename'] ) ) {
					$image_file_path = $export_path . '/' . $image_data['filename'];
					if ( file_exists( $image_file_path ) ) {
						wp_delete_file( $image_file_path );
					}
				}
			}
		}

		// Read ZIP and base64-encode for inline delivery.
		// This ensures downloads work in all environments including WordPress Playground
		// where the Service Worker intercepts static file requests.
		$zip_contents = file_get_contents( $zip_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		if ( false === $zip_contents ) {
			wp_send_json_error( __( 'Failed to read export file.', 'coming-soon' ) );
		}
		$zip_base64 = base64_encode( $zip_contents ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
		unset( $zip_contents );

		// Clean up ZIP file since it is delivered inline.
		wp_delete_file( $zip_path );

		// Prepare response data.
		$response_data = array(
			'success'  => true,
			'message'  => __( 'Export completed successfully', 'coming-soon' ),
			'filedata' => $zip_base64,
			'filename' => $zip_name,
		);

		// Add warning if some images failed to download.
		if ( ! empty( $all_failed_images ) ) {
			$response_data['warning']       = sprintf(
				/* translators: %d: number of failed images */
				__( '%d images could not be downloaded and were excluded from the export.', 'coming-soon' ),
				count( $all_failed_images )
			);
			$response_data['failed_images'] = count( $all_failed_images );
		}

		wp_send_json_success( $response_data );
	} else {
		wp_send_json_error( __( 'Failed to create ZIP file.', 'coming-soon' ) );
	}
}

/**
 * Import theme files (V2 implementation)
 * Imports theme templates from an uploaded ZIP file
 */
function seedprod_lite_v2_import_theme_files() {
	// Validate request and permissions
	seedprod_lite_v2_validate_ajax_request( 'install_themes' );

	// Check if user wants to delete existing theme templates
	$delete_existing = isset( $_POST['delete_existing'] ) && '1' === $_POST['delete_existing'];

	// Delete existing theme templates if requested
	if ( $delete_existing ) {
		seedprod_lite_v2_delete_theme_templates();
	}

	// Validate and get uploaded file
	$zip_validation = seedprod_lite_v2_validate_zip_upload( 'seedprod_theme_files' );
	if ( is_wp_error( $zip_validation ) ) {
		wp_send_json_error( $zip_validation->get_error_message() );
	}

	// Set script timeout longer
	set_time_limit( 60 );

	// Initialize filesystem
	seedprod_lite_v2_init_filesystem();

	if ( isset( $_FILES['seedprod_theme_files']['name'] ) ) {
		$filename = wp_unslash( $_FILES['seedprod_theme_files']['name'] ); // phpcs:ignore
		$source = $_FILES['seedprod_theme_files']['tmp_name']; // phpcs:ignore

		// Setup directories
		$filename_import = 'seedprod-themes-imports';
		$upload_dir      = wp_upload_dir();
		$path            = trailingslashit( $upload_dir['basedir'] );
		$webpath         = trailingslashit( $upload_dir['baseurl'] );
		$filenoext       = basename( $filename_import, '.zip' );
		$filenoext       = basename( $filenoext, '.ZIP' );
		$targetdir       = $path . $filenoext;
		$targetzip       = $path . $filename;
		$webtargetdir    = $webpath . $filenoext;

		// Remove existing import directory if exists
		if ( is_dir( $targetdir ) ) {
			// Directory cleanup handled by V2 function
			seedprod_lite_v2_recursive_rmdir( $targetdir );
		}

		// Create import directory
		wp_mkdir_p( $targetdir );

		// phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- move_uploaded_file is required for handling HTTP file uploads securely.
		if ( move_uploaded_file( $source, $targetzip ) ) {
			// Extract ZIP file
			$extract_result = seedprod_lite_v2_extract_zip( $targetzip, $targetdir, true );
			if ( is_wp_error( $extract_result ) ) {
				seedprod_lite_v2_recursive_rmdir( $targetdir );
				wp_send_json_error( $extract_result->get_error_message() );
			}

			// Delete the zip file after extraction
			wp_delete_file( $targetzip );

			$theme_json_data     = $targetdir . '/export_theme.json';
			$web_theme_json_data = $webtargetdir . '/export_theme.json';

			// Read and validate theme.json
			$data = seedprod_lite_v2_read_json_file( $theme_json_data, $web_theme_json_data );
			if ( is_wp_error( $data ) ) {
				seedprod_lite_v2_recursive_rmdir( $targetdir );
				wp_send_json_error( $data->get_error_message() );
			}

			// Validate theme type
			if ( ! empty( $data->type ) && 'theme-builder' !== $data->type ) {
				seedprod_lite_v2_recursive_rmdir( $targetdir );
				wp_send_json_error( __( 'This does not appear to be a SeedProd theme.', 'coming-soon' ) );
			}

			// Process the import
			// Process the theme import
			$warnings = seedprod_lite_v2_theme_import_json( $data );

			// Remove the json file for security
			wp_delete_file( $theme_json_data );

			// Clean up import directory
			seedprod_lite_v2_recursive_rmdir( $targetdir );

			wp_send_json_success( array( 'warnings' => is_array( $warnings ) ? $warnings : array() ) );
		} else {
			$message = __( 'There was a problem with the upload. Please try again.', 'coming-soon' );
			wp_send_json_error( $message );
		}
	} else {
		$message = __( 'There was a problem with the upload. Please try again.', 'coming-soon' );
		wp_send_json_error( $message );
	}
}

/**
 * Import theme by URL (V2 implementation)
 * Downloads and imports a theme from a remote URL
 */
function seedprod_lite_v2_import_theme_by_url( $theme_url = null ) {
	// Track if this is a direct AJAX call or internal call. AJAX hook callbacks
	// receive an empty string (not null) for unset args, so check with empty().
	$is_direct_ajax = empty( $theme_url );

	// Helper function to handle errors for both AJAX and internal calls
	$handle_error = function ( $message, $code = 'error' ) use ( $is_direct_ajax ) {
		if ( $is_direct_ajax ) {
			wp_send_json_error( $message );
		} else {
			return new WP_Error( $code, $message );
		}
	};

	// Check if this is an AJAX request or direct call
	if ( $is_direct_ajax ) {
		// Validate request and permissions for AJAX
		seedprod_lite_v2_validate_ajax_request( 'install_themes' );
	} else {
		// Check permissions for direct call
		if ( ! current_user_can( apply_filters( 'seedprod_import_export', 'install_themes' ) ) ) {
			return $handle_error( __( 'Insufficient permissions', 'coming-soon' ), 'insufficient_permissions' );
		}
	}

	// Check if user wants to delete existing theme templates
	$delete_existing = isset( $_REQUEST['delete_existing'] ) && '1' === $_REQUEST['delete_existing'];

	// Delete existing theme templates if requested
	if ( $delete_existing ) {
		seedprod_lite_v2_delete_theme_templates();
	}

	// Initialize filesystem
	seedprod_lite_v2_init_filesystem();

	// esc_url_raw() preserves query-string ampersands for signed CDN URLs.
	$source = isset( $_REQUEST['seedprod_theme_url'] ) ? esc_url_raw( wp_unslash( $_REQUEST['seedprod_theme_url'] ) ) : '';
	if ( ! empty( $theme_url ) ) {
		$source = $theme_url;
	}

	if ( empty( $source ) ) {
		return $handle_error( __( 'No URL provided', 'coming-soon' ), 'no_url' );
	}

	// Enforce HTTPS and reject path traversal to reduce SSRF surface.
	if ( ! seedprod_lite_v2_is_allowed_import_url( $source ) ) {
		return $handle_error( __( 'Invalid import URL. Please enter a valid HTTPS URL.', 'coming-soon' ), 'invalid_url' );
	}

	// Download the file from URL.
	$file_import_url_json = wp_safe_remote_get(
		$source,
		array(
			'sslverify' => false,
			'timeout'   => 60,
		)
	);

	if ( is_wp_error( $file_import_url_json ) ) {
		return $handle_error( $file_import_url_json->get_error_message(), $file_import_url_json->get_error_code() );
	}

	// ZipArchive::open() in seedprod_lite_v2_validate_import_zip() below is the
	// real ZIP check; Content-Type isn't reliable across CDNs.
	$response_code = (int) wp_remote_retrieve_response_code( $file_import_url_json );
	if ( 200 !== $response_code ) {
		return $handle_error(
			sprintf(
				/* translators: %d: HTTP status code returned by the import URL */
				__( 'The import URL returned HTTP %d.', 'coming-soon' ),
				$response_code
			),
			'http_error'
		);
	}

	$body = wp_remote_retrieve_body( $file_import_url_json );
	if ( empty( $body ) ) {
		return $handle_error( __( 'Failed to download file content.', 'coming-soon' ), 'download_failed' );
	}

	// Process the filename
	$url_data = pathinfo( $source );
	$filename = $url_data['basename'];

	// Truncate filename at .zip to remove query parameters (matching old logic)
	if ( strpos( $filename, '.zip' ) !== false ) {
		$filename = substr( $filename, 0, strpos( $filename, '.zip' ) + 4 );
	}

	// Ensure it's a ZIP file
	if ( ! preg_match( '/\.zip$/i', $filename ) ) {
		return $handle_error( __( 'The file you are trying to upload is not a .zip file. Please try again.', 'coming-soon' ), 'not_zip' );
	}

	// Setup directories
	$filename_import = 'seedprod-themes-imports';
	$upload_dir      = wp_upload_dir();
	$path            = trailingslashit( $upload_dir['basedir'] );
	$webpath         = trailingslashit( $upload_dir['baseurl'] );
	$filenoext       = basename( $filename_import, '.zip' );
	$filenoext       = basename( $filenoext, '.ZIP' );
	$targetdir       = $path . $filenoext;
	$targetzip       = $path . $filename;
	$webtargetdir    = $webpath . $filenoext;

	// Remove existing import directory if exists
	if ( is_dir( $targetdir ) ) {
		// Clean up existing directory
		seedprod_lite_v2_recursive_rmdir( $targetdir );
	}

	// Create import directory
	wp_mkdir_p( $targetdir );

	// Save the ZIP file
	if ( ! file_put_contents( $targetzip, $body ) ) { // phpcs:ignore
		seedprod_lite_v2_recursive_rmdir( $targetdir );
		return $handle_error( __( 'There was a problem saving the file. Please try again.', 'coming-soon' ), 'save_failed' );
	}

	// Validate zip contents
	$validation_result = seedprod_lite_v2_validate_import_zip( $targetzip, true );
	if ( is_wp_error( $validation_result ) ) {
		wp_delete_file( $targetzip );
		seedprod_lite_v2_recursive_rmdir( $targetdir );
		return $handle_error( $validation_result->get_error_message(), $validation_result->get_error_code() );
	}

	// Extract ZIP file
	$extract_result = seedprod_lite_v2_extract_zip( $targetzip, $targetdir, true );
	if ( is_wp_error( $extract_result ) ) {
		wp_delete_file( $targetzip );
		seedprod_lite_v2_recursive_rmdir( $targetdir );
		return $handle_error( $extract_result->get_error_message(), $extract_result->get_error_code() );
	}

	// Delete the zip file after extraction
	wp_delete_file( $targetzip );

	// Read and validate theme.json
	$theme_json_path = $targetdir . '/export_theme.json';
	$url_path        = $webtargetdir . '/export_theme.json';

	$data = seedprod_lite_v2_read_json_file( $theme_json_path, $url_path );
	if ( is_wp_error( $data ) ) {
		seedprod_lite_v2_recursive_rmdir( $targetdir );
		return $handle_error( $data->get_error_message(), $data->get_error_code() );
	}

	// Validate theme type
	if ( ! empty( $data->type ) && 'theme-builder' !== $data->type ) {
		seedprod_lite_v2_recursive_rmdir( $targetdir );
		return $handle_error( __( 'This does not appear to be a SeedProd theme.', 'coming-soon' ), 'invalid_theme' );
	}

	// Process the import
	// Process the theme import
	$warnings = seedprod_lite_v2_theme_import_json( $data );
	$warnings = is_array( $warnings ) ? $warnings : array();

	// Remove the json file for security
	wp_delete_file( $theme_json_path );

	// Clean up import directory
	seedprod_lite_v2_recursive_rmdir( $targetdir );

	// Only send JSON if this is a direct AJAX call, not an internal call
	if ( $is_direct_ajax ) {
		wp_send_json_success( array( 'warnings' => $warnings ) );
	} else {
		// Return success for internal calls
		return array(
			'success'  => true,
			'warnings' => $warnings,
		);
	}
}

/**
 * Export landing pages (V2 implementation)
 * Exports all landing pages as a downloadable ZIP file
 */
function seedprod_lite_v2_export_landing_pages() {

	// Validate request and permissions
	try {
		seedprod_lite_v2_validate_ajax_request( 'export' );
	} catch ( Exception $e ) {
		wp_send_json_error( 'Validation failed: ' . $e->getMessage() );
	}

	// Initialize filesystem
	try {
		seedprod_lite_v2_init_filesystem();
	} catch ( Exception $e ) {
		wp_send_json_error( 'Filesystem initialization failed: ' . $e->getMessage() );
	}

	$page_id = isset( $_POST['page_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['page_id'] ) ) ) : 0;
	$ptype   = isset( $_POST['ptype'] ) ? sanitize_text_field( wp_unslash( $_POST['ptype'] ) ) : null;

	global $wpdb;
	$tablename      = $wpdb->prefix . 'posts';
	$meta_tablename = $wpdb->prefix . 'postmeta';

	// Get list of landing pages - EXACT same logic as dropdown query
	// Get special page IDs (Coming Soon, Maintenance Mode, Login, 404)
	$coming_soon_id = get_option( 'seedprod_coming_soon_page_id' );
	$maintenance_id = get_option( 'seedprod_maintenance_mode_page_id' );
	$login_id       = get_option( 'seedprod_login_page_id' );
	$fourohfour_id  = get_option( 'seedprod_404_page_id' );

	$special_page_ids = array();
	if ( ! empty( $coming_soon_id ) ) {
		$special_page_ids[] = $coming_soon_id;
	}
	if ( ! empty( $maintenance_id ) ) {
		$special_page_ids[] = $maintenance_id;
	}
	if ( ! empty( $login_id ) ) {
		$special_page_ids[] = $login_id;
	}
	if ( ! empty( $fourohfour_id ) ) {
		$special_page_ids[] = $fourohfour_id;
	}

	$special_pages_condition = '';
	if ( ! empty( $special_page_ids ) ) {
		$special_ids_string      = implode( ',', array_map( 'intval', $special_page_ids ) );
		$special_pages_condition = " OR p.ID IN ($special_ids_string)";
	}

	$sql  = "SELECT p.*, pm.meta_key, pm.meta_value FROM $tablename p ";
	$sql .= "LEFT JOIN $meta_tablename pm ON (pm.post_id = p.ID AND pm.meta_key = '_seedprod_page_uuid') ";
	$sql .= "LEFT JOIN $meta_tablename pm4 ON (pm4.post_id = p.ID AND pm4.meta_key = '_seedprod_page_template_type') ";
	$sql .= "LEFT JOIN $meta_tablename pm5 ON (pm5.post_id = p.ID AND pm5.meta_key = '_seedprod_page') ";
	$sql .= "WHERE p.post_status != 'trash' ";
	$sql .= "AND p.post_type IN ('page', 'seedprod') ";
	$sql .= "AND ((p.post_type = 'page' AND pm4.meta_value = 'lp') OR (p.post_type = 'page' AND pm5.meta_value IS NOT NULL) $special_pages_condition) ";
	$sql .= "AND (pm.meta_value IS NOT NULL OR pm5.meta_value IS NOT NULL $special_pages_condition) ";

	if ( 0 !== $page_id ) {
		$sql .= "AND p.ID = $page_id ";
	}

	$sql .= 'GROUP BY p.ID ';
	$sql .= 'ORDER BY p.post_date DESC';

	$results = $wpdb->get_results( $sql ); // phpcs:ignore

	if ( $wpdb->last_error ) {
		wp_send_json_error( 'Database error: ' . $wpdb->last_error );
	}

	// Check if we have any pages to export
	if ( empty( $results ) ) {
		wp_send_json_error( __( 'No landing pages found to export.', 'coming-soon' ) );
	}

	// Wrap in try-catch to catch any errors
	try {
		// Process export data using helper function
		$export_result = seedprod_lite_v2_process_export_data( $results, 'page', $ptype );

		if ( is_wp_error( $export_result ) ) {
			wp_send_json_error( $export_result->get_error_message() );
		}

		// Validate export result structure
		if ( ! isset( $export_result['export'] ) || ! isset( $export_result['processed_data'] ) || ! isset( $export_result['shortcode_exports'] ) ) {
			wp_send_json_error( __( 'Export data structure is invalid.', 'coming-soon' ) );
		}

		$export            = $export_result['export'];
		$processed_data    = $export_result['processed_data'];
		$shortcode_exports = $export_result['shortcode_exports'];

	} catch ( Exception $e ) {
		/* translators: %s: Error message from the exception */
		wp_send_json_error( sprintf( __( 'Export failed during processing: %s', 'coming-soon' ), $e->getMessage() ) );
	}

	// Process shortcode mapping for landing pages (restored from old logic)
	foreach ( $shortcode_exports as $k => $sc_val ) {
		$shortcode_page_id = $sc_val['id'];  // Use different variable name to avoid overwriting original $page_id
		$page_shortcode    = $sc_val['shortcode'];

		$sql      = "SELECT p.post_title FROM $tablename p LEFT JOIN $meta_tablename pm ON (pm.post_id = p.ID)";
		$sql     .= " WHERE p.ID = %d and post_status != 'trash' AND post_type IN ('page','seedprod') AND meta_key = '_seedprod_page_uuid' ";
		$safe_sql = $wpdb->prepare( $sql, absint( $shortcode_page_id ) ); // phpcs:ignore
		$page     = $wpdb->get_row( $safe_sql ); // phpcs:ignore

		if ( ! empty( $page ) ) {
			$export['mapped'][] = array(
				'id'         => $shortcode_page_id,
				'shortcode'  => base64_encode( $page_shortcode ),// phpcs:ignore
				'page_title' => $page->post_title,
			);
		}
	}

	$export_json = wp_json_encode( $export );

	// Check if JSON encoding failed
	if ( false === $export_json ) {
		$json_error = json_last_error_msg();
		wp_send_json_error( __( 'Failed to encode export data. The page data may be too large or contain invalid characters.', 'coming-soon' ) . ' Error: ' . $json_error );
	}

	$files_to_download = array();

	global $wp_filesystem;
	$upload_dir = wp_upload_dir();
	$path       = trailingslashit( $upload_dir['basedir'] ) . 'seedprod-themes-exports/';
	$targetdir  = $path;

	// Create directory if it doesn't exist (don't delete - prevents race condition with concurrent exports).
	if ( ! is_dir( $targetdir ) ) {
		if ( ! wp_mkdir_p( $targetdir ) ) {
			wp_send_json_error( __( 'Failed to create export directory.', 'coming-soon' ) );
		}
	}

	// Clean up old files (older than 1 hour) to prevent disk bloat.
	// This preserves recent exports from other users while still managing disk space.
	$old_files    = glob( $targetdir . '*' );
	$one_hour_ago = time() - HOUR_IN_SECONDS;
	if ( $old_files ) {
		foreach ( $old_files as $old_file ) {
			if ( filemtime( $old_file ) < $one_hour_ago ) {
				wp_delete_file( $old_file );
			}
		}
	}

	// Save images locally
	$all_failed_images = array();

	// Process images
	try {
		foreach ( $processed_data as $k1 => $v1 ) {
			// Check if images key exists and has items
			if ( isset( $processed_data[ $k1 ]['images'] ) && is_array( $processed_data[ $k1 ]['images'] ) && count( $processed_data[ $k1 ]['images'] ) > 0 ) {
				$failed_images = seedprod_lite_v2_save_images_locally( $processed_data[ $k1 ]['images'] );

				if ( is_array( $failed_images ) ) {
					$all_failed_images = array_merge( $all_failed_images, $failed_images );
				}
			}

			// Only add successfully downloaded images to the zip
			if ( isset( $processed_data[ $k1 ]['images'] ) && is_array( $processed_data[ $k1 ]['images'] ) ) {
				foreach ( $processed_data[ $k1 ]['images'] as $image ) {
					if ( ! isset( $image['filename'] ) ) {
						continue;
					}

					$image_path = trailingslashit( $upload_dir['basedir'] ) . 'seedprod-themes-exports/' . $image['filename'];

					if ( file_exists( $image_path ) ) {
						$files_to_download[] = $image['filename'];
					}
				}
			}
		}
	} catch ( Exception $e ) {
		/* translators: %s: Error message from the exception */
		wp_send_json_error( sprintf( __( 'Export failed during image processing: %s', 'coming-soon' ), $e->getMessage() ) );
	}

	// Create zip and return download URL
	try {
		$zip_result = seedprod_lite_v2_prepare_zip( $files_to_download, $export_json, 'page' );

		if ( $zip_result && isset( $zip_result['filedata'] ) ) {
			wp_send_json_success( $zip_result );
		} else {
			wp_send_json_error( __( 'Export completed but file data not generated.', 'coming-soon' ) );
		}
	} catch ( Exception $e ) {
		/* translators: %s: Error message from the exception */
		wp_send_json_error( sprintf( __( 'Export failed during ZIP creation: %s', 'coming-soon' ), $e->getMessage() ) );
	}

	exit;
}

/**
 * Import landing pages (V2 implementation)
 * Imports landing pages from an uploaded ZIP file
 */
function seedprod_lite_v2_import_landing_pages() {
	// Validate request and permissions
	seedprod_lite_v2_validate_ajax_request( 'install_themes' );

	// Validate and get uploaded file
	$zip_validation = seedprod_lite_v2_validate_zip_upload( 'seedprod_landing_files' );
	if ( is_wp_error( $zip_validation ) ) {
		wp_send_json_error( $zip_validation->get_error_message() );
	}

	// Initialize filesystem
	// Note: V2 uses direct filesystem access because this is an AJAX handler
	// and request_filesystem_credentials() doesn't work properly in AJAX context
	seedprod_lite_v2_init_filesystem();

	if ( isset( $_FILES['seedprod_landing_files']['name'] ) ) {
		$filename = wp_unslash( $_FILES['seedprod_landing_files']['name'] ); // phpcs:ignore
		$source = $_FILES['seedprod_landing_files']['tmp_name']; // phpcs:ignore

		// Setup directories
		$filename_import = 'seedprod-themes-imports';
		$upload_dir      = wp_upload_dir();
		$path            = trailingslashit( $upload_dir['basedir'] );
		$path_baseurl    = trailingslashit( $upload_dir['baseurl'] );
		$filenoext       = basename( $filename_import, '.zip' );
		$filenoext       = basename( $filenoext, '.ZIP' );
		$targetdir       = $path . $filenoext;
		$targetzip       = $path . $filename;
		$target_url      = $path_baseurl . $filenoext;

		// Remove existing import directory if exists
		if ( is_dir( $targetdir ) ) {
			// Directory cleanup handled by V2 function
			seedprod_lite_v2_recursive_rmdir( $targetdir );
		}

		// Create import directory
		wp_mkdir_p( $targetdir );

		// phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- move_uploaded_file is required for handling HTTP file uploads securely.
		if ( move_uploaded_file( $source, $targetzip ) ) {
			// Validate zip contents
			$validation_result = seedprod_lite_v2_validate_import_zip( $targetzip, false );
			if ( is_wp_error( $validation_result ) ) {
				wp_delete_file( $targetzip );
				seedprod_lite_v2_recursive_rmdir( $targetdir );
				wp_send_json_error( $validation_result->get_error_message() );
			}

			// Extract ZIP file
			$extract_result = seedprod_lite_v2_extract_zip( $targetzip, $targetdir, false );
			if ( is_wp_error( $extract_result ) ) {
				wp_delete_file( $targetzip );
				seedprod_lite_v2_recursive_rmdir( $targetdir );
				wp_send_json_error( $extract_result->get_error_message() );
			}

			// Delete the zip file after extraction
			wp_delete_file( $targetzip );

			// Read and validate landing page data
			$theme_json_path = $targetdir . '/export_page.json';
			$url_path        = $target_url . '/export_page.json';

			$data = seedprod_lite_v2_read_json_file( $theme_json_path, $url_path );
			if ( is_wp_error( $data ) ) {
				seedprod_lite_v2_recursive_rmdir( $targetdir );
				wp_send_json_error( $data->get_error_message() );
			}

			// Validate landing page type
			if ( ! empty( $data->type ) && 'landing-page' !== $data->type ) {
				seedprod_lite_v2_recursive_rmdir( $targetdir );
				wp_send_json_error( __( 'This does not appear to be a SeedProd landing page.', 'coming-soon' ) );
			}

			// Process the import
			// Process the landing page import
			$import_result = seedprod_lite_v2_landing_import_json( $data );
			$warnings      = isset( $import_result['warnings'] ) && is_array( $import_result['warnings'] )
				? $import_result['warnings']
				: array();

			// Remove the json file for security
			wp_delete_file( $theme_json_path );

			// Clean up import directory
			seedprod_lite_v2_recursive_rmdir( $targetdir );

			wp_send_json_success( array( 'warnings' => $warnings ) );
		} else {
			$message = __( 'There was a problem with the upload. Please try again.', 'coming-soon' );
			wp_send_json_error( $message );
		}
	} else {
		wp_send_json_error( __( 'Invalid file upload.', 'coming-soon' ) );
	}
}

/**
 * Check if theme templates exist (AJAX handler)
 */
function seedprod_lite_v2_check_existing_theme() {
	// Validate request
	seedprod_lite_v2_validate_ajax_request( 'install_themes' );

	$has_templates = seedprod_lite_v2_has_theme_templates();

	wp_send_json_success( array( 'has_templates' => $has_templates ) );
}