plugin_name = $plugin_name; $this->version = $version; // Add admin notices add_action( 'admin_notices', array( $this, 'display_admin_notices' ) ); // Handle activation redirect and initial sync redirect (early priority to run before page render) add_action( 'admin_init', array( $this, 'activation_redirect' ), 1 ); add_action( 'admin_init', array( $this, 'handle_sync_redirect' ), 1 ); // Remove default WordPress "Settings saved" message to avoid duplication add_filter( 'pre_update_option_maplepress_settings', array( $this, 'suppress_default_success_message' ), 10, 2 ); // Hook into post/page save events for automatic syncing add_action( 'save_post', array( $this, 'sync_page_on_save' ), 10, 3 ); add_action( 'before_delete_post', array( $this, 'delete_page_on_delete' ) ); // Bulk sync all pages action add_action( 'admin_action_maplepress_bulk_sync', array( $this, 'bulk_sync_pages' ) ); // Handle domain verification action add_action( 'admin_action_maplepress_verify_domain', array( $this, 'handle_verify_domain' ) ); // Handle reset settings action (for both admin.php and admin-post.php forms) add_action( 'admin_action_maplepress_reset_settings', array( $this, 'handle_reset_settings' ) ); add_action( 'admin_post_maplepress_reset_settings', array( $this, 'handle_reset_settings' ) ); // Handle reset and delete action (for both admin.php and admin-post.php forms) add_action( 'admin_action_maplepress_reset_and_delete', array( $this, 'handle_reset_and_delete' ) ); add_action( 'admin_post_maplepress_reset_and_delete', array( $this, 'handle_reset_and_delete' ) ); // AJAX handler for speed test add_action( 'wp_ajax_mpss_run_test', array( $this, 'handle_speedtest_ajax' ) ); // AJAX handler for bulk sync add_action( 'wp_ajax_maplepress_bulk_sync_ajax', array( $this, 'handle_bulk_sync_ajax' ) ); // AJAX handler for verification with sync add_action( 'wp_ajax_maplepress_verify_and_sync_ajax', array( $this, 'handle_verify_and_sync_ajax' ) ); // AJAX handler for initial sync (from dedicated sync page) add_action( 'wp_ajax_maplepress_initial_sync', array( $this, 'handle_initial_sync_ajax' ) ); } /** * Register the stylesheets for the admin area. */ public function enqueue_styles() { wp_enqueue_style( $this->plugin_name, MAPLEPRESS_PLUGIN_URL . 'assets/css/maplepress-admin.css', array(), $this->version, 'all' ); // Enqueue speed test styles on speed test page $screen = get_current_screen(); // Check for speedtest page - try both possible screen ID formats $is_speedtest_page = $screen && ( $screen->id === 'admin_page_maplepress-speedtest' || $screen->id === 'maplepress_page_maplepress-speedtest' ); if ( $is_speedtest_page ) { wp_enqueue_style( 'maplepress-speedtest', MAPLEPRESS_PLUGIN_URL . 'assets/css/speedtest-admin.css', array(), $this->version, 'all' ); } } /** * Register the JavaScript for the admin area. */ public function enqueue_scripts() { wp_enqueue_script( $this->plugin_name, MAPLEPRESS_PLUGIN_URL . 'assets/js/maplepress-admin.js', array( 'jquery' ), $this->version, false ); // Enqueue Chart.js and speed test scripts on speed test page $screen = get_current_screen(); // Debug: Log screen ID to help diagnose issues if ( $screen ) { error_log( 'MaplePress: Current screen ID = ' . $screen->id ); } // Check for speedtest page - try both possible screen ID formats $is_speedtest_page = $screen && ( $screen->id === 'admin_page_maplepress-speedtest' || $screen->id === 'maplepress_page_maplepress-speedtest' ); if ( $is_speedtest_page ) { wp_enqueue_script( 'chartjs', 'https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js', array(), '4.4.0', true ); wp_enqueue_script( 'maplepress-speedtest', MAPLEPRESS_PLUGIN_URL . 'assets/js/speedtest-simple.js', array( 'jquery', 'chartjs' ), $this->version, true ); // Localize script for AJAX wp_localize_script( 'maplepress-speedtest', 'mpssAjax', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'mpss_test_nonce' ), ) ); } } /** * Add plugin admin menu. */ public function add_plugin_admin_menu() { // Add top-level menu item - Dashboard add_menu_page( __( 'MaplePress Dashboard', 'maplepress' ), // Page title __( 'MaplePress', 'maplepress' ), // Menu title 'manage_options', // Capability 'maplepress', // Menu slug array( $this, 'display_dashboard_page' ), // Callback 'data:image/svg+xml;base64,' . base64_encode( '🍁' ), // Maple leaf icon 30 // Position (below Comments) ); // Add Dashboard submenu (rename the first item) add_submenu_page( 'maplepress', // Parent slug __( 'Dashboard', 'maplepress' ), // Page title __( 'Dashboard', 'maplepress' ), // Menu title 'manage_options', // Capability 'maplepress', // Menu slug (same as parent) array( $this, 'display_dashboard_page' ) // Callback ); // Add Settings submenu add_submenu_page( 'maplepress', // Parent slug __( 'Settings', 'maplepress' ), // Page title __( 'Settings', 'maplepress' ), // Menu title 'manage_options', // Capability 'maplepress-settings', // Menu slug array( $this, 'display_settings_page' ) // Callback ); // Register pages without adding to menu (for Advanced section access) // Speed Test - accessible via dashboard Advanced section add_submenu_page( null, // No parent = hidden from menu __( 'Search Speed Test', 'maplepress' ), // Page title __( 'Search Speed Test', 'maplepress' ), // Menu title 'manage_options', // Capability 'maplepress-speedtest', // Menu slug array( $this, 'display_speedtest_page' ) // Callback ); // Ready to Sync - shown after first-time connection add_submenu_page( null, // No parent = hidden from menu __( 'Ready to Sync', 'maplepress' ), // Page title __( 'Ready to Sync', 'maplepress' ), // Menu title 'manage_options', // Capability 'maplepress-ready-to-sync', // Menu slug array( $this, 'display_ready_to_sync_page' ) // Callback ); // Initial Sync - accessible only via redirect after first connection add_submenu_page( null, // No parent = hidden from menu __( 'Initial Sync', 'maplepress' ), // Page title __( 'Initial Sync', 'maplepress' ), // Menu title 'manage_options', // Capability 'maplepress-initial-sync', // Menu slug array( $this, 'display_initial_sync_page' ) // Callback ); // System Info - accessible via dashboard Advanced section add_submenu_page( null, // No parent = hidden from menu __( 'System Info', 'maplepress' ), // Page title __( 'System Info', 'maplepress' ), // Menu title 'manage_options', // Capability 'maplepress-system-info', // Menu slug array( $this, 'display_system_info_page' ) // Callback ); // Also add under Settings menu for those who look there add_options_page( __( 'MaplePress Settings', 'maplepress' ), __( 'MaplePress', 'maplepress' ), 'manage_options', 'maplepress-settings', array( $this, 'display_settings_page' ) ); } /** * Register plugin settings. */ public function register_settings() { register_setting( 'maplepress_settings_group', 'maplepress_settings', array( 'sanitize_callback' => array( $this, 'validate_settings' ), 'show_in_rest' => false, ) ); add_settings_section( 'maplepress_api_section', __( 'API Configuration', 'maplepress' ), array( $this, 'api_section_callback' ), 'maplepress' ); add_settings_field( 'api_url', __( 'API URL', 'maplepress' ), array( $this, 'api_url_callback' ), 'maplepress', 'maplepress_api_section' ); add_settings_field( 'api_key', __( 'API Key', 'maplepress' ), array( $this, 'api_key_callback' ), 'maplepress', 'maplepress_api_section' ); add_settings_field( 'enabled', __( 'Enable MaplePress', 'maplepress' ), array( $this, 'enabled_callback' ), 'maplepress', 'maplepress_api_section' ); add_settings_field( 'enable_frontend_search', __( 'Frontend Search', 'maplepress' ), array( $this, 'enable_frontend_search_callback' ), 'maplepress', 'maplepress_api_section' ); add_settings_field( 'enable_admin_search', __( 'Admin Search', 'maplepress' ), array( $this, 'enable_admin_search_callback' ), 'maplepress', 'maplepress_api_section' ); } /** * Display the plugin settings page. */ public function display_plugin_setup_page() { include_once MAPLEPRESS_PLUGIN_DIR . 'includes/admin-settings-display.php'; } /** * Display dashboard page. */ public function display_dashboard_page() { include_once MAPLEPRESS_PLUGIN_DIR . 'includes/admin-dashboard.php'; } /** * Display settings page. */ public function display_settings_page() { // Handle disconnect action if ( isset( $_GET['action'] ) && $_GET['action'] === 'disconnect' ) { check_admin_referer( 'maplepress_disconnect' ); $this->handle_disconnect(); wp_safe_redirect( admin_url( 'admin.php?page=maplepress-settings&disconnected=1' ) ); exit; } // Display disconnected message if redirected after disconnect if ( isset( $_GET['disconnected'] ) && $_GET['disconnected'] === '1' ) { add_settings_error( 'maplepress_settings', 'disconnected', __( 'Successfully disconnected from MaplePress. Your data remains in the cloud.', 'maplepress' ), 'success' ); } // Display reset success message if redirected after reset if ( isset( $_GET['reset'] ) && $_GET['reset'] === 'success' ) { add_settings_error( 'maplepress_settings', 'reset_success', __( '✓ Settings have been reset successfully. You can now configure MaplePress from scratch.', 'maplepress' ), 'success' ); } include_once MAPLEPRESS_PLUGIN_DIR . 'includes/admin-settings-page.php'; } /** * Handle disconnect action. */ private function handle_disconnect() { // Keep API credentials but clear verification status $options = get_option( 'maplepress_settings', array() ); // Clear all fields except the basic ones $cleared_options = array( 'api_url' => '', 'api_key' => '', 'enabled' => false, 'is_verified' => false, 'needs_setup' => true, 'enable_frontend_search' => true, 'enable_admin_search' => false, ); update_option( 'maplepress_settings', $cleared_options ); } /** * API section callback. */ public function api_section_callback() { echo '

' . esc_html__( 'Configure your MaplePress backend API connection.', 'maplepress' ) . '

'; } /** * API URL field callback. */ public function api_url_callback() { $options = get_option( 'maplepress_settings' ); $api_url = isset( $options['api_url'] ) && ! empty( $options['api_url'] ) ? $options['api_url'] : 'https://getmaplepress.ca'; printf( '', esc_attr( $api_url ) ); echo '

' . esc_html__( 'The URL of your MaplePress backend API (e.g., https://getmaplepress.ca or http://localhost:8000 for local dev).', 'maplepress' ) . '

'; } /** * API Key field callback. */ public function api_key_callback() { $options = get_option( 'maplepress_settings' ); $api_key = isset( $options['api_key'] ) ? $options['api_key'] : ''; printf( '', esc_attr( $api_key ) ); echo '

' . esc_html__( 'Your site API key from the MaplePress dashboard. Use "live_" prefix for production (https://getmaplepress.ca) or "test_" prefix for development/custom URLs.', 'maplepress' ) . '

'; } /** * Enabled field callback. */ public function enabled_callback() { $options = get_option( 'maplepress_settings' ); $enabled = isset( $options['enabled'] ) ? $options['enabled'] : false; printf( '', checked( 1, $enabled, false ) ); echo ''; } /** * Enable frontend search field callback. */ public function enable_frontend_search_callback() { $options = get_option( 'maplepress_settings' ); $enable_frontend_search = isset( $options['enable_frontend_search'] ) ? $options['enable_frontend_search'] : true; printf( '', checked( 1, $enable_frontend_search, false ) ); echo ''; echo '

' . esc_html__( 'When visitors search on your site, use MaplePress cloud-powered search.', 'maplepress' ) . '

'; } /** * Enable admin search field callback. */ public function enable_admin_search_callback() { $options = get_option( 'maplepress_settings' ); $enable_admin_search = isset( $options['enable_admin_search'] ) ? $options['enable_admin_search'] : false; printf( '', checked( 1, $enable_admin_search, false ) ); echo ''; echo '

' . esc_html__( 'When searching posts in the WordPress admin area, use MaplePress cloud-powered search.', 'maplepress' ) . '

'; } /** * Validate settings. * * @param array $input Settings input. * @return array */ public function validate_settings( $input ) { $output = array(); // Preserve existing values $existing = get_option( 'maplepress_settings', array() ); // Validate API URL - use default if empty if ( empty( $input['api_url'] ) ) { // Default to production URL $output['api_url'] = 'https://getmaplepress.ca'; } else { $output['api_url'] = esc_url_raw( $input['api_url'] ); } // Validate API key if ( empty( $input['api_key'] ) ) { add_settings_error( 'maplepress_settings', 'api_key_empty', __( 'Please enter your MaplePress API key.', 'maplepress' ), 'error' ); $output['api_key'] = ''; } else { $output['api_key'] = sanitize_text_field( $input['api_key'] ); } // Validate API key prefix based on API URL if ( ! empty( $output['api_url'] ) && ! empty( $output['api_key'] ) ) { $default_api_url = 'https://getmaplepress.ca'; $is_production = ( rtrim( $output['api_url'], '/' ) === rtrim( $default_api_url, '/' ) ); if ( $is_production ) { // Production URL requires live_ prefix if ( ! preg_match( '/^live_/', $output['api_key'] ) ) { add_settings_error( 'maplepress_settings', 'api_key_wrong_prefix', sprintf( /* translators: %s: API URL */ __( 'API key must start with "live_" when using the production API URL (%s). Test keys (starting with "test_") are not allowed in production.', 'maplepress' ), $default_api_url ), 'error' ); // Clear the invalid key $output['api_key'] = ''; } } else { // Non-production URL requires test_ prefix if ( ! preg_match( '/^test_/', $output['api_key'] ) ) { add_settings_error( 'maplepress_settings', 'api_key_wrong_prefix', sprintf( /* translators: %s: API URL */ __( 'API key must start with "test_" when using a custom/development API URL (%s). Live keys (starting with "live_") are not allowed with non-production URLs.', 'maplepress' ), esc_html( $output['api_url'] ) ), 'error' ); // Clear the invalid key $output['api_key'] = ''; } } } $output['enabled'] = isset( $input['enabled'] ) ? true : false; $output['enable_frontend_search'] = isset( $input['enable_frontend_search'] ) ? true : false; $output['enable_admin_search'] = isset( $input['enable_admin_search'] ) ? true : false; // Verify API connection only if both API URL and API key are provided and passed validation. if ( ! empty( $output['api_url'] ) && ! empty( $output['api_key'] ) ) { require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php'; $api_client = new MaplePress_API_Client( $output['api_url'], $output['api_key'] ); $status = $api_client->verify_connection(); if ( is_wp_error( $status ) ) { // API key is invalid $output['is_verified'] = false; $output['needs_setup'] = true; add_settings_error( 'maplepress_settings', 'api_error', sprintf( /* translators: %s: error message */ __( 'API Connection Error: %s', 'maplepress' ), $status->get_error_message() ), 'error' ); } else { // API connection successful - check verification status $output['is_connected'] = true; // Store site details if ( isset( $status['site_id'] ) ) { $output['site_id'] = sanitize_text_field( $status['site_id'] ); } if ( isset( $status['tenant_id'] ) ) { $output['tenant_id'] = sanitize_text_field( $status['tenant_id'] ); } if ( isset( $status['domain'] ) ) { $output['domain'] = sanitize_text_field( $status['domain'] ); } if ( isset( $status['site_url'] ) ) { $output['site_url'] = esc_url_raw( $status['site_url'] ); } if ( isset( $status['api_key_prefix'] ) ) { $output['api_key_prefix'] = sanitize_text_field( $status['api_key_prefix'] ); } if ( isset( $status['api_key_last_four'] ) ) { $output['api_key_last_four'] = sanitize_text_field( $status['api_key_last_four'] ); } // Check verification status from backend $verification_status = isset( $status['verification_status'] ) ? $status['verification_status'] : 'pending'; $is_verified_remote = isset( $status['is_verified'] ) ? (bool) $status['is_verified'] : false; if ( $is_verified_remote || $verification_status === 'verified' ) { // Site is fully verified - enable all features $output['is_verified'] = true; $output['verification_status'] = 'verified'; $output['needs_setup'] = false; // Store usage data if ( isset( $status['storage_used_bytes'] ) ) { $output['storage_used_bytes'] = absint( $status['storage_used_bytes'] ); } if ( isset( $status['search_requests_count'] ) ) { $output['search_requests_count'] = absint( $status['search_requests_count'] ); } if ( isset( $status['monthly_pages_indexed'] ) ) { $output['monthly_pages_indexed'] = absint( $status['monthly_pages_indexed'] ); } if ( isset( $status['total_pages_indexed'] ) ) { $output['total_pages_indexed'] = absint( $status['total_pages_indexed'] ); } // Always redirect to ready-to-sync page after successful verification // The sync page will allow users to sync their pages if ( $output['enabled'] ) { // Set transient to trigger redirect // We can't redirect here directly because WordPress handles the redirect after validation set_transient( 'maplepress_redirect_to_sync', true, 60 ); } else { // Verified but sync disabled - just show success add_settings_error( 'maplepress_settings', 'api_success', __( '✓ Connected and verified! Your site is ready to use MaplePress.', 'maplepress' ), 'success' ); } } else { // Connected but NOT verified - show DNS instructions $output['is_verified'] = false; $output['verification_status'] = 'pending'; $output['needs_setup'] = false; // Connection works, just needs verification // Store verification details if ( isset( $status['verification_token'] ) ) { $output['verification_token'] = sanitize_text_field( $status['verification_token'] ); } if ( isset( $status['verification_instructions'] ) ) { $output['verification_instructions'] = wp_kses_post( $status['verification_instructions'] ); } add_settings_error( 'maplepress_settings', 'not_verified', __( '⚠️ API key connected successfully, but domain verification is pending. Please complete DNS verification below to enable syncing.', 'maplepress' ), 'warning' ); } } } else { // No API key provided $output['is_verified'] = false; $output['needs_setup'] = true; } return $output; } /** * Perform initial sync and return result (doesn't add settings errors). * Extracted for better control and reusability. * * @param MaplePress_API_Client $api_client API client instance. * @return array|WP_Error Sync result or WP_Error on failure. */ private function perform_initial_sync( $api_client ) { // Get all published posts and pages $allowed_post_types = apply_filters( 'maplepress_sync_post_types', array( 'post', 'page' ) ); $posts = get_posts( array( 'post_type' => $allowed_post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'modified', 'order' => 'DESC', ) ); if ( empty( $posts ) ) { return new WP_Error( 'no_posts', __( 'No published posts or pages found to sync.', 'maplepress' ) ); } // Format all pages for bulk sync $pages = array(); foreach ( $posts as $post ) { // Format timestamps in RFC3339 format (ISO 8601) // Use actual GMT time if available, otherwise use server time converted to GMT $published_at = ''; if ( $post->post_date_gmt && $post->post_date_gmt !== '0000-00-00 00:00:00' ) { $published_at = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_date_gmt . ' UTC' ) ); } $modified_at = ''; if ( $post->post_modified_gmt && $post->post_modified_gmt !== '0000-00-00 00:00:00' ) { $modified_at = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_modified_gmt . ' UTC' ) ); } $pages[] = array( 'page_id' => (string) $post->ID, 'title' => $post->post_title, 'content' => wp_strip_all_tags( $post->post_content ), 'excerpt' => ! empty( $post->post_excerpt ) ? $post->post_excerpt : wp_trim_words( $post->post_content, 55, '...' ), 'url' => get_permalink( $post->ID ), 'status' => $post->post_status, 'post_type' => $post->post_type, 'author' => get_the_author_meta( 'display_name', $post->post_author ), 'published_at' => $published_at, 'modified_at' => $modified_at, ); } // Sync pages in batches of 1000 (backend limit) $batch_size = 1000; $total_pages = count( $pages ); $total_synced = 0; $batches = array_chunk( $pages, $batch_size ); error_log( 'MaplePress: perform_initial_sync() - Syncing ' . $total_pages . ' pages in ' . count( $batches ) . ' batch(es)' ); foreach ( $batches as $batch_num => $batch ) { error_log( 'MaplePress: perform_initial_sync() - Processing batch ' . ( $batch_num + 1 ) . ' of ' . count( $batches ) . ' (' . count( $batch ) . ' pages)' ); $result = $api_client->sync_pages( $batch ); if ( is_wp_error( $result ) ) { error_log( 'MaplePress: perform_initial_sync() - Batch ' . ( $batch_num + 1 ) . ' failed: ' . $result->get_error_message() ); return new WP_Error( 'sync_failed', sprintf( /* translators: 1: batch number, 2: error message */ __( 'Sync failed at batch %1$d: %2$s', 'maplepress' ), $batch_num + 1, $result->get_error_message() ) ); } $synced_in_batch = isset( $result['synced_count'] ) ? $result['synced_count'] : count( $batch ); $total_synced += $synced_in_batch; error_log( 'MaplePress: perform_initial_sync() - Batch ' . ( $batch_num + 1 ) . ' completed. Synced ' . $synced_in_batch . ' pages. Total so far: ' . $total_synced ); } error_log( 'MaplePress: perform_initial_sync() - All batches completed. Total synced: ' . $total_synced ); // Return success with count return array( 'success' => true, 'synced_count' => $total_synced, 'batch_count' => count( $batches ), ); } /** * Suppress the default WordPress "Settings saved" message. * We show our own custom success message instead. * * @param mixed $value The new option value. * @param mixed $old_value The old option value. * @return mixed */ public function suppress_default_success_message( $value, $old_value ) { // Remove the default "settings-updated" notice add_filter( 'pre_set_transient_settings_errors', function( $settings_errors ) { if ( is_array( $settings_errors ) ) { // Remove the default "settings updated" message foreach ( $settings_errors as $key => $error ) { if ( isset( $error['code'] ) && $error['code'] === 'settings_updated' ) { unset( $settings_errors[ $key ] ); } } } return $settings_errors; } ); return $value; } /** * Display admin notices for setup and configuration. */ public function display_admin_notices() { $options = get_option( 'maplepress_settings' ); // Check if setup is needed if ( isset( $options['needs_setup'] ) && $options['needs_setup'] ) { $screen = get_current_screen(); // Don't show on the settings page itself if ( isset( $screen->id ) && $screen->id === 'settings_page_maplepress' ) { return; } ?>

sign up to get your API key, then configure the plugin.', 'maplepress' ), 'https://getmaplepress.com/register', admin_url( 'options-general.php?page=maplepress' ) ); ?>

post_type, $allowed_post_types, true ) ) { return; } // Only sync published posts if ( $post->post_status !== 'publish' ) { return; } // Initialize API client require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php'; $api_client = new MaplePress_API_Client( $options['api_url'], $options['api_key'] ); // Sync the page $result = $api_client->index_post( $post_id ); // Log result (for debugging) if ( is_wp_error( $result ) ) { error_log( 'MaplePress sync failed for post ' . $post_id . ': ' . $result->get_error_message() ); } else { error_log( 'MaplePress synced post ' . $post_id . ' successfully' ); } } /** * Delete a page from MaplePress when it's deleted from WordPress. * * @param int $post_id Post ID. */ public function delete_page_on_delete( $post_id ) { // Check if MaplePress is enabled $options = get_option( 'maplepress_settings' ); if ( empty( $options['enabled'] ) || empty( $options['is_verified'] ) ) { return; } // Get the post to check its type $post = get_post( $post_id ); if ( ! $post ) { return; } // Only handle posts and pages $allowed_post_types = apply_filters( 'maplepress_sync_post_types', array( 'post', 'page' ) ); if ( ! in_array( $post->post_type, $allowed_post_types, true ) ) { return; } // Initialize API client require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php'; $api_client = new MaplePress_API_Client( $options['api_url'], $options['api_key'] ); // Delete the page from MaplePress $result = $api_client->delete_post( $post_id ); // Log result if ( is_wp_error( $result ) ) { error_log( 'MaplePress delete failed for post ' . $post_id . ': ' . $result->get_error_message() ); } else { error_log( 'MaplePress deleted post ' . $post_id . ' successfully' ); } } /** * Bulk sync all existing posts and pages. * Triggered via admin action. */ public function bulk_sync_pages() { // Check permissions if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', 'maplepress' ) ); } // Check nonce check_admin_referer( 'maplepress_bulk_sync' ); // Check if MaplePress is enabled $options = get_option( 'maplepress_settings' ); if ( empty( $options['enabled'] ) || empty( $options['is_verified'] ) ) { wp_die( esc_html__( 'MaplePress is not configured properly.', 'maplepress' ) ); } // Get all published posts and pages $allowed_post_types = apply_filters( 'maplepress_sync_post_types', array( 'post', 'page' ) ); $posts = get_posts( array( 'post_type' => $allowed_post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'modified', 'order' => 'DESC', ) ); if ( empty( $posts ) ) { wp_safe_redirect( add_query_arg( array( 'page' => 'maplepress', 'sync_status' => 'no_posts', ), admin_url( 'admin.php' ) ) ); exit; } // Initialize API client require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php'; $api_client = new MaplePress_API_Client( $options['api_url'], $options['api_key'] ); // Format all pages for bulk sync $pages = array(); foreach ( $posts as $post ) { // Format timestamps in RFC3339 format (ISO 8601) // Use actual GMT time if available, otherwise use server time converted to GMT $published_at = ''; if ( $post->post_date_gmt && $post->post_date_gmt !== '0000-00-00 00:00:00' ) { $published_at = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_date_gmt . ' UTC' ) ); } $modified_at = ''; if ( $post->post_modified_gmt && $post->post_modified_gmt !== '0000-00-00 00:00:00' ) { $modified_at = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_modified_gmt . ' UTC' ) ); } $pages[] = array( 'page_id' => (string) $post->ID, 'title' => $post->post_title, 'content' => wp_strip_all_tags( $post->post_content ), 'excerpt' => ! empty( $post->post_excerpt ) ? $post->post_excerpt : wp_trim_words( $post->post_content, 55, '...' ), 'url' => get_permalink( $post->ID ), 'status' => $post->post_status, 'post_type' => $post->post_type, 'author' => get_the_author_meta( 'display_name', $post->post_author ), 'published_at' => $published_at, 'modified_at' => $modified_at, ); } // Sync pages in batches of 1000 (backend limit) $batch_size = 1000; $total_pages = count( $pages ); $total_synced = 0; $batches = array_chunk( $pages, $batch_size ); error_log( 'MaplePress: Syncing ' . $total_pages . ' pages in ' . count( $batches ) . ' batch(es)' ); foreach ( $batches as $batch_num => $batch ) { error_log( 'MaplePress: Processing batch ' . ( $batch_num + 1 ) . ' of ' . count( $batches ) . ' (' . count( $batch ) . ' pages)' ); $result = $api_client->sync_pages( $batch ); if ( is_wp_error( $result ) ) { error_log( 'MaplePress: Batch ' . ( $batch_num + 1 ) . ' failed: ' . $result->get_error_message() ); wp_safe_redirect( add_query_arg( array( 'page' => 'maplepress', 'sync_status' => 'error', 'sync_message' => urlencode( 'Batch ' . ( $batch_num + 1 ) . ' failed: ' . $result->get_error_message() ), ), admin_url( 'admin.php' ) ) ); exit; } $synced_in_batch = isset( $result['synced_count'] ) ? $result['synced_count'] : count( $batch ); $total_synced += $synced_in_batch; error_log( 'MaplePress: Batch ' . ( $batch_num + 1 ) . ' completed: ' . $synced_in_batch . ' pages synced' ); } // All batches completed successfully error_log( 'MaplePress: All batches completed. Total synced: ' . $total_synced . ' pages' ); wp_safe_redirect( add_query_arg( array( 'page' => 'maplepress', 'sync_status' => 'success', 'synced_count' => $total_synced, ), admin_url( 'admin.php' ) ) ); exit; } /** * Handle bulk sync via AJAX for better UX with progress feedback. */ public function handle_bulk_sync_ajax() { // Check permissions if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Permission denied.', 'maplepress' ), ) ); return; } // Check nonce check_ajax_referer( 'maplepress_bulk_sync_ajax', 'nonce' ); // Check if MaplePress is enabled $options = get_option( 'maplepress_settings' ); if ( empty( $options['enabled'] ) || empty( $options['is_verified'] ) ) { wp_send_json_error( array( 'message' => __( 'MaplePress is not configured properly.', 'maplepress' ), ) ); return; } // Get all published posts and pages $allowed_post_types = apply_filters( 'maplepress_sync_post_types', array( 'post', 'page' ) ); $posts = get_posts( array( 'post_type' => $allowed_post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'modified', 'order' => 'DESC', ) ); if ( empty( $posts ) ) { wp_send_json_error( array( 'message' => __( 'No published posts or pages found to sync.', 'maplepress' ), ) ); return; } // Initialize API client require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php'; $api_client = new MaplePress_API_Client( $options['api_url'], $options['api_key'] ); // Format all pages for bulk sync $pages = array(); foreach ( $posts as $post ) { // Format timestamps in RFC3339 format (ISO 8601) $published_at = ''; if ( $post->post_date_gmt && $post->post_date_gmt !== '0000-00-00 00:00:00' ) { $published_at = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_date_gmt . ' UTC' ) ); } $modified_at = ''; if ( $post->post_modified_gmt && $post->post_modified_gmt !== '0000-00-00 00:00:00' ) { $modified_at = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_modified_gmt . ' UTC' ) ); } $pages[] = array( 'page_id' => (string) $post->ID, 'title' => $post->post_title, 'content' => wp_strip_all_tags( $post->post_content ), 'excerpt' => ! empty( $post->post_excerpt ) ? $post->post_excerpt : wp_trim_words( $post->post_content, 55, '...' ), 'url' => get_permalink( $post->ID ), 'status' => $post->post_status, 'post_type' => $post->post_type, 'author' => get_the_author_meta( 'display_name', $post->post_author ), 'published_at' => $published_at, 'modified_at' => $modified_at, ); } // Sync pages in batches of 1000 (backend limit) $batch_size = 1000; $total_pages = count( $pages ); $total_synced = 0; $batches = array_chunk( $pages, $batch_size ); error_log( 'MaplePress AJAX: Syncing ' . $total_pages . ' pages in ' . count( $batches ) . ' batch(es)' ); foreach ( $batches as $batch_num => $batch ) { error_log( 'MaplePress AJAX: Processing batch ' . ( $batch_num + 1 ) . ' of ' . count( $batches ) . ' (' . count( $batch ) . ' pages)' ); $result = $api_client->sync_pages( $batch ); if ( is_wp_error( $result ) ) { error_log( 'MaplePress AJAX: Batch ' . ( $batch_num + 1 ) . ' failed: ' . $result->get_error_message() ); wp_send_json_error( array( 'message' => sprintf( /* translators: %d: batch number */ __( 'Sync failed at batch %d', 'maplepress' ), $batch_num + 1 ), 'details' => $result->get_error_message(), ) ); return; } $synced_in_batch = isset( $result['synced_count'] ) ? $result['synced_count'] : count( $batch ); $total_synced += $synced_in_batch; error_log( 'MaplePress AJAX: Batch ' . ( $batch_num + 1 ) . ' completed: ' . $synced_in_batch . ' pages synced' ); } // All batches completed successfully error_log( 'MaplePress AJAX: All batches completed. Total synced: ' . $total_synced . ' pages' ); wp_send_json_success( array( 'message' => sprintf( /* translators: %d: number of pages synced */ __( 'Successfully synced %d pages to MaplePress!', 'maplepress' ), $total_synced ), 'details' => sprintf( /* translators: %d: number of batches */ __( 'Completed in %d batch(es)', 'maplepress' ), count( $batches ) ), 'synced_count' => $total_synced, ) ); } /** * Handle AJAX verification with progress tracking. * Verifies domain and syncs pages with progress updates. */ public function handle_verify_and_sync_ajax() { // Verify nonce check_ajax_referer( 'maplepress_verify_and_sync_ajax', 'nonce' ); // Check permissions if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( __( 'You do not have permission to perform this action.', 'maplepress' ) ); } // Get settings $options = get_option( 'maplepress_settings' ); if ( empty( $options['api_url'] ) || empty( $options['api_key'] ) ) { wp_send_json_error( __( 'API URL and API Key are required.', 'maplepress' ) ); } // Step 1: Verify domain require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php'; $api_client = new MaplePress_API_Client( $options['api_url'], $options['api_key'] ); $result = $api_client->verify_domain(); if ( is_wp_error( $result ) ) { wp_send_json_error( $result->get_error_message() ); } // ✅ Verification succeeded! $options['is_verified'] = true; $options['verification_status'] = 'verified'; // Clear verification token unset( $options['verification_token'] ); unset( $options['verification_instructions'] ); update_option( 'maplepress_settings', $options ); // Step 2: Sync pages if enabled if ( isset( $options['enabled'] ) && $options['enabled'] ) { // Get all published posts and pages $allowed_post_types = apply_filters( 'maplepress_sync_post_types', array( 'post', 'page' ) ); $posts = get_posts( array( 'post_type' => $allowed_post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'modified', 'order' => 'DESC', ) ); if ( empty( $posts ) ) { wp_send_json_success( array( 'message' => __( 'Domain verified successfully! No pages to sync.', 'maplepress' ), 'details' => __( 'No published posts or pages found.', 'maplepress' ), ) ); } // Format all pages for bulk sync $pages = array(); foreach ( $posts as $post ) { $published_at = ''; if ( $post->post_date_gmt && $post->post_date_gmt !== '0000-00-00 00:00:00' ) { $published_at = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_date_gmt . ' UTC' ) ); } $modified_at = ''; if ( $post->post_modified_gmt && $post->post_modified_gmt !== '0000-00-00 00:00:00' ) { $modified_at = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_modified_gmt . ' UTC' ) ); } $pages[] = array( 'page_id' => (string) $post->ID, 'title' => $post->post_title, 'content' => wp_strip_all_tags( $post->post_content ), 'excerpt' => ! empty( $post->post_excerpt ) ? $post->post_excerpt : wp_trim_words( $post->post_content, 55, '...' ), 'url' => get_permalink( $post->ID ), 'status' => $post->post_status, 'post_type' => $post->post_type, 'author' => get_the_author_meta( 'display_name', $post->post_author ), 'published_at' => $published_at, 'modified_at' => $modified_at, ); } // Sync pages in batches of 1000 $batch_size = 1000; $total_pages = count( $pages ); $total_synced = 0; $batches = array_chunk( $pages, $batch_size ); foreach ( $batches as $batch_num => $batch ) { $result = $api_client->sync_pages( $batch ); if ( is_wp_error( $result ) ) { wp_send_json_error( sprintf( /* translators: 1: batch number, 2: error message */ __( 'Domain verified but sync failed at batch %1$d: %2$s', 'maplepress' ), $batch_num + 1, $result->get_error_message() ) ); } $synced_in_batch = isset( $result['synced_count'] ) ? $result['synced_count'] : count( $batch ); $total_synced += $synced_in_batch; } // Success with sync wp_send_json_success( array( 'message' => sprintf( /* translators: %d: number of pages synced */ __( 'Domain verified and %d pages synced successfully!', 'maplepress' ), $total_synced ), 'details' => sprintf( /* translators: 1: number of batches, 2: total pages */ __( 'Synced %1$d pages in %2$d batch(es)', 'maplepress' ), $total_synced, count( $batches ) ), ) ); } else { // Verified but sync disabled wp_send_json_success( array( 'message' => __( 'Domain verified successfully!', 'maplepress' ), 'details' => __( 'MaplePress is ready to use.', 'maplepress' ), ) ); } } /** * Handle AJAX initial sync from dedicated sync page. * This performs the actual sync with batch processing. */ public function handle_initial_sync_ajax() { // Verify nonce check_ajax_referer( 'maplepress_initial_sync', 'nonce' ); // Check permissions if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( __( 'You do not have permission to perform this action.', 'maplepress' ) ); } // Get settings $options = get_option( 'maplepress_settings', array() ); if ( empty( $options['api_url'] ) || empty( $options['api_key'] ) ) { wp_send_json_error( __( 'MaplePress is not configured properly.', 'maplepress' ) ); } if ( empty( $options['is_verified'] ) ) { wp_send_json_error( __( 'Domain not verified. Please verify your domain first.', 'maplepress' ) ); } // Initialize API client require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php'; $api_client = new MaplePress_API_Client( $options['api_url'], $options['api_key'] ); // Perform the sync using our existing batch processing method $sync_result = $this->perform_initial_sync( $api_client ); if ( is_wp_error( $sync_result ) ) { wp_send_json_error( $sync_result->get_error_message() ); } // Success $synced_count = isset( $sync_result['synced_count'] ) ? $sync_result['synced_count'] : 0; $batch_count = isset( $sync_result['batch_count'] ) ? $sync_result['batch_count'] : 0; wp_send_json_success( array( 'message' => sprintf( /* translators: %d: number of pages synced */ __( 'Successfully synced %d pages!', 'maplepress' ), $synced_count ), 'details' => sprintf( /* translators: 1: number of pages, 2: number of batches */ __( 'Synced %1$d pages in %2$d batch(es). Your content is now searchable via MaplePress.', 'maplepress' ), $synced_count, $batch_count ), 'synced_count' => $synced_count, 'batch_count' => $batch_count, ) ); } /** * Handle domain verification request from plugin. * This is where auto-sync happens (after successful verification). */ public function handle_verify_domain() { // Debug: Log that handler was called error_log( 'MaplePress: handle_verify_domain() called' ); error_log( 'MaplePress: POST data = ' . print_r( $_POST, true ) ); // Check permissions if ( ! current_user_can( 'manage_options' ) ) { error_log( 'MaplePress: Permission denied' ); wp_die( esc_html__( 'You do not have permission to perform this action.', 'maplepress' ) ); } // Check nonce error_log( 'MaplePress: Checking nonce' ); check_admin_referer( 'maplepress_verify_domain' ); error_log( 'MaplePress: Nonce validated successfully' ); // Get settings $options = get_option( 'maplepress_settings' ); error_log( 'MaplePress: api_url = ' . ( $options['api_url'] ?? 'NOT SET' ) ); error_log( 'MaplePress: api_key = ' . ( isset( $options['api_key'] ) ? substr( $options['api_key'], 0, 10 ) . '...' : 'NOT SET' ) ); if ( empty( $options['api_url'] ) || empty( $options['api_key'] ) ) { error_log( 'MaplePress: Configuration incomplete - api_url or api_key missing' ); // Redirect with detailed error message wp_safe_redirect( add_query_arg( array( 'page' => 'maplepress-settings', 'verification_status' => 'failed', 'verification_message' => urlencode( empty( $options['api_url'] ) && empty( $options['api_key'] ) ? __( 'API URL and API Key are required. Please save your settings first.', 'maplepress' ) : ( empty( $options['api_url'] ) ? __( 'API URL is required. Please save your settings first.', 'maplepress' ) : __( 'API Key is required. Please save your settings first.', 'maplepress' ) ) ), ), admin_url( 'admin.php' ) ) ); exit; } // Call verification endpoint error_log( 'MaplePress: Calling verify_domain() API endpoint' ); require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php'; $api_client = new MaplePress_API_Client( $options['api_url'], $options['api_key'] ); $result = $api_client->verify_domain(); error_log( 'MaplePress: verify_domain() returned: ' . print_r( $result, true ) ); if ( is_wp_error( $result ) ) { // Verification failed - show detailed error error_log( 'MaplePress: Verification failed - ' . $result->get_error_message() ); wp_safe_redirect( add_query_arg( array( 'page' => 'maplepress-settings', 'verification_status' => 'failed', 'verification_message' => urlencode( $result->get_error_message() ), ), admin_url( 'admin.php' ) ) ); exit; } // ✅ Verification succeeded! $options['is_verified'] = true; $options['verification_status'] = 'verified'; // Clear verification token (no longer needed) unset( $options['verification_token'] ); unset( $options['verification_instructions'] ); update_option( 'maplepress_settings', $options ); // 🎯 REDIRECT TO READY-TO-SYNC PAGE - user must manually start sync if ( isset( $options['enabled'] ) && $options['enabled'] ) { // Redirect to ready-to-sync page (user clicks button to start sync) wp_safe_redirect( admin_url( 'admin.php?page=maplepress-ready-to-sync' ) ); exit; } else { // Verified but sync disabled - just show verification success wp_safe_redirect( add_query_arg( array( 'page' => 'maplepress-settings', 'verification_status' => 'success', ), admin_url( 'admin.php' ) ) ); exit; } } /** * Display the speed test page. */ public function display_speedtest_page() { require_once MAPLEPRESS_PLUGIN_DIR . 'includes/admin-speedtest-page-simple.php'; } /** * Display the ready-to-sync page. */ public function display_ready_to_sync_page() { require_once MAPLEPRESS_PLUGIN_DIR . 'includes/admin-ready-to-sync-page.php'; } /** * Display the initial sync page. */ public function display_initial_sync_page() { require_once MAPLEPRESS_PLUGIN_DIR . 'includes/admin-initial-sync-page.php'; } /** * Display the system info page. */ public function display_system_info_page() { require_once MAPLEPRESS_PLUGIN_DIR . 'includes/admin-system-info-page.php'; } /** * Handle reset settings action. */ public function handle_reset_settings() { // Check permissions if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', 'maplepress' ) ); } // Check nonce check_admin_referer( 'maplepress_reset_settings' ); // Delete all plugin settings delete_option( 'maplepress_settings' ); // Log the reset error_log( 'MaplePress: Settings reset by user' ); // Build redirect URL $redirect_url = add_query_arg( array( 'page' => 'maplepress-settings', 'reset' => 'success', ), admin_url( 'admin.php' ) ); error_log( 'MaplePress: Redirecting to: ' . $redirect_url ); // Redirect back to settings page with success message wp_safe_redirect( $redirect_url ); exit; } /** * Handle reset and delete plugin action. * This removes all settings, deactivates, and deletes the plugin. */ public function handle_reset_and_delete() { // Check permissions if ( ! current_user_can( 'delete_plugins' ) ) { wp_die( esc_html__( 'You do not have permission to delete plugins.', 'maplepress' ) ); } // Check nonce check_admin_referer( 'maplepress_reset_and_delete' ); // Delete all plugin settings immediately (before deactivation) delete_option( 'maplepress_settings' ); delete_transient( 'maplepress_activation_redirect' ); delete_transient( 'maplepress_redirect_to_sync' ); // Log the reset and delete error_log( 'MaplePress: Plugin data cleared and plugin being deleted by user' ); // Get plugin file path $plugin_file = plugin_basename( MAPLEPRESS_PLUGIN_FILE ); // Deactivate the plugin first deactivate_plugins( $plugin_file ); // Require the plugin deletion functions if ( ! function_exists( 'delete_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( ! function_exists( 'request_filesystem_credentials' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } // Delete the plugin files $delete_result = delete_plugins( array( $plugin_file ) ); // Redirect to plugins page with status if ( is_wp_error( $delete_result ) ) { wp_safe_redirect( add_query_arg( array( 'error' => 'delete_failed', 'message' => urlencode( $delete_result->get_error_message() ), ), admin_url( 'plugins.php' ) ) ); } else { wp_safe_redirect( add_query_arg( array( 'deleted' => 'true', 'plugin_deleted' => 'maplepress', ), admin_url( 'plugins.php' ) ) ); } exit; } /** * Handle AJAX request to run speed test. */ public function handle_speedtest_ajax() { try { // Check permissions if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Permission denied.', 'maplepress' ) ) ); return; } // Check nonce check_ajax_referer( 'mpss_test_nonce', 'nonce' ); // Get parameters from request $query_count = isset( $_POST['query_count'] ) ? intval( $_POST['query_count'] ) : 10; $execution_mode = isset( $_POST['execution_mode'] ) ? sanitize_text_field( $_POST['execution_mode'] ) : 'serial'; // Validate execution mode if ( ! in_array( $execution_mode, array( 'serial', 'parallel' ), true ) ) { $execution_mode = 'serial'; } // Run speed test require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-speedtest-simple.php'; $speedtest = new MPSS_SpeedTest_Simple(); $result = $speedtest->run( $query_count, $execution_mode ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message(), ) ); return; } wp_send_json_success( $result ); } catch ( Exception $e ) { error_log( 'MaplePress Speed Test Exception: ' . $e->getMessage() ); wp_send_json_error( array( 'message' => 'Exception: ' . $e->getMessage(), ) ); } catch ( Error $e ) { error_log( 'MaplePress Speed Test Error: ' . $e->getMessage() ); wp_send_json_error( array( 'message' => 'Error: ' . $e->getMessage(), ) ); } } }