KOKINIO - MANAGER
Edit File: st-option-importer.php
<?php /** * Starter Templates Options Importer - Module. * * This file is used to register and manage the Zip AI Modules. * * @package Starter Templates Importer */ namespace STImporter\Importer; use STImporter\Importer\ST_Importer_Helper; use STImporter\Importer\ST_Importer_Log; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * The Module Class. */ class ST_Option_Importer { /** * Instance of this class. * * @since 1.0.0 * @var self Class object. */ private static $instance = null; /** * Images IDs * * @var array<int, int> The Array of already image IDs. * @since 1.0.0 */ private static $already_imported_ids = array(); // @phpstan-ignore-line /** * Initiator of this class. * * @since 1.0.0 * @return self initialized object of this class. */ public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Site Options * * @since 1.0.0 * * @return array<int, string> List of defined array. */ public static function site_options() { return apply_filters( 'st_importer_site_options', array( 'custom_logo', 'nav_menu_locations', 'show_on_front', 'page_on_front', 'page_for_posts', 'site_title', // Astra Theme Global Color Palette and Typography Preset options. 'astra-color-palettes', 'astra-typography-presets', // Plugin: Elementor. 'elementor_container_width', 'elementor_cpt_support', 'elementor_css_print_method', 'elementor_default_generic_fonts', 'elementor_disable_color_schemes', 'elementor_disable_typography_schemes', 'elementor_editor_break_lines', 'elementor_exclude_user_roles', 'elementor_global_image_lightbox', 'elementor_page_title_selector', 'elementor_scheme_color', 'elementor_scheme_color-picker', 'elementor_scheme_typography', 'elementor_space_between_widgets', 'elementor_stretched_section_container', 'elementor_load_fa4_shim', 'elementor_active_kit', 'elementor_experiment-container', ) ); } /** * Get post from post title and post type. * * @since 1.0.0 * * @param mixed $post_title post title. * @param string $post_type post type. * @return mixed */ public static function get_page_by_title( $post_title, $post_type ) { $page = array(); $query = new \WP_Query( array( 'post_type' => $post_type, 'title' => $post_title, 'posts_per_page' => 1, 'no_found_rows' => true, 'ignore_sticky_posts' => true, 'update_post_term_cache' => false, 'update_post_meta_cache' => false, 'orderby' => 'ID', 'order' => 'DESC', ) ); if ( $query->have_posts() ) { $page = $query->posts[0]; } return $page; } /** * Update post option * * @since 1.0.0 * * @param string $option_name Option name. * @param mixed $option_value Option value. * @return void */ public static function update_page_id_by_option_value( $option_name, $option_value ) { if ( empty( $option_value ) ) { ST_Importer_Log::add( 'warning', 'Empty option value provided, skipping page ID update', array( 'option_name' => $option_name, ) ); return; } ST_Importer_Log::add( 'info', 'Looking up page by title for option update', array( 'option_name' => $option_name, 'option_value' => $option_value, ) ); $page = self::get_page_by_title( $option_value, 'page' ); if ( is_object( $page ) && isset( $page->ID ) ) { update_option( $option_name, $page->ID ); ST_Importer_Log::add( 'success', 'Page ID updated successfully in options', array( 'option_name' => $option_name, 'page_title' => $option_value, 'page_id' => $page->ID, ) ); } else { ST_Importer_Log::add( 'error', 'Page not found for option update', array( 'option_name' => $option_name, 'page_title' => $option_value, ) ); } } /** * In WP nav menu is stored as ( 'menu_location' => 'menu_id' ); * In export we send 'menu_slug' like ( 'menu_location' => 'menu_slug' ); * In import we set 'menu_id' from menu slug like ( 'menu_location' => 'menu_id' ); * * @since 1.0.0 * @param array<string, mixed> $nav_menu_locations Array of nav menu locations. * * @return void */ public static function set_nav_menu_locations( $nav_menu_locations = array() ) { $menu_locations = array(); ST_Importer_Log::add( 'info', 'Starting navigation menu location mapping', array( 'total_locations' => count( $nav_menu_locations ), ) ); // Update menu locations. if ( is_array( $nav_menu_locations ) ) { $successful_mappings = 0; $failed_locations = array(); foreach ( $nav_menu_locations as $menu => $value ) { $term = get_term_by( 'slug', $value, 'nav_menu' ); if ( is_object( $term ) && property_exists( $term, 'term_id' ) ) { $menu_locations[ $menu ] = $term->term_id; $successful_mappings++; ST_Importer_Log::add( 'info', 'Menu location mapped successfully', array( 'location' => $menu, 'slug' => $value, 'menu_id' => $term->term_id, ) ); } else { $failed_locations[] = $menu; ST_Importer_Log::add( 'warning', 'Menu not found for location', array( 'location' => $menu, 'slug' => $value, ) ); } } set_theme_mod( 'nav_menu_locations', $menu_locations ); ST_Importer_Log::add( 'success', 'Navigation menu location mapping completed', array( 'successful_mappings' => $successful_mappings, 'failed_locations' => $failed_locations, 'total_locations' => count( $nav_menu_locations ), ) ); } else { // @phpstan-ignore-line - else branch is reachable despite PHPDoc ST_Importer_Log::add( 'warning', 'No navigation menu locations provided for mapping', array() ); } } /** * Insert Logo By URL * * @since 1.0.0 * @param string $image_url Logo URL. * @return void */ public static function insert_logo( $image_url = '' ) { ST_Importer_Log::add( 'info', 'Starting logo insertion process', array( 'url' => $image_url, ) ); $downloaded_image = self::import( array( 'url' => $image_url, 'id' => 0, ) ); if ( $downloaded_image['id'] ) { ST_Importer_Helper::track_post( $downloaded_image['id'] ); set_theme_mod( 'custom_logo', $downloaded_image['id'] ); ST_Importer_Log::add( 'success', 'Logo inserted successfully and set as custom logo', array( 'url' => $image_url, 'attachment_id' => $downloaded_image['id'], 'logo_url' => $downloaded_image['url'] ?? '', ) ); } else { ST_Importer_Log::add( 'error', 'Failed to insert logo: no attachment ID returned', array( 'url' => $image_url, ) ); } } /** * Import Image * * @since 1.0.0 * @param array<string, mixed> $attachment Attachment array. * @return array<string, mixed> Attachment array. * * @throws \Exception Exception that is catched. */ public static function import( $attachment ) { if ( isset( $attachment['url'] ) && ! self::is_valid_image_url( $attachment['url'] ) ) { ST_Importer_Log::add( 'error', 'Invalid image URL provided for import', array( 'url' => $attachment['url'] ?? '', // @phpstan-ignore-line ) ); return $attachment; } $image_url = $attachment['url'] ?? ''; ST_Importer_Log::add( 'info', 'Starting image download process', array( 'url' => $image_url, 'sha1' => ST_Importer_Helper::get_hash_image( $image_url ), ) ); $saved_image = self::get_saved_image( $attachment ); if ( $saved_image['status'] ) { ST_Importer_Log::add( 'success', 'Image already imported, using existing attachment', array( 'url' => $image_url, 'attachment_id' => $saved_image['attachment']['id'] ?? 0, ) ); return $saved_image['attachment']; } $file_content = wp_remote_retrieve_body( wp_safe_remote_get( $attachment['url'], array( 'timeout' => 60, 'sslverify' => false, ) ) ); // Empty file content? if ( empty( $file_content ) ) { ST_Importer_Log::add( 'error', 'Failed to download image: empty file content', array( 'url' => $image_url, 'file_size' => 0, ) ); return $attachment; } ST_Importer_Log::add( 'info', 'Image downloaded successfully', array( 'url' => $image_url, 'file_size' => strlen( $file_content ), ) ); // Extract the file name and extension from the URL. $filename = basename( $attachment['url'] ); $upload = wp_upload_bits( $filename, null, $file_content ); $post = array( 'post_title' => $filename, 'guid' => $upload['url'], ); $info = wp_check_filetype( $upload['file'] ); if ( is_array( $info ) && ! empty( $info['type'] ) ) { $post['post_mime_type'] = $info['type']; } else { // For now just return the origin attachment. ST_Importer_Log::add( 'error', 'Unable to determine file type for uploaded image', array( 'filename' => $filename, 'filepath' => $upload['file'] ?? '', // @phpstan-ignore-line ) ); return $attachment; } $post_id = wp_insert_attachment( $post, $upload['file'] ); try { if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) { include ABSPATH . 'wp-admin/includes/image.php'; } wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) ); } catch ( \Exception $e ) { ST_Importer_Log::add( 'error', 'Failed to generate attachment metadata', array( 'attachment_id' => $post_id, 'error_message' => $e->getMessage(), ) ); throw $e; } update_post_meta( $post_id, '_astra_sites_image_hash', ST_Importer_Helper::get_hash_image( $attachment['url'] ) ); ST_Importer_Helper::track_post( $post_id ); $new_attachment = array( 'id' => $post_id, 'url' => $upload['url'], ); self::$already_imported_ids[] = $post_id; ST_Importer_Log::add( 'success', 'Image imported successfully', array( 'url' => $image_url, 'attachment_id' => $post_id, 'attachment_url' => $upload['url'], 'filename' => $filename, ) ); return $new_attachment; } /** * Get Saved Image. * * @since 1.0.0 * @param array<string, mixed> $attachment Attachment Data. * @return array<string, mixed> Hash string. */ public static function get_saved_image( $attachment ) { if ( apply_filters( 'astra_sites_image_importer_skip_image', false, $attachment ) ) { $url = $attachment['url'] ?? ''; ST_Importer_Log::add( 'warning', 'Image import skipped by filter', array( 'url' => $url, ) ); return array( 'status' => true, 'attachment' => $attachment, ); } global $wpdb; $url = $attachment['url'] ?? ''; ST_Importer_Log::add( 'info', 'Looking up image in database by SHA1 hash', array( 'url' => $url, 'sha1' => ST_Importer_Helper::get_hash_image( $url ), ) ); // 1. Is already imported in Batch Import Process? $post_id = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We are checking if this image is already processed. WO_Query would have been overkill. $wpdb->prepare( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = \'_astra_sites_image_hash\' AND `meta_value` = %s ;', ST_Importer_Helper::get_hash_image( $url ) ) ); // 2. Is image already imported though XML? if ( empty( $post_id ) ) { // Get file name without extension. // To check it exist in attachment. $filename = basename( $url ); ST_Importer_Log::add( 'info', 'SHA1 hash lookup failed, trying filename lookup', array( 'filename' => $filename, ) ); // Find the attachment by meta value. // Code reused from Elementor plugin. $post_id = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We are checking if this attachment is already processed. WO_Query would have been overkill. $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s", '%/' . $filename . '%' ) ); } if ( $post_id ) { $new_attachment = array( 'id' => $post_id, 'url' => wp_get_attachment_url( $post_id ), ); self::$already_imported_ids[] = $post_id; ST_Importer_Log::add( 'success', 'Existing image found in database', array( 'url' => $url, 'attachment_id' => $post_id, 'attachment_url' => $new_attachment['url'], ) ); return array( 'status' => true, 'attachment' => $new_attachment, ); } ST_Importer_Log::add( 'info', 'No existing image found, will proceed with download', array( 'url' => $url, ) ); return array( 'status' => false, 'attachment' => $attachment, ); } /** * Check if valid image url. * * @since 1.0.0 * @param string $link image URL. * @return int|bool */ public static function is_valid_image_url( $link = '' ) { return preg_match( '/^((https?:\/\/)|(www\.))([a-z0-9-].?)+(:[0-9]+)?\/[\w\-\@]+\.(jpg|png|gif|jpeg|svg)\/?$/i', $link ); } }