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' ) . '
' . 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(),
)
);
}
}
}