' . esc_html__( 'Settings', '365i-performance-optimizer' ) . ''; array_unshift( $links, $settings_link ); return $links; } /** * Seed defaults on activation without overwriting existing settings. * * @return void */ public static function activate() { if ( ! get_option( I365_PO_OPTION_KEY ) ) { add_option( I365_PO_OPTION_KEY, self::defaults() ); } } /** * Load dependencies and kick off features. * * @return void */ public static function bootstrap() { require_once I365_PO_PLUGIN_DIR . 'includes/class-i365-po-settings.php'; require_once I365_PO_PLUGIN_DIR . 'includes/class-i365-po-frontend.php'; require_once I365_PO_PLUGIN_DIR . 'includes/class-i365-po-dashboard-widget.php'; require_once I365_PO_PLUGIN_DIR . 'includes/class-i365-po-woocommerce.php'; require_once I365_PO_PLUGIN_DIR . 'includes/class-i365-po-database.php'; require_once I365_PO_PLUGIN_DIR . 'includes/class-i365-po-font-detection.php'; require_once I365_PO_PLUGIN_DIR . 'includes/class-i365-po-local-fonts.php'; require_once I365_PO_PLUGIN_DIR . 'includes/class-i365-po-meta-box.php'; I365_PO_Settings::init(); I365_PO_Frontend::init(); I365_PO_Dashboard_Widget::init(); I365_PO_WooCommerce::init(); I365_PO_Database::init(); I365_PO_Local_Fonts::init(); I365_PO_Meta_Box::init(); } /** * Default settings. * * @return array */ public static function defaults() { return array( 'enable_speculation' => true, 'speculation_mode' => 'auto', 'speculation_eagerness' => 'eager', 'speculation_exclusions' => array(), 'enable_preload' => true, 'preconnect_hosts' => array_values( array_filter( array_unique( array( home_url(), 'https://fonts.gstatic.com', ) ) ) ), 'preload_stylesheet' => get_stylesheet_uri(), 'preload_font' => '', 'preload_hero' => '', 'hero_preload_on_front' => true, 'remove_emoji' => true, 'disable_embeds' => true, 'defer_scripts' => true, 'excluded_defer_handles' => array( 'elementor', 'jquery', 'wp-' ), 'disable_xmlrpc' => true, 'remove_rest_link' => true, 'remove_oembed_link' => true, 'lcp_priority' => true, 'disable_front_lazy' => true, 'enable_detect_log' => false, // JavaScript Delay settings. 'delay_js_enabled' => false, 'delay_js_timeout' => 5000, 'delay_js_exclude' => array( 'jquery', 'jquery-core', 'jquery-migrate', 'elementor', 'wp-' ), // Heartbeat control settings. 'heartbeat_behavior' => 'default', 'heartbeat_interval' => 60, // Query string removal. 'remove_query_strings' => false, // WooCommerce conditional loading. 'wc_conditional_enabled' => false, 'wc_disable_cart_fragments' => true, 'wc_disable_styles' => true, 'wc_disable_blocks_styles' => true, // Database cleanup settings. 'db_cleanup_enabled' => false, 'db_cleanup_schedule' => 'weekly', 'db_revisions_keep' => 5, // Local Google Fonts settings. 'local_fonts_enabled' => false, 'local_fonts_display' => 'swap', 'local_fonts_preload' => true, ); } /** * Get merged settings. * * @return array */ public static function get_settings() { if ( null === self::$settings ) { // Migrate old option key if present. $stored = get_option( I365_PO_OPTION_KEY, array() ); $legacy = get_option( 'wppo_settings', null ); if ( empty( $stored ) && is_array( $legacy ) ) { update_option( I365_PO_OPTION_KEY, $legacy ); $stored = $legacy; } self::$settings = wp_parse_args( $stored, self::defaults() ); } return self::$settings; } /** * Persist new settings in memory to avoid stale reads. * * @param array $settings Clean settings array. * * @return void */ public static function update_settings_cache( $settings ) { self::$settings = $settings; } /** * Sanitize settings coming from the form. * * @param array $input Raw input. * * @return array */ public static function sanitize_settings( $input ) { $input = is_array( $input ) ? $input : array(); $defaults = self::defaults(); $output = array(); $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; $is_reset = ! empty( $_POST['i365_po_reset_defaults'] ); // Always validate the nonce to satisfy plugin-check expectations. if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'i365_po_settings_group-options' ) ) { add_settings_error( 'i365_po_messages', 'i365_po_nonce', __( 'Security check failed.', '365i-performance-optimizer' ), 'error' ); return get_option( I365_PO_OPTION_KEY, $defaults ); } // Create a backup of current settings before making changes. self::backup_current_settings(); if ( $is_reset ) { add_settings_error( 'i365_po_messages', 'i365_po_reset', __( 'Settings restored to defaults. Previous settings backed up.', '365i-performance-optimizer' ), 'updated' ); self::update_settings_cache( $defaults ); return $defaults; } $output['enable_speculation'] = ! empty( $input['enable_speculation'] ); $speculation_mode = sanitize_text_field( $input['speculation_mode'] ?? '' ); $output['speculation_mode'] = in_array( $speculation_mode, array( 'auto', 'prerender', 'prefetch' ), true ) ? $speculation_mode : $defaults['speculation_mode']; $speculation_eagerness = sanitize_text_field( $input['speculation_eagerness'] ?? '' ); $output['speculation_eagerness'] = in_array( $speculation_eagerness, array( 'eager', 'moderate', 'conservative' ), true ) ? $speculation_eagerness : $defaults['speculation_eagerness']; $output['speculation_exclusions'] = array(); if ( ! empty( $input['speculation_exclusions'] ) ) { $exclusions_input = $input['speculation_exclusions']; if ( is_array( $exclusions_input ) ) { $exclusions = array_filter( $exclusions_input ); } else { $exclusions = preg_split( '/[\r\n,]+/', $exclusions_input, -1, PREG_SPLIT_NO_EMPTY ); } foreach ( (array) $exclusions as $path ) { $path = sanitize_text_field( $path ); $path = '/' . ltrim( trim( $path ), '/' ); $output['speculation_exclusions'][] = $path; } } $output['enable_preload'] = ! empty( $input['enable_preload'] ); $hosts_input = $input['preconnect_hosts'] ?? ''; if ( is_array( $hosts_input ) ) { $hosts = array_filter( array_map( 'trim', $hosts_input ) ); } else { $hosts = array_filter( array_map( 'trim', preg_split( '/[\r\n,]+/', $hosts_input, -1, PREG_SPLIT_NO_EMPTY ) ) ); } $output['preconnect_hosts'] = array(); foreach ( $hosts as $host ) { $sanitized = esc_url_raw( $host ); if ( ! empty( $sanitized ) ) { $output['preconnect_hosts'][] = $sanitized; } } $output['preload_stylesheet'] = ! empty( $input['preload_stylesheet'] ) ? esc_url_raw( $input['preload_stylesheet'] ) : ''; $output['preload_font'] = ! empty( $input['preload_font'] ) ? esc_url_raw( $input['preload_font'] ) : ''; $output['preload_hero'] = ! empty( $input['preload_hero'] ) ? esc_url_raw( $input['preload_hero'] ) : ''; $output['hero_preload_on_front'] = ! empty( $input['hero_preload_on_front'] ); $output['remove_emoji'] = ! empty( $input['remove_emoji'] ); $output['disable_embeds'] = ! empty( $input['disable_embeds'] ); $output['defer_scripts'] = ! empty( $input['defer_scripts'] ); $handles_input = $input['excluded_defer_handles'] ?? ''; if ( is_array( $handles_input ) ) { $handles_raw = array_filter( $handles_input ); } else { $handles_raw = preg_split( '/[\r\n,]+/', $handles_input, -1, PREG_SPLIT_NO_EMPTY ); } $handles = array(); if ( is_array( $handles_raw ) ) { foreach ( $handles_raw as $handle ) { $handle = sanitize_text_field( $handle ); if ( '' !== $handle ) { $handles[] = $handle; } } } $output['excluded_defer_handles'] = $handles; $output['disable_xmlrpc'] = ! empty( $input['disable_xmlrpc'] ); $output['remove_rest_link'] = ! empty( $input['remove_rest_link'] ); $output['remove_oembed_link'] = ! empty( $input['remove_oembed_link'] ); $output['lcp_priority'] = ! empty( $input['lcp_priority'] ); $output['disable_front_lazy'] = ! empty( $input['disable_front_lazy'] ); $output['enable_detect_log'] = ! empty( $input['enable_detect_log'] ); // JavaScript Delay settings. $output['delay_js_enabled'] = ! empty( $input['delay_js_enabled'] ); $output['delay_js_timeout'] = isset( $input['delay_js_timeout'] ) ? absint( $input['delay_js_timeout'] ) : 5000; // Clamp timeout between 1000ms and 30000ms. $output['delay_js_timeout'] = max( 1000, min( 30000, $output['delay_js_timeout'] ) ); $delay_input = $input['delay_js_exclude'] ?? ''; if ( is_array( $delay_input ) ) { $delay_exclude_raw = array_filter( $delay_input ); } else { $delay_exclude_raw = preg_split( '/[\r\n,]+/', $delay_input, -1, PREG_SPLIT_NO_EMPTY ); } $delay_exclude = array(); if ( is_array( $delay_exclude_raw ) ) { foreach ( $delay_exclude_raw as $handle ) { $handle = sanitize_text_field( trim( $handle ) ); if ( '' !== $handle ) { $delay_exclude[] = $handle; } } } $output['delay_js_exclude'] = ! empty( $delay_exclude ) ? $delay_exclude : $defaults['delay_js_exclude']; // Heartbeat control settings. $heartbeat_behavior = sanitize_text_field( $input['heartbeat_behavior'] ?? 'default' ); $output['heartbeat_behavior'] = in_array( $heartbeat_behavior, array( 'default', 'reduce', 'disable_frontend', 'disable_everywhere' ), true ) ? $heartbeat_behavior : 'default'; $output['heartbeat_interval'] = isset( $input['heartbeat_interval'] ) ? absint( $input['heartbeat_interval'] ) : 60; // Clamp interval between 15 and 300 seconds. $output['heartbeat_interval'] = max( 15, min( 300, $output['heartbeat_interval'] ) ); // Query string removal. $output['remove_query_strings'] = ! empty( $input['remove_query_strings'] ); // WooCommerce conditional loading. $output['wc_conditional_enabled'] = ! empty( $input['wc_conditional_enabled'] ); $output['wc_disable_cart_fragments'] = ! empty( $input['wc_disable_cart_fragments'] ); $output['wc_disable_styles'] = ! empty( $input['wc_disable_styles'] ); $output['wc_disable_blocks_styles'] = ! empty( $input['wc_disable_blocks_styles'] ); // Database cleanup settings. $output['db_cleanup_enabled'] = ! empty( $input['db_cleanup_enabled'] ); $db_schedule = sanitize_text_field( $input['db_cleanup_schedule'] ?? 'weekly' ); $output['db_cleanup_schedule'] = in_array( $db_schedule, array( 'daily', 'weekly', 'monthly' ), true ) ? $db_schedule : 'weekly'; $output['db_revisions_keep'] = isset( $input['db_revisions_keep'] ) ? absint( $input['db_revisions_keep'] ) : 5; // Clamp revisions between 0 and 50. $output['db_revisions_keep'] = min( 50, max( 0, $output['db_revisions_keep'] ) ); // Local Google Fonts settings. $output['local_fonts_enabled'] = ! empty( $input['local_fonts_enabled'] ); $font_display = sanitize_text_field( $input['local_fonts_display'] ?? 'swap' ); $output['local_fonts_display'] = in_array( $font_display, array( 'auto', 'block', 'swap', 'fallback', 'optional' ), true ) ? $font_display : 'swap'; $output['local_fonts_preload'] = ! empty( $input['local_fonts_preload'] ); // Ensure we always save an array for hosts. if ( empty( $output['preconnect_hosts'] ) && ! empty( $defaults['preconnect_hosts'] ) ) { $output['preconnect_hosts'] = $defaults['preconnect_hosts']; } self::validate_urls( $output ); self::update_settings_cache( $output ); return $output; } /** * Check if Elementor editor/preview is active. * * @return bool */ public static function is_elementor_editor() { if ( ! did_action( 'elementor/loaded' ) ) { return false; } try { $plugin = \Elementor\Plugin::$instance; return ( ! empty( $plugin->editor ) && $plugin->editor->is_edit_mode() ) || ( ! empty( $plugin->preview ) && $plugin->preview->is_preview_mode() ); } catch ( \Throwable $e ) { return false; } } /** * Should front-end-only features run? * * @return bool */ public static function is_frontend_context() { return ! is_admin() && ! self::is_elementor_editor(); } /** * Validate URLs and surface admin warnings. * * @param array $settings Settings. * * @return void */ private static function validate_urls( $settings ) { static $ran = false; if ( $ran ) { return; } $ran = true; $urls = array( 'preload_stylesheet' => __( 'Stylesheet preload URL', '365i-performance-optimizer' ), 'preload_font' => __( 'Font preload URL', '365i-performance-optimizer' ), 'preload_hero' => __( 'Hero image preload URL', '365i-performance-optimizer' ), ); $warnings = array(); foreach ( $urls as $key => $label ) { if ( empty( $settings[ $key ] ) ) { continue; } $scheme = wp_parse_url( $settings[ $key ], PHP_URL_SCHEME ); if ( $scheme && 'https' !== strtolower( $scheme ) ) { $warnings[] = sprintf( /* translators: 1: field label */ esc_html__( '%1$s is not using HTTPS. Consider using HTTPS to avoid mixed-content issues.', '365i-performance-optimizer' ), $label ); } $host_current = wp_parse_url( home_url(), PHP_URL_HOST ); $host_target = wp_parse_url( $settings[ $key ], PHP_URL_HOST ); if ( $host_current && $host_target && $host_current === $host_target ) { $response = wp_remote_head( $settings[ $key ], array( 'timeout' => 3, 'redirection' => 1, ) ); if ( is_wp_error( $response ) || (int) wp_remote_retrieve_response_code( $response ) >= 400 ) { $warnings[] = sprintf( /* translators: 1: field label */ esc_html__( '%1$s could not be reached. Please verify the URL.', '365i-performance-optimizer' ), $label ); } } } if ( ! empty( $warnings ) ) { add_settings_error( 'i365_po_messages', 'i365_po_url_warnings', '', 'warning' ); } } /** * Check if current path is excluded from speculation. * * @param array $exclusions Exclusions. * * @return bool */ public static function is_speculation_excluded( $exclusions ) { if ( empty( $exclusions ) ) { return false; } $current = wp_parse_url( home_url( add_query_arg( array() ) ), PHP_URL_PATH ); foreach ( $exclusions as $path ) { if ( $current === $path || ( $path !== '/' && strpos( $current, trailingslashit( $path ) ) === 0 ) ) { return true; } } return false; } /* |-------------------------------------------------------------------------- | Settings Backup System |-------------------------------------------------------------------------- */ /** * Create a backup of current settings before changes. * * @return bool True on success, false if no settings to backup. */ public static function backup_current_settings() { $current = get_option( I365_PO_OPTION_KEY, array() ); // Don't backup empty settings. if ( empty( $current ) ) { return false; } $backups = get_option( self::BACKUPS_OPTION_KEY, array() ); // Add timestamped backup with metadata. $backups[ time() ] = array( 'settings' => $current, 'version' => I365_PO_VERSION, 'user_id' => get_current_user_id(), 'user' => wp_get_current_user()->display_name, ); // Keep only the most recent backups. if ( count( $backups ) > self::MAX_BACKUPS ) { $keys = array_keys( $backups ); sort( $keys, SORT_NUMERIC ); // Remove oldest entries. while ( count( $backups ) > self::MAX_BACKUPS ) { unset( $backups[ array_shift( $keys ) ] ); } } return update_option( self::BACKUPS_OPTION_KEY, $backups ); } /** * Get all available backups. * * @return array Array of backups keyed by timestamp. */ public static function get_backups() { $backups = get_option( self::BACKUPS_OPTION_KEY, array() ); // Sort by timestamp descending (newest first). if ( ! empty( $backups ) ) { krsort( $backups, SORT_NUMERIC ); } return $backups; } /** * Restore settings from a specific backup. * * @param int $timestamp The backup timestamp to restore. * * @return bool|WP_Error True on success, WP_Error on failure. */ public static function restore_backup( $timestamp ) { $backups = get_option( self::BACKUPS_OPTION_KEY, array() ); if ( ! isset( $backups[ $timestamp ] ) ) { return new \WP_Error( 'invalid_backup', __( 'Backup not found.', '365i-performance-optimizer' ) ); } $backup_data = $backups[ $timestamp ]; if ( empty( $backup_data['settings'] ) || ! is_array( $backup_data['settings'] ) ) { return new \WP_Error( 'invalid_backup_data', __( 'Backup data is corrupted.', '365i-performance-optimizer' ) ); } // Create a backup of current settings before restore (so restore is reversible). self::backup_current_settings(); // Restore the settings. $result = update_option( I365_PO_OPTION_KEY, $backup_data['settings'] ); if ( $result ) { // Update the in-memory cache. self::update_settings_cache( wp_parse_args( $backup_data['settings'], self::defaults() ) ); } return $result; } /** * Delete a specific backup. * * @param int $timestamp The backup timestamp to delete. * * @return bool True on success, false on failure. */ public static function delete_backup( $timestamp ) { $backups = get_option( self::BACKUPS_OPTION_KEY, array() ); if ( ! isset( $backups[ $timestamp ] ) ) { return false; } unset( $backups[ $timestamp ] ); return update_option( self::BACKUPS_OPTION_KEY, $backups ); } /** * Get the most recent backup timestamp. * * @return int|null Timestamp or null if no backups exist. */ public static function get_last_backup_time() { $backups = self::get_backups(); if ( empty( $backups ) ) { return null; } // Already sorted newest first. return key( $backups ); } /* |-------------------------------------------------------------------------- | Configuration Profiles |-------------------------------------------------------------------------- */ /** * Get built-in preset profiles. * * @return array */ public static function get_builtin_profiles() { $defaults = self::defaults(); return array( 'safe_mode' => array( 'name' => __( 'Safe Mode', '365i-performance-optimizer' ), 'description' => __( 'All optimizations disabled for troubleshooting.', '365i-performance-optimizer' ), 'builtin' => true, 'settings' => array( 'enable_speculation' => false, 'speculation_mode' => 'auto', 'speculation_eagerness' => 'conservative', 'speculation_exclusions' => array(), 'enable_preload' => false, 'preconnect_hosts' => $defaults['preconnect_hosts'], 'preload_stylesheet' => '', 'preload_font' => '', 'preload_hero' => '', 'hero_preload_on_front' => true, 'remove_emoji' => false, 'disable_embeds' => false, 'defer_scripts' => false, 'excluded_defer_handles' => $defaults['excluded_defer_handles'], 'disable_xmlrpc' => false, 'remove_rest_link' => false, 'remove_oembed_link' => false, 'lcp_priority' => false, 'disable_front_lazy' => false, 'enable_detect_log' => false, 'delay_js_enabled' => false, 'delay_js_timeout' => 5000, 'delay_js_exclude' => $defaults['delay_js_exclude'], 'heartbeat_behavior' => 'default', 'heartbeat_interval' => 60, 'remove_query_strings' => false, 'wc_conditional_enabled' => false, 'wc_disable_cart_fragments' => true, 'wc_disable_styles' => true, 'wc_disable_blocks_styles' => true, 'local_fonts_enabled' => false, 'local_fonts_display' => 'swap', 'local_fonts_preload' => true, ), ), 'balanced' => array( 'name' => __( 'Balanced', '365i-performance-optimizer' ), 'description' => __( 'Recommended settings for most sites.', '365i-performance-optimizer' ), 'builtin' => true, 'settings' => $defaults, ), 'aggressive' => array( 'name' => __( 'Maximum Performance', '365i-performance-optimizer' ), 'description' => __( 'All optimizations enabled for maximum speed.', '365i-performance-optimizer' ), 'builtin' => true, 'settings' => array( 'enable_speculation' => true, 'speculation_mode' => 'prerender', 'speculation_eagerness' => 'eager', 'speculation_exclusions' => array( '/cart', '/checkout', '/my-account' ), 'enable_preload' => true, 'preconnect_hosts' => $defaults['preconnect_hosts'], 'preload_stylesheet' => $defaults['preload_stylesheet'], 'preload_font' => '', 'preload_hero' => '', 'hero_preload_on_front' => true, 'remove_emoji' => true, 'disable_embeds' => true, 'defer_scripts' => true, 'excluded_defer_handles' => array( 'elementor', 'jquery', 'wp-' ), 'disable_xmlrpc' => true, 'remove_rest_link' => true, 'remove_oembed_link' => true, 'lcp_priority' => true, 'disable_front_lazy' => true, 'enable_detect_log' => false, 'delay_js_enabled' => true, 'delay_js_timeout' => 5000, 'delay_js_exclude' => array( 'jquery', 'jquery-core', 'jquery-migrate', 'elementor', 'wp-' ), 'heartbeat_behavior' => 'disable_frontend', 'heartbeat_interval' => 120, 'remove_query_strings' => true, 'wc_conditional_enabled' => true, 'wc_disable_cart_fragments' => true, 'wc_disable_styles' => true, 'wc_disable_blocks_styles' => true, 'local_fonts_enabled' => true, 'local_fonts_display' => 'swap', 'local_fonts_preload' => true, ), ), ); } /** * Get all profiles (built-in + custom). * * @return array */ public static function get_all_profiles() { $builtin = self::get_builtin_profiles(); $custom = get_option( self::PROFILES_OPTION_KEY, array() ); return array_merge( $builtin, $custom ); } /** * Save current settings as a custom profile. * * @param string $name Profile name. * @param string $description Profile description. * * @return bool|WP_Error True on success, WP_Error on failure. */ public static function save_profile( $name, $description = '' ) { $name = sanitize_text_field( $name ); if ( empty( $name ) ) { return new \WP_Error( 'empty_name', __( 'Profile name cannot be empty.', '365i-performance-optimizer' ) ); } // Generate a slug from the name. $slug = sanitize_title( $name ); // Check if it conflicts with built-in profiles. $builtin = self::get_builtin_profiles(); if ( isset( $builtin[ $slug ] ) ) { return new \WP_Error( 'reserved_name', __( 'This name is reserved for a built-in profile.', '365i-performance-optimizer' ) ); } $custom = get_option( self::PROFILES_OPTION_KEY, array() ); $custom[ $slug ] = array( 'name' => $name, 'description' => sanitize_text_field( $description ), 'builtin' => false, 'settings' => self::get_settings(), 'created' => time(), ); return update_option( self::PROFILES_OPTION_KEY, $custom ); } /** * Apply a profile's settings. * * @param string $slug Profile slug. * * @return bool|WP_Error True on success, WP_Error on failure. */ public static function apply_profile( $slug ) { $profiles = self::get_all_profiles(); if ( ! isset( $profiles[ $slug ] ) ) { return new \WP_Error( 'invalid_profile', __( 'Profile not found.', '365i-performance-optimizer' ) ); } $profile = $profiles[ $slug ]; if ( empty( $profile['settings'] ) || ! is_array( $profile['settings'] ) ) { return new \WP_Error( 'invalid_profile_data', __( 'Profile data is corrupted.', '365i-performance-optimizer' ) ); } // Backup current settings first. self::backup_current_settings(); // Merge profile settings with defaults to ensure all keys exist. $settings = wp_parse_args( $profile['settings'], self::defaults() ); $result = update_option( I365_PO_OPTION_KEY, $settings ); if ( $result ) { self::update_settings_cache( $settings ); } return $result; } /** * Delete a custom profile. * * @param string $slug Profile slug. * * @return bool|WP_Error True on success, WP_Error on failure. */ public static function delete_profile( $slug ) { $builtin = self::get_builtin_profiles(); if ( isset( $builtin[ $slug ] ) ) { return new \WP_Error( 'cannot_delete_builtin', __( 'Built-in profiles cannot be deleted.', '365i-performance-optimizer' ) ); } $custom = get_option( self::PROFILES_OPTION_KEY, array() ); if ( ! isset( $custom[ $slug ] ) ) { return new \WP_Error( 'profile_not_found', __( 'Profile not found.', '365i-performance-optimizer' ) ); } unset( $custom[ $slug ] ); return update_option( self::PROFILES_OPTION_KEY, $custom ); } /* |-------------------------------------------------------------------------- | Import/Export Settings |-------------------------------------------------------------------------- */ /** * Export settings as JSON. * * @return string JSON string. */ public static function export_settings() { $export_data = array( 'plugin' => '365i-performance-optimizer', 'version' => I365_PO_VERSION, 'exported' => gmdate( 'c' ), 'site' => wp_parse_url( home_url(), PHP_URL_HOST ), 'settings' => self::get_settings(), ); return wp_json_encode( $export_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); } /** * Import settings from JSON. * * @param string $json JSON string. * * @return bool|WP_Error True on success, WP_Error on failure. */ public static function import_settings( $json ) { $data = json_decode( $json, true ); if ( json_last_error() !== JSON_ERROR_NONE ) { return new \WP_Error( 'invalid_json', __( 'Invalid JSON format.', '365i-performance-optimizer' ) ); } if ( empty( $data['plugin'] ) || '365i-performance-optimizer' !== $data['plugin'] ) { return new \WP_Error( 'invalid_export', __( 'This file is not a valid Performance Optimizer export.', '365i-performance-optimizer' ) ); } if ( empty( $data['settings'] ) || ! is_array( $data['settings'] ) ) { return new \WP_Error( 'missing_settings', __( 'Export file does not contain settings.', '365i-performance-optimizer' ) ); } // Backup current settings first. self::backup_current_settings(); // Sanitize imported settings through the existing sanitization process. // We simulate a POST to reuse the sanitization logic. $sanitized = array(); $defaults = self::defaults(); foreach ( $defaults as $key => $default_value ) { if ( isset( $data['settings'][ $key ] ) ) { $value = $data['settings'][ $key ]; // Type-specific sanitization. if ( is_bool( $default_value ) ) { $sanitized[ $key ] = (bool) $value; } elseif ( is_array( $default_value ) ) { $sanitized[ $key ] = is_array( $value ) ? array_map( 'sanitize_text_field', $value ) : $default_value; } elseif ( in_array( $key, array( 'preload_stylesheet', 'preload_font', 'preload_hero' ), true ) ) { $sanitized[ $key ] = esc_url_raw( $value ); } elseif ( 'speculation_mode' === $key ) { $sanitized[ $key ] = in_array( $value, array( 'auto', 'prerender', 'prefetch' ), true ) ? $value : $default_value; } elseif ( 'speculation_eagerness' === $key ) { $sanitized[ $key ] = in_array( $value, array( 'eager', 'moderate', 'conservative' ), true ) ? $value : $default_value; } else { $sanitized[ $key ] = sanitize_text_field( $value ); } } else { $sanitized[ $key ] = $default_value; } } // Handle preconnect_hosts array specially. if ( isset( $data['settings']['preconnect_hosts'] ) && is_array( $data['settings']['preconnect_hosts'] ) ) { $hosts = array(); foreach ( $data['settings']['preconnect_hosts'] as $host ) { $clean = esc_url_raw( $host ); if ( ! empty( $clean ) ) { $hosts[] = $clean; } } $sanitized['preconnect_hosts'] = $hosts; } $result = update_option( I365_PO_OPTION_KEY, $sanitized ); if ( $result ) { self::update_settings_cache( $sanitized ); } return $result; } }