*/ class Metasync_Admin { // Section constants for settings const SECTION_FEATURES = "features_settings"; const SECTION_METASYNC = "metasync_settings"; const SECTION_SEARCHENGINE = "searchengine_settings"; const SECTION_LOCALSEO = "local_seo"; const SECTION_CODESNIPPETS = "code_snippets"; const SECTION_OPTIMAL_SETTINGS = "optimal_settings"; const SECTION_SITE_SETTINGS = "site_settings"; const SECTION_COMMON_SETTINGS = "common_settings"; const SECTION_COMMON_META_SETTINGS = "common_meta_settings"; const SECTION_SOCIAL_META = "social_meta"; const SECTION_SEO_CONTROLS = "seo_controls"; const SECTION_SEO_CONTROLS_ADVANCED = "seo_controls_advanced"; const SECTION_SEO_CONTROLS_INSTANT_INDEX = "seo_controls_instant_index"; const SECTION_PLUGIN_VISIBILITY = "plugin_visibility_settings"; /** * The ID of this plugin. * * @since 1.0.0 * @access private * @var string $plugin_name The ID of this plugin. */ private $plugin_name; /** * The version of this plugin. * * @since 1.0.0 * @access private * @var string $version The current version of this plugin. */ private $version; /** * Holds the values to be used in the fields callbacks */ private $options; public $menu_title = "Search Atlas"; // Default, overridden by get_effective_menu_title() public const page_title = "MetaSync Settings"; /** * Get effective menu title with whitelabel company name */ public function get_effective_menu_title() { $whitelabel_company_name = Metasync::get_whitelabel_company_name(); if ($whitelabel_company_name) { return $whitelabel_company_name . ' SEO'; } // Use centralized method for getting effective plugin name return Metasync::get_effective_plugin_name(); } public const option_group = "metasync_group"; public const option_key = "metasync_options"; public static $page_slug = "searchatlas"; /** * Get the effective dashboard domain * Returns whitelabel domain if set, otherwise returns default production domain * Uses centralized constant from main Metasync class */ public static function get_effective_dashboard_domain() { // Delegate to main Metasync class for consistent domain resolution return Metasync::get_dashboard_domain(); } /** * Static method to render header and navigation for external pages (like AI Agent) * This creates a minimal instance just for rendering */ public static function render_standard_header_nav($page_title = null, $current_page = null) { Metasync_Admin_Navigation::instance()->render_standard_header_nav($page_title, $current_page); } /** * Static header render */ public static function render_static_header($page_title = null) { Metasync_Admin_Navigation::instance()->render_static_header($page_title); } /** * Static navigation render */ public static function render_static_navigation($current_page = null) { Metasync_Admin_Navigation::instance()->render_static_navigation($current_page); } /** * Get dashboard URL with authentication tokens and tracking parameters * Returns the complete URL for accessing the Search Atlas dashboard */ public function get_dashboard_url() { // Get the effective dashboard domain (whitelabel or production) $dashboard_url = self::get_effective_dashboard_domain(); // Get current options for token inclusion $general_options = Metasync::get_option('general'); // Add JWT token if available for seamless login if (isset($general_options['linkgraph_token']) && !empty($general_options['linkgraph_token'])) { $dashboard_url .= '/?jwtToken=' . urlencode($general_options['linkgraph_token']); } // Add source tracking parameter $dashboard_url .= (strpos($dashboard_url, '?') !== false ? '&' : '?') . 'source=wordpress-plugin'; // Add whitelabel identification if in whitelabel mode $whitelabel_company_name = Metasync::get_whitelabel_company_name(); if ($whitelabel_company_name) { $dashboard_url .= '&whitelabel=' . urlencode($whitelabel_company_name); } return $dashboard_url; } public const feature_sections = array( 'enable_404monitor' => 'Enable 404 Monitor', 'enable_siteverification' => 'Enable Site Verification', 'enable_localbusiness' => 'Enable Local Business', 'enable_codesnippets' => 'Enable Code Snippets', 'enable_googleconsole' => 'Enable Google Console', 'enable_optimalsettings' => 'Enable Optimal Settings', 'enable_globalsettings' => 'Enable Global Settings', 'enable_commonmetastatus' => 'Enable Common Meta Status', 'enable_socialmeta' => 'Enable Social Meta', 'enable_redirections' => 'Enable Redirections', 'enable_errorlogs' => 'Enable Error Logs' ); private $database; private $db_redirection; public $db_heartbeat_errors; public $setup_wizard; /** * Initialize the class and set its properties. * * @since 1.0.0 * @param string $plugin_name The name of this plugin. * @param string $version The version of this plugin. */ public function __construct($plugin_name, $version, &$database, $db_redirection, $db_heartbeat_errors) // , $data_error_log_list { $this->plugin_name = $plugin_name; $this->version = $version; $this->database = $database; $this->db_redirection = $db_redirection; $this->db_heartbeat_errors = $db_heartbeat_errors; // $this->data_error_log_list = $data_error_log_list; // Initialize setup wizard $this->setup_wizard = new Metasync_Setup_Wizard($plugin_name, $version); // Wire up the extracted settings-fields singleton Metasync_Settings_Fields::instance()->set_admin_instance($this); // Get data first for menu configuration $data = Metasync::get_option('general'); // Set menu title using the effective title (includes whitelabel company name if available) $this->menu_title = $this->get_effective_menu_title(); if(!isset( $data['white_label_plugin_menu_slug'])){ self::$page_slug = "searchatlas"; }else{ self::$page_slug = $data['white_label_plugin_menu_slug']=="" ? "searchatlas":$data['white_label_plugin_menu_slug']; } add_action('admin_menu', array($this, 'add_plugin_settings_page')); add_action('admin_menu', array($this, 'add_import_external_data_page')); add_action('admin_init', array($this, 'settings_page_init')); add_filter('all_plugins', array($this,'metasync_plugin_white_label')); add_filter( 'plugin_row_meta',array($this,'metasync_view_detials_url'),10,3); // Display transient error/success messages for redirections add_action('admin_notices', array($this, 'display_redirection_messages')); // Add custom column for HTML-converted pages add_filter('manage_posts_columns', array($this, 'add_html_converted_column')); add_filter('manage_pages_columns', array($this, 'add_html_converted_column')); add_action('manage_posts_custom_column', array($this, 'render_html_converted_column'), 10, 2); add_action('manage_pages_custom_column', array($this, 'render_html_converted_column'), 10, 2); // Add badge to page editor screen add_action('edit_form_after_title', array($this, 'add_editor_source_notice')); // Add badge to quick edit panel add_action('quick_edit_custom_box', array($this, 'add_quick_edit_source_display'), 10, 2); // Add dashboard widget add_action('wp_dashboard_setup', array($this, 'add_html_pages_dashboard_widget')); // Add Search Atlas status to WordPress admin bar (priority 999 to ensure plugin is fully loaded) // Always add the action - the method will check the setting internally add_action('admin_bar_menu', array($this, 'add_searchatlas_admin_bar_status'), 999); #add css into admin header for icon image add_action('admin_head', array($this,'metasync_admin_icon_style')); // Always add admin bar styles - the method will check the setting internally add_action('wp_head', array($this,'metasync_admin_bar_style')); // For frontend admin bar add_action('admin_head', array($this,'metasync_admin_bar_style')); // For backend admin bar // removing this as we don't need it anymore because we are using wp-ajax to implement the white label // add_action('update_option_metasync_options', array($this, 'check_and_redirect_slug'), 10, 3); // Sync plugin file headers whenever metasync_options is updated (covers AJAX save, Settings API, import) add_action('update_option_metasync_options', array($this, 'on_options_updated_sync_file_headers'), 10, 2); add_action('admin_init', array($this, 'initialize_cookie')); add_action('admin_init', array($this, 'maybe_redirect_to_wizard')); // Add admin_post hooks for form submissions (WordPress standard way - no output buffering needed) add_action('admin_post_metasync_clear_all_cache_plugins', array($this, 'handle_clear_all_cache_plugins')); add_action('admin_post_metasync_clear_otto_cache_all', array($this, 'handle_clear_otto_cache_all')); add_action('admin_post_metasync_clear_otto_cache_url', array($this, 'handle_clear_otto_cache_url')); add_action('admin_post_metasync_purge_hosting_cache', array($this, 'handle_purge_hosting_cache')); // Add AJAX for saving general settings add_action( 'wp_ajax_meta_sync_save_settings', array($this,'meta_sync_save_settings') ); // Add AJAX for saving Indexation Control settings add_action( 'wp_ajax_meta_sync_save_seo_controls', array($this,'meta_sync_save_seo_controls') ); // Add AJAX for saving execution settings add_action( 'wp_ajax_metasync_save_execution_settings', array($this, 'ajax_save_execution_settings') ); // Add AJAX for saving hosting cache settings add_action( 'wp_ajax_metasync_save_hosting_cache_settings', array($this, 'ajax_save_hosting_cache_settings') ); // Add AJAX for saving edge cache / CDN settings add_action( 'wp_ajax_metasync_save_edge_cache_settings', array('Metasync_Edge_Cache_Settings', 'ajax_save') ); // Add AJAX handler for Plugin Auth Token refresh add_action('wp_ajax_refresh_plugin_auth_token', array($this, 'refresh_plugin_auth_token')); // Add AJAX handler to get current Plugin Auth Token (for UI updates) add_action('wp_ajax_get_plugin_auth_token', array($this, 'get_plugin_auth_token')); // Add AJAX handler for creating redirects from 404 suggestions add_action('wp_ajax_metasync_create_redirect_from_404', array($this, 'ajax_create_redirect_from_404')); // Add AJAX handler for updating database structure add_action('wp_ajax_metasync_update_db_structure', array($this, 'ajax_update_db_structure')); // Add AJAX handlers for setup wizard add_action('wp_ajax_metasync_save_wizard_progress', array($this, 'ajax_save_wizard_progress')); add_action('wp_ajax_metasync_complete_wizard', array($this, 'ajax_complete_wizard')); // Add AJAX handlers for robots.txt add_action('wp_ajax_metasync_validate_robots', array($this, 'ajax_validate_robots')); add_action('wp_ajax_metasync_get_default_robots', array($this, 'ajax_get_default_robots')); add_action('wp_ajax_metasync_preview_robots_backup', array($this, 'ajax_preview_robots_backup')); add_action('wp_ajax_metasync_delete_robots_backup', array($this, 'ajax_delete_robots_backup')); add_action('wp_ajax_metasync_restore_robots_backup', array($this, 'ajax_restore_robots_backup')); // Add AJAX handlers for host blocking test add_action('wp_ajax_metasync_test_host_blocking_get', array($this, 'ajax_test_host_blocking_get')); add_action('wp_ajax_metasync_test_host_blocking_post', array($this, 'ajax_test_host_blocking_post')); // Add AJAX handlers for OTTO excluded URLs add_action('wp_ajax_metasync_otto_add_excluded_url', array($this, 'ajax_otto_add_excluded_url')); add_action('wp_ajax_metasync_otto_delete_excluded_url', array($this, 'ajax_otto_delete_excluded_url')); add_action('wp_ajax_metasync_burst_ping', array($this, 'ajax_burst_ping')); add_action('wp_ajax_metasync_otto_get_excluded_urls', array($this, 'ajax_otto_get_excluded_urls')); add_action('wp_ajax_metasync_otto_recheck_excluded_url', array($this, 'ajax_otto_recheck_excluded_url')); # Add AJAX handler for Mixpanel tracking add_action('wp_ajax_metasync_track_one_click_activation', array($this, 'ajax_track_one_click_activation')); # Add AJAX handler for submitting issue reports add_action('wp_ajax_metasync_submit_issue_report', array($this, 'ajax_submit_issue_report')); # Add AJAX handlers for support token management # Add AJAX handler for theme switcher add_action('wp_ajax_metasync_save_theme', array($this, 'ajax_save_theme')); # Add AJAX handler for external data import add_action('wp_ajax_metasync_import_external_data', array($this, 'ajax_import_external_data')); # Add AJAX handler for SEO metadata batch import add_action('wp_ajax_metasync_import_seo_metadata', array($this, 'ajax_import_seo_metadata')); # Add AJAX handler for password recovery add_action('wp_ajax_metasync_recover_password', array($this, 'ajax_recover_password')); # Add AJAX handler for resetting bot statistics add_action('wp_ajax_metasync_reset_bot_stats', array($this, 'ajax_reset_bot_stats')); # Add admin-post handler for exporting whitelabel settings (file download) add_action('admin_post_metasync_export_whitelabel_settings', array($this, 'handle_export_whitelabel_settings')); # Media Optimization AJAX handlers add_action('wp_ajax_metasync_optimize_single_image', array($this, 'ajax_optimize_single_image')); add_action('wp_ajax_metasync_revert_single_image', array($this, 'ajax_revert_single_image')); add_action('wp_ajax_metasync_start_batch_optimize', array($this, 'ajax_start_batch_optimize')); add_action('wp_ajax_metasync_cancel_batch_optimize', array($this, 'ajax_cancel_batch_optimize')); add_action('wp_ajax_metasync_batch_progress', array($this, 'ajax_batch_progress')); add_action('wp_ajax_metasync_bulk_optimize_selected', array($this, 'ajax_bulk_optimize_selected')); add_action('wp_ajax_metasync_process_batch_tick', array($this, 'ajax_process_batch_tick')); add_action('metasync_media_batch_optimize_cron', array($this, 'handle_media_batch_cron')); # Add AJAX handlers for Google Instant Indexing add_action('wp_ajax_send_giapi', array($this, 'ajax_send_giapi')); # Add AJAX handlers for Bing Instant Indexing (IndexNow) add_action('wp_ajax_send_bing_indexnow', array($this, 'ajax_send_bing_indexnow')); # Add hooks for instant indexing settings saves add_action('admin_init', array($this, 'save_instant_indexing_settings')); # Add hooks for instant indexing post actions add_filter('post_row_actions', array($this, 'add_instant_indexing_post_actions'), 10, 2); add_filter('page_row_actions', array($this, 'add_instant_indexing_post_actions'), 10, 2); # Add hooks for auto-submission on post publish add_action('save_post', array($this, 'auto_submit_to_instant_indexing'), 10, 3); // Add REST API endpoint for ping add_action('rest_api_init', array($this, 'register_ping_rest_endpoint')); // Add heartbeat cron functionality add_filter('cron_schedules', array($this, 'add_heartbeat_cron_schedule')); add_action('metasync_heartbeat_cron_check', array($this, 'execute_heartbeat_cron_check')); add_action('metasync_burst_heartbeat', array($this, 'execute_burst_heartbeat')); add_action('metasync_announce_cron', array($this, 'execute_announce_cron')); // PR3: Transition to KEY_PENDING when API key is added or rotated add_action('metasync_heartbeat_state_key_pending', array($this, 'set_heartbeat_state_key_pending')); // Add transient cleanup cron functionality add_action('metasync_cleanup_transients', array($this, 'execute_transient_cleanup')); // Schedule heartbeat cron on plugin load (if not already scheduled) add_action('init', array($this, 'maybe_schedule_heartbeat_cron')); // Schedule transient cleanup cron on plugin load add_action('init', array($this, 'maybe_schedule_transient_cleanup_cron')); // Pre-SSO announce: rate-limited ping when no API key yet (PR4 - heartbeat reliability) add_action('init', array($this, 'maybe_send_pre_sso_announce')); # Schedule hidden post manager cron on plugin load (runs every 7 days) add_action('init', array($this, 'maybe_schedule_hidden_post_check')); # Schedule OTTO 404 exclusion recheck (runs daily to recheck URLs excluded 7+ days ago) add_action('init', array($this, 'maybe_schedule_otto_recheck_404_cron')); # Schedule support token cleanup cron on plugin load (runs daily) // Listen for immediate heartbeat trigger requests from other parts of the plugin add_action('metasync_trigger_immediate_heartbeat', array($this, 'handle_immediate_heartbeat_trigger')); // Listen for cron scheduling requests (after Search Atlas connect authentication) add_action('metasync_ensure_heartbeat_cron_scheduled', array($this, 'maybe_schedule_heartbeat_cron')); // Action Scheduler configuration filters add_filter('action_scheduler_queue_runner_concurrent_batches', array($this, 'get_action_scheduler_batches')); add_filter('action_scheduler_retention_period', array($this, 'get_action_scheduler_retention_period')); // Note: Option change monitoring is now handled by the centralized API Key Monitor class // This provides more comprehensive and intelligent monitoring of API key changes #-------------------------- # we are disabling this code # To prevent enabling debuging on every pluging Update # This causes Issue #102 on gilab #-------------------------- # # NOTE: # Do not delete this as we may need it in implementing universal logging # add_action('upgrader_process_complete', array($this,'metasync_plugin_updated_action'), 10, 2); # Hook into post category creation and update (AFTER they are saved) add_action('saved_term', array($this,'admin_crud_term'), 10, 3); # Hook into post category deletion (AFTER it is deleted) // add_action('pre_delete_term', array($this,'admin_delete_term'), 10, 2); # Using delete_category hook which fires AFTER category is fully deleted and provides correct data add_action('delete_category', array($this,'admin_delete_category'), 10, 1); } /* This function add css to wp admin header */ public function metasync_admin_icon_style(){ # Get Metasync Option $data= Metasync::get_option('general'); # Get white label menu slug $menu_slug = empty($data['white_label_plugin_menu_slug']) ? self::$page_slug : $data['white_label_plugin_menu_slug']; ?> metasync_display_error_log($this); } public function metasync_update_wp_config() { Metasync_Debug_Manager::instance()->metasync_update_wp_config(); } /** * Sync plugin file headers when metasync_options is updated. * This ensures whitelabel values are written to the plugin file header * so they persist even when the plugin is deactivated. * * @param mixed $old_value The old option value. * @param mixed $new_value The new option value. * @since 2.5.0 */ public function on_options_updated_sync_file_headers($old_value, $new_value) { Metasync_Debug_Manager::instance()->on_options_updated_sync_file_headers($old_value, $new_value); } public function check_and_redirect_slug($option, $old_value, $new_value) { // Ensure this hook is only triggered for your specific option group if (!isset($option['general'] ) && !isset($option['general']['white_label_plugin_menu_slug'])) { return; } $new_slug = $new_value['general']['white_label_plugin_menu_slug'] ?? self::$page_slug; $old_slug = $old_value['general']['white_label_plugin_menu_slug'] ?? self::$page_slug; if ($new_slug !== $old_slug && $old_slug !=='' ){ // Set a new cookie setcookie('metasync_previous_slug', $new_slug, time() + 3600, COOKIEPATH, COOKIE_DOMAIN); $_COOKIE['metasync_previous_slug'] = $new_slug; // Redirect to the new slug $redirect_url = admin_url('admin.php?page=' . $old_slug); wp_redirect($redirect_url); exit; }else{ self::$page_slug = Metasync::get_option('general')['white_label_plugin_menu_slug']=="" ? "searchatlas":Metasync::get_option('general')['white_label_plugin_menu_slug']; $redirect_url = admin_url('admin.php?page=' . self::$page_slug); #add redirection for when the old slug is not defined #this fixes the redirect issue # wp_redirect($redirect_url); exit; } } public function metasync_view_detials_url( $plugin_meta, $plugin_file, $plugin_data ) { $plugin_uri = Metasync::get_option('general')['white_label_plugin_uri'] ?? ''; $this_plugin = plugin_basename(dirname(__DIR__) . '/metasync.php'); if ($this_plugin === $plugin_file && $plugin_uri !== '') { foreach ($plugin_meta as &$meta) { if (strpos($meta, 'open-plugin-details-modal') !== false) { $meta = sprintf( '%s', add_query_arg('TB_iframe', 'true', $plugin_uri), esc_attr(sprintf(esc_html__('More information about %s'), $plugin_data['Name'])), esc_attr($plugin_data['Name']), esc_html__('View details', 'metasync') ); break; // Exit loop after replacing the link } } } return $plugin_meta; } public function metasync_plugin_white_label($all_plugins) { $general = Metasync::get_option('general'); if (!is_array($general)) { return $all_plugins; } $plugin_name = $general['white_label_plugin_name'] ?? ''; $plugin_description = $general['white_label_plugin_description'] ?? ''; $plugin_author = $general['white_label_plugin_author'] ?? ''; $plugin_author_uri = $general['white_label_plugin_author_uri'] ?? ''; $plugin_uri = $general['white_label_plugin_uri'] ?? ''; // Dynamically resolve the plugin basename to handle renamed plugin folders $this_plugin = plugin_basename(dirname(__DIR__) . '/metasync.php'); if (isset($all_plugins[$this_plugin])) { if ($plugin_name !== '') { $all_plugins[$this_plugin]['Name'] = $plugin_name; $all_plugins[$this_plugin]['Title'] = $plugin_name; } if ($plugin_description !== '') { $all_plugins[$this_plugin]['Description'] = $plugin_description; } if ($plugin_author !== '') { $all_plugins[$this_plugin]['Author'] = $plugin_author; $all_plugins[$this_plugin]['AuthorName'] = $plugin_author; } if ($plugin_author_uri !== '') { $all_plugins[$this_plugin]['AuthorURI'] = $plugin_author_uri; } if ($plugin_uri !== '') { $all_plugins[$this_plugin]['PluginURI'] = $plugin_uri; } } return $all_plugins; } /** * Register the stylesheets for the admin area. * * @since 1.0.0 */ public function enqueue_styles() { wp_enqueue_style( $this->plugin_name, plugin_dir_url(__FILE__) . 'css/metasync-admin.css', array(), $this->version, 'all' ); // Enqueue dashboard-style CSS for admin pages wp_enqueue_style( $this->plugin_name . '-dashboard', plugin_dir_url(__FILE__) . 'css/metasync-dashboard.css', array($this->plugin_name), $this->version, 'all' ); // Enqueue wizard CSS if on wizard page if (isset($_GET['page']) && strpos($_GET['page'], '-setup-wizard') !== false) { wp_enqueue_style( $this->plugin_name . '-setup-wizard', plugin_dir_url(__FILE__) . 'css/metasync-setup-wizard.css', array($this->plugin_name . '-dashboard'), $this->version, 'all' ); } } /** * Register the JavaScript for the admin area. * * @since 1.0.0 */ public function enqueue_scripts() { wp_enqueue_media(); wp_enqueue_script( $this->plugin_name, plugin_dir_url(__FILE__) . 'js/metasync-admin.js', array('jquery'), $this->version, false ); // Enqueue dashboard-style JavaScript for enhanced interactions wp_enqueue_script( $this->plugin_name . '-dashboard', plugin_dir_url(__FILE__) . 'js/metasync-dashboard.js', array('jquery', $this->plugin_name), $this->version, true ); # Enqueue theme switcher wp_enqueue_script( $this->plugin_name . '-theme-switcher', plugin_dir_url(__FILE__) . 'js/metasync-theme-switcher.js', array('jquery'), $this->version, true ); // --- Phase 5 (#887): Extracted inline JS files --- $current_page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : ''; $plugin_root_url = plugin_dir_url(dirname(__FILE__)); // Dashboard iframe height (only on dashboard page) if ($current_page === self::$page_slug . '-dashboard' || $current_page === self::$page_slug) { wp_enqueue_script( $this->plugin_name . '-iframe', plugin_dir_url(__FILE__) . 'js/metasync-iframe.js', array(), $this->version, false // Load in head — needed before iframe renders ); } // Settings page scripts (save btn, clear settings, bing key, access roles) if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-settings', plugin_dir_url(__FILE__) . 'js/metasync-settings.js', array('jquery'), $this->version, true ); } // Tab switcher (redirections / 404-monitor page) if ($current_page === self::$page_slug . '-redirections') { wp_enqueue_script( $this->plugin_name . '-tab-switcher', plugin_dir_url(__FILE__) . 'js/metasync-tab-switcher.js', array('jquery'), $this->version, true ); } // Error logs — copy to clipboard if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-error-logs', $plugin_root_url . 'site-error-logs/js/metasync-error-logs.js', array(), $this->version, true ); } // Access control UI if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-access-control', $plugin_root_url . 'includes/js/metasync-access-control.js', array(), $this->version, true ); } // 404 monitor filter if ($current_page === self::$page_slug . '-redirections' || $current_page === self::$page_slug . '-404-monitor') { wp_enqueue_script( $this->plugin_name . '-404-monitor', $plugin_root_url . 'views/js/metasync-404-monitor.js', array(), $this->version, true ); } // Redirections filter if ($current_page === self::$page_slug . '-redirections') { wp_enqueue_script( $this->plugin_name . '-redirections', $plugin_root_url . 'views/js/metasync-redirections.js', array(), $this->version, true ); } // --- Phase 5 Part B: Extracted inline JS with wp_localize_script --- // Navigation portal menus (top bar + settings inner page) $whitelabel_data = Metasync::get_option('whitelabel'); if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-navigation', plugin_dir_url(__FILE__) . 'js/metasync-navigation.js', array(), $this->version, false // Load in head — global functions called from onclick attributes ); wp_localize_script($this->plugin_name . '-navigation', 'metasyncNavData', array( 'hideAdvanced' => !empty($whitelabel_data['hide_advanced']), 'showGeneral' => Metasync_Access_Control::user_can_access('hide_settings'), 'pageSlug' => self::$page_slug, )); } // Debug mode timer if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { $debug_manager = Metasync_Debug_Mode_Manager::get_instance(); $debug_status = $debug_manager->get_status(); wp_enqueue_script( $this->plugin_name . '-debug-mode', plugin_dir_url(__FILE__) . 'js/metasync-debug-mode.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-debug-mode', 'metasyncDebugData', array( 'enabled' => !empty($debug_status['enabled']), 'indefinite' => !empty($debug_status['indefinite']), 'timeRemaining' => isset($debug_status['time_remaining']) ? (int) $debug_status['time_remaining'] : 0, 'statusUrl' => rest_url('metasync/v1/debug-mode/status'), 'restNonce' => wp_create_nonce('wp_rest'), )); } // MetasyncConfig + admin bar sync if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-config', plugin_dir_url(__FILE__) . 'js/metasync-config.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-config', 'metasyncConfigData', array( 'pluginName' => Metasync::get_effective_plugin_name(), 'ottoName' => Metasync::get_whitelabel_otto_name(), )); } // Whitelabel password (forgot password handler) if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-whitelabel', plugin_dir_url(__FILE__) . 'js/metasync-whitelabel.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-whitelabel', 'metasyncWhitelabelData', array( 'recoverNonce' => wp_create_nonce('metasync_recover_password_nonce'), )); } // Whitelabel connect (validation modal + lock section + export) if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-connect', plugin_dir_url(__FILE__) . 'js/metasync-connect.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-connect', 'metasyncConnectData', array( 'optionKey' => self::option_key, 'storedPassword' => isset($whitelabel_data['settings_password']) ? $whitelabel_data['settings_password'] : '', 'adminPostUrl' => admin_url('admin-post.php'), 'exportNonce' => wp_create_nonce('metasync_export_whitelabel'), 'ajaxUrl' => admin_url('admin-ajax.php'), 'logoutNonceField' => wp_nonce_field('whitelabel_logout_nonce', 'whitelabel_logout_nonce', true, false), )); } // Host blocking test (settings + dashboard) if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-host-blocking', plugin_dir_url(__FILE__) . 'js/metasync-host-blocking.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-host-blocking', 'metasyncHostBlockingData', array( 'ajaxUrl' => admin_url('admin-ajax.php'), )); } // OTTO excluded URLs if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-excluded-urls', plugin_dir_url(__FILE__) . 'js/metasync-excluded-urls.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-excluded-urls', 'metasyncExcludedUrlsData', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('metasync_otto_excluded_urls'), )); } // Execution settings form if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { $server_limits = $this->get_server_limits(); wp_enqueue_script( $this->plugin_name . '-execution-settings', plugin_dir_url(__FILE__) . 'js/metasync-execution-settings.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-execution-settings', 'metasyncExecSettingsData', array( 'serverMaxExecTime' => ($server_limits['max_execution_time_raw'] == -1) ? 'Infinity' : (int) $server_limits['max_execution_time_raw'], 'serverMaxMemory' => ($server_limits['memory_limit_raw'] == -1) ? 'Infinity' : (int) $server_limits['memory_limit_raw'], 'canChangeMemory' => !empty($server_limits['can_change_memory']), )); } // Quick edit badge (custom pages) wp_enqueue_script( $this->plugin_name . '-quick-edit', plugin_dir_url(__FILE__) . 'js/metasync-quick-edit.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-quick-edit', 'metasyncQuickEditData', array( 'standardPageLabel' => __('Standard page', 'metasync'), )); // Report issue form if ($current_page === self::$page_slug . '-report-issue') { wp_enqueue_script( $this->plugin_name . '-report-issue', $plugin_root_url . 'views/js/metasync-report-issue.js', array('jquery'), $this->version, true ); } // Bing console if ($current_page === self::$page_slug . '-bing-console') { wp_enqueue_script( $this->plugin_name . '-bing-console', $plugin_root_url . 'views/js/metasync-bing-console.js', array('jquery'), $this->version, true ); } // Add redirection form if ($current_page === self::$page_slug . '-redirections') { wp_enqueue_script( $this->plugin_name . '-add-redirection', $plugin_root_url . 'views/js/metasync-add-redirection.js', array(), $this->version, true ); } // Import redirections if ($current_page === self::$page_slug . '-redirections') { wp_enqueue_script( $this->plugin_name . '-import-redirections', $plugin_root_url . 'views/js/metasync-import-redirections.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-import-redirections', 'metasyncImportRedirData', array( 'nonce' => wp_create_nonce('metasync_import_redirections'), 'redirectUrl' => admin_url('admin.php?page=' . self::$page_slug . '-redirections'), )); } // Import external data (SEO metadata) if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-import-external-data', $plugin_root_url . 'views/js/metasync-import-external-data.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-import-external-data', 'metasyncImportData', array( 'importNonce' => wp_create_nonce('metasync_import_external_data'), 'seoImportNonce' => wp_create_nonce('metasync_import_seo_metadata'), )); } // OTTO bot statistics if ($current_page === self::$page_slug || strpos($current_page, self::$page_slug) === 0) { wp_enqueue_script( $this->plugin_name . '-bot-statistics', $plugin_root_url . 'views/js/metasync-bot-statistics.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-bot-statistics', 'metasyncBotStatsData', array( 'resetNonce' => wp_create_nonce('metasync_reset_bot_stats'), )); } // OTTO debug page if ($current_page === self::$page_slug . '-otto-debug') { wp_enqueue_script( $this->plugin_name . '-otto-debug', plugin_dir_url(__FILE__) . 'js/metasync-otto-debug.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-otto-debug', 'metasyncOttoDebugData', array( 'nonce' => wp_create_nonce('metasync_otto_debug'), )); } // --- End Phase 5 enqueues --- # Localize theme switcher script wp_localize_script( $this->plugin_name . '-theme-switcher', 'metasyncThemeData', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('metasync_theme_nonce'), 'currentTheme' => get_option('metasync_theme', 'dark') ) ); // Localize the script to make the AJAX URL accessible $options = Metasync::get_option('general'); // Get connection status for JavaScript $general_settings = Metasync::get_option('general'); $searchatlas_api_key = isset($general_settings['searchatlas_api_key']) ? $general_settings['searchatlas_api_key'] : ''; $otto_pixel_uuid = isset($general_settings['otto_pixel_uuid']) ? $general_settings['otto_pixel_uuid'] : ''; // SECURITY FIX (CVE-2025-14386): Only generate Search Atlas connect nonce for administrators // Using strict capability check instead of plugin access roles $sa_connect_nonce = ''; if (current_user_can('manage_options')) { $sa_connect_nonce = wp_create_nonce('metasync_sa_connect_nonce'); } $heartbeat_state = $this->get_heartbeat_state(); wp_localize_script( $this->plugin_name, 'metaSync', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'admin_url'=>admin_url('admin.php'), 'sa_connect_nonce' => $sa_connect_nonce, 'reset_auth_nonce' => wp_create_nonce('metasync_reset_auth_nonce'), 'burst_ping_nonce' => wp_create_nonce('metasync_burst_ping'), 'heartbeat_state' => $heartbeat_state, 'dashboard_domain' => self::get_effective_dashboard_domain(), 'support_email' => Metasync::SUPPORT_EMAIL, 'documentation_domain' => Metasync::DOCUMENTATION_DOMAIN, 'debug_enabled' => WP_DEBUG || (defined('METASYNC_DEBUG') && constant('METASYNC_DEBUG')), 'searchatlas_api_key' => !empty($searchatlas_api_key), 'otto_pixel_uuid' => $otto_pixel_uuid, 'is_connected' => (bool)$this->is_heartbeat_connected() )); // Ensure ajaxurl is available for admin pages (WordPress standard) // This creates a global ajaxurl variable for JavaScript wp_enqueue_script('wp-util'); // Add inline script to ensure ajaxurl is defined $inline_script = " if (typeof ajaxurl === 'undefined') { var ajaxurl = '" . esc_js(admin_url('admin-ajax.php')) . "'; } // Add Plugin Auth Token refresh functionality jQuery(document).ready(function($) { $('#refresh-plugin-auth-token').click(function() { var button = $(this); var originalText = button.text(); if (confirm('Are you sure you want to refresh the Plugin Auth Token? This will generate a new token and update the heartbeat API.')) { // Disable button and show loading button.prop('disabled', true).text('🔄 Refreshing...'); $.post(ajaxurl, { action: 'refresh_plugin_auth_token', nonce: '" . wp_create_nonce('refresh_plugin_auth_token') . "' }) .done(function(response) { if (response.success && response.data && response.data.new_token) { // Update the field value immediately $('#apikey').val(response.data.new_token); // Visual feedback with green border $('#apikey').css('border', '2px solid #28a745').animate({borderColor: '#ddd'}, 2000); alert('✅ Plugin Auth Token refreshed successfully!\\n\\nNew token: ' + response.data.new_token.substring(0, 8) + '...'); } else { alert('❌ Error refreshing token: ' + (response.data ? response.data.message : 'Unknown error')); } }) .fail(function() { alert('❌ Network error while refreshing token'); }) .always(function() { // Re-enable button button.prop('disabled', false).text(originalText); }); } }); }); "; wp_add_inline_script($this->plugin_name, $inline_script); add_action('admin_notices', array($this, 'permalink_structure_dashboard_warning')); // Display update warning banner if plugin update is available add_action('admin_notices', array($this, 'display_update_warning_banner')); // Enqueue wizard assets if on wizard page if (isset($_GET['page']) && strpos($_GET['page'], '-setup-wizard') !== false) { wp_enqueue_script( $this->plugin_name . '-setup-wizard', plugin_dir_url(__FILE__) . 'js/metasync-setup-wizard.js', array('jquery'), $this->version, true ); wp_localize_script($this->plugin_name . '-setup-wizard', 'metasyncWizardData', array( 'nonce' => wp_create_nonce('metasync_wizard'), 'ssoNonce' => wp_create_nonce('metasync_sso_nonce'), 'saConnectNonce' => wp_create_nonce('metasync_sa_connect_nonce'), 'importNonce' => wp_create_nonce('metasync_import_external_data'), 'dashboardUrl' => admin_url('admin.php?page=' . self::$page_slug . '-dashboard'), 'currentStep' => 1, 'totalSteps' => 6, 'pluginName' => Metasync::get_effective_plugin_name() )); } wp_enqueue_script('heartbeat'); } /** * Settings of HeartBeat API for admin area. * Set time interval of send request. */ function metasync_heartbeat_settings($settings) { global $heartbeat_frequency; $settings['interval'] = 300; return $settings; } /** * Data or Response received from HeartBeat API for admin area. */ function metasync_received_data($response, $data) { // if ($data['client'] == 'marco') $response['server'] = wp_json_encode($data); return $response; } /** * Add Import External Data page */ public function add_import_external_data_page() { # Check if current user has plugin access based on role settings if (!$this->current_user_has_plugin_access()) { return; // Don't add this page for users without access } // Use 'read' capability since actual access is controlled by current_user_has_plugin_access() check above add_submenu_page( null, // Hidden from menu, linked from other pages 'Import External Data', 'Import External Data', 'read', 'metasync-import-external', array($this, 'render_import_external_data_page') ); } /** * Render Import External Data page */ public function render_import_external_data_page() { require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-external-importer.php'; require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-import-external-data.php'; } /** * AJAX handler for external data import */ public function ajax_import_external_data() { Metasync_Admin_Ajax::instance()->ajax_import_external_data(); } /** * AJAX handler for SEO metadata batch import * Supports batch processing with progress tracking */ public function ajax_import_seo_metadata() { Metasync_Admin_Ajax::instance()->ajax_import_seo_metadata(); } /** * Additional context validation for Search Atlas connect tokens. * * Validates site URL and optional IP/user-agent context for connect tokens * used during the Search Atlas API key + Otto UUID retrieval flow. * This does NOT create WordPress login sessions. */ private function validate_searchatlas_context($token_data) { return Metasync_Connect_Manager::instance()->validate_searchatlas_context($token_data); } private function should_validate_ip() { return Metasync_Connect_Manager::instance()->should_validate_ip(); } private function are_user_agents_incompatible($old_ua, $new_ua) { return Metasync_Connect_Manager::instance()->are_user_agents_incompatible($old_ua, $new_ua); } private function extract_browser_name($ua) { return Metasync_Connect_Manager::instance()->extract_browser_name($ua); } public function generate_searchatlas_wp_connect_token($regenerate = false) { return Metasync_Connect_Manager::instance()->generate_searchatlas_wp_connect_token($regenerate); } private function ensure_plugin_auth_token_exists() { Metasync_Connect_Manager::instance()->ensure_plugin_auth_token_exists(); } public function generate_searchatlas_connect_url() { Metasync_Connect_Manager::instance()->generate_searchatlas_connect_url(); } public function check_searchatlas_connect_status() { Metasync_Connect_Manager::instance()->check_searchatlas_connect_status(); } private function create_searchatlas_nonce_token() { return Metasync_Connect_Manager::instance()->create_searchatlas_nonce_token(); } private function get_client_ip() { return Metasync_Connect_Manager::instance()->get_client_ip(); } private function create_encrypted_searchatlas_token($metadata = array()) { return Metasync_Connect_Manager::instance()->create_encrypted_searchatlas_token($metadata); } private function wp_encrypt_token($payload) { return Metasync_Connect_Manager::instance()->wp_encrypt_token($payload); } public function test_enhanced_searchatlas_tokens() { return Metasync_Connect_Manager::instance()->test_enhanced_searchatlas_tokens(); } public function test_searchatlas_ajax_endpoint() { Metasync_Connect_Manager::instance()->test_searchatlas_ajax_endpoint(); } public function simple_ajax_test() { Metasync_Connect_Manager::instance()->simple_ajax_test(); } /** * Create Admin Dashboard Iframe Page * Embeds the Search Atlas dashboard directly in WordPress admin */ public function create_admin_dashboard_iframe() { Metasync_Admin_Pages::get_instance($this)->create_admin_dashboard_iframe(); } /** * Render OTTO cache management interface */ public function render_otto_cache_management() { Metasync_Otto_Cache_Manager::instance()->render_otto_cache_management(); } /** * Render debug mode section for inclusion in Advanced settings */ public function render_debug_mode_section() { Metasync_Debug_Manager::instance()->render_debug_mode_section(); } /** * Render error log content for inclusion in Advanced settings */ public function render_error_log_content() { Metasync_Debug_Manager::instance()->render_error_log_content(); } /** * WordPress standard handler for clearing all cache plugins (admin_post hook) * This method runs early and prevents any output before redirect */ public function handle_clear_all_cache_plugins() { Metasync_Otto_Cache_Manager::instance()->handle_clear_all_cache_plugins(); } /** * WordPress standard handler for clearing OTTO cache (admin_post hook) */ public function handle_clear_otto_cache_all() { Metasync_Otto_Cache_Manager::instance()->handle_clear_otto_cache_all(); } /** * WordPress standard handler for clearing OTTO cache by URL (admin_post hook) */ public function handle_clear_otto_cache_url() { Metasync_Otto_Cache_Manager::instance()->handle_clear_otto_cache_url(); } /** * Get hosting cache integration settings with defaults * * @return array Settings array with 'wpengine_enabled' and 'kinsta_enabled' keys */ private function get_hosting_cache_settings() { $defaults = array( 'wpengine_enabled' => true, 'kinsta_enabled' => true, ); $saved = get_option('metasync_hosting_cache_options', array()); return wp_parse_args($saved, $defaults); } /** * AJAX handler for saving hosting cache settings */ public function ajax_save_hosting_cache_settings() { if (!isset($_POST['hosting_cache_nonce']) || !wp_verify_nonce($_POST['hosting_cache_nonce'], 'metasync_hosting_cache_nonce')) { wp_send_json_error(array('message' => 'Security check failed')); return; } if (!Metasync::current_user_has_plugin_access()) { wp_send_json_error(array('message' => 'Insufficient permissions')); return; } $settings = array( 'wpengine_enabled' => !empty($_POST['wpengine_enabled']) && $_POST['wpengine_enabled'] === '1', 'kinsta_enabled' => !empty($_POST['kinsta_enabled']) && $_POST['kinsta_enabled'] === '1', ); update_option('metasync_hosting_cache_options', $settings); wp_send_json_success(array('message' => 'Hosting cache settings saved')); } /** * admin_post handler: purge WP Engine and Kinsta hosting-level caches */ public function handle_purge_hosting_cache() { if (!isset($_POST['hosting_cache_purge_nonce']) || !wp_verify_nonce($_POST['hosting_cache_purge_nonce'], 'metasync_hosting_cache_purge_nonce')) { wp_die('Security check failed'); } if (!Metasync::current_user_has_plugin_access()) { wp_die('You do not have permission to perform this action'); } $redirect_url = admin_url('admin.php?page=' . self::$page_slug . '&tab=advanced'); $settings = $this->get_hosting_cache_settings(); $cleared = array(); $failed = array(); $not_detected = array(); // WP Engine native purge (Varnish + Memcached) if (!empty($settings['wpengine_enabled'])) { if (class_exists('WpeCommon')) { try { WpeCommon::purge_varnish_cache(); WpeCommon::purge_memcached(); $cleared[] = 'WP Engine'; } catch (Exception $e) { error_log('MetaSync: WP Engine hosting cache purge failed - ' . $e->getMessage()); $failed[] = 'WP Engine'; } } else { $not_detected[] = 'WP Engine'; } } // Kinsta native purge (full site) if (!empty($settings['kinsta_enabled'])) { if (class_exists('KinstaCache')) { try { KinstaCache::get_instance()->kinsta_cache_purge_full(); $cleared[] = 'Kinsta'; } catch (Exception $e) { error_log('MetaSync: Kinsta hosting cache purge failed - ' . $e->getMessage()); $failed[] = 'Kinsta'; } } else { $not_detected[] = 'Kinsta'; } } $redirect_url .= '&hosting_cache_cleared=1'; if (!empty($cleared)) { $redirect_url .= '&hc_cleared=' . urlencode(implode(',', $cleared)); } if (!empty($failed)) { $redirect_url .= '&hc_failed=' . urlencode(implode(',', $failed)); } if (!empty($not_detected)) { $redirect_url .= '&hc_not_detected=' . urlencode(implode(',', $not_detected)); } wp_safe_redirect($redirect_url); exit; } /** * Handle debug mode operations (enable/disable/extend) */ private function handle_debug_mode_operations() { Metasync_Debug_Manager::instance()->handle_debug_mode_operations(); } /** * Handle error log operations (clear) */ private function handle_error_log_operations() { Metasync_Debug_Manager::instance()->handle_error_log_operations(); } /** * Handle clear all settings operations */ private function handle_clear_all_settings() { Metasync_Debug_Manager::instance()->handle_clear_all_settings(); } /** * Handle saving plugin access roles from Advanced Settings * @deprecated Now handled by Metasync_Settings_Registration::settings_page_init() */ private function handle_plugin_access_roles_save() { } /** * Get error log content for display */ private function get_error_log_content() { return Metasync_Debug_Manager::instance()->get_error_log_content(); } /** * Memory-efficient function to get last N lines from a large file */ private function get_log_tail($file_path, $lines = null) { return Metasync_Debug_Manager::instance()->get_log_tail($file_path, $lines); } /** * Test whitelabel domain functionality (development/debugging) */ public function test_whitelabel_domain() { Metasync_Connect_Manager::instance()->test_whitelabel_domain(); } /** * Decrypt token using WordPress SALTs */ private function wp_decrypt_token($encrypted_token) { return Metasync_Connect_Manager::instance()->wp_decrypt_token($encrypted_token); } /** * Get active JWT token for the plugin * Public static method accessible from anywhere in the plugin * * @param bool $force_refresh Force generation of new token even if cached one exists * @return string|false JWT token on success, false on failure */ public static function get_active_jwt_token($force_refresh = false) { return Metasync_Connect_Manager::instance()->get_active_jwt_token($force_refresh); } /** * Get fresh JWT token from Search Atlas API with caching * Generates and caches JWT tokens to avoid repeated API calls * * @return string|false JWT token on success, false on failure */ public function get_fresh_jwt_token() { return Metasync_Connect_Manager::instance()->get_fresh_jwt_token(); } /** * Clear cached JWT tokens * Useful when authentication is reset or API key changes */ private function clear_jwt_token_cache() { Metasync_Connect_Manager::instance()->clear_jwt_token_cache(); } /** * Data or Response received from HeartBeat API for admin area. */ public function lgSendCustomerParams() { Metasync_Admin_Ajax::instance()->lgSendCustomerParams(); } /** * Add CSS styles for Search Atlas admin bar status indicator */ public function metasync_admin_bar_style() { Metasync_Admin_Navigation::instance()->metasync_admin_bar_style(); } /** * Add Search Atlas status indicator to WordPress admin bar * Shows sync status with green/red emoji */ public function add_searchatlas_admin_bar_status($wp_admin_bar) { Metasync_Admin_Navigation::instance()->add_searchatlas_admin_bar_status($wp_admin_bar); } // ------------------------------------------------------------------ // Heartbeat / connection-monitoring – delegated to Metasync_Heartbeat_Manager // ------------------------------------------------------------------ public function is_heartbeat_connected($general_settings = null) { return Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_settings); } public function fetch_public_hash($otto_pixel_uuid, $jwt_token) { return Metasync_Heartbeat_Manager::instance()->fetch_public_hash($otto_pixel_uuid, $jwt_token); } public function schedule_heartbeat_cron() { Metasync_Heartbeat_Manager::instance()->schedule_heartbeat_cron(); } public function unschedule_heartbeat_cron() { Metasync_Heartbeat_Manager::instance()->unschedule_heartbeat_cron(); } public function execute_heartbeat_cron_check() { return Metasync_Heartbeat_Manager::instance()->execute_heartbeat_cron_check(); } public function add_heartbeat_cron_schedule($schedules) { return Metasync_Heartbeat_Manager::instance()->add_heartbeat_cron_schedule($schedules); } public function get_heartbeat_state() { return Metasync_Heartbeat_Manager::instance()->get_heartbeat_state(); } public function set_heartbeat_state_key_pending() { Metasync_Heartbeat_Manager::instance()->set_heartbeat_state_key_pending(); } public function execute_burst_heartbeat() { Metasync_Heartbeat_Manager::instance()->execute_burst_heartbeat(); } public function execute_announce_cron() { Metasync_Heartbeat_Manager::instance()->execute_announce_cron(); } public function unschedule_burst_heartbeat_cron() { Metasync_Heartbeat_Manager::instance()->unschedule_burst_heartbeat_cron(); } public function unschedule_announce_cron() { Metasync_Heartbeat_Manager::instance()->unschedule_announce_cron(); } public function maybe_schedule_heartbeat_cron() { Metasync_Heartbeat_Manager::instance()->maybe_schedule_heartbeat_cron(); } public function maybe_send_pre_sso_announce() { Metasync_Heartbeat_Manager::instance()->maybe_send_pre_sso_announce(); } public function trigger_immediate_heartbeat_check($context = 'Manual trigger') { return Metasync_Heartbeat_Manager::instance()->trigger_immediate_heartbeat_check($context); } public function handle_immediate_heartbeat_trigger($context = 'WordPress action trigger') { Metasync_Heartbeat_Manager::instance()->handle_immediate_heartbeat_trigger($context); } public function ajax_burst_ping() { Metasync_Heartbeat_Manager::instance()->ajax_burst_ping(); } public function update_heartbeat_cache_after_sync($is_connected, $context = 'Sync operation') { return Metasync_Heartbeat_Manager::instance()->update_heartbeat_cache_after_sync($is_connected, $context); } /** * Refresh Plugin Auth Token * Generates a new Plugin Auth Token and updates heartbeat API */ public function refresh_plugin_auth_token() { Metasync_Connect_Manager::instance()->refresh_plugin_auth_token(); } public function get_plugin_auth_token() { Metasync_Connect_Manager::instance()->get_plugin_auth_token(); } public function reset_searchatlas_authentication() { Metasync_Connect_Manager::instance()->reset_searchatlas_authentication(); } private function cleanup_searchatlas_nonce_tokens() { return Metasync_Connect_Manager::instance()->cleanup_searchatlas_nonce_tokens(); } private function cleanup_searchatlas_rate_limits() { return Metasync_Connect_Manager::instance()->cleanup_searchatlas_rate_limits(); } private function get_available_menu_items() { return Metasync_Admin_Navigation::instance()->get_available_menu_items(); } /** * Add options page */ public function add_plugin_settings_page() { Metasync_Admin_Navigation::instance()->add_plugin_settings_page($this); } /** * General Options page callback */ public function create_admin_settings_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_settings_page(); } public function render_navigation_menu($current_page = null) { Metasync_Admin_Navigation::instance()->render_navigation_menu($current_page); } public function render_plugin_header($page_title = null) { Metasync_Admin_Navigation::instance()->render_plugin_header($page_title); } /* Method to handle Ajax request from "General Settings" page */ public function meta_sync_save_settings() { Metasync_Settings_Registration::instance()->meta_sync_save_settings(); } /** * AJAX handler for saving execution settings */ public function ajax_save_execution_settings() { Metasync_Settings_Registration::instance()->ajax_save_execution_settings(); } /** * Get Action Scheduler concurrent batches from execution settings * * @param int $default_batches Default concurrent batches * @return int Configured concurrent batches */ public function get_action_scheduler_batches($default_batches) { // Only apply if Action Scheduler is active if (!class_exists('ActionScheduler')) { return $default_batches; } return $this->get_execution_setting('action_scheduler_batches'); } /** * Get Action Scheduler retention period from execution settings * Converts days to seconds for Action Scheduler * * @param int $default_seconds Default retention period in seconds * @return int Configured retention period in seconds */ public function get_action_scheduler_retention_period($default_seconds) { // Only apply if Action Scheduler is active if (!class_exists('ActionScheduler')) { return $default_seconds; // Default is 30 days = 2592000 seconds } $cleanup_days = $this->get_execution_setting('queue_cleanup_days'); return $cleanup_days * DAY_IN_SECONDS; } /* * Sync setting on CRUD term category */ public function admin_crud_term($term_id,$term_tax_id,$taxonomy) { # Handle term creation, update, or deletion $this->sync_term($term_id, $taxonomy); } /* * Sync setting on Delete term category */ public function admin_delete_term($term_id,$taxonomy) { # Handle term deletion $this->sync_term($term_id, $taxonomy); } /* * Handle category deletion - Sync after category is deleted * This hook fires AFTER the category is fully deleted from the database */ public function admin_delete_category($term_id) { try { # Initialize MetaSync API request class and trigger synchronization (new Metasync_Sync_Requests())->SyncCustomerParams(); } catch (Exception $e) { # Log any API request errors for debugging error_log('Metasync API Error: ' . $e->getMessage()); } } /* * Call the SYNC API */ private function sync_term($term_id, $taxonomy) { # Ensure the term belongs to the 'category' taxonomy and is not an error if ($taxonomy !== 'category' ) return; try { # Initialize MetaSync API request class and trigger synchronization (new Metasync_Sync_Requests())->SyncCustomerParams(); } catch (Exception $e) { # Log any API request errors for debugging error_log('Metasync API Error: ' . $e->getMessage()); } } /** * Dashboard page callback */ public function create_admin_dashboard_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_dashboard_page(); } /** * Robots.txt page callback */ public function create_admin_robots_txt_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_robots_txt_page(); } /** * Media Optimization page callback */ public function create_admin_media_optimization_page() { ?>
Metasync_Media_Library_List_Table::render_status_html($attachment_id), ]); } wp_send_json_error(__('Optimization failed.', 'metasync')); } /** * AJAX: Revert a single image. */ public function ajax_revert_single_image() { check_ajax_referer('metasync_media_opt_nonce', 'nonce'); if (!current_user_can('upload_files')) { wp_send_json_error(__('Permission denied.', 'metasync')); } $attachment_id = isset($_POST['attachment_id']) ? absint($_POST['attachment_id']) : 0; if (!$attachment_id) { wp_send_json_error(__('Invalid attachment ID.', 'metasync')); } $success = Metasync_Image_Converter::revert_attachment($attachment_id); if ($success) { wp_send_json_success(); } wp_send_json_error(__('Revert failed. Original file may not exist (replace strategy).', 'metasync')); } /** * AJAX: Start batch optimization. */ public function ajax_start_batch_optimize() { check_ajax_referer('metasync_media_opt_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'metasync')); } require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-settings.php'; require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-batch-optimizer.php'; $settings = Metasync_Media_Settings::get_settings(); $progress = Metasync_Media_Batch_Optimizer::start_batch($settings); wp_send_json_success($progress); } /** * AJAX: Cancel batch optimization. */ public function ajax_cancel_batch_optimize() { check_ajax_referer('metasync_media_opt_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'metasync')); } require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-batch-optimizer.php'; Metasync_Media_Batch_Optimizer::cancel_batch(); wp_send_json_success(); } /** * AJAX: Get batch progress + stats. */ public function ajax_batch_progress() { check_ajax_referer('metasync_media_opt_nonce', 'nonce'); require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-batch-optimizer.php'; require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-library-list-table.php'; $progress = Metasync_Media_Batch_Optimizer::get_progress(); $progress['stats'] = Metasync_Media_Library_List_Table::get_stats(); wp_send_json_success($progress); } /** * AJAX: Bulk optimize selected images. */ public function ajax_bulk_optimize_selected() { check_ajax_referer('metasync_media_opt_nonce', 'nonce'); if (!current_user_can('upload_files')) { wp_send_json_error(__('Permission denied.', 'metasync')); } $ids = isset($_POST['ids']) ? array_map('absint', explode(',', sanitize_text_field($_POST['ids']))) : []; $ids = array_filter($ids); if (empty($ids)) { wp_send_json_error(__('No images selected.', 'metasync')); } require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-settings.php'; $settings = Metasync_Media_Settings::get_settings(); $success = 0; $failed = 0; foreach ($ids as $id) { if (Metasync_Image_Converter::convert_attachment($id, $settings)) { $success++; } else { $failed++; } } wp_send_json_success([ 'success' => $success, 'failed' => $failed, ]); } /** * AJAX: Process one batch tick (browser-driven chaining). */ public function ajax_process_batch_tick() { check_ajax_referer('metasync_media_opt_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'metasync')); } require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-batch-optimizer.php'; require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-library-list-table.php'; $progress = Metasync_Media_Batch_Optimizer::process_ajax_tick(); $progress['stats'] = Metasync_Media_Library_List_Table::get_stats(); wp_send_json_success($progress); } /** * Cron handler: Process batch optimization tick. */ public function handle_media_batch_cron() { require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-settings.php'; require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/class-media-batch-optimizer.php'; Metasync_Media_Batch_Optimizer::process_batch_tick(); } /** * Report Issue page callback */ public function create_admin_report_issue_page() { // Load the Report Issue view require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php'; } /** * XML Sitemap page callback */ public function create_admin_xml_sitemap_page() { // Load sitemap generator class require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php'; $sitemap_generator = new Metasync_Sitemap_Generator(); // Handle form submissions if (isset($_POST['metasync_sitemap_nonce'])) { check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce'); if (isset($_POST['generate_sitemap'])) { // Auto-disable other sitemap generators before generating $disabled_plugins = $sitemap_generator->disable_other_sitemap_generators(); $result = $sitemap_generator->generate_sitemap(); if (is_wp_error($result)) { echo '' . esc_html($result->get_error_message()) . '
' . esc_html($message) . '
' . esc_html__('Auto-update enabled!', 'metasync') . '
' . esc_html__('Auto-update disabled!', 'metasync') . '
' . esc_html__('Sitemap deleted successfully!', 'metasync') . '
' . esc_html__('Failed to delete sitemap. The file may not exist or is not writable.', 'metasync') . '
' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '
' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '
Warning from %s
To ensure compatibility, please update your permalink structure to any option other than "Plain".
For any inquiries, contact support.
Warning from %s
A new version of %s is available. Please update to the latest version to ensure compatibility and access new features.
For any inquiries, contact support.