*/ 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() { ?>
render_plugin_header('Media Optimization'); ?> render_navigation_menu('media_optimization'); ?> prepare_items(); $stats = Metasync_Media_Library_List_Table::get_stats(); $batch_progress = Metasync_Media_Batch_Optimizer::get_progress(); // Render the admin page view require_once plugin_dir_path(dirname(__FILE__)) . 'media-optimization/views/admin-page.php'; ?>
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()) . '

'; } else { $message = esc_html__('Sitemap generated successfully!', 'metasync'); if ($disabled_plugins) { $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync'); } // Check if robots.txt was updated $robots_result = get_transient('metasync_sitemap_robots_updated'); if ($robots_result && $robots_result['success']) { if ($robots_result['action'] === 'added') { $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync'); } elseif ($robots_result['action'] === 'updated') { $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync'); } elseif ($robots_result['action'] === 'created') { $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync'); } delete_transient('metasync_sitemap_robots_updated'); } echo '

' . esc_html($message) . '

'; } } elseif (isset($_POST['enable_auto_update'])) { update_option('metasync_sitemap_auto_update', true); $sitemap_generator->setup_auto_update_hooks(); echo '

' . esc_html__('Auto-update enabled!', 'metasync') . '

'; } elseif (isset($_POST['disable_auto_update'])) { update_option('metasync_sitemap_auto_update', false); echo '

' . esc_html__('Auto-update disabled!', 'metasync') . '

'; } elseif (isset($_POST['delete_sitemap'])) { // Delete the sitemap file (physical and/or virtual) $deleted = $sitemap_generator->delete_sitemap(); if ($deleted) { // Also disable auto-update when deleting update_option('metasync_sitemap_auto_update', false); echo '

' . esc_html__('Sitemap deleted successfully!', 'metasync') . '

'; } else { echo '

' . esc_html__('Failed to delete sitemap. The file may not exist or is not writable.', 'metasync') . '

'; } } elseif (isset($_POST['enable_other_sitemaps'])) { // Re-enable other sitemap plugins $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators(); if ($enabled_plugins) { echo '

' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '

'; } else { echo '

' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '

'; } } } // Get sitemap info $sitemap_exists = $sitemap_generator->sitemap_exists(); $sitemap_url = $sitemap_generator->get_sitemap_url(); $url_count = $sitemap_generator->count_urls(); $last_generated = $sitemap_generator->get_last_generated_time(); $auto_update_enabled = get_option('metasync_sitemap_auto_update', false); $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins(); // Load view require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php'; } /** * Custom Pages page callback */ public function create_admin_custom_pages_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_custom_pages_page(); } /** * 404 Monitor page callback */ public function create_admin_404_monitor_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_404_monitor_page(); } /** * Site Verification page callback */ public function create_admin_search_engine_verification_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_search_engine_verification_page(); } /** * Local Business page callback */ public function create_admin_local_business_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_local_business_page(); } /** * Code Snippets page callback */ public function create_admin_code_snippets_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_code_snippets_page(); } /** * Google Instant Index Setting page callback */ public function create_admin_google_instant_index_page() { ?>
render_plugin_header('Instant Indexing'); ?> render_navigation_menu('instant_index'); ?> '', 'post_types' => []]); $json_key = isset($options['json_key']) ? $options['json_key'] : ''; $google_guide_url = 'https://developers.google.com/search/apis/indexing-api/v3/quickstart'; $post_types_settings = isset($options['post_types']) && is_array($options['post_types']) ? $options['post_types'] : []; include_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-google-instant-settings.php'; ?>
render_plugin_header('Google Console'); ?> render_navigation_menu('google_console'); ?> get_service_account_info() : ['error' => 'Module not loaded']; $is_configured = !isset($service_info['error']); include_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-google-console.php'; ?>
render_plugin_header('Bing Console'); ?> render_navigation_menu('bing_console'); ?> show_bing_instant_indexing_console(); ?>
create_admin_optimal_settings_page(); } /** * Global Options page callback */ public function create_admin_global_settings_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_global_settings_page(); } /** * Common Meta Options page callback */ public function create_admin_common_meta_settings_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_common_meta_settings_page(); } /** * Social meta page callback */ public function create_admin_social_meta_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_social_meta_page(); } /** * Indexation Control page callback */ public function create_admin_seo_controls_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_seo_controls_page(); } /** * Site Optimal Settings page callback */ public function optimization_settings_options() { Metasync_Admin_Pages::get_instance($this)->optimization_settings_options(); } /** * redirection page callback with tabs */ public function create_admin_redirections_page() { Metasync_Redirections_Admin::get_instance($this->db_redirection, $this)->create_admin_redirections_page(); } /** * Display transient error/success messages for redirections */ public function display_redirection_messages() { Metasync_Redirections_Admin::get_instance($this->db_redirection, $this)->display_redirection_messages(); } /** * AJAX handler for updating database structure */ public function ajax_update_db_structure() { Metasync_Admin_Ajax::instance()->ajax_update_db_structure(); } /** * AJAX handler to save wizard progress * * @since 1.0.0 */ public function ajax_save_wizard_progress() { Metasync_Admin_Ajax::instance()->ajax_save_wizard_progress(); } /** * AJAX handler to complete wizard * * @since 1.0.0 */ public function ajax_complete_wizard() { Metasync_Admin_Ajax::instance()->ajax_complete_wizard(); } /** * AJAX handler to validate robots.txt content */ public function ajax_validate_robots() { Metasync_Admin_Ajax::instance()->ajax_validate_robots(); } /** * AJAX handler to get default robots.txt content */ public function ajax_get_default_robots() { Metasync_Admin_Ajax::instance()->ajax_get_default_robots(); } /** * AJAX handler to preview robots.txt backup content */ public function ajax_preview_robots_backup() { Metasync_Admin_Ajax::instance()->ajax_preview_robots_backup(); } /** * AJAX handler to delete robots.txt backup */ public function ajax_delete_robots_backup() { Metasync_Admin_Ajax::instance()->ajax_delete_robots_backup(); } /** * AJAX handler to restore robots.txt backup */ public function ajax_restore_robots_backup() { Metasync_Admin_Ajax::instance()->ajax_restore_robots_backup(); } public function ajax_create_redirect_from_404() { Metasync_Admin_Ajax::instance()->ajax_create_redirect_from_404(); } /** * AJAX handler for testing host blocking with GET request */ public function ajax_test_host_blocking_get() { Metasync_Admin_Ajax::instance()->ajax_test_host_blocking_get(); } /** * AJAX handler for testing host blocking with POST request */ public function ajax_test_host_blocking_post() { Metasync_Admin_Ajax::instance()->ajax_test_host_blocking_post(); } /** * Register REST API endpoint for ping */ public function register_ping_rest_endpoint() { register_rest_route('metasync/v1', '/ping', array( 'methods' => array('GET', 'POST'), 'callback' => array($this, 'handle_ping_rest_endpoint'), 'permission_callback' => '__return_true', // Allow public access 'args' => array( 'test' => array( 'description' => 'Optional test parameter', 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), ), )); } /** * Handle REST API ping endpoint */ public function handle_ping_rest_endpoint($request) { // Get request method $method = $request->get_method(); // Prepare response data $response_data = array( 'response' => 'pong', 'method' => $method, 'timestamp' => current_time('mysql'), 'site_url' => home_url(), 'plugin_version' => METASYNC_VERSION ); // Add request data for POST requests if ($method === 'POST') { $body = $request->get_body(); if (!empty($body)) { $response_data['received_data'] = json_decode($body, true); } // Add any query parameters $params = $request->get_params(); if (!empty($params)) { $response_data['query_params'] = $params; } } // Add test parameter if provided $test_param = $request->get_param('test'); if (!empty($test_param)) { $response_data['test_param'] = $test_param; } return new WP_REST_Response($response_data, 200); } /** * Site error logs page callback */ public function create_admin_error_logs_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_error_logs_page(); } /** * Compatibility page callback */ public function create_admin_compatibility_page() { Metasync_Compatibility_Checker::instance()->create_admin_compatibility_page($this); } /** * Sync Log page callback */ public function create_admin_sync_log_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_sync_log_page(); } /** * Build filter query string for pagination */ private function build_filter_query_string($filters) { $query_parts = []; foreach ($filters as $key => $value) { if (!empty($value)) { $query_parts[] = $key . '=' . urlencode($value); } } return !empty($query_parts) ? '&' . implode('&', $query_parts) : ''; } /** * Handle AJAX requests for sync log data */ private function handle_sync_log_ajax() { // This can be used for future AJAX functionality like real-time updates wp_die(); } /** * Render compatibility sections */ private function render_compatibility_sections() { Metasync_Compatibility_Checker::instance()->render_compatibility_sections(); } /** * Render Page Builders section */ private function render_page_builders_section() { Metasync_Compatibility_Checker::instance()->render_page_builders_section(); } /** * Render SEO Plugins section */ private function render_seo_plugins_section() { Metasync_Compatibility_Checker::instance()->render_seo_plugins_section(); } /** * Render Cache Plugins section */ private function render_cache_plugins_section() { Metasync_Compatibility_Checker::instance()->render_cache_plugins_section(); } /** * Render Lock Section button for protected tabs * * @param string $tab The tab identifier (general, whitelabel, advanced) */ private function render_lock_button($tab) { Metasync_Compatibility_Checker::instance()->render_lock_button($tab); } /** * Get Page Builders compatibility information */ private function get_page_builders_compatibility() { return Metasync_Compatibility_Checker::instance()->get_page_builders_compatibility(); } /** * Get SEO Plugins compatibility information */ private function get_seo_plugins_compatibility() { return Metasync_Compatibility_Checker::instance()->get_seo_plugins_compatibility(); } /** * Get Cache Plugins compatibility information */ private function get_cache_plugins_compatibility() { return Metasync_Compatibility_Checker::instance()->get_cache_plugins_compatibility(); } /** * Check if a plugin is installed and active * @deprecated Use get_plugin_status() instead */ private function is_plugin_installed($plugin_file) { return Metasync_Compatibility_Checker::instance()->is_plugin_installed($plugin_file); } /** * Get detailed plugin status (installed and/or active) * Checks multiple plugin file paths (e.g., free and premium versions) * * @param array $plugin_files Array of plugin file paths to check (e.g., ['free/plugin.php', 'pro/plugin.php']) * @param bool $is_core Whether this is a WordPress core feature (always installed/active) * @param string $theme_name Optional theme name to check if it's a theme instead of plugin * @return array ['is_installed' => bool, 'is_active' => bool, 'active_version' => string|null] */ private function get_plugin_status($plugin_files, $is_core = false, $theme_name = null) { return Metasync_Compatibility_Checker::instance()->get_plugin_status($plugin_files, $is_core, $theme_name); } /** * Get plugin logo URL (optimized for performance) */ private function get_plugin_logo($plugin_key, $type) { return Metasync_Compatibility_Checker::instance()->get_plugin_logo($plugin_key, $type); } public function creat_error_Logs_List() { Metasync_Admin_Pages::get_instance($this)->creat_error_Logs_List(); } /** * Site error logs page callback */ public function create_admin_heartbeat_error_logs_page() { Metasync_Admin_Pages::get_instance($this)->create_admin_heartbeat_error_logs_page(); } /** * Handle session management early for whitelabel functionality */ private function handle_session_management_early() { Metasync_Connect_Manager::instance()->handle_session_management_early(); } /** * @deprecated 2.5.12 Use Metasync_Auth_Manager instead of sessions for authentication */ private function safe_session_start() { // This method is deprecated and no longer used // Authentication now uses Metasync_Auth_Manager with WordPress transients and user meta _deprecated_function(__METHOD__, '2.5.12', 'Metasync_Auth_Manager'); return Metasync_Session_Helper::safe_start(); } private function handle_whitelabel_session_logic() { Metasync_Connect_Manager::instance()->handle_whitelabel_session_logic(); } private function handle_whitelabel_password_early() { Metasync_Connect_Manager::instance()->handle_whitelabel_password_early(); } /** * Get accordion sections configuration for General Settings * * @return array Accordion sections with field IDs, icons, and descriptions */ private function get_accordion_sections_config() { return Metasync_Settings_Fields::instance()->get_accordion_sections_config(); } /** * Get accordion sections configuration for Advanced Settings Tab * * @return array Accordion sections configuration */ private function get_advanced_accordion_config() { return Metasync_Settings_Fields::instance()->get_advanced_accordion_config(); } /** * Render accordion sections for Advanced Settings Tab */ public function render_advanced_accordion() { Metasync_Settings_Fields::instance()->render_advanced_accordion(); } /** * Render reset settings section for Advanced tab accordion */ private function render_reset_settings_section() { Metasync_Settings_Fields::instance()->render_reset_settings_section(); } /** * Render Google Index API section for Indexation Control page */ public function render_google_index_section() { Metasync_Settings_Fields::instance()->render_google_index_section(); } /** * Render Bing Index (IndexNow) section * * @since 2.6.0 * @return void */ public function render_bing_index_section() { Metasync_Settings_Fields::instance()->render_bing_index_section(); } /** * Render Plugin Access Roles section for Advanced tab accordion */ private function render_plugin_access_roles_section() { Metasync_Settings_Fields::instance()->render_plugin_access_roles_section(); } /** * Check if the current user has access to the plugin based on role settings * Wrapper method that delegates to the common Metasync::current_user_has_plugin_access() * but also requires manage_options capability for admin area access * * @return bool True if user has access, false otherwise */ public function current_user_has_plugin_access() { return Metasync::current_user_has_plugin_access(); } /** * Get default execution settings * * @return array Default execution settings */ private function get_default_execution_settings() { return Metasync_Settings_Fields::instance()->get_default_execution_settings(); } /** * Get execution setting value * * @param string $key Setting key * @param mixed $default Default value if setting doesn't exist * @return mixed Setting value or default */ public function get_execution_setting($key, $default = null) { return Metasync_Settings_Fields::instance()->get_execution_setting($key, $default); } /** * Get all execution settings * * @return array All execution settings with defaults merged */ public function get_all_execution_settings() { return Metasync_Settings_Fields::instance()->get_all_execution_settings(); } /** * Check if server allows changing memory limit * Tests if ini_set('memory_limit') is allowed * * @return bool True if memory limit can be changed, false otherwise */ private function can_change_memory_limit() { return Metasync_Settings_Fields::instance()->can_change_memory_limit(); } /** * Get PHP server limits for display * * @return array Server limits (execution_time, memory_limit, can_change_memory) */ private function get_server_limits() { return Metasync_Settings_Fields::instance()->get_server_limits(); } /** * Apply memory limit from execution settings * Only applies if server allows changing memory limit * * @return bool True if memory limit was applied, false otherwise */ public function apply_memory_limit() { return Metasync_Settings_Fields::instance()->apply_memory_limit(); } /** * Parse memory limit string to MB * * @param string $memory_limit Memory limit string (e.g., "256M", "1G") * @return int Memory limit in MB */ private function parse_memory_limit_to_mb($memory_limit) { return Metasync_Settings_Fields::instance()->parse_memory_limit_to_mb($memory_limit); } /** * Render Execution Settings section for Advanced tab accordion */ private function render_execution_settings_section() { Metasync_Settings_Fields::instance()->render_execution_settings_section(); } /** * Get tooltip content for settings fields * * @return array Field ID => Tooltip text mapping */ private function get_field_tooltips() { return Metasync_Settings_Fields::instance()->get_field_tooltips(); } /** * Get the section key for a given field ID * * @param string $field_id The settings field ID * @return string|null Section key or null if not found */ private function get_field_section($field_id) { return Metasync_Settings_Fields::instance()->get_field_section($field_id); } /** * Render accordion sections for General Settings * * @param string $page The settings page slug */ public function render_accordion_sections($page) { Metasync_Settings_Fields::instance()->render_accordion_sections($page); } /** * Register and add settings */ public function settings_page_init() { Metasync_Settings_Registration::instance()->settings_page_init(); } /** * Sanitize each setting field as needed * * @param array $input Contains all settings fields as array keys */ public function sanitize($input) { return Metasync_Settings_Registration::instance()->sanitize($input); } public function metasync_settings_genkey_callback() { Metasync_Settings_Fields::instance()->metasync_settings_genkey_callback(); } /** * Get the settings option array and print one of its values */ public function linkgraph_token_callback() { Metasync_Settings_Fields::instance()->linkgraph_token_callback(); } private function time_elapsed_string($datetime, $full = false) { return Metasync_Settings_Fields::instance()->time_elapsed_string($datetime, $full); } /** * Get the settings option array and print one of its values */ public function searchatlas_api_key_callback() { Metasync_Settings_Fields::instance()->searchatlas_api_key_callback(); } /** * Site Verification Tools * * Bing Site Verification * Baidu Site Verification * Alexa Site Verification * Yandex Site Verification * Google Site Verification * Pinterest Site Verification * Norton Safe Web Site Verification */ /** * Get the settings option array and print one of its values */ public function bing_site_verification_callback() { Metasync_Settings_Fields::instance()->bing_site_verification_callback(); } /** * Get the settings option array and print one of its values */ public function yandex_site_verification_callback() { Metasync_Settings_Fields::instance()->yandex_site_verification_callback(); } /** * Get the settings option array and print one of its values */ public function google_site_verification_callback() { Metasync_Settings_Fields::instance()->google_site_verification_callback(); } /** * Get the settings option array and print one of its values */ public function pinterest_site_verification_callback() { Metasync_Settings_Fields::instance()->pinterest_site_verification_callback(); } /** * Local SEO for business and person * */ /** * Get the settings option array and print one of its values */ public function local_seo_person_organization_callback() { Metasync_Settings_Fields::instance()->local_seo_person_organization_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_name_callback() { Metasync_Settings_Fields::instance()->local_seo_name_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_logo_callback() { Metasync_Settings_Fields::instance()->local_seo_logo_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_url_callback() { Metasync_Settings_Fields::instance()->local_seo_url_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_email_callback() { Metasync_Settings_Fields::instance()->local_seo_email_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_phone_callback() { Metasync_Settings_Fields::instance()->local_seo_phone_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_address_callback() { Metasync_Settings_Fields::instance()->local_seo_address_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_business_type_callback() { Metasync_Settings_Fields::instance()->local_seo_business_type_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_opening_hours_callback() { Metasync_Settings_Fields::instance()->local_seo_opening_hours_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_phone_numbers_callback() { Metasync_Settings_Fields::instance()->local_seo_phone_numbers_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_price_range_callback() { Metasync_Settings_Fields::instance()->local_seo_price_range_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_about_page_callback() { Metasync_Settings_Fields::instance()->local_seo_about_page_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_contact_page_callback() { Metasync_Settings_Fields::instance()->local_seo_contact_page_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_map_key_callback() { Metasync_Settings_Fields::instance()->local_seo_map_key_callback(); } /** * Get the settings option array and print one of its values */ public function local_seo_geo_coordinates_callback() { Metasync_Settings_Fields::instance()->local_seo_geo_coordinates_callback(); } /** * Get the settings option array and print one of its values */ public function header_snippets_callback() { Metasync_Settings_Fields::instance()->header_snippets_callback(); } /** * Get the settings option array and print one of its values */ public function footer_snippets_callback() { Metasync_Settings_Fields::instance()->footer_snippets_callback(); } /** * Get the settings option array and print one of its values */ public function no_index_posts_callback() { Metasync_Settings_Fields::instance()->no_index_posts_callback(); } /** * Get the settings option array and print one of its values */ public function no_follow_links_callback() { Metasync_Settings_Fields::instance()->no_follow_links_callback(); } /** * Get the settings option array and print one of its values */ public function open_external_links_callback() { Metasync_Settings_Fields::instance()->open_external_links_callback(); } /** * Get the settings option array and print one of its values */ public function add_alt_image_tags_callback() { Metasync_Settings_Fields::instance()->add_alt_image_tags_callback(); } /** * Get the settings option array and print one of its values */ public function add_title_image_tags_callback() { Metasync_Settings_Fields::instance()->add_title_image_tags_callback(); } /** * Get the settings option array and print one of its values */ public function site_type_callback() { Metasync_Settings_Fields::instance()->site_type_callback(); } /** * Get the settings option array and print one of its values */ public function site_business_type_callback() { Metasync_Settings_Fields::instance()->site_business_type_callback(); } /** * Get the settings option array and print one of its values */ public function site_company_name_callback() { Metasync_Settings_Fields::instance()->site_company_name_callback(); } /** * Get the settings option array and print one of its values */ public function site_google_logo_callback() { Metasync_Settings_Fields::instance()->site_google_logo_callback(); } /** * Get the settings option array and print one of its values */ public function site_social_share_image_callback() { Metasync_Settings_Fields::instance()->site_social_share_image_callback(); } /** * Get the settings option array and print one of its values */ public function common_robot_meta_tags_callback() { Metasync_Settings_Fields::instance()->common_robot_meta_tags_callback(); } /** * Backward compatibility alias for common_robot_mata_tags_callback * @deprecated Use common_robot_meta_tags_callback() instead */ public function common_robot_mata_tags_callback() { Metasync_Settings_Fields::instance()->common_robot_mata_tags_callback(); } /** * Get the settings option array and print one of its values */ public function advance_robot_meta_tags_callback() { Metasync_Settings_Fields::instance()->advance_robot_meta_tags_callback(); } /** * Backward compatibility alias for advance_robot_mata_tags_callback * @deprecated Use advance_robot_meta_tags_callback() instead */ public function advance_robot_mata_tags_callback() { Metasync_Settings_Fields::instance()->advance_robot_mata_tags_callback(); } /** * Get the settings option array and print one of its values */ public function global_twitter_card_type_callback() { Metasync_Settings_Fields::instance()->global_twitter_card_type_callback(); } /** * Get the settings option array and print one of its values */ public function global_open_graph_meta_callback() { Metasync_Settings_Fields::instance()->global_open_graph_meta_callback(); } /** * Get the settings option array and print one of its values */ public function global_facebook_meta_callback() { Metasync_Settings_Fields::instance()->global_facebook_meta_callback(); } /** * Get the settings option array and print one of its values */ public function global_twitter_meta_callback() { Metasync_Settings_Fields::instance()->global_twitter_meta_callback(); } /** * Get the settings option array and print one of its values */ public function facebook_page_url_callback() { Metasync_Settings_Fields::instance()->facebook_page_url_callback(); } /** * Get the settings option array and print one of its values */ public function facebook_authorship_callback() { Metasync_Settings_Fields::instance()->facebook_authorship_callback(); } /** * Get the settings option array and print one of its values */ public function facebook_admin_callback() { Metasync_Settings_Fields::instance()->facebook_admin_callback(); } /** * Get the settings option array and print one of its values */ public function facebook_app_callback() { Metasync_Settings_Fields::instance()->facebook_app_callback(); } /** * Get the settings option array and print one of its values */ public function facebook_secret_callback() { Metasync_Settings_Fields::instance()->facebook_secret_callback(); } /** * Get the settings option array and print one of its values */ public function twitter_username_callback() { Metasync_Settings_Fields::instance()->twitter_username_callback(); } /** * Get business types as choices in local business. * * @return array */ public static function get_business_types() { return Metasync_Settings_Fields::get_business_types(); } /** * Display a dashboard warning when using the plain permalink structure. * @param $data An array of data passed. */ public function permalink_structure_dashboard_warning() { $current_permalink_structure = get_option('permalink_structure'); # Get the plugin name using centralized method $plugin_name = Metasync::get_effective_plugin_name(); $current_rewrite_rules = get_option('rewrite_rules'); # Check if the current permalink structure is set to "Plain" if (($current_permalink_structure == '/%post_id%/' || $current_permalink_structure == '') && $current_rewrite_rules == '') { # Show admin notice with plugin name included in the message printf( '

Warning from %s
To ensure compatibility, please update your permalink structure to any option other than "Plain". For any inquiries, contact support.

', esc_html($plugin_name) ); } flush_rewrite_rules(); } /** * Display update warning banner if plugin update is available * Checks WordPress update API to see if a newer version is available * * @since 1.0.0 */ public function display_update_warning_banner() { // Get the installed version from database $installed_version = get_option('metasync_version', '0.0.0'); // Get plugin basename for WordPress update API check // This is the plugin file path relative to plugins directory (e.g., 'metasync/metasync.php') $plugin_file = plugin_basename(plugin_dir_path(dirname(__FILE__)) . 'metasync.php'); // Get WordPress update plugins transient (contains available updates) $update_plugins = get_site_transient('update_plugins'); // Check if update information exists and if our plugin has an update available if ($update_plugins && isset($update_plugins->response) && isset($update_plugins->response[$plugin_file])) { $update_info = $update_plugins->response[$plugin_file]; $latest_version = isset($update_info->new_version) ? $update_info->new_version : ''; // Compare installed version with latest available version if ($latest_version && version_compare($installed_version, $latest_version, '<')) { // Get the plugin name using centralized method $plugin_name = Metasync::get_effective_plugin_name(); // Show admin notice with plugin name included in the message printf( '

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.

', esc_html($plugin_name), esc_html($plugin_name) ); } } } /* Method to handle Ajax request from "Indexation Control" page */ public function meta_sync_save_seo_controls() { Metasync_Settings_Registration::instance()->meta_sync_save_seo_controls(); } /** * Schedule transient cleanup cron job * Runs daily to clean up expired transients and reduce database load */ public function schedule_transient_cleanup_cron() { // Clear any existing scheduled event first $this->unschedule_transient_cleanup_cron(); // Schedule new cron job daily if (!wp_next_scheduled('metasync_cleanup_transients')) { $scheduled = wp_schedule_event(time(), 'metasync_daily_cleanup', 'metasync_cleanup_transients'); if (!$scheduled) { error_log('MetaSync: Failed to schedule transient cleanup cron job'); } } } /** * Unschedule transient cleanup cron job */ public function unschedule_transient_cleanup_cron() { $timestamp = wp_next_scheduled('metasync_cleanup_transients'); if ($timestamp) { wp_unschedule_event($timestamp, 'metasync_cleanup_transients'); error_log('MetaSync: Transient cleanup cron job unscheduled'); } } /** * Maybe schedule transient cleanup cron job * Called on init hook - always schedules for database maintenance */ public function maybe_schedule_transient_cleanup_cron() { if (!wp_next_scheduled('metasync_cleanup_transients')) { $this->schedule_transient_cleanup_cron(); } } /** * Schedule hidden post manager cron job (runs every 7 days) * Called on init hook - always schedules for template checking */ public function maybe_schedule_hidden_post_check() { if (!wp_next_scheduled('metasync_hidden_post_check')) { $scheduled = wp_schedule_event(time(), 'metasync_weekly', 'metasync_hidden_post_check'); if ($scheduled) { error_log('MetaSync: Hidden post manager cron job scheduled successfully (runs every 7 days)'); } else { error_log('MetaSync: Failed to schedule hidden post manager cron job'); } } } /** * Schedule OTTO 404 exclusion recheck cron job (runs daily) * Rechecks URLs auto-excluded due to 404 after 7 days; removes from exclusion if now available */ public function maybe_schedule_otto_recheck_404_cron() { if (!wp_next_scheduled('metasync_otto_recheck_404_exclusions')) { $scheduled = wp_schedule_event(time(), 'metasync_daily_cleanup', 'metasync_otto_recheck_404_exclusions'); if ($scheduled) { error_log('MetaSync: OTTO 404 recheck cron job scheduled successfully (runs daily)'); } else { error_log('MetaSync: Failed to schedule OTTO 404 recheck cron job'); } } } /** * Execute transient cleanup cron job * Cleans up expired transients and plugin-specific transients to reduce database load */ public function execute_transient_cleanup() { Metasync_Admin_Ajax::instance()->execute_transient_cleanup(); } /** * Control plugin auto-updates based on user setting * * @param bool $update Whether to update * @param object $item Update offer * @return bool Whether to allow auto-update */ public function control_plugin_auto_updates($update, $item) { # Check if the item object has the slug property if (!isset($item->slug)) { return $update; } // Check if this is our plugin if ($item->slug === 'metasync') { $general_settings = Metasync::get_option('general') ?? []; $enable_auto_updates = $general_settings['enable_auto_updates'] ?? false; // Return the user's preference (true = allow auto-updates, false = prevent) return $enable_auto_updates === 'true' || $enable_auto_updates === true; } // For other plugins, don't interfere with their auto-update settings return $update; } /** * AJAX handler to add excluded URL for OTTO */ public function ajax_otto_add_excluded_url() { Metasync_Otto_Cache_Manager::instance()->ajax_otto_add_excluded_url(); } /** * AJAX handler to delete excluded URL for OTTO */ public function ajax_otto_delete_excluded_url() { Metasync_Otto_Cache_Manager::instance()->ajax_otto_delete_excluded_url(); } /** * AJAX handler to recheck if an excluded URL is now available * Used for "Recheck" action on Excluded URLs list */ public function ajax_otto_recheck_excluded_url() { Metasync_Otto_Cache_Manager::instance()->ajax_otto_recheck_excluded_url(); } /** * AJAX handler to get excluded URLs with pagination */ public function ajax_otto_get_excluded_urls() { Metasync_Otto_Cache_Manager::instance()->ajax_otto_get_excluded_urls(); } /** * AJAX handler for submitting issue reports to Sentry * * @since 2.5.10 * @return void Sends JSON response and exits */ public function ajax_submit_issue_report() { Metasync_Admin_Ajax::instance()->ajax_submit_issue_report(); } /** * Format duration seconds into human-readable label * * @since 2.5.11 * @param int $seconds Duration in seconds * @return string Human-readable duration */ private function format_duration_label($seconds) { $labels = array( 3600 => '1 hour', 14400 => '4 hours', 28800 => '8 hours', 86400 => '24 hours', 172800 => '48 hours', 604800 => '7 days', 1209600 => '14 days', 2592000 => '30 days' ); if (isset($labels[$seconds])) { return $labels[$seconds]; } # Calculate hours if not a standard duration $hours = round($seconds / 3600); return $hours . ' hours'; } /** * AJAX handler for password recovery * Sends the whitelabel settings password to the configured recovery email */ public function ajax_recover_password() { Metasync_Admin_Ajax::instance()->ajax_recover_password(); } /** * AJAX handler for saving theme preference * Saves the user's theme choice (light/dark) to WordPress options */ public function ajax_save_theme() { Metasync_Admin_Ajax::instance()->ajax_save_theme(); } /** * AJAX handler for tracking 1-click activation in Mixpanel */ public function ajax_track_one_click_activation() { Metasync_Admin_Ajax::instance()->ajax_track_one_click_activation(); } /** * Handler for exporting whitelabel settings to a zip file * Uses admin-post action for file downloads */ public function handle_export_whitelabel_settings() { Metasync_Admin_Ajax::instance()->handle_export_whitelabel_settings(); } /** * Add custom column to posts/pages list for HTML-converted pages * * @param array $columns Existing columns * @return array Modified columns */ public function add_html_converted_column($columns) { // Add column after the title column $new_columns = array(); foreach ($columns as $key => $value) { $new_columns[$key] = $value; if ($key === 'title') { $new_columns['metasync_html_source'] = __('Source', 'metasync'); } } return $new_columns; } /** * Render content for the HTML-converted column * * @param string $column_name Name of the column * @param int $post_id Post ID */ public function render_html_converted_column($column_name, $post_id) { if ($column_name !== 'metasync_html_source') { return; } // Check if this is an HTML-converted page $has_raw_html = get_post_meta($post_id, '_metasync_raw_html_enabled', true); $has_custom_css = get_post_meta($post_id, '_metasync_custom_css', true); // If page has raw HTML or custom CSS from conversion, show badge if ($has_raw_html || !empty($has_custom_css)) { $label = $this->get_html_source_label(); $tooltip = sprintf( __('This page was created using %s HTML-to-Builder converter', 'metasync'), $label ); echo sprintf( ' ⚡ %s ', esc_attr($tooltip), esc_html($label) ); } } /** * Get the label for HTML-converted pages (respects whitelabel settings) * * @return string Label to display */ private function get_html_source_label() { $whitelabel_company = Metasync::get_whitelabel_company_name(); if (!empty($whitelabel_company)) { return $whitelabel_company . ' AI'; } return 'SearchAtlas AI'; } /** * Add source notice banner in the page editor * * @param WP_Post $post Current post object */ public function add_editor_source_notice($post) { if (!$post || !in_array($post->post_type, array('post', 'page'))) { return; } $has_raw_html = get_post_meta($post->ID, '_metasync_raw_html_enabled', true); $has_custom_css = get_post_meta($post->ID, '_metasync_custom_css', true); if ($has_raw_html || !empty($has_custom_css)) { $label = $this->get_html_source_label(); $message = sprintf( __('This page was created using %s HTML-to-Builder converter. The design is preserved with custom CSS and inline styles.', 'metasync'), '' . esc_html($label) . '' ); echo sprintf( '
⚡ %s

%s

', esc_html($label), $message ); } } /** * Add source display in quick edit panel * * @param string $column_name Column name * @param string $post_type Post type */ public function add_quick_edit_source_display($column_name, $post_type) { if ($column_name !== 'metasync_html_source') { return; } if (!in_array($post_type, array('post', 'page'))) { return; } ?>
get_html_source_label(); $widget_title = sprintf(__('%s Pages', 'metasync'), $label); wp_add_dashboard_widget( 'metasync_html_pages_widget', $widget_title, array($this, 'render_html_pages_dashboard_widget') ); } /** * Render the dashboard widget content */ public function render_html_pages_dashboard_widget() { Metasync_Admin_Ajax::instance()->render_html_pages_dashboard_widget(); } /** * Bot Statistics page callback * Displays bot detection statistics and logs */ public function create_admin_bot_statistics_page() { require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-otto-bot-statistics.php'; } /** * AJAX handler for resetting bot statistics */ public function ajax_reset_bot_stats() { check_ajax_referer('metasync_reset_bot_stats', 'nonce'); if (!Metasync::current_user_has_plugin_access()) { wp_send_json_error(['message' => 'Insufficient permissions.']); } require_once plugin_dir_path(dirname(__FILE__)) . 'otto/class-metasync-otto-bot-statistics-database.php'; $db = Metasync_Otto_Bot_Statistics_Database::get_instance(); $result = $db->reset_statistics(); if ($result) { wp_send_json_success(['message' => 'Statistics reset successfully.']); } else { wp_send_json_error(['message' => 'Failed to reset statistics.']); } } /** * AJAX handler for sending URLs to Google Instant Indexing API * * @since 2.6.0 * @return void Sends JSON response and exits */ public function ajax_send_giapi() { $post_data = metasync_sanitize_input_array($_POST); if (!isset($post_data['metasync_giapi_url'])) { return; } // Parse URLs from textarea input (one per line) $urls = array_values(array_filter(array_map('trim', explode("\n", sanitize_textarea_field(wp_unslash($post_data['metasync_giapi_url'])))))); if (empty($urls)) { return; } if (!isset($post_data['metasync_giapi_action'])) { return; } $action = sanitize_title($post_data['metasync_giapi_action']); // Map form action values to google_index_direct action values if ($action === 'remove') { $action = 'delete'; } header('Content-type: application/json'); $result_data = []; foreach ($urls as $i => $url) { $url = esc_url_raw($url); if (empty($url)) { continue; } if ($action === 'status') { $result = google_index_direct()->get_url_status($url); } else { $result = google_index_direct()->index_url($url, $action); } $key = 'url-' . $i; if (!empty($result['success'])) { $result_data[$key] = $result['data']; } else { $result_data[$key] = (object) [ 'error' => (object) [ 'code' => isset($result['error']['code']) ? $result['error']['code'] : 400, 'message' => isset($result['error']['message']) ? $result['error']['message'] : 'Unknown error', ] ]; } } // For single URL, unwrap from the batch format (matches old behavior) if (count($result_data) === 1) { $result_data = reset($result_data); } wp_send_json($result_data); wp_die(); } /** * AJAX handler for sending URLs to Bing via IndexNow API * * @since 2.6.0 * @return void Sends JSON response and exits */ public function ajax_send_bing_indexnow() { require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php'; $bing_instant_index = new Metasync_Bing_Instant_Index(); $bing_instant_index->send(); } /** * Save instant indexing settings (Google and Bing) * * @since 2.6.0 * @return void */ public function save_instant_indexing_settings() { // Check if this is a settings submission if (!isset($_POST['submit'])) { return; } // Save Google Instant Indexing settings if (isset($_POST['metasync_google_json_key'])) { $post_data = metasync_sanitize_input_array($_POST); $settings = get_option('metasync_options_instant_indexing', ['json_key' => '', 'post_types' => []]); // Get JSON key from textarea or file upload $json = sanitize_textarea_field(wp_unslash($post_data['metasync_google_json_key'])); if (isset($_FILES['metasync_google_json_file']) && !empty($_FILES['metasync_google_json_file']['tmp_name']) && file_exists($_FILES['metasync_google_json_file']['tmp_name'])) { $json = wp_unslash(file_get_contents($_FILES['metasync_google_json_file']['tmp_name'])); } $post_types = isset($post_data['metasync_post_types']) && is_array($post_data['metasync_post_types']) && !empty($json) ? array_map('sanitize_title', $post_data['metasync_post_types']) : []; $new_settings = [ 'json_key' => $json, 'post_types' => array_values($post_types), ]; $settings = array_merge($settings, $new_settings); if (!empty($settings)) { update_option('metasync_options_instant_indexing', $settings); } // Also save parsed credentials to google_index_direct for native API usage if (!empty($json) && function_exists('google_index_direct')) { $decoded = json_decode($json, true); if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { google_index_direct()->save_service_account_config($decoded); } } } // Note: Bing Instant Indexing settings are saved via AJAX in save_bing_inline_settings_ajax() } /** * Save Bing instant indexing settings from inline form (Indexation Control page) * * @since 2.6.0 * @return bool True on success, false on failure */ private function save_bing_inline_settings_ajax() { return Metasync_Settings_Registration::instance()->save_bing_inline_settings_ajax(); } /** * Add instant indexing action links to post/page rows * * @since 2.6.0 * @param array $actions Current actions * @param WP_Post $post Current post object * @return array Modified actions */ public function add_instant_indexing_post_actions($actions, $post) { // Add Google Instant Indexing links $options = get_option('metasync_options_instant_indexing', ['json_key' => '', 'post_types' => []]); $post_types = isset($options['post_types']) && is_array($options['post_types']) ? $options['post_types'] : []; if (in_array($post->post_type, $post_types) && $post->post_status == 'publish') { $link = get_permalink($post); // Get menu slug (support white label) $general_options = Metasync::get_option('general') ?? []; $menu_slug = !empty($general_options['white_label_plugin_menu_slug']) ? $general_options['white_label_plugin_menu_slug'] : 'searchatlas'; $page_slug = $menu_slug . '-google-console'; $actions['index-update'] = 'Update Google Index'; $actions['index-status'] = 'Status Google Index'; } // Add Bing Instant Indexing links require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php'; $bing_instant_index = new Metasync_Bing_Instant_Index(); $actions = $bing_instant_index->bing_instant_index_post_link($actions, $post); return $actions; } /** * Auto-submit post to instant indexing services when published * * @since 2.6.0 * @param int $post_id Post ID * @param WP_Post $post Post object * @param bool $update Whether this is an update * @return void */ public function auto_submit_to_instant_indexing($post_id, $post, $update) { // Google Instant Indexing auto-submit is not yet implemented via google_index_direct(). // The previous implementation was also a no-op for Google. // TODO: Implement async auto-submit via WP-Cron or Action Scheduler in a future phase. // Auto-submit to Bing Instant Indexing require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php'; $bing_instant_index = new Metasync_Bing_Instant_Index(); $bing_instant_index->auto_submit_on_publish($post_id, $post, $update); } }