Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,362 @@
<?php
/**
* Admin dashboard page template.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$options = get_option( 'maplepress_settings' );
$has_api_key = ! empty( $options['api_key'] );
$is_verified = isset( $options['is_verified'] ) && $options['is_verified'];
// Refresh quota data if verified and API credentials are available
if ( $is_verified && ! empty( $options['api_url'] ) && ! empty( $options['api_key'] ) ) {
require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php';
$api_client = new MaplePress_API_Client( $options['api_url'], $options['api_key'] );
$fresh_status = $api_client->verify_connection();
// Update options with fresh quota data if API call succeeded
if ( ! is_wp_error( $fresh_status ) ) {
if ( isset( $fresh_status['search_requests_count'] ) ) {
$options['search_requests_count'] = absint( $fresh_status['search_requests_count'] );
}
if ( isset( $fresh_status['storage_used_bytes'] ) ) {
$options['storage_used_bytes'] = absint( $fresh_status['storage_used_bytes'] );
}
if ( isset( $fresh_status['monthly_pages_indexed'] ) ) {
$options['monthly_pages_indexed'] = absint( $fresh_status['monthly_pages_indexed'] );
}
if ( isset( $fresh_status['total_pages_indexed'] ) ) {
$options['total_pages_indexed'] = absint( $fresh_status['total_pages_indexed'] );
}
}
}
?>
<div class="wrap">
<h1><?php esc_html_e( 'MaplePress Dashboard', 'maplepress' ); ?></h1>
<?php
// Display sync status messages
if ( isset( $_GET['sync_status'] ) ) {
if ( $_GET['sync_status'] === 'success' && isset( $_GET['synced_count'] ) ) {
?>
<div class="notice notice-success is-dismissible">
<p>
<?php
printf(
/* translators: %d: number of pages synced */
esc_html__( '✓ Successfully synced %d pages to MaplePress!', 'maplepress' ),
absint( $_GET['synced_count'] )
);
?>
</p>
</div>
<?php
} elseif ( $_GET['sync_status'] === 'error' && isset( $_GET['sync_message'] ) ) {
?>
<div class="notice notice-error is-dismissible">
<p>
<?php
printf(
/* translators: %s: error message */
esc_html__( '✗ Sync failed: %s', 'maplepress' ),
esc_html( urldecode( $_GET['sync_message'] ) )
);
?>
</p>
</div>
<?php
} elseif ( $_GET['sync_status'] === 'no_posts' ) {
?>
<div class="notice notice-warning is-dismissible">
<p><?php esc_html_e( 'No published posts or pages found to sync.', 'maplepress' ); ?></p>
</div>
<?php
}
}
?>
<?php if ( ! $has_api_key ) : ?>
<!-- No API key - prompt setup -->
<div class="notice notice-warning" style="padding: 20px; margin: 20px 0;">
<h2 style="margin-top: 0;"><?php esc_html_e( 'Welcome to MaplePress!', 'maplepress' ); ?></h2>
<p><?php esc_html_e( 'You need to connect your site to MaplePress to use the cloud services.', 'maplepress' ); ?></p>
<p>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=maplepress-settings' ) ); ?>" class="button button-primary button-large">
<?php esc_html_e( 'Connect to MaplePress', 'maplepress' ); ?>
</a>
</p>
</div>
<?php elseif ( ! $is_verified ) : ?>
<!-- API key connected but NOT verified - show verification required notice -->
<div class="notice notice-error" style="padding: 20px; margin: 20px 0; border-left: 4px solid #dc3232;">
<h2 style="margin-top: 0; color: #dc3232;">
⚠️ <?php esc_html_e( 'Domain Verification Required', 'maplepress' ); ?>
</h2>
<p>
<?php esc_html_e( 'Your API key is connected, but your domain needs to be verified before you can use MaplePress features.', 'maplepress' ); ?>
</p>
<p>
<strong><?php esc_html_e( 'What you need to do:', 'maplepress' ); ?></strong>
</p>
<ol style="margin-left: 20px;">
<li><?php esc_html_e( 'Go to Settings → MaplePress', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Follow the DNS verification instructions', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Add the DNS TXT record to your domain', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Click "Verify Domain Now"', 'maplepress' ); ?></li>
</ol>
<p>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=maplepress-settings' ) ); ?>" class="button button-primary button-large">
<?php esc_html_e( 'Complete Verification', 'maplepress' ); ?>
</a>
</p>
<hr style="margin: 20px 0;">
<p style="margin: 0;">
<strong><?php esc_html_e( 'Features disabled until verification:', 'maplepress' ); ?></strong><br>
<span style="color: #666;">
<?php esc_html_e( 'Page syncing, search indexing, and all MaplePress cloud services are currently unavailable.', 'maplepress' ); ?>
</span>
</p>
</div>
<?php return; // Stop rendering dashboard content if not verified ?>
<?php else : ?>
<!-- Connected - show dashboard -->
<!-- Connection Status -->
<div class="notice notice-success" style="padding: 15px; margin: 20px 0; border-left-width: 4px;">
<h2 style="margin: 0; color: #46b450;">
<?php esc_html_e( 'Connected to MaplePress', 'maplepress' ); ?>
</h2>
<p style="margin: 10px 0 0 0;">
<strong><?php esc_html_e( 'Domain:', 'maplepress' ); ?></strong> <?php echo esc_html( $options['domain'] ?? 'N/A' ); ?>
&nbsp;|&nbsp;
<strong><?php esc_html_e( 'Billing:', 'maplepress' ); ?></strong> <?php esc_html_e( 'Usage-Based', 'maplepress' ); ?>
</p>
</div>
<!-- Usage-Based Billing Notice -->
<div class="notice notice-info" style="padding: 15px; margin: 20px 0;">
<h3 style="margin: 0 0 10px 0;"> <?php esc_html_e( 'Usage-Based Billing', 'maplepress' ); ?></h3>
<p><?php esc_html_e( 'MaplePress now uses usage-based billing with no limits or quotas. You pay only for what you use:', 'maplepress' ); ?></p>
<ul style="margin: 10px 0 10px 20px;">
<li><?php esc_html_e( '✓ No search request limits', 'maplepress' ); ?></li>
<li><?php esc_html_e( '✓ No page indexing limits', 'maplepress' ); ?></li>
<li><?php esc_html_e( '✓ No storage limits', 'maplepress' ); ?></li>
<li><?php esc_html_e( '✓ Generous rate limits for anti-abuse (10,000 requests/hour)', 'maplepress' ); ?></li>
</ul>
<p>
<a href="https://getmaplepress.com/pricing" target="_blank" class="button">
<?php esc_html_e( 'View Pricing Details', 'maplepress' ); ?>
</a>
</p>
</div>
<!-- Quick Stats -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 30px 0;">
<!-- Monthly Searches -->
<div class="postbox" style="padding: 20px;">
<h3 style="margin: 0 0 10px 0;"><?php esc_html_e( 'Monthly Searches', 'maplepress' ); ?></h3>
<p style="font-size: 32px; font-weight: bold; margin: 0; color: #2271b1;">
<?php echo esc_html( number_format( $options['search_requests_count'] ?? 0 ) ); ?>
</p>
<p style="margin: 5px 0 0 0; color: #666;">
<?php esc_html_e( 'this billing cycle', 'maplepress' ); ?>
</p>
</div>
<!-- Monthly Pages Indexed -->
<div class="postbox" style="padding: 20px;">
<h3 style="margin: 0 0 10px 0;"><?php esc_html_e( 'Monthly Indexing', 'maplepress' ); ?></h3>
<p style="font-size: 32px; font-weight: bold; margin: 0; color: #2271b1;">
<?php echo esc_html( number_format( $options['monthly_pages_indexed'] ?? 0 ) ); ?>
</p>
<p style="margin: 5px 0 0 0; color: #666;">
<?php esc_html_e( 'pages this cycle', 'maplepress' ); ?>
</p>
</div>
<!-- Total Pages Indexed -->
<div class="postbox" style="padding: 20px;">
<h3 style="margin: 0 0 10px 0;"><?php esc_html_e( 'Total Pages', 'maplepress' ); ?></h3>
<p style="font-size: 32px; font-weight: bold; margin: 0; color: #2271b1;">
<?php echo esc_html( number_format( $options['total_pages_indexed'] ?? 0 ) ); ?>
</p>
<p style="margin: 5px 0 0 0; color: #666;">
<?php esc_html_e( 'all-time indexed', 'maplepress' ); ?>
</p>
</div>
<!-- Storage Used -->
<div class="postbox" style="padding: 20px;">
<h3 style="margin: 0 0 10px 0;"><?php esc_html_e( 'Storage Used', 'maplepress' ); ?></h3>
<p style="font-size: 32px; font-weight: bold; margin: 0; color: #2271b1;">
<?php
$storage_mb = round( ( $options['storage_used_bytes'] ?? 0 ) / 1024 / 1024, 1 );
echo esc_html( $storage_mb );
?>
<span style="font-size: 16px;">MB</span>
</p>
<p style="margin: 5px 0 0 0; color: #666;">
<?php esc_html_e( 'cumulative usage', 'maplepress' ); ?>
</p>
</div>
</div>
<!-- Detailed Usage -->
<h2><?php esc_html_e( 'Usage Details', 'maplepress' ); ?></h2>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th style="width: 40%;"><?php esc_html_e( 'Resource', 'maplepress' ); ?></th>
<th style="width: 30%;"><?php esc_html_e( 'Usage', 'maplepress' ); ?></th>
<th style="width: 30%;"><?php esc_html_e( 'Period', 'maplepress' ); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong><?php esc_html_e( 'Monthly Searches', 'maplepress' ); ?></strong></td>
<td><?php echo esc_html( number_format( $options['search_requests_count'] ?? 0 ) ); ?> requests</td>
<td><?php esc_html_e( 'Current billing cycle', 'maplepress' ); ?></td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Monthly Indexing', 'maplepress' ); ?></strong></td>
<td><?php echo esc_html( number_format( $options['monthly_pages_indexed'] ?? 0 ) ); ?> pages</td>
<td><?php esc_html_e( 'Current billing cycle', 'maplepress' ); ?></td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Total Pages Indexed', 'maplepress' ); ?></strong></td>
<td><?php echo esc_html( number_format( $options['total_pages_indexed'] ?? 0 ) ); ?> pages</td>
<td><?php esc_html_e( 'All-time cumulative', 'maplepress' ); ?></td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Storage Used', 'maplepress' ); ?></strong></td>
<td>
<?php
$used_mb = round( ( $options['storage_used_bytes'] ?? 0 ) / 1024 / 1024, 2 );
echo esc_html( $used_mb ) . ' MB';
?>
</td>
<td><?php esc_html_e( 'All-time cumulative', 'maplepress' ); ?></td>
</tr>
</tbody>
</table>
<!-- Quick Actions -->
<h2 style="margin-top: 40px;"><?php esc_html_e( 'Quick Actions', 'maplepress' ); ?></h2>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=maplepress-initial-sync' ) ); ?>" class="button button-primary button-large">
<?php esc_html_e( 'Sync All Pages Now', 'maplepress' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=maplepress-settings' ) ); ?>" class="button button-large">
<?php esc_html_e( 'Settings', 'maplepress' ); ?>
</a>
<a href="https://getmaplepress.com/dashboard" target="_blank" class="button button-large">
<?php esc_html_e( '→ Open Cloud Dashboard', 'maplepress' ); ?>
</a>
</div>
<!-- Advanced Tools -->
<h2 style="margin-top: 40px;"><?php esc_html_e( 'Advanced', 'maplepress' ); ?></h2>
<div class="postbox" style="padding: 20px; margin-bottom: 20px;">
<h3 style="margin-top: 0;"><?php esc_html_e( 'Diagnostic Tools', 'maplepress' ); ?></h3>
<p>
<?php esc_html_e( 'Access advanced diagnostic and testing tools for your MaplePress integration.', 'maplepress' ); ?>
</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 30px;">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=maplepress-speedtest' ) ); ?>" class="button button-large">
<span class="dashicons dashicons-performance" style="vertical-align: middle;"></span>
<?php esc_html_e( 'Search Speed Test', 'maplepress' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=maplepress-system-info' ) ); ?>" class="button button-large">
<span class="dashicons dashicons-info" style="vertical-align: middle;"></span>
<?php esc_html_e( 'System Info', 'maplepress' ); ?>
</a>
</div>
<hr style="margin: 20px 0;">
<h3 style="margin-top: 20px; color: #d63638;"><?php esc_html_e( 'Danger Zone', 'maplepress' ); ?></h3>
<p>
<?php esc_html_e( 'Permanently remove MaplePress plugin and all local data.', 'maplepress' ); ?>
</p>
<form method="post" action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>" style="margin-top: 15px;">
<input type="hidden" name="action" value="maplepress_reset_and_delete">
<?php wp_nonce_field( 'maplepress_reset_and_delete' ); ?>
<button type="submit"
class="button button-large"
style="color: #ffffff; background-color: #d63638; border-color: #d63638;"
onmouseover="this.style.backgroundColor='#b32d2e';"
onmouseout="this.style.backgroundColor='#d63638';"
onclick="return confirm('<?php echo esc_js( __( '⚠️ WARNING: This action is PERMANENT and cannot be undone!\n\nThis will:\n✗ Delete all MaplePress settings from WordPress\n✗ Remove API keys and configuration\n✗ Clear verification status\n✗ Deactivate and DELETE the plugin\n\n⚠️ Your data in the MaplePress cloud will remain safe and can be accessed again if you reinstall.\n\nAre you absolutely sure you want to continue?', 'maplepress' ) ); ?>">
<span class="dashicons dashicons-trash" style="vertical-align: middle;"></span>
<?php esc_html_e( 'Reset & Delete Plugin', 'maplepress' ); ?>
</button>
</form>
<p class="description" style="color: #d63638; margin-top: 10px;">
<strong><?php esc_html_e( 'Warning:', 'maplepress' ); ?></strong>
<?php esc_html_e( 'This will permanently delete the plugin and all local settings. Your cloud data remains safe.', 'maplepress' ); ?>
</p>
</div>
<!-- Site Information -->
<h2><?php esc_html_e( 'Site Information', 'maplepress' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Domain', 'maplepress' ); ?></th>
<td><?php echo esc_html( $options['domain'] ?? 'N/A' ); ?></td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Billing Model', 'maplepress' ); ?></th>
<td><strong><?php esc_html_e( 'Usage-Based', 'maplepress' ); ?></strong></td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Status', 'maplepress' ); ?></th>
<td>
<?php if ( $options['enabled'] ) : ?>
<span style="color: #46b450; font-weight: bold;"> <?php esc_html_e( 'Active', 'maplepress' ); ?></span>
<?php else : ?>
<span style="color: #dc3232; font-weight: bold;"> <?php esc_html_e( 'Inactive', 'maplepress' ); ?></span>
<?php endif; ?>
</td>
</tr>
</table>
<?php endif; ?>
<!-- Footer -->
<div style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #dcdcde; text-align: center; color: #646970; font-size: 13px;">
<p style="margin: 0;">
<?php
printf(
/* translators: %s: link to source code */
esc_html__( 'MaplePress is open-source software. %s', 'maplepress' ),
'<a href="https://codeberg.org/mapleopentech/monorepo/src/branch/main/native/wordpress/maplepress-plugin" target="_blank" rel="noopener noreferrer" style="color: #2271b1; text-decoration: none;">' .
esc_html__( 'View source code', 'maplepress' ) .
' <span class="dashicons dashicons-external" style="font-size: 12px; vertical-align: text-top;"></span></a>'
);
?>
&nbsp;|&nbsp;
<a href="https://getmaplepress.com" target="_blank" rel="noopener noreferrer" style="color: #2271b1; text-decoration: none;">
<?php esc_html_e( 'Documentation', 'maplepress' ); ?>
</a>
&nbsp;|&nbsp;
<a href="https://codeberg.org/mapleopentech/monorepo/issues" target="_blank" rel="noopener noreferrer" style="color: #2271b1; text-decoration: none;">
<?php esc_html_e( 'Report an issue', 'maplepress' ); ?>
</a>
</p>
</div>
</div>

View file

@ -0,0 +1,187 @@
<?php
/**
* Admin sync page template.
* This page is shown when syncing all pages to MaplePress (initial or manual sync).
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$options = get_option( 'maplepress_settings', array() );
?>
<div class="wrap">
<h1><?php esc_html_e( 'Syncing Pages', 'maplepress' ); ?></h1>
<div style="max-width: 800px; margin: 40px auto; text-align: center;">
<div style="background: #fff; border: 1px solid #dcdcde; padding: 40px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<!-- Loading animation -->
<div style="margin-bottom: 30px;">
<span class="spinner is-active" style="float: none; width: 40px; height: 40px; margin: 0 auto; background-size: 40px 40px;"></span>
</div>
<!-- Status heading -->
<h2 id="maplepress-sync-status" style="margin: 20px 0; color: #2271b1; font-size: 24px;">
<?php esc_html_e( 'Preparing to sync your content...', 'maplepress' ); ?>
</h2>
<!-- Details text -->
<p id="maplepress-sync-details" style="color: #666; font-size: 16px; margin: 15px 0;">
<?php esc_html_e( 'This will only take a moment.', 'maplepress' ); ?>
</p>
<!-- Progress bar -->
<div id="maplepress-sync-progressbar" style="margin: 30px 0;">
<div style="background: #f0f0f1; border-radius: 4px; height: 30px; position: relative; overflow: hidden;">
<div id="maplepress-sync-progressbar-fill" style="background: linear-gradient(90deg, #2271b1 0%, #135e96 100%); height: 100%; width: 0%; transition: width 0.5s ease;">
<span id="maplepress-sync-progressbar-text" style="color: #fff; font-size: 14px; font-weight: 600; line-height: 30px; display: block; text-align: center;"></span>
</div>
</div>
</div>
<!-- Batch information -->
<div id="maplepress-sync-batch-info" style="background: #f9f9f9; border: 1px solid #e0e0e0; border-radius: 4px; padding: 20px; margin: 20px 0; display: none;">
<div style="display: flex; justify-content: space-around; text-align: center;">
<div>
<div style="font-size: 28px; font-weight: 600; color: #2271b1;" id="maplepress-sync-pages-synced">0</div>
<div style="color: #666; font-size: 14px;"><?php esc_html_e( 'Pages Synced', 'maplepress' ); ?></div>
</div>
<div>
<div style="font-size: 28px; font-weight: 600; color: #2271b1;" id="maplepress-sync-current-batch">0</div>
<div style="color: #666; font-size: 14px;"><?php esc_html_e( 'Current Batch', 'maplepress' ); ?></div>
</div>
<div>
<div style="font-size: 28px; font-weight: 600; color: #2271b1;" id="maplepress-sync-total-batches">0</div>
<div style="color: #666; font-size: 14px;"><?php esc_html_e( 'Total Batches', 'maplepress' ); ?></div>
</div>
</div>
</div>
<!-- Important note -->
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin-top: 30px; text-align: left;">
<p style="margin: 0; color: #856404;">
<strong><?php esc_html_e( 'Please do not close this page.', 'maplepress' ); ?></strong><br>
<?php esc_html_e( 'We are syncing all your published content to MaplePress.', 'maplepress' ); ?>
</p>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
var statusText = $('#maplepress-sync-status');
var detailsText = $('#maplepress-sync-details');
var progressBar = $('#maplepress-sync-progressbar-fill');
var progressText = $('#maplepress-sync-progressbar-text');
var batchInfo = $('#maplepress-sync-batch-info');
var pagesSynced = $('#maplepress-sync-pages-synced');
var currentBatch = $('#maplepress-sync-current-batch');
var totalBatches = $('#maplepress-sync-total-batches');
// Start the sync
function startSync() {
statusText.text('<?php esc_html_e( 'Counting pages to sync...', 'maplepress' ); ?>');
detailsText.text('<?php esc_html_e( 'Analyzing your content...', 'maplepress' ); ?>');
progressBar.css('width', '5%');
progressText.text('5%');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'maplepress_initial_sync',
nonce: '<?php echo wp_create_nonce( 'maplepress_initial_sync' ); ?>'
},
timeout: 600000, // 10 minute timeout
success: function(response) {
if (response.success) {
// Show completion
progressBar.css('width', '100%');
progressText.text('100%');
statusText.html('<span style="color: #008a00;">✓ ' + response.data.message + '</span>');
detailsText.html(response.data.details || '');
// Redirect to dashboard after 2 seconds
setTimeout(function() {
window.location.href = '<?php echo admin_url( 'admin.php?page=maplepress&sync_status=success&synced_count=' ); ?>' + (response.data.synced_count || 0);
}, 2000);
} else {
// Show error
statusText.html('<span style="color: #d63638;">✗ Sync failed</span>');
detailsText.html('<strong><?php esc_html_e( 'Error:', 'maplepress' ); ?></strong> ' + (response.data || '<?php esc_html_e( 'Unknown error', 'maplepress' ); ?>'));
// Show retry button
$('<div style="margin-top: 20px;"><a href="<?php echo admin_url( 'admin.php?page=maplepress-settings' ); ?>" class="button button-primary"><?php esc_html_e( 'Return to Settings', 'maplepress' ); ?></a></div>').insertAfter('#maplepress-sync-progressbar');
}
},
error: function(xhr, status, error) {
statusText.html('<span style="color: #d63638;">✗ Network error</span>');
detailsText.html('<strong><?php esc_html_e( 'Error:', 'maplepress' ); ?></strong> ' + error);
// Show retry button
$('<div style="margin-top: 20px;"><a href="<?php echo admin_url( 'admin.php?page=maplepress-settings' ); ?>" class="button button-primary"><?php esc_html_e( 'Return to Settings', 'maplepress' ); ?></a></div>').insertAfter('#maplepress-sync-progressbar');
},
xhr: function() {
var xhr = new window.XMLHttpRequest();
var progressSimulator;
var startTime = Date.now();
// Start progress simulation
progressSimulator = setInterval(function() {
var elapsed = (Date.now() - startTime) / 1000;
if (elapsed < 5) {
// 5-15% - Analyzing content
var progress = 5 + (elapsed / 5) * 10;
progressBar.css('width', progress + '%');
progressText.text(Math.round(progress) + '%');
statusText.text('<?php esc_html_e( 'Analyzing your content...', 'maplepress' ); ?>');
} else if (elapsed < 10) {
// 15-30% - Preparing batches
var progress = 15 + ((elapsed - 5) / 5) * 15;
progressBar.css('width', progress + '%');
progressText.text(Math.round(progress) + '%');
statusText.text('<?php esc_html_e( 'Preparing batches for sync...', 'maplepress' ); ?>');
batchInfo.show();
} else if (elapsed < 60) {
// 30-80% - Syncing batches
var progress = 30 + ((elapsed - 10) / 50) * 50;
progressBar.css('width', Math.min(progress, 80) + '%');
progressText.text(Math.round(Math.min(progress, 80)) + '%');
statusText.text('<?php esc_html_e( 'Syncing pages to MaplePress...', 'maplepress' ); ?>');
detailsText.html('<?php esc_html_e( 'Processing batches...', 'maplepress' ); ?><br><small><?php esc_html_e( 'This may take a moment for large sites.', 'maplepress' ); ?></small>');
// Simulate batch progress
var estimatedBatches = Math.ceil(elapsed / 3);
currentBatch.text(estimatedBatches);
totalBatches.text(estimatedBatches + Math.ceil((60 - elapsed) / 3));
pagesSynced.text(estimatedBatches * 950);
} else {
// 80-95% - Finalizing
var progress = 80 + ((elapsed - 60) / 60) * 15;
progressBar.css('width', Math.min(progress, 95) + '%');
progressText.text(Math.round(Math.min(progress, 95)) + '%');
statusText.text('<?php esc_html_e( 'Finalizing sync...', 'maplepress' ); ?>');
detailsText.html('<?php esc_html_e( 'Almost done!', 'maplepress' ); ?>');
}
}, 500);
xhr.addEventListener('loadend', function() {
clearInterval(progressSimulator);
});
return xhr;
}
});
}
// Auto-start sync when page loads
setTimeout(startSync, 500);
});
</script>

View file

@ -0,0 +1,74 @@
<?php
/**
* Admin ready-to-sync page template.
* This page is shown after first-time connection to prompt user to manually start sync.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$options = get_option( 'maplepress_settings', array() );
?>
<div class="wrap">
<h1><?php esc_html_e( 'MaplePress - Ready to Sync', 'maplepress' ); ?></h1>
<div style="max-width: 800px; margin: 40px auto;">
<div style="background: #fff; border: 1px solid #dcdcde; padding: 40px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<!-- Success icon -->
<div style="text-align: center; margin-bottom: 30px;">
<span style="display: inline-block; width: 80px; height: 80px; line-height: 80px; border-radius: 50%; background: #00a32a; color: #fff; font-size: 48px;"></span>
</div>
<!-- Main message -->
<h2 style="text-align: center; margin: 20px 0; color: #1d2327; font-size: 28px;">
<?php esc_html_e( 'Connection Successful!', 'maplepress' ); ?>
</h2>
<p style="text-align: center; color: #666; font-size: 18px; margin: 20px 0;">
<?php esc_html_e( 'Your site is now connected to MaplePress.', 'maplepress' ); ?>
</p>
<!-- Important notice -->
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 20px; margin: 30px 0; text-align: center;">
<p style="margin: 0; color: #856404; font-size: 16px; font-weight: 600;">
<?php esc_html_e( 'You must sync all your pages to continue.', 'maplepress' ); ?>
</p>
<p style="margin: 10px 0 0 0; color: #856404; font-size: 14px;">
<?php esc_html_e( 'This is a one-time process that will make all your published content searchable via MaplePress.', 'maplepress' ); ?>
</p>
</div>
<!-- Action buttons -->
<div style="text-align: center; margin: 40px 0 20px 0;">
<a href="<?php echo admin_url( 'admin.php?page=maplepress-initial-sync' ); ?>" class="button button-primary button-hero" style="font-size: 18px; padding: 12px 36px; height: auto;">
<?php esc_html_e( 'Start Sync Now', 'maplepress' ); ?>
</a>
</div>
<!-- Divider -->
<hr style="margin: 40px 0; border: none; border-top: 1px solid #dcdcde;">
<!-- Secondary actions -->
<div style="text-align: center;">
<p style="color: #666; font-size: 14px; margin-bottom: 15px;">
<?php esc_html_e( 'Need to start over?', 'maplepress' ); ?>
</p>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" onsubmit="return confirm('<?php echo esc_js( __( 'Are you sure you want to reset all MaplePress settings? This will disconnect your site from MaplePress.', 'maplepress' ) ); ?>');">
<?php wp_nonce_field( 'maplepress_reset_settings' ); ?>
<input type="hidden" name="action" value="maplepress_reset_settings">
<button type="submit" class="button button-link-delete" style="color: #d63638; text-decoration: none;">
<?php esc_html_e( 'Reset All Settings', 'maplepress' ); ?>
</button>
</form>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,332 @@
<?php
/**
* Admin settings page template.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
// Exit if accessed directly.
if (!defined("ABSPATH")) {
exit();
} ?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<?php
$options = get_option("maplepress_settings");
$has_api_key = !empty($options["api_key"]);
$is_verified = isset($options["is_verified"]) && $options["is_verified"];
// Refresh quota data if verified and API credentials are available
// Fetch fresh data on every page load without persisting to avoid triggering hooks
if (
$is_verified &&
!empty($options["api_url"]) &&
!empty($options["api_key"])
) {
require_once MAPLEPRESS_PLUGIN_DIR .
"includes/class-maplepress-api-client.php";
$api_client = new MaplePress_API_Client(
$options["api_url"],
$options["api_key"],
);
$fresh_status = $api_client->verify_connection();
// Update options with fresh quota data if API call succeeded
// Note: We only update the in-memory $options array, not the database
// This ensures fresh display without triggering validation hooks
if (!is_wp_error($fresh_status)) {
// Update quota fields in options for display only
if (isset($fresh_status["search_requests_count"])) {
$options["search_requests_count"] = absint(
$fresh_status["search_requests_count"],
);
}
if (isset($fresh_status["storage_used_bytes"])) {
$options["storage_used_bytes"] = absint(
$fresh_status["storage_used_bytes"],
);
}
if (isset($fresh_status["monthly_pages_indexed"])) {
$options["monthly_pages_indexed"] = absint(
$fresh_status["monthly_pages_indexed"],
);
}
if (isset($fresh_status["total_pages_indexed"])) {
$options["total_pages_indexed"] = absint(
$fresh_status["total_pages_indexed"],
);
}
// Do NOT call update_option() here - it could trigger validation/sync hooks
}
}
?>
<?php if (!$has_api_key): ?>
<!-- First-time setup instructions -->
<div class="notice notice-info" style="padding: 15px; margin: 20px 0;">
<h2 style="margin-top: 0;"><?php esc_html_e(
"🚀 Welcome to MaplePress!",
"maplepress",
); ?></h2>
<p><?php esc_html_e(
"MaplePress provides cloud services for your WordPress site - starting with advanced search, with more features coming soon (uploads, metrics, analytics, etc.).",
"maplepress",
); ?></p>
<p><strong><?php esc_html_e(
'✨ When you save settings with "Enable MaplePress search" checked, all your existing pages will automatically sync!',
"maplepress",
); ?></strong></p>
<p><?php esc_html_e("To get started, follow these steps:", "maplepress"); ?></p>
<ol style="margin-left: 20px;">
<li>
<strong><?php esc_html_e(
"Create a MaplePress account:",
"maplepress",
); ?></strong><br>
<a href="https://getmaplepress.com/register" target="_blank" class="button button-primary" style="margin-top: 5px;">
<?php esc_html_e("Sign Up at MaplePress.io", "maplepress"); ?>
</a>
<?php esc_html_e("or", "maplepress"); ?>
<a href="https://getmaplepress.com/login" target="_blank" class="button" style="margin-top: 5px;">
<?php esc_html_e("Login to Existing Account", "maplepress"); ?>
</a>
</li>
<li style="margin-top: 10px;">
<strong><?php esc_html_e(
"Create a site in your dashboard",
"maplepress",
); ?></strong><br>
<span style="color: #666;"><?php esc_html_e(
"After logging in, create a new site for this WordPress installation.",
"maplepress",
); ?></span>
</li>
<li style="margin-top: 10px;">
<strong><?php esc_html_e(
"Copy your API URL and API key",
"maplepress",
); ?></strong><br>
<span style="color: #666;">
<?php esc_html_e(
"Your API URL is https://getmaplepress.ca (or your custom backend URL). Your API key will be displayed once - make sure to copy both!",
"maplepress",
); ?>
</span>
</li>
<li style="margin-top: 10px;">
<strong><?php esc_html_e(
"Paste your API URL and API key below and save",
"maplepress",
); ?></strong>
</li>
</ol>
</div>
<?php endif; ?>
<?php settings_errors("maplepress_settings"); ?>
<?php // Display sync status messages
if (isset($_GET["sync_status"])) {
if (
$_GET["sync_status"] === "success" &&
isset($_GET["synced_count"])
) { ?>
<div class="notice notice-success is-dismissible">
<p>
<?php printf(
/* translators: %d: number of pages synced */
esc_html__(
"✓ Successfully synced %d pages to MaplePress!",
"maplepress",
),
absint($_GET["synced_count"]),
); ?>
</p>
</div>
<?php } elseif (
$_GET["sync_status"] === "error" &&
isset($_GET["sync_message"])
) { ?>
<div class="notice notice-error is-dismissible">
<p>
<?php printf(
/* translators: %s: error message */
esc_html__("✗ Sync failed: %s", "maplepress"),
esc_html(urldecode($_GET["sync_message"])),
); ?>
</p>
</div>
<?php } elseif ($_GET["sync_status"] === "no_posts") { ?>
<div class="notice notice-warning is-dismissible">
<p><?php esc_html_e(
"No published posts or pages found to sync.",
"maplepress",
); ?></p>
</div>
<?php }
} ?>
<form method="post" action="options.php">
<?php
settings_fields("maplepress_settings_group");
do_settings_sections("maplepress");
submit_button(__("Save Settings & Verify Connection", "maplepress"));
?>
</form>
<?php if ($is_verified): ?>
<!-- Connection status - show site details -->
<hr>
<h2><?php esc_html_e("✓ Connected to MaplePress", "maplepress"); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e("Domain", "maplepress"); ?></th>
<td><?php echo esc_html($options["domain"] ?? "N/A"); ?></td>
</tr>
<tr>
<th scope="row"><?php esc_html_e("Plan", "maplepress"); ?></th>
<td><strong><?php echo esc_html(
ucfirst($options["plan_tier"] ?? "free"),
); ?></strong></td>
</tr>
<tr>
<th scope="row"><?php esc_html_e("Status", "maplepress"); ?></th>
<td>
<?php if ($options["enabled"]): ?>
<span style="color: #46b450; font-weight: bold;"> <?php esc_html_e(
"Active",
"maplepress",
); ?></span>
<?php else: ?>
<span style="color: #dc3232; font-weight: bold;"> <?php esc_html_e(
"Inactive (Enable above to activate)",
"maplepress",
); ?></span>
<?php endif; ?>
</td>
</tr>
</table>
<?php if (
isset($options["storage_quota_bytes"]) ||
isset($options["search_quota_monthly"]) ||
isset($options["indexing_quota_pages"])
): ?>
<h3><?php esc_html_e("Usage & Quotas", "maplepress"); ?></h3>
<table class="form-table">
<?php if (isset($options["storage_quota_bytes"])): ?>
<tr>
<th scope="row"><?php esc_html_e("Storage", "maplepress"); ?></th>
<td>
<?php
$used = isset($options["storage_used_bytes"])
? $options["storage_used_bytes"]
: 0;
$quota = $options["storage_quota_bytes"];
$used_mb = round($used / 1024 / 1024, 2);
$quota_mb = round($quota / 1024 / 1024, 2);
$percent = $quota > 0 ? round(($used / $quota) * 100, 1) : 0;
?>
<strong><?php echo esc_html($used_mb); ?> MB</strong> / <?php echo esc_html(
$quota_mb,
); ?> MB
<span style="color: <?php echo $percent > 80
? "#dc3232"
: "#46b450"; ?>;">(<?php echo esc_html($percent); ?>% used)</span>
</td>
</tr>
<?php endif; ?>
<?php if (isset($options["search_quota_monthly"])): ?>
<tr>
<th scope="row"><?php esc_html_e("Monthly Searches", "maplepress"); ?></th>
<td>
<?php
$used = isset($options["search_requests_count"])
? $options["search_requests_count"]
: 0;
$quota = $options["search_quota_monthly"];
$percent = $quota > 0 ? round(($used / $quota) * 100, 1) : 0;
?>
<strong><?php echo esc_html(
number_format($used),
); ?></strong> / <?php echo esc_html(number_format($quota)); ?>
<span style="color: <?php echo $percent > 80
? "#dc3232"
: "#46b450"; ?>;">(<?php echo esc_html($percent); ?>% used)</span>
</td>
</tr>
<?php endif; ?>
<?php if (isset($options["indexing_quota_pages"])): ?>
<tr>
<th scope="row"><?php esc_html_e("Monthly Indexing", "maplepress"); ?></th>
<td>
<?php
$used = isset($options["monthly_pages_indexed"])
? $options["monthly_pages_indexed"]
: 0;
$quota = $options["indexing_quota_pages"];
$percent = $quota > 0 ? round(($used / $quota) * 100, 1) : 0;
?>
<strong><?php echo esc_html(
number_format($used),
); ?></strong> / <?php echo esc_html(number_format($quota)); ?> pages
<span style="color: <?php echo $percent > 80
? "#dc3232"
: "#46b450"; ?>;">(<?php echo esc_html(
$percent,
); ?>% used this month)</span>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e("Total Pages Indexed", "maplepress"); ?></th>
<td>
<?php $total = isset($options["total_pages_indexed"])
? $options["total_pages_indexed"]
: 0; ?>
<strong><?php echo esc_html(number_format($total)); ?></strong> pages (all-time)
</td>
</tr>
<?php endif; ?>
</table>
<?php endif; ?>
<hr>
<h2><?php esc_html_e("Sync Pages", "maplepress"); ?></h2>
<p><?php esc_html_e(
"Sync all your published posts and pages to MaplePress. New posts and pages are automatically synced when published.",
"maplepress",
); ?></p>
<form method="post" action="<?php echo esc_url(admin_url("admin.php")); ?>">
<input type="hidden" name="action" value="maplepress_bulk_sync">
<?php wp_nonce_field("maplepress_bulk_sync"); ?>
<p>
<button type="submit" class="button button-primary">
<?php esc_html_e("Sync All Pages Now", "maplepress"); ?>
</button>
</p>
</form>
<p>
<a href="https://getmaplepress.com/dashboard" target="_blank" class="button">
<?php esc_html_e("→ Open MaplePress Dashboard", "maplepress"); ?>
</a>
</p>
<?php endif; ?>
<hr>
<h2><?php esc_html_e("About MaplePress", "maplepress"); ?></h2>
<p>
<?php printf(
/* translators: %s: MaplePress website URL */
esc_html__(
"MaplePress is a cloud services platform for WordPress that offloads computationally intensive tasks to dedicated infrastructure. Currently featuring advanced search capabilities, with more services coming soon including uploads, metrics, and analytics. Visit %s to learn more.",
"maplepress",
),
'<a href="https://mapleopentech.io" target="_blank">https://mapleopentech.io</a>',
); ?>
</p>
</div>

View file

@ -0,0 +1,536 @@
<?php
/**
* Admin settings page template.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$options = get_option( 'maplepress_settings', array() );
$has_api_key = ! empty( $options['api_key'] );
$is_verified = isset( $options['is_verified'] ) && $options['is_verified'];
// Debug: Log current options
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( 'MaplePress Settings Page - api_url: ' . ( $options['api_url'] ?? 'NOT SET' ) );
error_log( 'MaplePress Settings Page - api_key: ' . ( isset( $options['api_key'] ) ? 'SET (' . strlen( $options['api_key'] ) . ' chars)' : 'NOT SET' ) );
error_log( 'MaplePress Settings Page - has_api_key: ' . ( $has_api_key ? 'true' : 'false' ) );
error_log( 'MaplePress Settings Page - is_verified: ' . ( $is_verified ? 'true' : 'false' ) );
}
?>
<div class="wrap">
<h1><?php esc_html_e( 'MaplePress Settings', 'maplepress' ); ?></h1>
<?php
// WordPress automatically displays settings errors, no need to call settings_errors() manually
?>
<?php if ( ! $has_api_key ) : ?>
<!-- First-time setup instructions -->
<div class="notice notice-info" style="padding: 15px; margin: 20px 0;">
<h2 style="margin-top: 0;"><?php esc_html_e( '🚀 Getting Started', 'maplepress' ); ?></h2>
<p><?php esc_html_e( 'Connect your WordPress site to MaplePress cloud services to enable advanced search and more.', 'maplepress' ); ?></p>
<ol style="margin-left: 20px;">
<li>
<strong><?php esc_html_e( 'Create a MaplePress account:', 'maplepress' ); ?></strong><br>
<a href="https://getmaplepress.com/register" target="_blank" class="button button-primary" style="margin-top: 5px;">
<?php esc_html_e( 'Sign Up', 'maplepress' ); ?>
</a>
<?php esc_html_e( 'or', 'maplepress' ); ?>
<a href="https://getmaplepress.com/login" target="_blank" class="button" style="margin-top: 5px;">
<?php esc_html_e( 'Login', 'maplepress' ); ?>
</a>
</li>
<li style="margin-top: 10px;">
<strong><?php esc_html_e( 'Create a site in your dashboard', 'maplepress' ); ?></strong>
</li>
<li style="margin-top: 10px;">
<strong><?php esc_html_e( 'Copy your API credentials and paste them below', 'maplepress' ); ?></strong>
</li>
</ol>
</div>
<?php endif; ?>
<form method="post" action="options.php">
<?php settings_fields( 'maplepress_settings_group' ); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="api_url"><?php esc_html_e( 'API URL', 'maplepress' ); ?></label>
</th>
<td>
<?php if ( $is_verified ) : ?>
<!-- Show URL as read-only when verified -->
<input type="text"
id="api_url"
value="<?php echo esc_attr( $options['api_url'] ?? '' ); ?>"
class="regular-text"
disabled>
<input type="hidden"
name="maplepress_settings[api_url]"
value="<?php echo esc_attr( $options['api_url'] ?? '' ); ?>">
<p class="description">
<?php esc_html_e( 'Connected. To change, disconnect first.', 'maplepress' ); ?>
</p>
<?php else : ?>
<!-- Allow editing when not verified -->
<input type="text"
id="api_url"
name="maplepress_settings[api_url]"
value="<?php echo esc_attr( ! empty( $options['api_url'] ) ? $options['api_url'] : 'https://getmaplepress.ca' ); ?>"
class="regular-text"
placeholder="https://getmaplepress.ca">
<p class="description">
<?php esc_html_e( 'Your MaplePress API endpoint URL', 'maplepress' ); ?>
</p>
<?php endif; ?>
</td>
</tr>
<tr>
<th scope="row">
<label for="api_key"><?php esc_html_e( 'API Key', 'maplepress' ); ?></label>
</th>
<td>
<?php if ( $is_verified ) : ?>
<!-- Show masked key when verified -->
<div style="margin-bottom: 10px;">
<code style="background: #f0f0f1; padding: 8px 12px; border-radius: 3px; font-size: 13px;">
<?php
$prefix = $options['api_key_prefix'] ?? 'mpsk';
$last_four = $options['api_key_last_four'] ?? '****';
echo esc_html( $prefix . '_' . str_repeat( '*', 28 ) . $last_four );
?>
</code>
</div>
<input type="hidden"
name="maplepress_settings[api_key]"
value="<?php echo esc_attr( $options['api_key'] ?? '' ); ?>">
<p class="description">
<?php esc_html_e( 'Your API key is securely stored. To change it, disconnect first.', 'maplepress' ); ?>
</p>
<?php else : ?>
<!-- Allow editing when not verified -->
<input type="password"
id="api_key"
name="maplepress_settings[api_key]"
value="<?php echo esc_attr( $options['api_key'] ?? '' ); ?>"
class="regular-text"
placeholder="live_sk_... or test_sk_...">
<p class="description">
<?php esc_html_e( 'Your MaplePress API key. Use "live_" prefix for production (https://getmaplepress.ca) or "test_" prefix for development/custom URLs.', 'maplepress' ); ?>
</p>
<?php endif; ?>
</td>
</tr>
<?php if ( $has_api_key && ! $is_verified ) : ?>
<!-- DNS Verification Required Section -->
<tr>
<th scope="row" colspan="2">
<h3 style="margin: 30px 0 10px 0; color: #d63638;">
⚠️ <?php esc_html_e( 'Domain Verification Required', 'maplepress' ); ?>
</h3>
</th>
</tr>
<tr>
<td colspan="2">
<div class="notice notice-warning inline" style="margin: 0; padding: 20px; background: #fff3cd; border-left: 4px solid #ffc107;">
<h4 style="margin-top: 0;">
<?php esc_html_e( '🔐 Your API key is connected, but your domain needs verification to enable syncing.', 'maplepress' ); ?>
</h4>
<p><?php esc_html_e( 'To verify ownership of your domain, please add the following DNS TXT record:', 'maplepress' ); ?></p>
<table class="widefat" style="background: #f9f9f9; margin: 15px 0; max-width: 600px;">
<tbody>
<tr>
<td style="width: 150px; font-weight: bold; padding: 12px;">
<?php esc_html_e( 'Host/Name:', 'maplepress' ); ?>
</td>
<td style="padding: 12px;">
<code style="background: #fff; padding: 5px 10px; font-size: 13px;">
<?php echo esc_html( $options['domain'] ?? 'your-domain.com' ); ?>
</code>
</td>
</tr>
<tr>
<td style="font-weight: bold; padding: 12px;">
<?php esc_html_e( 'Type:', 'maplepress' ); ?>
</td>
<td style="padding: 12px;">
<code style="background: #fff; padding: 5px 10px; font-size: 13px;">TXT</code>
</td>
</tr>
<tr>
<td style="font-weight: bold; padding: 12px;">
<?php esc_html_e( 'Value:', 'maplepress' ); ?>
</td>
<td style="padding: 12px;">
<code style="background: #fff; padding: 5px 10px; font-size: 13px; word-break: break-all;">
maplepress-verify=<?php echo esc_html( $options['verification_token'] ?? 'xxx' ); ?>
</code>
</td>
</tr>
</tbody>
</table>
<div style="background: #e7f3ff; border-left: 3px solid #2271b1; padding: 15px; margin: 20px 0;">
<h4 style="margin-top: 0;">
<?php esc_html_e( '📋 Step-by-Step Instructions:', 'maplepress' ); ?>
</h4>
<ol style="margin: 10px 0 10px 20px; line-height: 1.8;">
<li><?php esc_html_e( 'Log in to your domain registrar (GoDaddy, Namecheap, Cloudflare, etc.)', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Navigate to DNS settings for your domain', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Add a new TXT record with the values shown above', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Wait 5-10 minutes for DNS propagation (can take up to 48 hours)', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Click the "Verify Domain Now" button below', 'maplepress' ); ?></li>
</ol>
</div>
<!-- Progress indicator (hidden by default) -->
<div id="maplepress-verification-progress" style="display: none; background: #fff; border: 1px solid #dcdcde; padding: 20px; margin: 20px 0; border-radius: 4px;">
<div style="display: flex; align-items: center; margin-bottom: 15px;">
<span class="spinner is-active" style="float: none; margin: 0 10px 0 0;"></span>
<strong id="maplepress-verification-status" style="font-size: 14px;">Verifying domain...</strong>
</div>
<div id="maplepress-verification-details" style="color: #666; font-size: 13px; margin-top: 10px;">
<!-- Progress details will appear here -->
</div>
<!-- Progress bar -->
<div id="maplepress-verification-progressbar" style="display: none; margin-top: 15px;">
<div style="background: #f0f0f1; border-radius: 4px; height: 24px; position: relative; overflow: hidden;">
<div id="maplepress-verification-progressbar-fill" style="background: linear-gradient(90deg, #2271b1 0%, #135e96 100%); height: 100%; width: 0%; transition: width 0.3s ease; display: flex; align-items: center; justify-content: center;">
<span id="maplepress-verification-progressbar-text" style="color: #fff; font-size: 12px; font-weight: 600; position: absolute; width: 100%; text-align: center; z-index: 2;"></span>
</div>
</div>
</div>
</div>
<form id="maplepress-verify-form" method="post" action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>" style="margin-top: 20px;">
<input type="hidden" name="action" value="maplepress_verify_domain">
<?php wp_nonce_field( 'maplepress_verify_domain' ); ?>
<p>
<button type="submit" id="maplepress-verify-button" class="button button-primary button-large">
🔍 <?php esc_html_e( 'Verify Domain Now', 'maplepress' ); ?>
</button>
<span style="margin-left: 15px; color: #666;">
<?php esc_html_e( 'Make sure you\'ve added the DNS record and waited a few minutes before clicking.', 'maplepress' ); ?>
</span>
</p>
</form>
<script>
jQuery(document).ready(function($) {
$('#maplepress-verify-form').on('submit', function(e) {
e.preventDefault();
var button = $('#maplepress-verify-button');
var progressBox = $('#maplepress-verification-progress');
var statusText = $('#maplepress-verification-status');
var detailsBox = $('#maplepress-verification-details');
var progressBar = $('#maplepress-verification-progressbar');
var progressBarFill = $('#maplepress-verification-progressbar-fill');
var progressBarText = $('#maplepress-verification-progressbar-text');
// Disable button and show progress
button.prop('disabled', true);
button.text('⏳ Verifying...');
progressBox.show();
statusText.text('Step 1 of 2: Verifying domain ownership...');
detailsBox.html('Checking DNS TXT record...');
var startTime = Date.now();
// Make AJAX request
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'maplepress_verify_and_sync_ajax',
nonce: '<?php echo wp_create_nonce( 'maplepress_verify_and_sync_ajax' ); ?>'
},
timeout: 300000, // 5 minute timeout for large sites
xhr: function() {
var xhr = new window.XMLHttpRequest();
// Simulate progress during long operations
var progressSimulator = setInterval(function() {
var elapsed = (Date.now() - startTime) / 1000;
if (elapsed < 5) {
detailsBox.html('Checking DNS TXT record...');
} else if (elapsed < 10) {
statusText.text('Step 2 of 2: Syncing pages to MaplePress...');
detailsBox.html('Preparing pages for sync...');
progressBar.show();
progressBarFill.css('width', '10%');
progressBarText.text('10%');
} else if (elapsed < 30) {
var progress = Math.min(10 + ((elapsed - 10) / 20) * 60, 70);
detailsBox.html('Syncing pages to MaplePress cloud...<br><small>This may take a moment for large sites...</small>');
progressBarFill.css('width', progress + '%');
progressBarText.text(Math.round(progress) + '%');
} else {
var progress = Math.min(70 + ((elapsed - 30) / 30) * 25, 95);
detailsBox.html('Almost done, finalizing sync...<br><small>Processing batches...</small>');
progressBarFill.css('width', progress + '%');
progressBarText.text(Math.round(progress) + '%');
}
}, 1000);
xhr.addEventListener('loadend', function() {
clearInterval(progressSimulator);
});
return xhr;
},
success: function(response) {
if (response.success) {
// Show 100% complete
progressBar.show();
progressBarFill.css('width', '100%');
progressBarText.text('100%');
statusText.html('<span style="color: #008a00;">✓ ' + response.data.message + '</span>');
detailsBox.html(response.data.details || '');
// Reload page after 2 seconds
setTimeout(function() {
window.location.href = '<?php echo admin_url( 'admin.php?page=maplepress-settings&verification_status=success' ); ?>';
}, 2000);
} else {
progressBar.hide();
statusText.html('<span style="color: #d63638;">✗ Verification failed</span>');
detailsBox.html('<strong>Error:</strong> ' + (response.data || 'Unknown error'));
button.prop('disabled', false);
button.text('🔍 <?php esc_html_e( 'Verify Domain Now', 'maplepress' ); ?>');
}
},
error: function(xhr, status, error) {
progressBar.hide();
statusText.html('<span style="color: #d63638;">✗ Network error</span>');
detailsBox.html('<strong>Error:</strong> ' + error);
button.prop('disabled', false);
button.text('🔍 <?php esc_html_e( 'Verify Domain Now', 'maplepress' ); ?>');
}
});
});
});
</script>
<?php if ( isset( $_GET['verification_status'] ) && $_GET['verification_status'] === 'failed' ) : ?>
<div class="notice notice-error inline" style="margin-top: 20px; padding: 15px; background: #fee; border-left: 4px solid #dc3232;">
<h4 style="margin: 0 0 10px 0; color: #dc3232;">
<?php esc_html_e( 'Verification Failed', 'maplepress' ); ?>
</h4>
<p style="margin: 0 0 15px 0; font-size: 14px;">
<strong><?php esc_html_e( 'Error:', 'maplepress' ); ?></strong><br>
<?php
if ( isset( $_GET['verification_message'] ) ) {
echo esc_html( urldecode( $_GET['verification_message'] ) );
} else {
esc_html_e( 'Unknown verification error. Please check your settings and try again.', 'maplepress' );
}
?>
</p>
<?php
$error_message = isset( $_GET['verification_message'] ) ? urldecode( $_GET['verification_message'] ) : '';
// Show different troubleshooting tips based on error type
if ( strpos( $error_message, 'API URL' ) !== false || strpos( $error_message, 'API Key' ) !== false ) :
?>
<div style="background: #fff; padding: 15px; border-radius: 4px; margin-top: 15px;">
<h4 style="margin: 0 0 10px 0;"><?php esc_html_e( 'Configuration Issue:', 'maplepress' ); ?></h4>
<p style="margin: 0;"><?php esc_html_e( 'Please go back and save your API URL and API Key before attempting verification.', 'maplepress' ); ?></p>
</div>
<?php else : ?>
<div style="background: #fff; padding: 15px; border-radius: 4px; margin-top: 15px;">
<h4 style="margin: 0 0 10px 0;"><?php esc_html_e( 'Common Issues:', 'maplepress' ); ?></h4>
<ul style="margin: 0; padding-left: 20px; line-height: 1.8;">
<li><?php esc_html_e( 'DNS record hasn\'t propagated yet (usually takes 5-10 minutes, can take up to 48 hours)', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'TXT record value doesn\'t match exactly (check for typos or extra spaces)', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'TXT record added to wrong domain or subdomain', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Backend API connectivity issue (check if https://getmaplepress.ca is accessible)', 'maplepress' ); ?></li>
</ul>
</div>
<div style="background: #e7f3ff; border-left: 3px solid #2271b1; padding: 15px; margin-top: 15px;">
<p style="margin: 0; font-size: 14px;">
<strong>💡 <?php esc_html_e( 'Tip:', 'maplepress' ); ?></strong>
<?php esc_html_e( 'You can check if your DNS record is visible using online tools like "whatsmydns.net" or "dnschecker.org". Search for TXT records for your domain.', 'maplepress' ); ?>
</p>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</td>
</tr>
<?php endif; ?>
<?php if ( isset( $_GET['verification_status'] ) && $_GET['verification_status'] === 'success' ) : ?>
<tr>
<td colspan="2">
<div class="notice notice-success inline" style="margin: 20px 0; padding: 15px;">
<p style="margin: 0;">
<strong> <?php esc_html_e( 'Domain Verified Successfully!', 'maplepress' ); ?></strong><br>
<?php
if ( isset( $_GET['sync_status'] ) && $_GET['sync_status'] === 'success' && isset( $_GET['synced_count'] ) ) {
printf(
esc_html__( 'Your domain has been verified and %d pages have been synced to MaplePress!', 'maplepress' ),
absint( $_GET['synced_count'] )
);
} else {
esc_html_e( 'Your site is now fully connected to MaplePress. You can now sync pages and use all features.', 'maplepress' );
}
?>
</p>
</div>
</td>
</tr>
<?php endif; ?>
<?php if ( $is_verified ) : ?>
<!-- Connection Status -->
<tr>
<th scope="row"><?php esc_html_e( 'Connection Status', 'maplepress' ); ?></th>
<td>
<span style="color: #46b450; font-weight: bold;">
<?php esc_html_e( 'Connected', 'maplepress' ); ?>
</span>
<p class="description">
<?php esc_html_e( 'Your site is successfully connected to MaplePress.', 'maplepress' ); ?>
</p>
</td>
</tr>
<!-- Billing Information -->
<tr>
<th scope="row"><?php esc_html_e( 'Billing Model', 'maplepress' ); ?></th>
<td>
<strong><?php esc_html_e( 'Usage-Based', 'maplepress' ); ?></strong>
<p class="description">
<?php esc_html_e( 'Pay only for what you use with no limits or quotas.', 'maplepress' ); ?>
<a href="https://getmaplepress.com/pricing" target="_blank">
<?php esc_html_e( 'View pricing details', 'maplepress' ); ?>
</a>
</p>
</td>
</tr>
<?php endif; ?>
<!-- Enable MaplePress -->
<tr>
<th scope="row"><?php esc_html_e( 'Enable Services', 'maplepress' ); ?></th>
<td>
<fieldset>
<label>
<input type="checkbox"
name="maplepress_settings[enabled]"
value="1"
<?php checked( 1, $options['enabled'] ?? false ); ?>>
<strong><?php esc_html_e( 'Enable MaplePress', 'maplepress' ); ?></strong>
</label>
<p class="description">
<?php esc_html_e( 'Turn on MaplePress cloud services for this site.', 'maplepress' ); ?>
</p>
</fieldset>
</td>
</tr>
<!-- Search Settings -->
<tr>
<th scope="row"><?php esc_html_e( 'Search Settings', 'maplepress' ); ?></th>
<td>
<fieldset>
<label>
<input type="checkbox"
name="maplepress_settings[enable_frontend_search]"
value="1"
<?php checked( 1, $options['enable_frontend_search'] ?? true ); ?>>
<?php esc_html_e( 'Enable frontend search', 'maplepress' ); ?>
</label>
<br>
<label style="margin-top: 8px; display: inline-block;">
<input type="checkbox"
name="maplepress_settings[enable_admin_search]"
value="1"
<?php checked( 1, $options['enable_admin_search'] ?? false ); ?>>
<?php esc_html_e( 'Enable admin search', 'maplepress' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'Choose where MaplePress search should be used.', 'maplepress' ); ?>
</p>
</fieldset>
</td>
</tr>
</table>
<?php if ( $is_verified ) : ?>
<!-- Show both Save and Disconnect buttons when connected -->
<p class="submit">
<?php submit_button( __( 'Save Settings', 'maplepress' ), 'primary', 'submit', false ); ?>
&nbsp;
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=maplepress-settings&action=disconnect' ), 'maplepress_disconnect' ) ); ?>"
class="button button-secondary"
onclick="return confirm('<?php esc_attr_e( 'Are you sure you want to disconnect from MaplePress? Your data will remain in the cloud.', 'maplepress' ); ?>');">
<?php esc_html_e( 'Disconnect', 'maplepress' ); ?>
</a>
</p>
<?php else : ?>
<!-- Show only Connect button when not connected -->
<?php submit_button( __( 'Connect to MaplePress', 'maplepress' ), 'primary', 'submit', true ); ?>
<?php endif; ?>
</form>
<!-- Reset Settings -->
<hr style="margin: 40px 0 20px 0;">
<h3><?php esc_html_e( 'Reset Settings', 'maplepress' ); ?></h3>
<p><?php esc_html_e( 'If you need to start over, you can reset all MaplePress settings.', 'maplepress' ); ?></p>
<form method="post" action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>" style="margin-top: 20px;">
<input type="hidden" name="action" value="maplepress_reset_settings">
<?php wp_nonce_field( 'maplepress_reset_settings' ); ?>
<button type="submit"
class="button button-secondary"
style="color: #d63638; border-color: #d63638;"
onclick="return confirm('<?php esc_attr_e( 'Are you sure you want to reset all MaplePress settings?\n\nThis will:\n- Delete API URL and API Key\n- Clear verification status\n- Remove all configuration\n\nYour data in the MaplePress cloud will NOT be affected.\n\nYou will need to reconnect after reset.', 'maplepress' ); ?>');">
🔄 <?php esc_html_e( 'Reset All Settings', 'maplepress' ); ?>
</button>
<p class="description">
<?php esc_html_e( 'This will delete all plugin settings from WordPress. Your data in the MaplePress cloud will remain safe.', 'maplepress' ); ?>
</p>
</form>
<?php if ( $is_verified ) : ?>
<!-- Show connection info when connected -->
<hr style="margin: 40px 0;">
<h2><?php esc_html_e( 'Connection Details', 'maplepress' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Domain', 'maplepress' ); ?></th>
<td><?php echo esc_html( $options['domain'] ?? 'N/A' ); ?></td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Connected Since', 'maplepress' ); ?></th>
<td>
<?php
// Assuming connection doesn't change often, we can use current time as reference
// In a real scenario, you'd store a 'connected_at' timestamp
esc_html_e( 'Active connection', 'maplepress' );
?>
</td>
</tr>
</table>
<?php endif; ?>
</div>

View file

@ -0,0 +1,243 @@
<?php
/**
* Simple Admin Speed Test Page
*
* @package MaplePress_SearchSpeedTest
*/
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have permission to access this page.', 'maplepress-searchspeedtest' ) );
}
?>
<div class="wrap mpss-wrap">
<!-- Header -->
<div style="margin: 30px 0 40px 0;">
<h1 style="font-size: 32px; font-weight: 300; margin: 0 0 10px 0; color: #1e1e1e;">
<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-weight: 600;">
<?php esc_html_e( 'How Fast is Your Search?', 'maplepress-searchspeedtest' ); ?>
</span>
</h1>
<p style="font-size: 16px; color: #666; margin: 0;">
<?php esc_html_e( 'Find out how quickly visitors can search your website', 'maplepress-searchspeedtest' ); ?>
</p>
</div>
<!-- Test Configuration -->
<div class="mpss-test-config">
<div class="mpss-card" style="max-width: 900px; background: linear-gradient(to bottom, #ffffff 0%, #f8f9fa 100%); border: none; box-shadow: 0 2px 20px rgba(0, 0, 0, 0.08); border-radius: 16px; padding: 40px;">
<!-- Number of Queries Section -->
<div style="margin-bottom: 35px;">
<label for="mpss-query-count" style="display: block; font-size: 15px; font-weight: 600; margin-bottom: 8px; color: #1e1e1e;">
<span style="display: inline-block; width: 32px; height: 32px; line-height: 32px; text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 50%; margin-right: 10px; font-size: 14px;">1</span>
<?php esc_html_e( 'How thorough should the test be?', 'maplepress-searchspeedtest' ); ?>
</label>
<p style="font-size: 13px; color: #666; margin: 0 0 15px 42px; line-height: 1.5;">
<?php esc_html_e( 'More searches = more accurate results, but takes longer to complete', 'maplepress-searchspeedtest' ); ?>
</p>
<div style="background: white; padding: 20px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);">
<select id="mpss-query-count" name="query_count" style="width: 100%; padding: 12px 16px; font-size: 15px; border: 2px solid #e0e0e0; border-radius: 8px; background: white; cursor: pointer; transition: all 0.2s;">
<option value="10" selected>10 searches - Super quick (recommended to start) </option>
<option value="50">50 searches - Quick and accurate</option>
<option value="100">100 searches - Balanced (good choice)</option>
<option value="250">250 searches - Very thorough</option>
<option value="500">500 searches - Heavy load test</option>
<option value="1000">1,000 searches - Serious stress test</option>
<option value="2500">2,500 searches - Extreme testing</option>
<option value="5000">5,000 searches - Maximum stress 🔥</option>
<option value="10000">10,000 searches - Ultimate challenge 💪</option>
</select>
</div>
</div>
<!-- Execution Mode Section -->
<div style="margin-bottom: 35px;">
<label style="display: block; font-size: 15px; font-weight: 600; margin-bottom: 8px; color: #1e1e1e;">
<span style="display: inline-block; width: 32px; height: 32px; line-height: 32px; text-align: center; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; border-radius: 50%; margin-right: 10px; font-size: 14px;">2</span>
<?php esc_html_e( 'How should we test?', 'maplepress-searchspeedtest' ); ?>
</label>
<p style="font-size: 13px; color: #666; margin: 0 0 15px 42px; line-height: 1.5;">
<?php esc_html_e( 'Choose how the test simulates visitors searching your site', 'maplepress-searchspeedtest' ); ?>
</p>
<!-- Normal Mode Card -->
<label class="mpss-mode-card" for="mpss-mode-serial" style="display: block; margin-bottom: 15px; cursor: pointer;">
<input type="radio" name="execution_mode" id="mpss-mode-serial" value="serial" checked style="display: none;">
<div class="mpss-mode-card-inner" style="background: white; padding: 20px; border-radius: 12px; border: 2px solid #e0e0e0; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); transition: all 0.3s;">
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<span style="font-size: 24px; margin-right: 12px;">👤</span>
<div>
<div style="font-size: 16px; font-weight: 600; color: #1e1e1e;">
<?php esc_html_e( 'Normal - One Person at a Time', 'maplepress-searchspeedtest' ); ?>
<span class="mpss-mode-badge" style="display: inline-block; margin-left: 8px; padding: 2px 8px; background: #e8f5e9; color: #2e7d32; font-size: 11px; font-weight: 600; border-radius: 4px; text-transform: uppercase;">Start Here</span>
</div>
<div style="font-size: 13px; color: #666; margin-top: 4px;">
<?php esc_html_e( 'Simulates one visitor searching at a time', 'maplepress-searchspeedtest' ); ?>
</div>
</div>
</div>
<div id="mpss-serial-explanation" style="margin-top: 12px; padding: 12px; background: linear-gradient(to right, rgba(102, 126, 234, 0.05), rgba(118, 75, 162, 0.05)); border-left: 3px solid #667eea; border-radius: 6px; font-size: 13px; color: #666; line-height: 1.6;">
💡 <?php esc_html_e( 'Best for everyday testing. Shows how fast searches are under normal conditions.', 'maplepress-searchspeedtest' ); ?>
</div>
</div>
</label>
<!-- Stress Test Mode Card -->
<label class="mpss-mode-card" for="mpss-mode-parallel" style="display: block; cursor: pointer;">
<input type="radio" name="execution_mode" id="mpss-mode-parallel" value="parallel" style="display: none;">
<div class="mpss-mode-card-inner" style="background: white; padding: 20px; border-radius: 12px; border: 2px solid #e0e0e0; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); transition: all 0.3s;">
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<span style="font-size: 24px; margin-right: 12px;">👥</span>
<div>
<div style="font-size: 16px; font-weight: 600; color: #1e1e1e;">
<?php esc_html_e( 'Stress Test - Many People at Once', 'maplepress-searchspeedtest' ); ?>
<span class="mpss-mode-badge" style="display: inline-block; margin-left: 8px; padding: 2px 8px; background: #fff3e0; color: #e65100; font-size: 11px; font-weight: 600; border-radius: 4px; text-transform: uppercase;">Advanced</span>
</div>
<div style="font-size: 13px; color: #666; margin-top: 4px;">
<?php esc_html_e( 'Simulates many visitors searching at the same time', 'maplepress-searchspeedtest' ); ?>
</div>
</div>
</div>
<div id="mpss-parallel-explanation" style="margin-top: 12px; padding: 12px; background: linear-gradient(to right, rgba(255, 152, 0, 0.05), rgba(255, 87, 34, 0.05)); border-left: 3px solid #ff9800; border-radius: 6px; font-size: 13px; color: #e65100; line-height: 1.6;">
⚠️ <?php esc_html_e( 'Tests your site under heavy load. May temporarily slow down your website.', 'maplepress-searchspeedtest' ); ?>
</div>
</div>
</label>
</div>
<!-- Run Button -->
<div style="text-align: center; margin-top: 40px;">
<button id="mpss-run-test" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 18px 56px; font-size: 17px; font-weight: 600; border-radius: 50px; cursor: pointer; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); transition: all 0.3s; display: inline-flex; align-items: center; gap: 10px;">
<span style="font-size: 22px;">🚀</span>
<?php esc_html_e( 'Start Test Now', 'maplepress-searchspeedtest' ); ?>
</button>
<p style="font-size: 12px; color: #999; margin-top: 15px;">
<?php esc_html_e( 'This won\'t affect your live website or visitors', 'maplepress-searchspeedtest' ); ?>
</p>
</div>
</div>
</div>
<!-- Progress -->
<div id="mpss-progress" class="mpss-progress" style="display: none;">
<div class="mpss-card" style="max-width: 900px; background: linear-gradient(to bottom, #ffffff 0%, #f8f9fa 100%); border: none; box-shadow: 0 2px 20px rgba(0, 0, 0, 0.08); border-radius: 16px; padding: 40px;">
<div style="text-align: center;">
<div style="font-size: 48px; margin-bottom: 20px;"></div>
<h2 style="font-size: 24px; font-weight: 600; margin: 0 0 15px 0;">
<?php esc_html_e( 'Testing Your Search Speed...', 'maplepress-searchspeedtest' ); ?>
</h2>
<p id="mpss-progress-text" style="font-size: 15px; color: #666; margin-bottom: 10px;">
<?php esc_html_e( 'Please wait, this will only take a moment...', 'maplepress-searchspeedtest' ); ?>
</p>
<div id="mpss-progress-counter" style="font-size: 32px; font-weight: bold; color: #667eea; margin: 15px 0;">
<span id="mpss-completed-count">0</span> / <span id="mpss-total-count">0</span>
</div>
<p style="font-size: 13px; color: #999; margin-bottom: 5px;">searches completed</p>
<p id="mpss-time-estimate" style="font-size: 13px; color: #667eea; font-weight: 600; margin-bottom: 25px;">
<?php esc_html_e( 'Estimated time: calculating...', 'maplepress-searchspeedtest' ); ?>
</p>
</div>
<div class="mpss-progress-bar">
<div id="mpss-progress-fill" class="mpss-progress-fill" style="width: 0%;"></div>
</div>
<div style="text-align: center; margin-top: 20px;">
<button id="mpss-cancel-test" style="background: transparent; color: #dc3232; border: 2px solid #dc3232; padding: 10px 24px; font-size: 14px; font-weight: 600; border-radius: 50px; cursor: pointer; transition: all 0.3s;">
<span style="font-size: 16px;"></span> <?php esc_html_e( 'Cancel Test', 'maplepress-searchspeedtest' ); ?>
</button>
</div>
</div>
</div>
<!-- Results -->
<div id="mpss-results" class="mpss-results" style="display: none;">
<div class="mpss-card" style="max-width: 900px; background: linear-gradient(to bottom, #ffffff 0%, #f8f9fa 100%); border: none; box-shadow: 0 2px 20px rgba(0, 0, 0, 0.08); border-radius: 16px; padding: 40px;">
<div style="text-align: center; margin-bottom: 30px;">
<div style="font-size: 64px; margin-bottom: 15px;"></div>
<h2 style="font-size: 28px; font-weight: 600; margin: 0 0 10px 0;">
<?php esc_html_e( 'Your Results Are Ready!', 'maplepress-searchspeedtest' ); ?>
</h2>
<p style="font-size: 15px; color: #666; margin: 0;">
<?php esc_html_e( 'Here\'s how your search performed', 'maplepress-searchspeedtest' ); ?>
</p>
</div>
<!-- Summary Stats -->
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 30px; margin: 30px auto; max-width: 800px;">
<div style="text-align: center; padding: 30px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);">
<div style="font-size: 48px; font-weight: bold; color: #fff; margin-bottom: 10px;" id="mpss-total-queries">-</div>
<div style="font-size: 14px; color: rgba(255, 255, 255, 0.9); text-transform: uppercase; letter-spacing: 1px; font-weight: 600;"><?php esc_html_e( 'Searches Tested', 'maplepress-searchspeedtest' ); ?></div>
</div>
<div style="text-align: center; padding: 30px 20px; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: 12px; box-shadow: 0 4px 15px rgba(245, 87, 108, 0.4);">
<div style="font-size: 48px; font-weight: bold; color: #fff; margin-bottom: 10px;" id="mpss-total-time">-</div>
<div style="font-size: 14px; color: rgba(255, 255, 255, 0.9); text-transform: uppercase; letter-spacing: 1px; font-weight: 600;"><?php esc_html_e( 'Time Taken', 'maplepress-searchspeedtest' ); ?></div>
</div>
</div>
<!-- Quick Recommendations -->
<div id="mpss-recommendations" style="margin: 30px 0;"></div>
<!-- Chart -->
<div style="margin: 40px 0;">
<h3 style="font-size: 18px; font-weight: 600; margin: 0 0 20px 0; color: #1e1e1e;">
📊 <?php esc_html_e( 'Individual Search Times', 'maplepress-searchspeedtest' ); ?>
</h3>
<canvas id="mpss-chart"></canvas>
</div>
<!-- Actions -->
<div style="display: flex; gap: 15px; justify-content: center; margin-top: 40px; padding-top: 30px; border-top: 1px solid #e0e0e0;">
<button id="mpss-run-again" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 12px 32px; font-size: 15px; font-weight: 600; border-radius: 50px; cursor: pointer; box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3); transition: all 0.3s; display: inline-flex; align-items: center; gap: 8px;">
<span style="font-size: 16px;">🔄</span>
<?php esc_html_e( 'Test Again', 'maplepress-searchspeedtest' ); ?>
</button>
<button id="mpss-share-results" style="background: #3b5998; color: white; border: none; padding: 12px 32px; font-size: 15px; font-weight: 600; border-radius: 50px; cursor: pointer; box-shadow: 0 2px 10px rgba(59, 89, 152, 0.3); transition: all 0.3s; display: inline-flex; align-items: center; gap: 8px;">
<span style="font-size: 16px;">📤</span>
<?php esc_html_e( 'Share Results', 'maplepress-searchspeedtest' ); ?>
</button>
<button id="mpss-export-results" style="background: white; color: #667eea; border: 2px solid #667eea; padding: 12px 32px; font-size: 15px; font-weight: 600; border-radius: 50px; cursor: pointer; transition: all 0.3s; display: inline-flex; align-items: center; gap: 8px;">
<span style="font-size: 16px;">📥</span>
<?php esc_html_e( 'Download Results', 'maplepress-searchspeedtest' ); ?>
</button>
</div>
</div>
</div>
<!-- Share Modal -->
<div id="mpss-share-modal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); z-index: 9999; align-items: center; justify-content: center;">
<div style="background: white; padding: 40px; border-radius: 20px; max-width: 600px; width: 90%; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); position: relative;">
<button id="mpss-close-share-modal" style="position: absolute; top: 15px; right: 15px; background: none; border: none; font-size: 24px; cursor: pointer; color: #999; transition: color 0.2s;" onmouseover="this.style.color='#333'" onmouseout="this.style.color='#999'"></button>
<h2 style="margin: 0 0 25px 0; font-size: 24px; text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">
📤 Share Your Results
</h2>
<div id="mpss-share-content" style="margin-bottom: 30px; text-align: center;">
<div id="mpss-share-graphic" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px; border-radius: 16px; color: white; margin-bottom: 20px;">
<!-- Share graphic will be generated here -->
</div>
<p style="color: #666; font-size: 13px;">Click a button below to share your results</p>
</div>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px;">
<button class="mpss-share-twitter" style="background: #1DA1F2; color: white; border: none; padding: 15px; font-size: 15px; font-weight: 600; border-radius: 12px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 15px rgba(29, 161, 242, 0.4)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'">
<span style="font-size: 18px;">🐦</span>
Twitter / X
</button>
<button class="mpss-share-facebook" style="background: #1877F2; color: white; border: none; padding: 15px; font-size: 15px; font-weight: 600; border-radius: 12px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 15px rgba(24, 119, 242, 0.4)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'">
<span style="font-size: 18px;">👥</span>
Facebook
</button>
<button class="mpss-share-linkedin" style="background: #0A66C2; color: white; border: none; padding: 15px; font-size: 15px; font-weight: 600; border-radius: 12px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 15px rgba(10, 102, 194, 0.4)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'">
<span style="font-size: 18px;">💼</span>
LinkedIn
</button>
<button class="mpss-share-copy" style="background: #10b981; color: white; border: none; padding: 15px; font-size: 15px; font-weight: 600; border-radius: 12px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 15px rgba(16, 185, 129, 0.4)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'">
<span style="font-size: 18px;">📋</span>
Copy Link
</button>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,152 @@
<?php
/**
* Admin System Information Page
*
* @package MaplePress
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Load system info class
require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-system-info.php';
// Get system information
$system_info = MaplePress_System_Info::get_all_info();
?>
<div class="wrap">
<h1><?php esc_html_e( 'System Information', 'maplepress' ); ?></h1>
<div class="notice notice-info" style="padding: 15px; margin: 20px 0;">
<h2 style="margin-top: 0;">
📊 <?php esc_html_e( 'System Diagnostics', 'maplepress' ); ?>
</h2>
<p>
<?php esc_html_e( 'This page shows detailed information about your server configuration, PHP-FPM workers, and system resources. Use this data to diagnose performance bottlenecks.', 'maplepress' ); ?>
</p>
<p>
<strong><?php esc_html_e( 'Key Metrics to Check:', 'maplepress' ); ?></strong>
</p>
<ul style="margin-left: 20px;">
<li><?php esc_html_e( 'PHP-FPM Max Children: Should be ≥50 for 4GB servers to handle concurrent requests', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'OpCache Status: Should be enabled for better PHP performance', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Memory Limits: PHP memory_limit should be 256M+', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Load Average: Should be below CPU count for healthy performance', 'maplepress' ); ?></li>
<li><?php esc_html_e( 'Database Connections: max_connections should be 200+ for high traffic', 'maplepress' ); ?></li>
</ul>
</div>
<?php
// Check for critical issues
$warnings = array();
// Check PHP-FPM max_children
if ( isset( $system_info['php_fpm']['max_children'] ) && is_numeric( $system_info['php_fpm']['max_children'] ) ) {
if ( $system_info['php_fpm']['max_children'] < 25 ) {
$warnings[] = sprintf(
__( '⚠️ PHP-FPM max_children is %d. Recommended: 50+ for 4GB servers. This is the #1 WordPress performance bottleneck.', 'maplepress' ),
$system_info['php_fpm']['max_children']
);
}
}
// Check OpCache
if ( ! $system_info['php']['opcache_enabled'] ) {
$warnings[] = __( '⚠️ OpCache is not enabled. Enable it for 30-50% faster PHP execution.', 'maplepress' );
}
// Check memory limit
$memory_limit = ini_get( 'memory_limit' );
if ( preg_match( '/^(\d+)/', $memory_limit, $matches ) ) {
if ( (int) $matches[1] < 128 ) {
$warnings[] = sprintf(
__( '⚠️ PHP memory_limit is %s. Recommended: 256M or higher.', 'maplepress' ),
$memory_limit
);
}
}
// Display warnings
if ( ! empty( $warnings ) ) :
?>
<div class="notice notice-warning" style="padding: 15px; margin: 20px 0; border-left-color: #ffc107;">
<h3 style="margin-top: 0; color: #856404;">
⚠️ <?php esc_html_e( 'Performance Warnings', 'maplepress' ); ?>
</h3>
<ul style="margin-left: 20px; color: #856404;">
<?php foreach ( $warnings as $warning ) : ?>
<li><?php echo esc_html( $warning ); ?></li>
<?php endforeach; ?>
</ul>
<p style="margin-bottom: 0;">
<a href="<?php echo esc_url( MAPLEPRESS_PLUGIN_URL . 'docs/PERFORMANCE_OPTIMIZATION.md' ); ?>"
class="button button-primary"
target="_blank"
style="background: #856404; border-color: #856404;">
📖 <?php esc_html_e( 'Read Performance Optimization Guide', 'maplepress' ); ?>
</a>
</p>
</div>
<?php else : ?>
<div class="notice notice-success" style="padding: 15px; margin: 20px 0;">
<p style="margin: 0;">
<strong> <?php esc_html_e( 'System configuration looks good!', 'maplepress' ); ?></strong>
</p>
</div>
<?php endif; ?>
<!-- System Information Tables -->
<?php echo MaplePress_System_Info::format_as_html( $system_info ); ?>
<!-- Export Button -->
<div style="margin-top: 30px;">
<button id="copy-system-info" class="button button-primary">
📋 <?php esc_html_e( 'Copy All Info to Clipboard', 'maplepress' ); ?>
</button>
<button id="download-system-info" class="button">
💾 <?php esc_html_e( 'Download as JSON', 'maplepress' ); ?>
</button>
</div>
<script>
jQuery(document).ready(function($) {
// Copy to clipboard
$('#copy-system-info').on('click', function() {
var systemInfo = <?php echo wp_json_encode( $system_info ); ?>;
var text = JSON.stringify(systemInfo, null, 2);
navigator.clipboard.writeText(text).then(function() {
alert('<?php esc_html_e( 'System information copied to clipboard!', 'maplepress' ); ?>');
}).catch(function() {
// Fallback for older browsers
var textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('<?php esc_html_e( 'System information copied to clipboard!', 'maplepress' ); ?>');
});
});
// Download as JSON
$('#download-system-info').on('click', function() {
var systemInfo = <?php echo wp_json_encode( $system_info ); ?>;
var dataStr = JSON.stringify(systemInfo, null, 2);
var dataBlob = new Blob([dataStr], {type: 'application/json'});
var url = URL.createObjectURL(dataBlob);
var link = document.createElement('a');
link.href = url;
link.download = 'maplepress-system-info-' + Date.now() + '.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
});
});
</script>
</div>

View file

@ -0,0 +1,41 @@
<?php
/**
* Fired during plugin activation.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
class MaplePress_Activator {
/**
* Activation tasks.
*
* Creates default options and flushes rewrite rules.
*/
public static function activate() {
// Set default options.
// Allow developers to override API URL via wp-config.php constant
$default_api_url = defined( 'MAPLEPRESS_API_URL' ) ? MAPLEPRESS_API_URL : '';
$default_options = array(
'api_url' => $default_api_url, // Empty by default - user must configure
'api_key' => '',
'site_id' => '',
'tenant_id' => '',
'domain' => '',
'plan_tier' => '',
'is_verified' => false,
'enabled' => false,
'needs_setup' => true, // Flag to show setup notice
);
add_option( 'maplepress_settings', $default_options );
// Set activation redirect flag
set_transient( 'maplepress_activation_redirect', true, 30 );
// Flush rewrite rules.
flush_rewrite_rules();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,226 @@
<?php
/**
* MaplePress API Client - handles communication with MaplePress backend.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
class MaplePress_API_Client {
/**
* API base URL.
*
* @var string
*/
private $api_url;
/**
* API key.
*
* @var string
*/
private $api_key;
/**
* Constructor.
*
* @param string $api_url API base URL.
* @param string $api_key API key.
*/
public function __construct( $api_url, $api_key ) {
$this->api_url = trailingslashit( $api_url );
$this->api_key = $api_key;
}
/**
* Verify API connection and API key.
*
* @return array|WP_Error Response data or WP_Error on failure.
*/
public function verify_connection() {
return $this->request( 'GET', 'api/v1/plugin/status' );
}
/**
* Trigger domain verification via DNS TXT record check.
*
* @return array|WP_Error Response data or WP_Error on failure.
*/
public function verify_domain() {
error_log( 'MaplePress API: verify_domain() called' );
$result = $this->request( 'POST', 'api/v1/plugin/verify' );
error_log( 'MaplePress API: verify_domain() result = ' . print_r( $result, true ) );
return $result;
}
/**
* Send a request to the MaplePress API.
*
* @param string $method HTTP method (GET, POST, PUT, DELETE).
* @param string $endpoint API endpoint (relative to base URL).
* @param array $body Optional. Request body data.
* @return array|WP_Error Response data or WP_Error on failure.
*/
private function request( $method, $endpoint, $body = array() ) {
$url = $this->api_url . ltrim( $endpoint, '/' );
// Debug logging (only enable when WP_DEBUG is true)
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( "MaplePress API: Making $method request to: $url" );
}
$args = array(
'method' => $method,
'headers' => array(
'Authorization' => 'Bearer ' . $this->api_key,
'Content-Type' => 'application/json',
),
'timeout' => 15,
);
if ( ! empty( $body ) && in_array( $method, array( 'POST', 'PUT', 'PATCH' ), true ) ) {
$args['body'] = wp_json_encode( $body );
}
$response = wp_remote_request( $url, $args );
if ( is_wp_error( $response ) ) {
return $response;
}
$status_code = wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( $status_code >= 400 ) {
$error_message = isset( $data['message'] ) ? $data['message'] : __( 'Unknown API error', 'maplepress' );
// Log full response for debugging
error_log( 'MaplePress API Error Response:' );
error_log( 'Status Code: ' . $status_code );
error_log( 'Raw Body: ' . $body );
error_log( 'Parsed Data: ' . print_r( $data, true ) );
// Determine error code based on status
$error_code = 'maplepress_api_error';
if ( $status_code === 429 ) {
$error_code = 'maplepress_rate_limited';
} elseif ( $status_code === 403 ) {
$error_code = 'maplepress_forbidden';
} elseif ( $status_code === 404 ) {
$error_code = 'maplepress_not_found';
}
return new WP_Error(
$error_code,
sprintf(
/* translators: 1: HTTP status code, 2: error message */
__( 'API Error (%1$d): %2$s', 'maplepress' ),
$status_code,
$error_message
),
array( 'status' => $status_code )
);
}
return $data;
}
/**
* Sync pages to MaplePress backend.
*
* @param array $pages Array of page data to sync.
* @return array|WP_Error Response data or WP_Error on failure.
*/
public function sync_pages( $pages ) {
$data = array(
'pages' => $pages,
);
return $this->request( 'POST', 'api/v1/plugin/pages/sync', $data );
}
/**
* Index a post in MaplePress (single page).
*
* @param int $post_id Post ID.
* @return array|WP_Error Response data or WP_Error on failure.
*/
public function index_post( $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_Error( 'invalid_post', __( 'Post not found', 'maplepress' ) );
}
// Format page data for API
$page_data = $this->format_page_data( $post );
// Sync single page
return $this->sync_pages( array( $page_data ) );
}
/**
* Format post/page data for API sync.
*
* @param WP_Post $post Post object.
* @return array Formatted page data.
*/
private function format_page_data( $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' ) );
}
return 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,
);
}
/**
* Remove a post from MaplePress index.
*
* @param int $post_id Post ID.
* @return array|WP_Error Response data or WP_Error on failure.
*/
public function delete_post( $post_id ) {
return $this->request( 'DELETE', 'api/v1/plugin/index/' . $post_id );
}
/**
* Search using MaplePress API.
*
* @param string $query Search query.
* @param array $args Optional. Additional search parameters.
* @return array|WP_Error Search results or WP_Error on failure.
*/
public function search( $query, $args = array() ) {
$params = array_merge(
array(
'query' => $query,
'limit' => 10,
'offset' => 0,
),
$args
);
return $this->request( 'POST', 'api/v1/plugin/pages/search', $params );
}
}

View file

@ -0,0 +1,20 @@
<?php
/**
* Fired during plugin deactivation.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
class MaplePress_Deactivator {
/**
* Deactivation tasks.
*
* Flushes rewrite rules.
*/
public static function deactivate() {
// Flush rewrite rules.
flush_rewrite_rules();
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
* Register all actions and filters for the plugin.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
class MaplePress_Loader {
/**
* The array of actions registered with WordPress.
*
* @var array
*/
protected $actions;
/**
* The array of filters registered with WordPress.
*
* @var array
*/
protected $filters;
/**
* Initialize the collections used to maintain the actions and filters.
*/
public function __construct() {
$this->actions = array();
$this->filters = array();
}
/**
* Add a new action to the collection to be registered with WordPress.
*
* @param string $hook The name of the WordPress action.
* @param object $component A reference to the instance of the object.
* @param string $callback The name of the function definition on the $component.
* @param int $priority Optional. The priority at which the function should be fired. Default is 10.
* @param int $accepted_args Optional. The number of arguments that should be passed to the $callback. Default is 1.
*/
public function add_action( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
$this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args );
}
/**
* Add a new filter to the collection to be registered with WordPress.
*
* @param string $hook The name of the WordPress filter.
* @param object $component A reference to the instance of the object.
* @param string $callback The name of the function definition on the $component.
* @param int $priority Optional. The priority at which the function should be fired. Default is 10.
* @param int $accepted_args Optional. The number of arguments that should be passed to the $callback. Default is 1.
*/
public function add_filter( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
$this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args );
}
/**
* A utility function that is used to register the actions and hooks into a single collection.
*
* @param array $hooks The collection of hooks that is being registered.
* @param string $hook The name of the WordPress filter/action.
* @param object $component A reference to the instance of the object.
* @param string $callback The name of the function definition on the $component.
* @param int $priority The priority at which the function should be fired.
* @param int $accepted_args The number of arguments that should be passed to the $callback.
* @return array
*/
private function add( $hooks, $hook, $component, $callback, $priority, $accepted_args ) {
$hooks[] = array(
'hook' => $hook,
'component' => $component,
'callback' => $callback,
'priority' => $priority,
'accepted_args' => $accepted_args,
);
return $hooks;
}
/**
* Register the filters and actions with WordPress.
*/
public function run() {
foreach ( $this->filters as $hook ) {
add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
}
foreach ( $this->actions as $hook ) {
add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
}
}
}

View file

@ -0,0 +1,252 @@
<?php
/**
* The public-facing functionality of the plugin.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
class MaplePress_Public {
/**
* The ID of this plugin.
*
* @var string
*/
private $plugin_name;
/**
* The version of this plugin.
*
* @var string
*/
private $version;
/**
* Initialize the class and set its properties.
*
* @param string $plugin_name The name of the plugin.
* @param string $version The version of this plugin.
*/
public function __construct( $plugin_name, $version ) {
$this->plugin_name = $plugin_name;
$this->version = $version;
}
/**
* Register the stylesheets for the public-facing side of the site.
*/
public function enqueue_styles() {
wp_enqueue_style(
$this->plugin_name,
MAPLEPRESS_PLUGIN_URL . 'assets/css/maplepress-public.css',
array(),
$this->version,
'all'
);
}
/**
* Register the JavaScript for the public-facing side of the site.
*/
public function enqueue_scripts() {
wp_enqueue_script(
$this->plugin_name,
MAPLEPRESS_PLUGIN_URL . 'assets/js/maplepress-public.js',
array( 'jquery' ),
$this->version,
false
);
}
/**
* Intercept WordPress search and use MaplePress search instead.
*
* @param WP_Query $query The WordPress query object.
*/
public function intercept_search( $query ) {
// Debug logging - detect if this is from CLI test
$is_cli_test = isset( $_SERVER['HTTP_X_MAPLEPRESS_TEST'] );
// Log every invocation when WP_DEBUG is on or when it's a CLI test
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( '=== MaplePress intercept_search called ===' );
error_log( 'is_main_query: ' . ( $query->is_main_query() ? 'true' : 'false' ) );
error_log( 'is_search: ' . ( $query->is_search() ? 'true' : 'false' ) );
error_log( 's param: ' . var_export( $query->get( 's' ), true ) );
error_log( 'CLI test header: ' . ( $is_cli_test ? 'true' : 'false' ) );
}
// Only intercept main search queries
// Check both is_search() and presence of 's' parameter to catch all search scenarios
$is_search_query = $query->is_search() || ! empty( $query->get( 's' ) );
if ( ! $query->is_main_query() || ! $is_search_query ) {
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: Early return - not main query or not search' );
}
return;
}
// Check if MaplePress is enabled
$options = get_option( 'maplepress_settings' );
if ( empty( $options['enabled'] ) || empty( $options['api_url'] ) || empty( $options['api_key'] ) ) {
// MaplePress not enabled or configured, use default WordPress search
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: Early return - not enabled or not configured' );
error_log( 'enabled: ' . var_export( $options['enabled'] ?? null, true ) );
error_log( 'api_url: ' . var_export( $options['api_url'] ?? null, true ) );
error_log( 'api_key present: ' . ( ! empty( $options['api_key'] ) ? 'yes' : 'no' ) );
}
return;
}
// Check if connection is verified
if ( empty( $options['is_verified'] ) ) {
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: Early return - connection not verified' );
}
return;
}
// Check if search is enabled for the current context
// Speed test queries always proceed regardless of admin/frontend settings
if ( ! $is_speed_test ) {
$is_admin_area = is_admin();
$frontend_search_enabled = isset( $options['enable_frontend_search'] ) ? $options['enable_frontend_search'] : true;
$admin_search_enabled = isset( $options['enable_admin_search'] ) ? $options['enable_admin_search'] : false;
// Skip if admin search is not enabled and we're in admin
if ( $is_admin_area && ! $admin_search_enabled ) {
// In admin but admin search not enabled
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: Early return - admin search not enabled' );
}
return;
}
// Skip if frontend search is not enabled and we're on frontend
if ( ! $is_admin_area && ! $frontend_search_enabled ) {
// On frontend but frontend search not enabled
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: Early return - frontend search not enabled' );
}
return;
}
} else {
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: Speed test query - bypassing admin/frontend check' );
}
}
// Get the search query
// Try get_search_query() first, then fall back to direct query parameter
$search_query = get_search_query();
if ( empty( $search_query ) ) {
$search_query = $query->get( 's' );
}
if ( empty( $search_query ) ) {
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: Early return - empty search query' );
}
return;
}
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: Proceeding with search for: ' . $search_query );
}
// Call MaplePress search API
require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php';
$api_client = new MaplePress_API_Client( $options['api_url'], $options['api_key'] );
$search_args = array(
'limit' => (int) $query->get( 'posts_per_page', 10 ),
'offset' => (int) ( ( $query->get( 'paged', 1 ) - 1 ) * $query->get( 'posts_per_page', 10 ) ),
);
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: Calling API with query: ' . $search_query );
error_log( 'MaplePress: API URL: ' . $options['api_url'] );
error_log( 'MaplePress: Search args: ' . json_encode( $search_args ) );
}
$result = $api_client->search( $search_query, $search_args );
if ( is_wp_error( $result ) ) {
// API error - fall back to default WordPress search
// Log the error for debugging
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( 'MaplePress search error: ' . $result->get_error_message() );
}
if ( $is_cli_test ) {
error_log( 'MaplePress search error (CLI test): ' . $result->get_error_message() );
}
return;
}
if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || $is_cli_test ) {
error_log( 'MaplePress: API call successful, results: ' . json_encode( $result ) );
}
// Extract page IDs from MaplePress results
$page_ids = array();
if ( ! empty( $result['hits'] ) ) {
foreach ( $result['hits'] as $result_item ) {
// Meilisearch returns 'id' field, not 'page_id'
if ( ! empty( $result_item['id'] ) ) {
$page_ids[] = (int) $result_item['id'];
}
}
}
// If no results, show empty results
if ( empty( $page_ids ) ) {
$query->set( 'post__in', array( 0 ) ); // Force no results
} else {
// Set query to only return posts matching MaplePress results in the correct order
$query->set( 'post__in', $page_ids );
$query->set( 'orderby', 'post__in' ); // Maintain MaplePress result order
// In admin context, respect the existing post_type filter (e.g., 'page' when in Pages list)
// On frontend, allow any post type
if ( ! is_admin() ) {
$query->set( 'post_type', 'any' ); // Allow any post type on frontend
}
// Note: In admin, we preserve whatever post_type was already set by WordPress
$query->set( 'post_status', 'publish' );
// Disable the default search query
$query->set( 's', '' );
}
// Store the original search query for display
$query->set( 'maplepress_search_query', $search_query );
// Store total results for pagination
if ( ! empty( $result['total_hits'] ) ) {
add_filter( 'found_posts', function( $found_posts, $query ) use ( $result ) {
if ( $query->get( 'maplepress_search_query' ) ) {
return (int) $result['total_hits'];
}
return $found_posts;
}, 10, 2 );
}
}
/**
* Restore the search query for display purposes.
*
* @param string $search_query The search query.
* @return string
*/
public function restore_search_query( $search_query ) {
global $wp_query;
$maplepress_query = $wp_query->get( 'maplepress_search_query' );
if ( ! empty( $maplepress_query ) ) {
return $maplepress_query;
}
return $search_query;
}
}

View file

@ -0,0 +1,454 @@
<?php
/**
* System Information Collector
*
* Collects detailed system information for diagnosing performance bottlenecks.
*
* @package MaplePress
*/
class MaplePress_System_Info {
/**
* Get all system information.
*
* @return array System information array.
*/
public static function get_all_info() {
return array(
'php' => self::get_php_info(),
'php_fpm' => self::get_php_fpm_info(),
'server' => self::get_server_info(),
'wordpress' => self::get_wordpress_info(),
'database' => self::get_database_info(),
'performance' => self::get_performance_info(),
);
}
/**
* Get PHP configuration information.
*
* @return array PHP configuration.
*/
private static function get_php_info() {
return array(
'version' => PHP_VERSION,
'sapi' => php_sapi_name(),
'memory_limit' => ini_get( 'memory_limit' ),
'max_execution_time' => ini_get( 'max_execution_time' ),
'max_input_time' => ini_get( 'max_input_time' ),
'post_max_size' => ini_get( 'post_max_size' ),
'upload_max_filesize' => ini_get( 'upload_max_filesize' ),
'max_input_vars' => ini_get( 'max_input_vars' ),
'display_errors' => ini_get( 'display_errors' ),
'error_reporting' => ini_get( 'error_reporting' ),
'opcache_enabled' => function_exists( 'opcache_get_status' ) && opcache_get_status() !== false,
'opcache_status' => function_exists( 'opcache_get_status' ) ? opcache_get_status() : null,
);
}
/**
* Get PHP-FPM configuration information.
*
* @return array PHP-FPM configuration.
*/
private static function get_php_fpm_info() {
$info = array(
'is_fpm' => false,
'pool_name' => 'N/A',
'pm_type' => 'N/A',
'max_children' => 'N/A',
'start_servers' => 'N/A',
'min_spare_servers' => 'N/A',
'max_spare_servers' => 'N/A',
'max_requests' => 'N/A',
'status_url' => 'N/A',
'active_processes' => 'N/A',
'total_processes' => 'N/A',
'idle_processes' => 'N/A',
'listen_queue' => 'N/A',
'listen_queue_len' => 'N/A',
'config_file' => 'N/A',
);
// Check if running under PHP-FPM
if ( php_sapi_name() === 'fpm-fcgi' || php_sapi_name() === 'cgi-fcgi' ) {
$info['is_fpm'] = true;
// Try to find PHP-FPM config file
$possible_config_paths = array(
'/etc/php/' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '/fpm/pool.d/www.conf',
'/usr/local/etc/php-fpm.d/www.conf',
'/etc/php-fpm.d/www.conf',
'/etc/php/fpm/pool.d/www.conf',
);
foreach ( $possible_config_paths as $path ) {
if ( file_exists( $path ) && is_readable( $path ) ) {
$info['config_file'] = $path;
$config = self::parse_php_fpm_config( $path );
if ( $config ) {
$info = array_merge( $info, $config );
}
break;
}
}
// Try to get PHP-FPM status via FastCGI
$status = self::get_php_fpm_status();
if ( $status ) {
$info = array_merge( $info, $status );
}
}
return $info;
}
/**
* Parse PHP-FPM configuration file.
*
* @param string $config_file Path to config file.
* @return array|null Parsed configuration or null.
*/
private static function parse_php_fpm_config( $config_file ) {
$config = array();
$content = file_get_contents( $config_file );
if ( ! $content ) {
return null;
}
// Parse key configuration values (ignore comments after values)
$patterns = array(
'pool_name' => '/^\[([^\]]+)\]/m',
'pm_type' => '/^pm\s*=\s*(\w+)/m',
'max_children' => '/^pm\.max_children\s*=\s*(\d+)/m',
'start_servers' => '/^pm\.start_servers\s*=\s*(\d+)/m',
'min_spare_servers' => '/^pm\.min_spare_servers\s*=\s*(\d+)/m',
'max_spare_servers' => '/^pm\.max_spare_servers\s*=\s*(\d+)/m',
'max_requests' => '/^pm\.max_requests\s*=\s*(\d+)/m',
);
foreach ( $patterns as $key => $pattern ) {
if ( preg_match( $pattern, $content, $matches ) ) {
$config[ $key ] = trim( $matches[1] );
}
}
return $config;
}
/**
* Get PHP-FPM status from status endpoint.
*
* @return array|null Status information or null.
*/
private static function get_php_fpm_status() {
// Try common PHP-FPM status URLs
$status_urls = array(
'http://localhost/fpm-status',
'http://127.0.0.1/fpm-status',
'http://localhost/status',
);
foreach ( $status_urls as $url ) {
$response = wp_remote_get( $url . '?json' );
if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( $data ) {
return array(
'pool_name' => $data['pool'] ?? 'N/A',
'total_processes' => $data['total processes'] ?? 'N/A',
'active_processes' => $data['active processes'] ?? 'N/A',
'idle_processes' => $data['idle processes'] ?? 'N/A',
'listen_queue' => $data['listen queue'] ?? 'N/A',
'listen_queue_len' => $data['listen queue len'] ?? 'N/A',
'max_children' => $data['max children reached'] ?? 'N/A',
);
}
}
}
return null;
}
/**
* Get server information.
*
* @return array Server information.
*/
private static function get_server_info() {
return array(
'software' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : 'Unknown',
'os' => PHP_OS,
'hostname' => gethostname(),
'server_addr' => isset( $_SERVER['SERVER_ADDR'] ) ? $_SERVER['SERVER_ADDR'] : 'Unknown',
'document_root' => isset( $_SERVER['DOCUMENT_ROOT'] ) ? $_SERVER['DOCUMENT_ROOT'] : 'Unknown',
'cpu_count' => self::get_cpu_count(),
'memory_total' => self::get_memory_total(),
'memory_available' => self::get_memory_available(),
'load_average' => self::get_load_average(),
);
}
/**
* Get CPU core count.
*
* @return int|string CPU count or 'Unknown'.
*/
private static function get_cpu_count() {
if ( function_exists( 'shell_exec' ) ) {
// Linux
$output = shell_exec( 'nproc 2>/dev/null' );
if ( $output ) {
return (int) trim( $output );
}
// macOS
$output = shell_exec( 'sysctl -n hw.ncpu 2>/dev/null' );
if ( $output ) {
return (int) trim( $output );
}
}
return 'Unknown';
}
/**
* Get total system memory in MB.
*
* @return string Memory in MB or 'Unknown'.
*/
private static function get_memory_total() {
if ( function_exists( 'shell_exec' ) ) {
// Linux
$output = shell_exec( "free -m | grep Mem | awk '{print $2}'" );
if ( $output ) {
return trim( $output ) . ' MB';
}
// macOS
$output = shell_exec( 'sysctl -n hw.memsize 2>/dev/null' );
if ( $output ) {
return round( (int) trim( $output ) / 1024 / 1024 ) . ' MB';
}
}
return 'Unknown';
}
/**
* Get available system memory in MB.
*
* @return string Available memory or 'Unknown'.
*/
private static function get_memory_available() {
if ( function_exists( 'shell_exec' ) ) {
// Linux
$output = shell_exec( "free -m | grep Mem | awk '{print $7}'" );
if ( $output ) {
return trim( $output ) . ' MB';
}
}
return 'Unknown';
}
/**
* Get system load average.
*
* @return string Load average or 'Unknown'.
*/
private static function get_load_average() {
if ( function_exists( 'sys_getloadavg' ) ) {
$load = sys_getloadavg();
return sprintf( '%.2f, %.2f, %.2f', $load[0], $load[1], $load[2] );
}
return 'Unknown';
}
/**
* Get WordPress information.
*
* @return array WordPress information.
*/
private static function get_wordpress_info() {
global $wp_version;
return array(
'version' => $wp_version,
'multisite' => is_multisite(),
'site_url' => get_site_url(),
'home_url' => get_home_url(),
'debug_mode' => defined( 'WP_DEBUG' ) && WP_DEBUG,
'memory_limit' => WP_MEMORY_LIMIT,
'max_memory_limit' => WP_MAX_MEMORY_LIMIT,
'theme' => wp_get_theme()->get( 'Name' ),
'active_plugins' => count( get_option( 'active_plugins', array() ) ),
);
}
/**
* Get database information.
*
* @return array Database information.
*/
private static function get_database_info() {
global $wpdb;
$info = array(
'extension' => $wpdb->db_version(),
'server' => $wpdb->get_var( 'SELECT VERSION()' ),
'client_version' => $wpdb->db_version(),
'database_name' => DB_NAME,
'database_host' => DB_HOST,
'database_charset' => DB_CHARSET,
'table_prefix' => $wpdb->prefix,
);
// Get MySQL configuration
$variables = array(
'max_connections',
'max_allowed_packet',
'query_cache_size',
'innodb_buffer_pool_size',
'tmp_table_size',
'max_heap_table_size',
);
foreach ( $variables as $var ) {
$value = $wpdb->get_var( "SHOW VARIABLES LIKE '$var'" );
if ( $value ) {
$info[ $var ] = $wpdb->get_var( "SHOW VARIABLES LIKE '$var'", 1 );
}
}
// Get current connections
$info['current_connections'] = $wpdb->get_var( "SHOW STATUS LIKE 'Threads_connected'" )
? $wpdb->get_var( "SHOW STATUS LIKE 'Threads_connected'", 1 )
: 'Unknown';
return $info;
}
/**
* Get performance-related information.
*
* @return array Performance information.
*/
private static function get_performance_info() {
return array(
'object_cache' => wp_using_ext_object_cache() ? 'External' : 'Built-in',
'curl_available' => function_exists( 'curl_version' ),
'curl_version' => function_exists( 'curl_version' ) ? curl_version()['version'] : 'N/A',
'allow_url_fopen' => ini_get( 'allow_url_fopen' ) ? 'Yes' : 'No',
'compression' => extension_loaded( 'zlib' ) ? 'Available' : 'Not available',
'https_support' => is_ssl() ? 'Yes' : 'No',
);
}
/**
* Format system info as HTML.
*
* @param array $info System information array.
* @return string HTML output.
*/
public static function format_as_html( $info ) {
ob_start();
?>
<div class="maplepress-system-info">
<?php foreach ( $info as $section => $data ) : ?>
<div class="system-info-section">
<h3 style="margin-top: 0; padding: 10px; background: #f0f0f1; border-left: 4px solid #2271b1;">
<?php echo esc_html( ucwords( str_replace( '_', ' ', $section ) ) ); ?>
</h3>
<table class="widefat" style="margin-top: 0;">
<tbody>
<?php foreach ( $data as $key => $value ) : ?>
<?php
// Check if this is the opcache_status field (which contains a large array)
$is_opcache_status = ( $key === 'opcache_status' && is_array( $value ) );
$field_id = 'maplepress-field-' . sanitize_title( $section . '-' . $key );
?>
<tr>
<td style="width: 250px; font-weight: bold; vertical-align: top;">
<?php echo esc_html( ucwords( str_replace( '_', ' ', $key ) ) ); ?>
<?php if ( $is_opcache_status ) : ?>
<br>
<button type="button"
class="button button-secondary button-small maplepress-toggle-field"
data-target="<?php echo esc_attr( $field_id ); ?>"
style="margin-top: 5px;">
<span class="dashicons dashicons-visibility" style="vertical-align: middle; font-size: 14px;"></span>
<span class="toggle-text">Show</span>
</button>
<?php endif; ?>
</td>
<td>
<?php if ( $is_opcache_status ) : ?>
<div id="<?php echo esc_attr( $field_id ); ?>" style="display: none;">
<pre><?php echo esc_html( print_r( $value, true ) ); ?></pre>
</div>
<span class="opcache-hidden-notice" style="color: #666; font-style: italic;">
Click "Show" to view opcache details
</span>
<?php elseif ( is_array( $value ) ) : ?>
<pre><?php echo esc_html( print_r( $value, true ) ); ?></pre>
<?php elseif ( is_bool( $value ) ) : ?>
<?php echo $value ? '<span style="color: green;">✓ Yes</span>' : '<span style="color: red;">✗ No</span>'; ?>
<?php else : ?>
<?php echo esc_html( $value ); ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endforeach; ?>
</div>
<style>
.maplepress-system-info .system-info-section {
margin-bottom: 30px;
}
.maplepress-system-info pre {
margin: 0;
padding: 10px;
background: #f9f9f9;
border-left: 3px solid #ccc;
overflow-x: auto;
}
</style>
<script>
jQuery(document).ready(function($) {
// Toggle field visibility for opcache_status
$('.maplepress-toggle-field').on('click', function() {
var button = $(this);
var targetId = button.data('target');
var content = $('#' + targetId);
var notice = button.closest('tr').find('.opcache-hidden-notice');
var icon = button.find('.dashicons');
var text = button.find('.toggle-text');
if (content.is(':visible')) {
content.slideUp(300);
notice.show();
icon.removeClass('dashicons-hidden').addClass('dashicons-visibility');
text.text('Show');
} else {
content.slideDown(300);
notice.hide();
icon.removeClass('dashicons-visibility').addClass('dashicons-hidden');
text.text('Hide');
}
});
});
</script>
<?php
return ob_get_clean();
}
}

View file

@ -0,0 +1,115 @@
<?php
/**
* The core plugin class.
*
* @package MaplePress
* @subpackage MaplePress/includes
*/
class MaplePress {
/**
* The loader that's responsible for maintaining and registering all hooks.
*
* @var MaplePress_Loader
*/
protected $loader;
/**
* The unique identifier of this plugin.
*
* @var string
*/
protected $plugin_name;
/**
* The current version of the plugin.
*
* @var string
*/
protected $version;
/**
* Initialize the class and set its properties.
*/
public function __construct() {
$this->version = MAPLEPRESS_VERSION;
$this->plugin_name = 'maplepress';
$this->load_dependencies();
$this->define_admin_hooks();
$this->define_public_hooks();
}
/**
* Load the required dependencies for this plugin.
*/
private function load_dependencies() {
require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-loader.php';
require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-admin.php';
require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-public.php';
require_once MAPLEPRESS_PLUGIN_DIR . 'includes/class-maplepress-api-client.php';
$this->loader = new MaplePress_Loader();
}
/**
* Register all hooks related to the admin area functionality.
*/
private function define_admin_hooks() {
$plugin_admin = new MaplePress_Admin( $this->get_plugin_name(), $this->get_version() );
$this->loader->add_action( 'admin_menu', $plugin_admin, 'add_plugin_admin_menu' );
$this->loader->add_action( 'admin_init', $plugin_admin, 'register_settings' );
$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' );
}
/**
* Register all hooks related to the public-facing functionality.
*/
private function define_public_hooks() {
$plugin_public = new MaplePress_Public( $this->get_plugin_name(), $this->get_version() );
$this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_styles' );
$this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_scripts' );
// Search integration
$this->loader->add_action( 'pre_get_posts', $plugin_public, 'intercept_search' );
$this->loader->add_filter( 'get_search_query', $plugin_public, 'restore_search_query' );
}
/**
* Run the loader to execute all hooks.
*/
public function run() {
$this->loader->run();
}
/**
* The name of the plugin.
*
* @return string
*/
public function get_plugin_name() {
return $this->plugin_name;
}
/**
* The reference to the class that orchestrates the hooks.
*
* @return MaplePress_Loader
*/
public function get_loader() {
return $this->loader;
}
/**
* Retrieve the version number of the plugin.
*
* @return string
*/
public function get_version() {
return $this->version;
}
}

View file

@ -0,0 +1,133 @@
<?php
/**
* Parallel Executor Class
*
* Executes search queries in parallel (concurrent requests) to simulate
* multiple simultaneous users searching the site.
*
* @package MaplePress_SearchSpeedTest
*/
class MPSS_Parallel_Executor {
/**
* Execute queries in parallel batches.
*
* @param array $queries Array of search query strings.
* @param int $concurrency Number of concurrent requests per batch.
* @return array Results array with timing and metadata.
*/
public function execute( $queries, $concurrency = 5 ) {
$results = array();
// Split queries into batches based on concurrency
$batches = array_chunk( $queries, $concurrency );
foreach ( $batches as $batch ) {
$batch_results = $this->execute_batch( $batch );
$results = array_merge( $results, $batch_results );
// Small delay between batches to prevent server overload
usleep( 50000 ); // 50ms
}
return $results;
}
/**
* Execute a batch of queries concurrently using cURL multi-handle.
*
* @param array $queries Array of query strings for this batch.
* @return array Results for this batch.
*/
private function execute_batch( $queries ) {
$results = array();
// Prepare requests
$requests = array();
foreach ( $queries as $query ) {
$search_url = add_query_arg( 's', urlencode( $query ), home_url( '/' ) );
$requests[] = array(
'url' => $search_url,
'query' => $query,
);
}
// Execute in parallel
$parallel_results = $this->execute_parallel_requests( $requests );
return $parallel_results;
}
/**
* Execute multiple HTTP requests in parallel using cURL multi-handle.
*
* @param array $requests Array of request data (url, query).
* @return array Results array.
*/
private function execute_parallel_requests( $requests ) {
$results = array();
$mh = curl_multi_init();
$curl_handles = array();
// Add all requests to multi-handle
foreach ( $requests as $index => $request ) {
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $request['url'] );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $ch, CURLOPT_TIMEOUT, 30 );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // For local testing
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false ); // For local testing
// Add custom headers to identify test traffic (optional)
curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
array(
'X-Speed-Test: MaplePress',
)
);
curl_multi_add_handle( $mh, $ch );
$curl_handles[ (int) $ch ] = array(
'handle' => $ch,
'query' => $request['query'],
'start' => microtime( true ),
);
}
// Execute all requests
$running = null;
do {
curl_multi_exec( $mh, $running );
curl_multi_select( $mh );
} while ( $running > 0 );
// Collect results
foreach ( $curl_handles as $data ) {
$info = curl_getinfo( $data['handle'] );
// Use cURL's total_time which accurately measures the individual request time
// NOT the time from batch start to collection (which would be incorrect)
$duration_ms = round( $info['total_time'] * 1000, 2 );
$results[] = array(
'query' => $data['query'],
'duration_ms' => $duration_ms,
'http_code' => $info['http_code'],
'timestamp' => $data['start'],
'success' => $info['http_code'] === 200,
);
curl_multi_remove_handle( $mh, $data['handle'] );
curl_close( $data['handle'] );
}
curl_multi_close( $mh );
return $results;
}
}

View file

@ -0,0 +1,278 @@
<?php
/**
* Query Generator Class
*
* Generates realistic search queries based on user behavior patterns.
*
* @package MaplePress_SearchSpeedTest
*/
class MPSS_Query_Generator {
/**
* Pre-defined realistic search query templates by category.
*
* @var array
*/
private $query_templates = array(
// Navigation queries (30%)
'navigation' => array(
'contact',
'about',
'home',
'services',
'pricing',
'support',
'login',
'register',
'cart',
'checkout',
'account',
'profile',
'settings',
'help',
'faq',
'terms',
'privacy',
'policy',
'sitemap',
'search',
),
// Informational queries (25%)
'informational' => array(
'how to',
'what is',
'why does',
'when should',
'where can',
'getting started',
'tutorial',
'guide',
'documentation',
'manual',
'learn',
'course',
'training',
'video',
'webinar',
'ebook',
'whitepaper',
'case study',
'best practices',
'tips',
),
// Product/Service queries (20%)
'product' => array(
'wordpress plugin',
'theme',
'template',
'widget',
'extension',
'free shipping',
'discount',
'coupon',
'promo',
'deal',
'best seller',
'popular',
'recommended',
'featured',
'top rated',
'new arrival',
'latest',
'coming soon',
'pre order',
'limited',
'on sale',
'clearance',
'bundle',
'package',
'subscription',
),
// Content discovery queries (15%)
'content' => array(
'blog',
'post',
'article',
'news',
'announcement',
'latest',
'recent',
'new',
'updated',
'archive',
'category',
'tag',
'topic',
'subject',
'theme',
'author',
'contributor',
'guest',
'interview',
'podcast',
),
// Troubleshooting queries (10%)
'troubleshooting' => array(
'error',
'not working',
'fix',
'repair',
'restore',
'problem',
'issue',
'bug',
'glitch',
'crash',
'troubleshoot',
'diagnose',
'solve',
'resolve',
'debug',
'broken',
'missing',
'invalid',
'failed',
'rejected',
),
);
/**
* Category weights for distribution.
*
* @var array
*/
private $category_weights = array(
'navigation' => 0.30,
'informational' => 0.25,
'product' => 0.20,
'content' => 0.15,
'troubleshooting' => 0.10,
);
/**
* Generate query set based on site size profile.
*
* @param string $profile_key Profile key (tiny, small, medium, big, gigantic).
* @param int $count Number of queries to generate.
* @return array Array of search query strings.
*/
public function generate_queries( $profile_key, $count ) {
$queries = array();
// Generate queries from each category based on weight
foreach ( $this->category_weights as $category => $weight ) {
$category_count = (int) ( $count * $weight );
$category_queries = $this->get_random_from_category( $category, $category_count );
$queries = array_merge( $queries, $category_queries );
}
// Shuffle to mix categories
shuffle( $queries );
// Add some variations (typos, mixed case, etc) - 20% of queries
$queries = $this->add_query_variations( $queries, 0.20 );
// Ensure we have exactly the requested count
return array_slice( $queries, 0, $count );
}
/**
* Get random queries from a specific category.
*
* @param string $category Category name.
* @param int $count Number of queries to get.
* @return array Array of queries.
*/
private function get_random_from_category( $category, $count ) {
if ( ! isset( $this->query_templates[ $category ] ) ) {
return array();
}
$available = $this->query_templates[ $category ];
// If we need more queries than available, allow duplicates
if ( $count > count( $available ) ) {
$queries = array();
for ( $i = 0; $i < $count; $i++ ) {
$queries[] = $available[ array_rand( $available ) ];
}
return $queries;
}
// Otherwise, shuffle and slice
shuffle( $available );
return array_slice( $available, 0, $count );
}
/**
* Add realistic query variations to the set.
*
* @param array $queries Base queries.
* @param float $variation_ratio Ratio of queries to vary (0.0-1.0).
* @return array Queries with variations added.
*/
private function add_query_variations( $queries, $variation_ratio = 0.20 ) {
$with_variations = array();
foreach ( $queries as $query ) {
// Always add original query
$with_variations[] = $query;
// Randomly add variations based on ratio
if ( mt_rand( 1, 100 ) <= ( $variation_ratio * 100 ) ) {
$variant = $this->create_variation( $query );
if ( $variant !== $query ) {
$with_variations[] = $variant;
}
}
}
return $with_variations;
}
/**
* Create a variation of a query (case changes, partial queries, suffixes).
*
* @param string $query Original query.
* @return string Variation of the query.
*/
private function create_variation( $query ) {
$rand = mt_rand( 1, 5 );
switch ( $rand ) {
case 1:
// All uppercase
return strtoupper( $query );
case 2:
// Title case
return ucwords( $query );
case 3:
// Partial query (first word only)
$words = explode( ' ', $query );
return $words[0];
case 4:
// Add common suffix
$suffixes = array( ' guide', ' help', ' info', ' page', ' wordpress' );
return $query . $suffixes[ array_rand( $suffixes ) ];
case 5:
// Mixed case (simulate typo)
$chars = str_split( $query );
$result = '';
foreach ( $chars as $char ) {
$result .= ( mt_rand( 0, 1 ) === 1 ) ? strtoupper( $char ) : strtolower( $char );
}
return $result;
}
return $query;
}
}

View file

@ -0,0 +1,224 @@
<?php
/**
* Results Analyzer Class
*
* Analyzes speed test results and generates recommendations.
*
* @package MaplePress_SearchSpeedTest
*/
class MPSS_Results_Analyzer {
/**
* Analyze speed test results.
*
* @param array $all_results All profile results.
* @return array Analyzed results with summary and recommendations.
*/
public function analyze( $all_results ) {
$summary = array();
foreach ( $all_results as $profile_key => $data ) {
$times = array_column( $data['results'], 'duration_ms' );
if ( empty( $times ) ) {
continue;
}
$avg_ms = array_sum( $times ) / count( $times );
$summary[ $profile_key ] = array(
'profile_name' => $data['profile']['name'],
'query_count' => count( $data['results'] ),
'avg_ms' => round( $avg_ms, 2 ),
'min_ms' => round( min( $times ), 2 ),
'max_ms' => round( max( $times ), 2 ),
'median_ms' => $this->median( $times ),
'p95_ms' => $this->percentile( $times, 95 ),
'p99_ms' => $this->percentile( $times, 99 ),
'status' => $this->get_status( $avg_ms ),
);
}
return array(
'summary' => $summary,
'recommendations' => $this->generate_recommendations( $summary ),
);
}
/**
* Calculate median of an array.
*
* @param array $array Numeric array.
* @return float Median value.
*/
private function median( $array ) {
sort( $array );
$count = count( $array );
$middle = floor( $count / 2 );
if ( $count % 2 === 0 ) {
return round( ( $array[ $middle - 1 ] + $array[ $middle ] ) / 2, 2 );
}
return round( $array[ $middle ], 2 );
}
/**
* Calculate percentile of an array.
*
* @param array $array Numeric array.
* @param int $percentile Percentile to calculate (e.g., 95 for P95).
* @return float Percentile value.
*/
private function percentile( $array, $percentile ) {
sort( $array );
$index = ceil( ( $percentile / 100 ) * count( $array ) ) - 1;
$index = max( 0, $index );
return round( $array[ $index ], 2 );
}
/**
* Get performance status based on average response time.
*
* @param float $avg_ms Average response time in milliseconds.
* @return string Status (excellent, good, fair, poor, critical).
*/
private function get_status( $avg_ms ) {
if ( $avg_ms < 50 ) {
return 'excellent';
} elseif ( $avg_ms < 150 ) {
return 'good';
} elseif ( $avg_ms < 500 ) {
return 'fair';
} elseif ( $avg_ms < 1000 ) {
return 'poor';
} else {
return 'critical';
}
}
/**
* Get current site information.
*
* @return array Site information.
*/
private function get_site_info() {
$post_count = wp_count_posts( 'post' );
$page_count = wp_count_posts( 'page' );
$published_posts = isset( $post_count->publish ) ? $post_count->publish : 0;
$published_pages = isset( $page_count->publish ) ? $page_count->publish : 0;
$total_content = $published_posts + $published_pages;
// Determine profile
if ( $total_content < 50 ) {
$profile = 'tiny';
} elseif ( $total_content < 500 ) {
$profile = 'small';
} elseif ( $total_content < 5000 ) {
$profile = 'medium';
} elseif ( $total_content < 50000 ) {
$profile = 'big';
} else {
$profile = 'gigantic';
}
return array(
'posts' => $published_posts,
'pages' => $published_pages,
'total_content' => $total_content,
'profile' => $profile,
);
}
/**
* Generate actionable recommendations based on results.
*
* @param array $summary Profile summaries.
* @return array Recommendations array.
*/
private function generate_recommendations( $summary ) {
$recommendations = array();
// Get the actual test results (could be 'custom' or any profile key)
$test_result = reset( $summary );
if ( ! $test_result ) {
return $recommendations;
}
$avg_ms = $test_result['avg_ms'];
// Performance-based recommendations
if ( $avg_ms < 50 ) {
$recommendations[] = array(
'level' => 'success',
'title' => __( 'Excellent Performance', 'maplepress-searchspeedtest' ),
'message' => sprintf(
/* translators: %s: average response time */
__( 'Average response time of %.2f ms is excellent! Your search is performing very well.', 'maplepress-searchspeedtest' ),
$avg_ms
),
);
} elseif ( $avg_ms < 150 ) {
$recommendations[] = array(
'level' => 'success',
'title' => __( 'Good Performance', 'maplepress-searchspeedtest' ),
'message' => sprintf(
/* translators: %s: average response time */
__( 'Average response time of %.2f ms is good. Your search is performing well.', 'maplepress-searchspeedtest' ),
$avg_ms
),
);
} elseif ( $avg_ms < 500 ) {
$recommendations[] = array(
'level' => 'warning',
'title' => __( 'Fair Performance - Room for Improvement', 'maplepress-searchspeedtest' ),
'message' => sprintf(
/* translators: %s: average response time */
__( 'Average response time of %.2f ms is acceptable but could be improved. Consider implementing search caching or optimizing your database queries.', 'maplepress-searchspeedtest' ),
$avg_ms
),
);
} elseif ( $avg_ms < 1000 ) {
$recommendations[] = array(
'level' => 'error',
'title' => __( 'Poor Performance', 'maplepress-searchspeedtest' ),
'message' => sprintf(
/* translators: %s: average response time */
__( 'Average response time of %.2f ms indicates performance issues. Consider database optimization, adding indexes, or using a dedicated search solution.', 'maplepress-searchspeedtest' ),
$avg_ms
),
);
} else {
$recommendations[] = array(
'level' => 'critical',
'title' => __( 'Critical: Very Slow Performance', 'maplepress-searchspeedtest' ),
'message' => sprintf(
/* translators: %s: average response time */
__( 'Average response time of %.2f ms is critically slow. Your database may need optimization, or you should consider a cloud-based search solution like MaplePress.', 'maplepress-searchspeedtest' ),
$avg_ms
),
);
}
// Add database optimization tip if showing slowness
if ( $avg_ms > 200 ) {
$recommendations[] = array(
'level' => 'info',
'title' => __( 'Database Optimization Tip', 'maplepress-searchspeedtest' ),
'message' => __( 'Consider adding database indexes on post_title and post_content columns, implementing search result caching, or using a cloud-based search solution like MaplePress for better performance.', 'maplepress-searchspeedtest' ),
);
}
// Add general optimization tips
$recommendations[] = array(
'level' => 'info',
'title' => __( 'Optimization Tips', 'maplepress-searchspeedtest' ),
'message' => __( 'Consider: 1) Implementing search result caching, 2) Adding database indexes on post_title and post_content, 3) Using a CDN, 4) Upgrading server resources, 5) Trying MaplePress for cloud-powered search.', 'maplepress-searchspeedtest' ),
);
return $recommendations;
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* Serial Executor Class
*
* Executes search queries sequentially (one after another) via HTTP requests.
* This ensures all WordPress hooks (including MaplePress search intercept) are triggered.
*
* @package MaplePress_SearchSpeedTest
*/
class MPSS_Serial_Executor {
/**
* Execute queries serially (one at a time) using WP_Query directly.
*
* @param array $queries Array of search query strings.
* @return array Results array with timing and metadata.
*/
public function execute( $queries ) {
$results = array();
foreach ( $queries as $index => $query ) {
// Start timing
$start_time = microtime( true );
// Execute WordPress search directly using WP_Query
$search_query = new WP_Query(
array(
's' => $query,
'post_type' => 'any',
'post_status' => 'publish',
'posts_per_page' => 10,
)
);
// End timing
$end_time = microtime( true );
$duration_ms = ( $end_time - $start_time ) * 1000;
$results[] = array(
'query' => $query,
'duration_ms' => round( $duration_ms, 2 ),
'result_count' => $search_query->found_posts,
'timestamp' => $start_time,
'success' => true,
);
// Reset post data
wp_reset_postdata();
// Small delay to prevent overwhelming the database (10ms)
usleep( 10000 );
}
return $results;
}
}

View file

@ -0,0 +1,340 @@
<?php
/**
* Simple Speed Test Class
*
* Just runs queries and times them. No complexity.
*
* @package MaplePress_SearchSpeedTest
*/
class MPSS_SpeedTest_Simple {
/**
* Run speed test with specified number of queries.
*
* @param int $query_count Number of queries to run.
* @param string $execution_mode Execution mode: 'serial' or 'parallel'.
* @return array Results with timing data.
*/
public function run( $query_count = 10, $execution_mode = 'serial' ) {
$start_time = microtime( true );
// Debug logging
error_log( 'MPSS_SpeedTest_Simple::run() called with mode: ' . $execution_mode );
// Generate search queries
$queries = $this->generate_queries( $query_count );
// Execute based on mode
if ( $execution_mode === 'parallel' ) {
error_log( 'MPSS: Executing in PARALLEL mode' );
$results = $this->run_parallel( $queries );
} else {
error_log( 'MPSS: Executing in SERIAL mode' );
$results = $this->run_serial( $queries );
}
$end_time = microtime( true );
$total_time = $end_time - $start_time;
// Calculate statistics
$times = array_column( $results, 'duration_ms' );
$avg_ms = array_sum( $times ) / count( $times );
$min_ms = min( $times );
$max_ms = max( $times );
return array(
'results' => $results,
'total_time' => round( $total_time, 2 ),
'total_queries' => count( $results ),
'avg_ms' => round( $avg_ms, 2 ),
'min_ms' => round( $min_ms, 2 ),
'max_ms' => round( $max_ms, 2 ),
'mode' => $execution_mode,
);
}
/**
* Run queries in serial mode (one at a time).
*
* @param array $queries Array of search query strings.
* @return array Results array.
*/
private function run_serial( $queries ) {
$results = array();
foreach ( $queries as $query ) {
$query_start = microtime( true );
// Make HTTP request to search URL to properly trigger MaplePress hooks
$search_url = home_url( '/?s=' . urlencode( $query ) );
$response = wp_remote_get(
$search_url,
array(
'timeout' => 30,
'headers' => array(
'X-MaplePress-Test' => '1',
),
)
);
$query_end = microtime( true );
$duration_ms = ( $query_end - $query_start ) * 1000;
// Extract result count from response - try multiple patterns
$result_count = 0;
if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
$body = wp_remote_retrieve_body( $response );
// Try various patterns to find result count
$patterns = array(
'/(\d+)\s+results?/i', // "5 results" or "1 result"
'/found\s+(\d+)/i', // "Found 5"
'/showing\s+(\d+)/i', // "Showing 5"
'/<article/i', // Count article tags
'/<div[^>]*class="[^"]*post[^"]*"/i', // Count post divs
);
foreach ( $patterns as $pattern ) {
if ( $pattern === '/<article/i' || strpos( $pattern, '<div' ) === 0 ) {
// Count HTML elements
$count = preg_match_all( $pattern, $body );
if ( $count > 0 ) {
$result_count = $count;
break;
}
} else {
// Extract number from text
if ( preg_match( $pattern, $body, $matches ) ) {
$result_count = intval( $matches[1] );
break;
}
}
}
}
$results[] = array(
'query' => $query,
'duration_ms' => round( $duration_ms, 2 ),
'result_count' => $result_count,
);
}
return $results;
}
/**
* Run queries in parallel mode (multiple concurrent requests).
*
* @param array $queries Array of search query strings.
* @return array Results array.
*/
private function run_parallel( $queries ) {
error_log( 'MPSS: run_parallel called with ' . count( $queries ) . ' queries' );
$results = array();
// Build search URLs
$search_urls = array();
foreach ( $queries as $query ) {
$search_urls[] = array(
'url' => home_url( '/?s=' . urlencode( $query ) ),
'query' => $query,
);
}
error_log( 'MPSS: Built ' . count( $search_urls ) . ' search URLs' );
// Initialize cURL multi handle
$mh = curl_multi_init();
$curl_handles = array();
$request_info = array();
// Add all requests to multi handle
foreach ( $search_urls as $index => $search_data ) {
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $search_data['url'] );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $ch, CURLOPT_TIMEOUT, 30 );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
'X-MaplePress-Test: 1',
) );
curl_multi_add_handle( $mh, $ch );
$ch_id = (int) $ch;
$curl_handles[ $ch_id ] = $ch;
$request_info[ $ch_id ] = array(
'query' => $search_data['query'],
'index' => $index,
'start_time' => null,
'end_time' => null,
);
}
error_log( 'MPSS: Added ' . count( $curl_handles ) . ' handles, request_info has ' . count( $request_info ) . ' entries' );
// Execute all queries simultaneously and track start/end times
$running = null;
// Record start time for ALL requests (they all start together)
$batch_start = microtime( true );
foreach ( $request_info as $ch_id => $info ) {
$request_info[ $ch_id ]['start_time'] = $batch_start;
}
// Start executing - all requests run concurrently
do {
curl_multi_exec( $mh, $running );
// Check for completed requests and record their end time
while ( $done = curl_multi_info_read( $mh ) ) {
if ( $done['msg'] == CURLMSG_DONE ) {
$ch_id = (int) $done['handle'];
// Record when THIS specific request finished
if ( ! isset( $request_info[ $ch_id ]['end_time'] ) ) {
$request_info[ $ch_id ]['end_time'] = microtime( true );
}
}
}
if ( $running > 0 ) {
curl_multi_select( $mh );
}
} while ( $running > 0 );
$batch_end = microtime( true );
$batch_duration_ms = ( $batch_end - $batch_start ) * 1000;
error_log( 'MPSS: Batch completed in ' . round( $batch_duration_ms, 2 ) . 'ms, processing ' . count( $request_info ) . ' results' );
// Collect results - calculate duration from start/end times
foreach ( $request_info as $ch_id => $info ) {
$ch = $curl_handles[ $ch_id ];
// Calculate duration from start to end time
if ( isset( $info['start_time'] ) && isset( $info['end_time'] ) ) {
$duration_seconds = $info['end_time'] - $info['start_time'];
$duration_ms = $duration_seconds * 1000;
// Debug: log a few samples
static $debug_count = 0;
if ( $debug_count < 3 ) {
error_log( sprintf(
'MPSS: Sample #%d - start=%.6f, end=%.6f, duration_sec=%.6f, duration_ms=%.2f',
$debug_count,
$info['start_time'],
$info['end_time'],
$duration_seconds,
$duration_ms
) );
$debug_count++;
}
} else {
// Fallback - shouldn't happen
$duration_ms = 0;
}
$curl_info = curl_getinfo( $ch );
$response = curl_multi_getcontent( $ch );
$http_code = $curl_info['http_code'];
// Extract result count using multiple patterns
$result_count = 0;
if ( $http_code === 200 && ! empty( $response ) ) {
$patterns = array(
'/(\d+)\s+results?/i',
'/found\s+(\d+)/i',
'/showing\s+(\d+)/i',
'/<article/i',
'/<div[^>]*class="[^"]*post[^"]*"/i',
);
foreach ( $patterns as $pattern ) {
if ( $pattern === '/<article/i' || strpos( $pattern, '<div' ) === 0 ) {
$count = preg_match_all( $pattern, $response );
if ( $count > 0 ) {
$result_count = $count;
break;
}
} else {
if ( preg_match( $pattern, $response, $matches ) ) {
$result_count = intval( $matches[1] );
break;
}
}
}
}
$results[ $info['index'] ] = array(
'query' => $info['query'],
'duration_ms' => round( $duration_ms, 2 ),
'result_count' => $result_count,
);
curl_multi_remove_handle( $mh, $ch );
curl_close( $ch );
}
curl_multi_close( $mh );
// Sort results by original index to maintain query order
ksort( $results );
return array_values( $results );
}
/**
* Generate random search queries from existing content.
*
* @param int $count Number of queries to generate.
* @return array Array of search query strings.
*/
private function generate_queries( $count ) {
error_log( 'MPSS: generate_queries called with count=' . $count );
$posts = get_posts(
array(
'post_type' => array( 'post', 'page' ),
'post_status' => 'publish',
'posts_per_page' => $count * 3,
'orderby' => 'rand',
)
);
error_log( 'MPSS: get_posts returned ' . count( $posts ) . ' posts' );
$queries = array();
foreach ( $posts as $post ) {
if ( count( $queries ) >= $count ) {
break;
}
// Get words from title
$words = array_filter( explode( ' ', $post->post_title ) );
if ( empty( $words ) ) {
continue;
}
// 50% single word, 50% two words
if ( mt_rand( 1, 2 ) === 1 || count( $words ) === 1 ) {
$queries[] = $words[ array_rand( $words ) ];
} else {
$start = mt_rand( 0, count( $words ) - 2 );
$queries[] = $words[ $start ] . ' ' . $words[ $start + 1 ];
}
}
// If we don't have enough, pad with generic searches
while ( count( $queries ) < $count ) {
$queries[] = 'test';
}
$final_queries = array_slice( $queries, 0, $count );
error_log( 'MPSS: generate_queries returning ' . count( $final_queries ) . ' queries' );
return $final_queries;
}
}