Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
278
native/wordpress/README.md
Normal file
278
native/wordpress/README.md
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
# 🔌 WordPress Native Applications
|
||||
|
||||
This directory contains native WordPress applications (plugins and themes) for the Maple Open Technologies ecosystem.
|
||||
|
||||
## 🗂️ Directory Structure
|
||||
|
||||
```
|
||||
native/wordpress/
|
||||
└── maplepress-plugin/ # MaplePress cloud services plugin
|
||||
```
|
||||
|
||||
## 🚀 MaplePress Plugin
|
||||
|
||||
The MaplePress plugin connects your WordPress site to the MaplePress cloud platform, providing cloud-powered services that offload computationally intensive tasks to remote infrastructure. Currently features advanced search capabilities, with more services coming soon (uploads, metrics, analytics, etc.).
|
||||
|
||||
### Features
|
||||
|
||||
- **Cloud-powered search** - Offsite search processing with advanced indexing
|
||||
- **Automatic content indexing** - Your content is automatically synced
|
||||
- **Real-time synchronization** - Changes are reflected immediately
|
||||
- **API key authentication** - Secure connection to MaplePress backend
|
||||
- **WordPress admin settings** - Easy configuration and monitoring
|
||||
- **Extensible platform** - Ready for additional cloud services as they become available
|
||||
|
||||
### Development Workflow
|
||||
|
||||
#### Local Development with Docker
|
||||
|
||||
The plugin is automatically mounted into the local WordPress container for development:
|
||||
|
||||
```bash
|
||||
# Start infrastructure (from cloud/infrastructure/development)
|
||||
cd ../../cloud/infrastructure/development
|
||||
task dev:start
|
||||
|
||||
# WordPress will be available at http://localhost:8081
|
||||
# Plugin is auto-mounted at /wp-content/plugins/maplepress-plugin
|
||||
```
|
||||
|
||||
#### First Time Setup
|
||||
|
||||
Configure the plugin for local development:
|
||||
|
||||
```bash
|
||||
cd maplepress-plugin
|
||||
|
||||
# Set up local development environment
|
||||
task dev:setup
|
||||
|
||||
# This will:
|
||||
# 1. Create wp-config.local.php with API URL = http://localhost:8000
|
||||
# 2. Inject loader into WordPress wp-config.php
|
||||
# 3. Pre-configure API URL for local development
|
||||
|
||||
# Now activate the plugin in WordPress:
|
||||
# - Go to http://localhost:8081/wp-admin/plugins.php
|
||||
# - Activate "MaplePress" plugin
|
||||
# - Go to Settings → MaplePress
|
||||
# - API URL will be pre-filled with http://localhost:8000
|
||||
# - Enter your API key and save
|
||||
```
|
||||
|
||||
**Why this approach?**
|
||||
- Production plugin distributed via WordPress.org has **empty API URL** (users must configure)
|
||||
- Local development needs **http://localhost:8000** pre-configured for convenience
|
||||
- Can't use `.env` files (not part of plugin distribution)
|
||||
- Solution: Local-only config file (`wp-config.local.php`) that is git-ignored and excluded from builds
|
||||
|
||||
#### Plugin Development Commands
|
||||
|
||||
```bash
|
||||
cd maplepress-plugin
|
||||
|
||||
# Set up local development (first time)
|
||||
task dev:setup
|
||||
|
||||
# Reset to production configuration (removes local dev settings)
|
||||
task dev:reset
|
||||
|
||||
# Sync plugin to WordPress container (manual sync)
|
||||
task sync
|
||||
|
||||
# Watch for changes and auto-sync
|
||||
task watch
|
||||
|
||||
# View WordPress debug logs
|
||||
task logs
|
||||
|
||||
# Open shell in WordPress container
|
||||
task shell
|
||||
|
||||
# Run PHP CodeSniffer
|
||||
task lint
|
||||
|
||||
# Auto-fix coding standards issues
|
||||
task lint:fix
|
||||
|
||||
# Run tests
|
||||
task test
|
||||
|
||||
# Build distribution ZIP
|
||||
task build
|
||||
|
||||
# Clean build artifacts
|
||||
task clean
|
||||
|
||||
# Install Composer dependencies
|
||||
task install
|
||||
```
|
||||
|
||||
### Initial Setup
|
||||
|
||||
1. **Start WordPress:**
|
||||
```bash
|
||||
cd cloud/infrastructure/development
|
||||
task dev:start
|
||||
```
|
||||
|
||||
2. **Complete WordPress Installation:**
|
||||
- Visit http://localhost:8081
|
||||
- Complete the WordPress setup wizard
|
||||
- Create admin account
|
||||
|
||||
3. **Activate Plugin:**
|
||||
- Go to Plugins → Installed Plugins
|
||||
- Activate "MaplePress"
|
||||
|
||||
4. **Configure Plugin:**
|
||||
- Go to Settings → MaplePress
|
||||
- Enter API URL: `http://maplepress-backend:8000` (or your backend URL)
|
||||
- Enter your API key from the MaplePress dashboard
|
||||
- Enable MaplePress and save settings
|
||||
|
||||
### Plugin Structure
|
||||
|
||||
```
|
||||
maplepress-plugin/
|
||||
├── maplepress-plugin.php # Main plugin file
|
||||
├── readme.txt # WordPress.org readme
|
||||
├── composer.json # PHP dependencies
|
||||
├── Taskfile.yml # Development tasks
|
||||
├── includes/ # Core plugin logic
|
||||
│ ├── class-maplepress.php # Main plugin class
|
||||
│ ├── class-maplepress-loader.php # Hook loader
|
||||
│ ├── class-maplepress-activator.php # Activation logic
|
||||
│ ├── class-maplepress-deactivator.php # Deactivation logic
|
||||
│ ├── class-maplepress-admin.php # Admin functionality
|
||||
│ ├── class-maplepress-public.php # Public functionality
|
||||
│ ├── class-maplepress-api-client.php # API client
|
||||
│ └── admin-settings-display.php # Settings page template
|
||||
├── assets/ # Frontend assets
|
||||
│ ├── css/
|
||||
│ │ ├── maplepress-admin.css
|
||||
│ │ └── maplepress-public.css
|
||||
│ └── js/
|
||||
│ ├── maplepress-admin.js
|
||||
│ └── maplepress-public.js
|
||||
├── languages/ # Translation files
|
||||
└── tests/ # PHPUnit tests
|
||||
```
|
||||
|
||||
### Publishing to WordPress.org
|
||||
|
||||
1. **Prepare for Release:**
|
||||
```bash
|
||||
# Update version in:
|
||||
# - maplepress-plugin.php (header and constant)
|
||||
# - readme.txt (Stable tag)
|
||||
|
||||
# Build distribution package
|
||||
task build
|
||||
```
|
||||
|
||||
2. **Create Release:**
|
||||
- Distribution ZIP will be in `dist/maplepress-plugin.zip`
|
||||
- Test the ZIP in a fresh WordPress installation
|
||||
|
||||
3. **Submit to WordPress.org:**
|
||||
- Create account at https://wordpress.org/plugins/developers/
|
||||
- Submit plugin for review
|
||||
- Follow WordPress plugin guidelines
|
||||
|
||||
### API Integration
|
||||
|
||||
The plugin communicates with the MaplePress backend using the API client:
|
||||
|
||||
```php
|
||||
// Example: Verify API connection
|
||||
$api_client = new MaplePress_API_Client( $api_url, $api_key );
|
||||
$status = $api_client->verify_connection();
|
||||
|
||||
// Example: Index a post
|
||||
$api_client->index_post( $post_id );
|
||||
|
||||
// Example: Search
|
||||
$results = $api_client->search( 'search query' );
|
||||
```
|
||||
|
||||
### WordPress Coding Standards
|
||||
|
||||
The plugin follows WordPress Coding Standards:
|
||||
|
||||
```bash
|
||||
# Check coding standards
|
||||
task lint
|
||||
|
||||
# Auto-fix issues
|
||||
task lint:fix
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run PHPUnit tests
|
||||
task test
|
||||
|
||||
# Test in local WordPress
|
||||
# 1. Make changes to plugin code
|
||||
# 2. Refresh WordPress admin to see changes
|
||||
# 3. Check debug logs: task logs
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
WordPress debug mode is enabled in the development environment:
|
||||
|
||||
```bash
|
||||
# View debug logs
|
||||
task logs
|
||||
|
||||
# Or directly:
|
||||
docker exec -it maple-wordpress-dev tail -f /var/www/html/wp-content/debug.log
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
The plugin uses WordPress options for configuration:
|
||||
|
||||
- `maplepress_settings['api_url']` - MaplePress backend URL
|
||||
- `maplepress_settings['api_key']` - Site API key
|
||||
- `maplepress_settings['site_id']` - Site ID (auto-populated)
|
||||
- `maplepress_settings['enabled']` - Enable/disable plugin
|
||||
|
||||
### Docker Volume Mount
|
||||
|
||||
The plugin is mounted as a read-only volume in docker-compose.dev.yml:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ../../native/wordpress/maplepress-plugin:/var/www/html/wp-content/plugins/maplepress-plugin:ro
|
||||
```
|
||||
|
||||
This allows live editing without rebuilding the container.
|
||||
|
||||
## ➕ Adding New WordPress Plugins
|
||||
|
||||
To add additional WordPress plugins to the monorepo:
|
||||
|
||||
1. Create new directory: `native/wordpress/new-plugin-name/`
|
||||
2. Follow WordPress plugin structure
|
||||
3. Add Taskfile.yml for development workflow
|
||||
4. Mount in docker-compose.dev.yml if needed for local development
|
||||
5. Update this README
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- [WordPress Plugin Handbook](https://developer.wordpress.org/plugins/)
|
||||
- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/)
|
||||
- [Plugin API Reference](https://developer.wordpress.org/reference/)
|
||||
- [WordPress.org Plugin Guidelines](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/)
|
||||
|
||||
## Contributing
|
||||
|
||||
Found a bug? Want a feature to improve the WordPress plugins? Please create an [issue](https://codeberg.org/mapleopentech/monorepo/issues/new).
|
||||
|
||||
## License
|
||||
|
||||
This application is licensed under the [**GNU Affero General Public License v3.0**](https://opensource.org/license/agpl-v3). See [LICENSE](../../LICENSE) for more information.
|
||||
26
native/wordpress/maplepress-plugin/.gitignore
vendored
Normal file
26
native/wordpress/maplepress-plugin/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Local development configuration
|
||||
wp-config.local.php
|
||||
|
||||
# Composer
|
||||
/vendor/
|
||||
composer.lock
|
||||
|
||||
# Node
|
||||
/node_modules/
|
||||
package-lock.json
|
||||
|
||||
# Build artifacts
|
||||
/dist/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# WordPress
|
||||
*.log
|
||||
104
native/wordpress/maplepress-plugin/CHANGELOG.md
Normal file
104
native/wordpress/maplepress-plugin/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# MaplePress Plugin Changelog
|
||||
|
||||
## [1.0.0] - 2024-10-27
|
||||
|
||||
### Added - API Key Authentication Flow
|
||||
|
||||
#### Activation Experience
|
||||
- Plugin now redirects to settings page upon activation
|
||||
- Setup wizard shows welcome message with step-by-step instructions
|
||||
- Admin notice appears on all pages until API key is configured
|
||||
- Links to external MaplePress dashboard for account creation
|
||||
|
||||
#### Settings Page Enhancements
|
||||
- **Welcome Screen**: First-time users see detailed setup instructions
|
||||
- **External Dashboard Links**:
|
||||
- Sign Up button: `https://getmaplepress.com/register`
|
||||
- Login button: `https://getmaplepress.com/login`
|
||||
- Dashboard link: `https://getmaplepress.com/dashboard`
|
||||
- **API Key Validation**:
|
||||
- Validates key with backend on save
|
||||
- Shows clear success/error messages
|
||||
- Displays site details after successful connection
|
||||
- **Connection Status Display**:
|
||||
- Site ID
|
||||
- Tenant ID
|
||||
- Domain
|
||||
- Plan tier
|
||||
- Active/Inactive status indicator
|
||||
|
||||
#### API Integration
|
||||
- Enhanced API client error handling
|
||||
- Stores site details from `/api/v1/plugin/status` response:
|
||||
- `site_id`
|
||||
- `tenant_id`
|
||||
- `domain`
|
||||
- `plan_tier`
|
||||
- `is_verified`
|
||||
- Validates API key on every settings save
|
||||
- Clear error messages for connection failures
|
||||
|
||||
#### Admin Experience
|
||||
- **Admin Notice**: Persistent warning banner when setup is incomplete
|
||||
- **Auto-redirect**: Redirects to settings page after activation
|
||||
- **Smart Display**: Notices don't show on settings page itself
|
||||
- **Status Indicators**: Visual feedback (✓/✗ and colored dots)
|
||||
|
||||
### Technical Changes
|
||||
|
||||
#### New Fields in Settings
|
||||
- `tenant_id` - Tenant UUID from API
|
||||
- `domain` - Site domain from API
|
||||
- `plan_tier` - User's subscription plan
|
||||
- `is_verified` - Boolean flag for valid API key
|
||||
- `needs_setup` - Boolean flag to trigger setup notices
|
||||
|
||||
#### Modified Files
|
||||
- `includes/class-maplepress-activator.php`
|
||||
- Added new settings fields
|
||||
- Default API URL set to `http://localhost:8000`
|
||||
- Added activation redirect transient
|
||||
|
||||
- `includes/class-maplepress-admin.php`
|
||||
- Added `display_admin_notices()` method
|
||||
- Added `activation_redirect()` method
|
||||
- Enhanced `validate_settings()` with site details storage
|
||||
- Better error handling and user feedback
|
||||
|
||||
- `includes/admin-settings-display.php`
|
||||
- Complete redesign of settings page
|
||||
- Conditional display based on connection status
|
||||
- Welcome wizard for first-time setup
|
||||
- Site details table for connected sites
|
||||
|
||||
### User Workflow
|
||||
|
||||
1. **Install & Activate**
|
||||
- Plugin activates → redirects to settings page
|
||||
- Sees welcome message with instructions
|
||||
|
||||
2. **Get API Key**
|
||||
- Clicks "Sign Up at MaplePress.io"
|
||||
- Creates account and site in external dashboard
|
||||
- Copies API key (shown only once)
|
||||
|
||||
3. **Configure Plugin**
|
||||
- Pastes API key in settings
|
||||
- Clicks "Save Settings & Verify Connection"
|
||||
- Plugin validates key with backend
|
||||
- Success: Shows site details and status
|
||||
- Failure: Shows error message
|
||||
|
||||
4. **Ongoing Use**
|
||||
- If not configured: Admin notice appears on all pages
|
||||
- If configured: Status shows in settings page
|
||||
- Can manage sites from external dashboard
|
||||
|
||||
### API Endpoints Used
|
||||
- `GET /api/v1/plugin/status` - Verify API key and get site details
|
||||
|
||||
### Security
|
||||
- API keys stored in WordPress options table
|
||||
- All user input sanitized and escaped
|
||||
- API communication over HTTPS (production)
|
||||
- WordPress nonces for form submissions
|
||||
745
native/wordpress/maplepress-plugin/GETTING-STARTED.md
Normal file
745
native/wordpress/maplepress-plugin/GETTING-STARTED.md
Normal file
|
|
@ -0,0 +1,745 @@
|
|||
# MaplePress Plugin - Getting Started Guide
|
||||
|
||||
This guide will walk you through setting up the MaplePress plugin for both local development and production release. No prior WordPress experience required!
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [What is WordPress?](#what-is-wordpress)
|
||||
2. [Local Development Setup](#local-development-setup)
|
||||
3. [Production Release Workflow](#production-release-workflow)
|
||||
4. [Common Tasks](#common-tasks)
|
||||
5. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## What is WordPress?
|
||||
|
||||
**WordPress** is a content management system (CMS) written in PHP. It powers over 40% of websites on the internet.
|
||||
|
||||
**WordPress Plugin** = A piece of software that adds specific functionality to a WordPress site. Our MaplePress plugin adds cloud-powered search functionality.
|
||||
|
||||
**Key WordPress Concepts:**
|
||||
- **wp-admin** = WordPress admin dashboard (where you configure plugins)
|
||||
- **wp-config.php** = WordPress configuration file (like a .env file for WordPress)
|
||||
- **wp-content/plugins/** = Directory where all plugins are stored
|
||||
- **Activation** = Turning on a plugin in WordPress
|
||||
- **Settings Page** = Admin interface where users configure your plugin
|
||||
|
||||
---
|
||||
|
||||
## Local Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Make sure you have:
|
||||
- Docker running
|
||||
- Task (taskfile.dev) installed
|
||||
- MaplePress backend running
|
||||
|
||||
### Step 1: Start WordPress
|
||||
|
||||
```bash
|
||||
# From the infrastructure directory
|
||||
cd cloud/infrastructure/development
|
||||
|
||||
# Start all services (includes WordPress)
|
||||
task dev:start
|
||||
|
||||
# OR if using docker-compose directly:
|
||||
docker-compose -f docker-compose.dev.yml up -d
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
- Starts MariaDB (WordPress database) on port 3306
|
||||
- Starts WordPress on port 8081
|
||||
- Starts Cassandra cluster (3 nodes) for backend
|
||||
- Starts Redis for caching
|
||||
- Starts Meilisearch for search indexing
|
||||
- Starts SeaweedFS for object storage
|
||||
- Mounts your plugin directory into WordPress
|
||||
|
||||
**Note:** This starts the entire infrastructure, not just WordPress. The `task dev:start` command will:
|
||||
1. Start all Docker containers
|
||||
2. Wait for services to be healthy (this can take 1-2 minutes)
|
||||
3. Initialize Cassandra keyspaces
|
||||
4. Show you the status of all running services
|
||||
|
||||
**Access WordPress:**
|
||||
- Frontend: http://localhost:8081
|
||||
- Admin: http://localhost:8081/wp-admin
|
||||
|
||||
**Other Services (for backend development):**
|
||||
- Backend API: http://localhost:8000 (when you start maplepress-backend)
|
||||
- Meilisearch: http://localhost:7700
|
||||
- SeaweedFS: http://localhost:9333
|
||||
|
||||
### Step 2: Initial WordPress Setup (First Time Only)
|
||||
|
||||
If this is your first time, WordPress will ask you to set it up:
|
||||
|
||||
1. Open http://localhost:8081 in your browser
|
||||
2. Select language (English)
|
||||
3. Click "Let's go!"
|
||||
4. Database settings are already configured (from docker-compose.dev.yml):
|
||||
- Database Name: `wordpress`
|
||||
- Username: `wordpress`
|
||||
- Password: `wordpress`
|
||||
- Database Host: `mariadb` (the container name)
|
||||
- Table Prefix: `wp_` (leave default)
|
||||
5. Click "Submit" then "Run the installation"
|
||||
6. Create admin user:
|
||||
- Site Title: "MaplePress Development"
|
||||
- Username: `admin` (or your choice)
|
||||
- Password: (choose a strong password)
|
||||
- Email: your email
|
||||
7. Click "Install WordPress"
|
||||
8. Login with your admin credentials
|
||||
|
||||
**You only need to do this once.** WordPress saves everything in the database.
|
||||
|
||||
### Step 3: Configure Plugin for Local Development
|
||||
|
||||
```bash
|
||||
# From the plugin directory
|
||||
cd native/wordpress/maplepress-plugin
|
||||
|
||||
# Run the setup command
|
||||
task dev:setup
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
1. Creates `wp-config.local.php` with `MAPLEPRESS_API_URL` set to `http://localhost:8000`
|
||||
2. Modifies WordPress `wp-config.php` to load your local config
|
||||
3. Pre-configures the plugin to connect to your local backend
|
||||
|
||||
**Output:**
|
||||
```
|
||||
Created wp-config.local.php
|
||||
Added loader to wp-config.php
|
||||
Local development environment configured
|
||||
API URL pre-configured to http://localhost:8000
|
||||
Go to Settings > MaplePress to enter your API key
|
||||
```
|
||||
|
||||
### Step 4: Activate the Plugin in WordPress
|
||||
|
||||
1. Go to http://localhost:8081/wp-admin/plugins.php
|
||||
2. Find "MaplePress" in the plugin list
|
||||
3. Click "Activate"
|
||||
4. You'll be redirected to Settings → MaplePress
|
||||
|
||||
### Step 5: Enter Your API Key
|
||||
|
||||
1. You should now be on the MaplePress settings page
|
||||
2. You'll see:
|
||||
- **API URL** - Pre-filled with `http://localhost:8000` ✓
|
||||
- **API Key** - Empty (you need to enter this)
|
||||
3. Get your API key:
|
||||
- If you don't have one yet, follow the backend setup guide:
|
||||
- See `cloud/maplepress-backend/GETTING-STARTED.md` → "Create Test Data"
|
||||
- Or use the quick commands below:
|
||||
|
||||
```bash
|
||||
# Register a user and create a site (returns API key)
|
||||
# See backend GETTING-STARTED.md for detailed instructions
|
||||
curl -X POST http://localhost:8000/api/v1/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"test@example.com","password":"TestPassword123!","name":"Test User","tenant_name":"Test Org","tenant_slug":"test-org"}'
|
||||
|
||||
# Then create a site (save the API key from response)
|
||||
curl -X POST http://localhost:8000/api/v1/sites \
|
||||
-H "Authorization: JWT YOUR_TOKEN_HERE" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"domain":"localhost:8081","site_url":"http://localhost:8081","plan_tier":"free"}'
|
||||
```
|
||||
4. Paste the API key
|
||||
5. Click "Save Settings & Verify Connection"
|
||||
|
||||
**If successful:**
|
||||
- Green message: "✓ API connection verified successfully!"
|
||||
- You'll see your site details (Site ID, Tenant ID, Domain, Plan)
|
||||
- Status shows "Active"
|
||||
|
||||
**If it fails:**
|
||||
- Red error message explaining what went wrong
|
||||
- Check that backend is running: `curl http://localhost:8000/health`
|
||||
- Check API key is valid
|
||||
- See backend docs: `cloud/maplepress-backend/GETTING-STARTED.md`
|
||||
|
||||
### Step 6: Start Developing
|
||||
|
||||
Your local development environment is now ready!
|
||||
|
||||
**Making code changes:**
|
||||
|
||||
Option 1: Auto-sync (Recommended)
|
||||
```bash
|
||||
cd native/wordpress/maplepress-plugin
|
||||
|
||||
# Watch for changes and auto-sync
|
||||
task watch
|
||||
```
|
||||
This will automatically copy changes to WordPress whenever you save a file.
|
||||
|
||||
Option 2: Manual sync
|
||||
```bash
|
||||
# After making changes, sync manually
|
||||
task sync
|
||||
```
|
||||
|
||||
Option 3: Direct editing (Advanced)
|
||||
- Plugin is mounted as a volume, so changes are usually reflected immediately
|
||||
- Just refresh your browser after editing PHP files
|
||||
- Some changes require plugin reactivation
|
||||
|
||||
**Viewing logs:**
|
||||
```bash
|
||||
# See WordPress debug logs
|
||||
task logs
|
||||
|
||||
# Access WordPress container shell
|
||||
task shell
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Production Release Workflow
|
||||
|
||||
### Understanding Production vs Development
|
||||
|
||||
**Development:**
|
||||
- API URL: `http://localhost:8000` (your local backend)
|
||||
- API Key: Your test API key
|
||||
- Config file: `wp-config.local.php` (git-ignored)
|
||||
|
||||
**Production:**
|
||||
- API URL: `https://getmaplepress.ca` (production backend)
|
||||
- API Key: User enters their own API key from dashboard
|
||||
- Config file: None (users configure via settings)
|
||||
|
||||
### Step 1: Prepare for Release
|
||||
|
||||
**Clean up local config:**
|
||||
```bash
|
||||
cd native/wordpress/maplepress-plugin
|
||||
|
||||
# Remove local development settings
|
||||
task dev:reset
|
||||
```
|
||||
|
||||
This removes:
|
||||
- `wp-config.local.php` (local config file)
|
||||
- Loader code from WordPress `wp-config.php`
|
||||
|
||||
**Verify production behavior:**
|
||||
1. Deactivate the plugin in WordPress
|
||||
2. Delete plugin options: Settings → MaplePress → Delete (if there's an option)
|
||||
3. Reactivate the plugin
|
||||
4. Go to Settings → MaplePress
|
||||
5. Both API URL and API Key should be **empty** ✓
|
||||
|
||||
This is what users will see when they install your plugin.
|
||||
|
||||
### Step 2: Build Production Zip
|
||||
|
||||
```bash
|
||||
cd native/wordpress/maplepress-plugin
|
||||
|
||||
# Build the distribution package
|
||||
task build
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
1. Creates `dist/` directory
|
||||
2. Copies all plugin files EXCEPT:
|
||||
- `wp-config.local.php` (local dev config)
|
||||
- `.git/` (version control)
|
||||
- `node_modules/` (dependencies)
|
||||
- `Taskfile.yml` (development tool)
|
||||
- `composer.json/lock` (PHP dependencies)
|
||||
- `.gitignore` (git config)
|
||||
- `dist/` (avoid nested builds)
|
||||
3. Creates `dist/maplepress-plugin.zip`
|
||||
|
||||
**Output:**
|
||||
```
|
||||
Plugin built at dist/maplepress-plugin.zip
|
||||
Local dev files excluded from build
|
||||
```
|
||||
|
||||
### Step 3: Test Production Build Locally (Optional but Recommended)
|
||||
|
||||
Before releasing, test the production build:
|
||||
|
||||
```bash
|
||||
# Extract and verify contents
|
||||
cd dist
|
||||
unzip -l maplepress-plugin.zip
|
||||
|
||||
# Should NOT contain:
|
||||
# - wp-config.local.php
|
||||
# - Taskfile.yml
|
||||
# - composer.json
|
||||
# - .gitignore
|
||||
# - .git/
|
||||
|
||||
# Should contain:
|
||||
# - maplepress-plugin.php (main file)
|
||||
# - includes/ (PHP classes)
|
||||
# - assets/ (CSS/JS)
|
||||
# - readme.txt (WordPress.org readme)
|
||||
# - CHANGELOG.md, TESTING.md, etc.
|
||||
```
|
||||
|
||||
**Test installation:**
|
||||
1. Copy the zip to a clean WordPress install (or delete current plugin)
|
||||
2. Go to Plugins → Add New → Upload Plugin
|
||||
3. Choose `dist/maplepress-plugin.zip`
|
||||
4. Click "Install Now"
|
||||
5. Activate the plugin
|
||||
6. Verify settings page shows:
|
||||
- Empty API URL field ✓
|
||||
- Empty API Key field ✓
|
||||
- Placeholder: `https://getmaplepress.ca` ✓
|
||||
|
||||
### Step 4: Release to WordPress.org
|
||||
|
||||
**WordPress Plugin Directory Submission:**
|
||||
|
||||
1. **Create WordPress.org Account**
|
||||
- Go to https://wordpress.org/support/register.php
|
||||
- Create an account (you'll need this to submit plugins)
|
||||
|
||||
2. **Submit Plugin for Review**
|
||||
- Go to https://wordpress.org/plugins/developers/add/
|
||||
- Upload your `dist/maplepress-plugin.zip`
|
||||
- Wait for manual review (can take 2-14 days)
|
||||
- You'll receive an email with SVN repository access
|
||||
|
||||
3. **Using SVN (Subversion)**
|
||||
|
||||
WordPress.org uses SVN, not Git:
|
||||
|
||||
```bash
|
||||
# Checkout your plugin's SVN repository
|
||||
# (URL provided in approval email)
|
||||
svn co https://plugins.svn.wordpress.org/maplepress YOUR_LOCAL_DIR
|
||||
cd YOUR_LOCAL_DIR
|
||||
|
||||
# Directory structure:
|
||||
# /trunk/ - Development version
|
||||
# /tags/ - Released versions (1.0.0, 1.0.1, etc.)
|
||||
# /assets/ - Screenshots, banners, icons
|
||||
```
|
||||
|
||||
4. **Initial Release (Version 1.0.0)**
|
||||
|
||||
```bash
|
||||
# Extract your plugin to /trunk/
|
||||
cd trunk
|
||||
unzip ../../dist/maplepress-plugin.zip
|
||||
mv maplepress-plugin/* .
|
||||
rmdir maplepress-plugin
|
||||
|
||||
# Add files to SVN
|
||||
svn add --force * --auto-props --parents --depth infinity -q
|
||||
|
||||
# Commit to trunk
|
||||
svn ci -m "Initial release v1.0.0"
|
||||
|
||||
# Create a tag for this version
|
||||
cd ..
|
||||
svn cp trunk tags/1.0.0
|
||||
svn ci -m "Tagging version 1.0.0"
|
||||
```
|
||||
|
||||
5. **Add Screenshots and Assets**
|
||||
|
||||
```bash
|
||||
# Add to /assets/ directory
|
||||
cd assets
|
||||
|
||||
# Required files:
|
||||
# - icon-128x128.png (plugin icon)
|
||||
# - icon-256x256.png (retina icon)
|
||||
# - screenshot-1.png (settings page)
|
||||
# - screenshot-2.png (search widget)
|
||||
# - banner-772x250.png (plugin page banner)
|
||||
# - banner-1544x500.png (retina banner)
|
||||
|
||||
svn add *.png
|
||||
svn ci -m "Add plugin assets"
|
||||
```
|
||||
|
||||
### Step 5: Version Updates
|
||||
|
||||
When releasing a new version:
|
||||
|
||||
```bash
|
||||
# 1. Update version in plugin files
|
||||
# Edit maplepress-plugin.php:
|
||||
# Version: 1.0.1
|
||||
|
||||
# Edit readme.txt:
|
||||
# Stable tag: 1.0.1
|
||||
|
||||
# 2. Build new release
|
||||
cd native/wordpress/maplepress-plugin
|
||||
task build
|
||||
|
||||
# 3. Update SVN trunk
|
||||
cd YOUR_SVN_DIR/trunk
|
||||
# Replace files with new build
|
||||
unzip ../../dist/maplepress-plugin.zip
|
||||
mv maplepress-plugin/* .
|
||||
|
||||
# Commit to trunk
|
||||
svn stat # See what changed
|
||||
svn ci -m "Update to version 1.0.1"
|
||||
|
||||
# 4. Create new tag
|
||||
cd ..
|
||||
svn cp trunk tags/1.0.1
|
||||
svn ci -m "Tagging version 1.0.1"
|
||||
```
|
||||
|
||||
WordPress.org will automatically update users' plugins within 24 hours.
|
||||
|
||||
---
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Switch Between Local Dev and Production Testing
|
||||
|
||||
**Enable local development:**
|
||||
```bash
|
||||
task dev:setup
|
||||
# Restart WordPress
|
||||
docker-compose -f ../../cloud/infrastructure/development/docker-compose.dev.yml restart wordpress
|
||||
```
|
||||
|
||||
**Test production behavior:**
|
||||
```bash
|
||||
task dev:reset
|
||||
# Deactivate/reactivate plugin in WordPress
|
||||
```
|
||||
|
||||
### View Plugin in WordPress
|
||||
|
||||
- **Plugins page:** http://localhost:8081/wp-admin/plugins.php
|
||||
- **Settings page:** http://localhost:8081/wp-admin/options-general.php?page=maplepress
|
||||
|
||||
### Check If Plugin Is Active
|
||||
|
||||
```bash
|
||||
# From container
|
||||
docker exec maple-wordpress-dev wp plugin list
|
||||
|
||||
# Should show:
|
||||
# name status
|
||||
# maplepress active
|
||||
```
|
||||
|
||||
### Manually Inspect Database
|
||||
|
||||
```bash
|
||||
# Connect to MariaDB
|
||||
docker exec -it maple-mariadb-dev mysql -u wordpress -pwordpress wordpress
|
||||
|
||||
# Check plugin options
|
||||
SELECT * FROM wp_options WHERE option_name = 'maplepress_settings';
|
||||
|
||||
# Exit
|
||||
exit
|
||||
```
|
||||
|
||||
### Reset WordPress Completely
|
||||
|
||||
```bash
|
||||
# Stop containers
|
||||
docker-compose -f docker-compose.dev.yml down
|
||||
|
||||
# Delete volumes (WARNING: Deletes all data)
|
||||
docker volume rm maple-wordpress-dev
|
||||
docker volume rm maple-mariadb-dev
|
||||
|
||||
# Start fresh
|
||||
docker-compose -f docker-compose.dev.yml up -d
|
||||
|
||||
# You'll need to go through WordPress setup again
|
||||
```
|
||||
|
||||
### Check Plugin Files in Container
|
||||
|
||||
```bash
|
||||
# Access container shell
|
||||
task shell
|
||||
|
||||
# Inside container:
|
||||
cd /var/www/html/wp-content/plugins/maplepress-plugin
|
||||
ls -la
|
||||
|
||||
# Check if wp-config.local.php exists
|
||||
cat wp-config.local.php # Should show MAPLEPRESS_API_URL
|
||||
|
||||
# Check WordPress config has loader
|
||||
grep -A2 "MaplePress local" /var/www/html/wp-config.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Task does not exist" Error
|
||||
|
||||
**Error:** `task: Task "dev" does not exist`
|
||||
|
||||
**Solution:** The correct command is `task dev:start` (not `task dev`)
|
||||
|
||||
```bash
|
||||
cd cloud/infrastructure/development
|
||||
task dev:start # ✓ Correct
|
||||
# NOT: task dev # ✗ Wrong
|
||||
```
|
||||
|
||||
**Available infrastructure tasks:**
|
||||
- `task dev:start` - Start all services
|
||||
- `task dev:stop` - Stop all services
|
||||
- `task dev:status` - Show service status
|
||||
- `task dev:restart` - Restart all services
|
||||
- `task dev:logs` - View logs
|
||||
- `task dev:clean` - Remove all data (destructive)
|
||||
|
||||
### "Cannot connect to API" Error
|
||||
|
||||
**Possible causes:**
|
||||
|
||||
1. **Backend not running**
|
||||
```bash
|
||||
# Check if backend is up
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# Should return: {"status":"healthy"}
|
||||
# If not, start backend:
|
||||
cd cloud/maplepress-backend
|
||||
task dev
|
||||
|
||||
# For detailed setup, see: cloud/maplepress-backend/GETTING-STARTED.md
|
||||
```
|
||||
|
||||
2. **Wrong API URL**
|
||||
- Check Settings → MaplePress
|
||||
- For local dev, should be: `http://localhost:8000`
|
||||
- Not `localhost:8081` (that's WordPress)
|
||||
- Not `https://` (local dev is http)
|
||||
|
||||
3. **Invalid API Key**
|
||||
- API key must exist in backend database
|
||||
- Check backend logs for authentication errors
|
||||
|
||||
### "Plugin not showing up"
|
||||
|
||||
1. **Check plugin directory:**
|
||||
```bash
|
||||
docker exec maple-wordpress-dev ls -la /var/www/html/wp-content/plugins/
|
||||
```
|
||||
Should see `maplepress-plugin/`
|
||||
|
||||
2. **Check main plugin file exists:**
|
||||
```bash
|
||||
docker exec maple-wordpress-dev ls -la /var/www/html/wp-content/plugins/maplepress-plugin/maplepress-plugin.php
|
||||
```
|
||||
|
||||
3. **Check plugin header:**
|
||||
```bash
|
||||
docker exec maple-wordpress-dev head -n 15 /var/www/html/wp-content/plugins/maplepress-plugin/maplepress-plugin.php
|
||||
```
|
||||
Should show "Plugin Name: MaplePress"
|
||||
|
||||
4. **Restart WordPress:**
|
||||
```bash
|
||||
docker-compose -f docker-compose.dev.yml restart wordpress
|
||||
```
|
||||
|
||||
### "API URL not pre-filled" in Local Dev
|
||||
|
||||
1. **Check local config exists:**
|
||||
```bash
|
||||
cat wp-config.local.php
|
||||
```
|
||||
Should show `define('MAPLEPRESS_API_URL', 'http://localhost:8000');`
|
||||
|
||||
2. **Check WordPress config has loader:**
|
||||
```bash
|
||||
docker exec maple-wordpress-dev grep -A2 "MaplePress" /var/www/html/wp-config.php
|
||||
```
|
||||
Should show loader code
|
||||
|
||||
3. **Re-run setup:**
|
||||
```bash
|
||||
task dev:reset
|
||||
task dev:setup
|
||||
```
|
||||
|
||||
4. **Deactivate and reactivate plugin:**
|
||||
- Go to http://localhost:8081/wp-admin/plugins.php
|
||||
- Click "Deactivate" under MaplePress
|
||||
- Click "Activate"
|
||||
- Check settings page again
|
||||
|
||||
### "Changes not showing up"
|
||||
|
||||
**For PHP changes:**
|
||||
1. **Deactivate and reactivate plugin**
|
||||
- WordPress caches plugin code
|
||||
|
||||
2. **Or sync manually:**
|
||||
```bash
|
||||
task sync
|
||||
```
|
||||
|
||||
3. **Or restart WordPress:**
|
||||
```bash
|
||||
docker-compose -f ../../cloud/infrastructure/development/docker-compose.dev.yml restart wordpress
|
||||
```
|
||||
|
||||
**For CSS/JS changes:**
|
||||
- Hard refresh browser: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows)
|
||||
- Or clear browser cache
|
||||
|
||||
### "Permission denied" Errors
|
||||
|
||||
```bash
|
||||
# Fix plugin directory permissions
|
||||
docker exec maple-wordpress-dev chown -R www-data:www-data /var/www/html/wp-content/plugins/maplepress-plugin
|
||||
|
||||
# Restart WordPress
|
||||
docker-compose -f docker-compose.dev.yml restart wordpress
|
||||
```
|
||||
|
||||
### WordPress Shows "Fatal Error"
|
||||
|
||||
**Check PHP error log:**
|
||||
```bash
|
||||
docker exec maple-wordpress-dev tail -f /var/www/html/wp-content/debug.log
|
||||
```
|
||||
|
||||
**Enable WordPress debug mode:**
|
||||
```bash
|
||||
docker exec maple-wordpress-dev bash -c "
|
||||
grep -q 'WP_DEBUG' /var/www/html/wp-config.php ||
|
||||
sed -i \"/That's all, stop editing/i define('WP_DEBUG', true);\ndefine('WP_DEBUG_LOG', true);\ndefine('WP_DEBUG_DISPLAY', false);\" /var/www/html/wp-config.php
|
||||
"
|
||||
```
|
||||
|
||||
**Syntax error in PHP:**
|
||||
- Check PHP syntax: `php -l includes/class-maplepress-admin.php`
|
||||
- Look for missing semicolons, quotes, or brackets
|
||||
|
||||
### "Can't access wp-admin"
|
||||
|
||||
```bash
|
||||
# Reset admin password
|
||||
docker exec maple-wordpress-dev wp user update admin --user_pass=newpassword
|
||||
```
|
||||
|
||||
### Build Includes Local Dev Files
|
||||
|
||||
**Verify build excludes:**
|
||||
```bash
|
||||
cd dist
|
||||
unzip -l maplepress-plugin.zip | grep "wp-config.local"
|
||||
# Should return nothing
|
||||
|
||||
unzip -l maplepress-plugin.zip | grep "Taskfile"
|
||||
# Should return nothing
|
||||
```
|
||||
|
||||
**If they're included, rebuild:**
|
||||
```bash
|
||||
task clean
|
||||
task build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Commands Cheat Sheet
|
||||
|
||||
```bash
|
||||
# Infrastructure
|
||||
cd cloud/infrastructure/development
|
||||
task dev:start # Start all services
|
||||
task dev:stop # Stop all services
|
||||
task dev:status # Show service status
|
||||
task dev:logs # View logs
|
||||
|
||||
# Plugin Development
|
||||
cd native/wordpress/maplepress-plugin
|
||||
task dev:setup # Set up local dev (first time)
|
||||
task dev:reset # Reset to production config
|
||||
task sync # Sync changes to WordPress
|
||||
task watch # Auto-sync on file changes
|
||||
task logs # View WordPress logs
|
||||
task shell # Access WordPress container
|
||||
|
||||
# Production Release
|
||||
task build # Build distribution zip
|
||||
task clean # Clean build artifacts
|
||||
|
||||
# Testing
|
||||
task lint # Check code style
|
||||
task test # Run tests
|
||||
```
|
||||
|
||||
### URLs
|
||||
|
||||
- WordPress Frontend: http://localhost:8081
|
||||
- WordPress Admin: http://localhost:8081/wp-admin
|
||||
- Plugin Settings: http://localhost:8081/wp-admin/options-general.php?page=maplepress
|
||||
- Backend API: http://localhost:8000
|
||||
|
||||
### WordPress CLI (inside container)
|
||||
|
||||
```bash
|
||||
# Access container
|
||||
docker exec -it maple-wordpress-dev /bin/bash
|
||||
|
||||
# WordPress CLI commands
|
||||
wp plugin list # List plugins
|
||||
wp plugin activate maplepress
|
||||
wp plugin deactivate maplepress
|
||||
wp option get maplepress_settings
|
||||
wp option delete maplepress_settings
|
||||
wp user list # List users
|
||||
wp db query "SELECT * FROM wp_options WHERE option_name LIKE 'maplepress%'"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **For Development:**
|
||||
- Follow "Local Development Setup"
|
||||
- Run `task dev:setup`
|
||||
- Start coding!
|
||||
|
||||
2. **For First Release:**
|
||||
- Follow "Production Release Workflow"
|
||||
- Build with `task build`
|
||||
- Submit to WordPress.org
|
||||
|
||||
3. **Learn More:**
|
||||
- WordPress Plugin Handbook: https://developer.wordpress.org/plugins/
|
||||
- WordPress Coding Standards: https://developer.wordpress.org/coding-standards/
|
||||
- Plugin Directory Guidelines: https://developer.wordpress.org/plugins/wordpress-org/
|
||||
|
||||
---
|
||||
|
||||
**Still stuck? Check:**
|
||||
- `TESTING.md` - Testing scenarios and checklist
|
||||
- `CHANGELOG.md` - Recent changes and updates
|
||||
- Backend docs: `cloud/maplepress-backend/GETTING-STARTED.md` and `API.md`
|
||||
226
native/wordpress/maplepress-plugin/TESTING.md
Normal file
226
native/wordpress/maplepress-plugin/TESTING.md
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
# MaplePress Plugin Testing Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- WordPress running at http://localhost:8081
|
||||
- MaplePress backend running at http://localhost:8000 (or update API URL in settings)
|
||||
- WordPress admin access
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### Scenario 1: Fresh Installation (Activation Flow)
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Deactivate the plugin** (if already activated)
|
||||
- Go to WordPress Admin → Plugins
|
||||
- Find "MaplePress"
|
||||
- Click "Deactivate"
|
||||
|
||||
2. **Activate the plugin**
|
||||
- Click "Activate" on MaplePress
|
||||
- **Expected:** Auto-redirect to Settings → MaplePress
|
||||
|
||||
3. **Verify Welcome Screen**
|
||||
- **Expected:** See "🚀 Welcome to MaplePress!" banner
|
||||
- **Expected:** See 4-step setup instructions
|
||||
- **Expected:** See "Sign Up at MaplePress.io" button
|
||||
- **Expected:** See "Login to Existing Account" button
|
||||
|
||||
4. **Verify Admin Notice** (if you navigate away)
|
||||
- Go to Dashboard or any other admin page
|
||||
- **Expected:** See yellow warning banner at top
|
||||
- **Expected:** Message says "MaplePress Setup Required"
|
||||
- **Expected:** Contains links to sign up and configure
|
||||
|
||||
### Scenario 2: API Key Entry (Success Path)
|
||||
|
||||
**Prerequisites:**
|
||||
- You have a valid API key from the backend
|
||||
- To get one:
|
||||
```bash
|
||||
# Use the backend API to register and create a site
|
||||
# Or use existing test API key
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Go to Settings → MaplePress**
|
||||
|
||||
2. **Verify default API URL**
|
||||
- **Expected:** API URL field shows `http://localhost:8000`
|
||||
|
||||
3. **Enter a valid API key**
|
||||
- Paste your API key in the "API Key" field
|
||||
- **Optional:** Check "Enable MaplePress"
|
||||
- Click "Save Settings & Verify Connection"
|
||||
|
||||
4. **Verify Success Response**
|
||||
- **Expected:** Green success message: "✓ API connection verified successfully!"
|
||||
- **Expected:** Welcome banner disappears
|
||||
- **Expected:** New section appears: "✓ Connected to MaplePress"
|
||||
- **Expected:** Site details table shows:
|
||||
- Site ID (UUID)
|
||||
- Tenant ID (UUID)
|
||||
- Domain (your site domain)
|
||||
- Plan (e.g., "Free")
|
||||
- Status (● Active or ● Inactive)
|
||||
- **Expected:** "→ Open MaplePress Dashboard" button appears
|
||||
|
||||
5. **Verify Admin Notice Removed**
|
||||
- Navigate to Dashboard or Posts
|
||||
- **Expected:** Yellow warning banner is GONE
|
||||
|
||||
### Scenario 3: Invalid API Key (Error Path)
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Go to Settings → MaplePress**
|
||||
|
||||
2. **Enter an invalid API key**
|
||||
- Enter: `invalid_key_12345`
|
||||
- Click "Save Settings & Verify Connection"
|
||||
|
||||
3. **Verify Error Response**
|
||||
- **Expected:** Red error message appears
|
||||
- **Expected:** Message says "API Connection Error: [error details]"
|
||||
- **Expected:** Site details table does NOT appear
|
||||
- **Expected:** Plugin status remains "needs setup"
|
||||
|
||||
4. **Verify Admin Notice Persists**
|
||||
- Navigate to Dashboard
|
||||
- **Expected:** Yellow warning banner STILL shows
|
||||
|
||||
### Scenario 4: Empty API Key
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Go to Settings → MaplePress**
|
||||
|
||||
2. **Leave API key field empty**
|
||||
- Click "Save Settings & Verify Connection"
|
||||
|
||||
3. **Verify Behavior**
|
||||
- **Expected:** No error message (just saves empty)
|
||||
- **Expected:** Welcome banner still shows
|
||||
- **Expected:** Admin notice persists on other pages
|
||||
|
||||
### Scenario 5: API Key Update/Change
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Start with a valid, connected API key**
|
||||
- Verify you see "✓ Connected to MaplePress"
|
||||
|
||||
2. **Change to a different valid API key**
|
||||
- Enter new API key
|
||||
- Click "Save Settings & Verify Connection"
|
||||
|
||||
3. **Verify Update**
|
||||
- **Expected:** Success message appears
|
||||
- **Expected:** Site details update to reflect new site
|
||||
|
||||
4. **Change to invalid key**
|
||||
- Enter invalid key
|
||||
- Click save
|
||||
|
||||
5. **Verify Validation**
|
||||
- **Expected:** Error message appears
|
||||
- **Expected:** Connection status changes back to "needs setup"
|
||||
- **Expected:** Admin notice returns
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Plugin activation redirects to settings page
|
||||
- [ ] Welcome banner shows on first visit
|
||||
- [ ] "Sign Up" button links to https://getmaplepress.com/register (opens in new tab)
|
||||
- [ ] "Login" button links to https://getmaplepress.com/login (opens in new tab)
|
||||
- [ ] Admin notice appears on all pages when not configured
|
||||
- [ ] Admin notice does NOT appear on settings page itself
|
||||
- [ ] Admin notice is dismissible
|
||||
- [ ] Valid API key shows success message
|
||||
- [ ] Valid API key populates site details correctly
|
||||
- [ ] Invalid API key shows clear error message
|
||||
- [ ] Site details table shows after successful connection
|
||||
- [ ] Site details include: site_id, tenant_id, domain, plan_tier
|
||||
- [ ] Status indicator shows correct state (Active/Inactive)
|
||||
- [ ] "Open MaplePress Dashboard" button appears when connected
|
||||
- [ ] Dashboard button links to https://getmaplepress.com/dashboard (opens in new tab)
|
||||
- [ ] Admin notice disappears after successful configuration
|
||||
- [ ] Enable checkbox works correctly
|
||||
- [ ] Settings persist after save
|
||||
- [ ] API URL can be changed (for dev vs production)
|
||||
|
||||
## Manual API Key Testing
|
||||
|
||||
If you need to test with a real API key, follow the backend setup guide:
|
||||
|
||||
**See: `cloud/maplepress-backend/GETTING-STARTED.md` → "Create Test Data" section**
|
||||
|
||||
Quick summary:
|
||||
1. Register a user with `POST /api/v1/register`
|
||||
2. Create a site with `POST /api/v1/sites`
|
||||
3. Save the `api_key` from the site creation response
|
||||
|
||||
For detailed curl examples and full instructions, see the backend GETTING-STARTED.md file.
|
||||
|
||||
## Expected Settings Structure
|
||||
|
||||
After successful configuration, WordPress options should contain:
|
||||
|
||||
```php
|
||||
array(
|
||||
'api_url' => 'http://localhost:8000',
|
||||
'api_key' => 'live_sk_...',
|
||||
'site_id' => 'abc-123-def',
|
||||
'tenant_id' => 'xyz-456-uvw',
|
||||
'domain' => 'localhost',
|
||||
'plan_tier' => 'free',
|
||||
'is_verified' => true,
|
||||
'enabled' => true,
|
||||
'needs_setup' => false,
|
||||
)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin doesn't redirect after activation
|
||||
- Make sure you're not activating multiple plugins at once
|
||||
- Try deactivating and reactivating
|
||||
- Check if transient was set: `get_transient('maplepress_activation_redirect')`
|
||||
|
||||
### API connection fails
|
||||
- Verify backend is running: `curl http://localhost:8000/health`
|
||||
- Check API URL in settings matches backend URL
|
||||
- Verify API key is correct and not expired
|
||||
- Check WordPress debug logs: `docker exec -it maple-wordpress-dev tail -f /var/www/html/wp-content/debug.log`
|
||||
- See backend troubleshooting: `cloud/maplepress-backend/GETTING-STARTED.md`
|
||||
|
||||
### Admin notice doesn't disappear
|
||||
- Verify `needs_setup` is set to `false` in settings
|
||||
- Clear WordPress transients/cache
|
||||
- Deactivate and reactivate plugin
|
||||
|
||||
### Site details don't show
|
||||
- Verify API response includes all fields (site_id, tenant_id, domain, plan_tier)
|
||||
- Check backend `/api/v1/plugin/status` endpoint response
|
||||
- Enable WordPress debug mode and check for PHP errors
|
||||
|
||||
## Success Criteria
|
||||
|
||||
✅ User can activate plugin and see setup instructions
|
||||
✅ User can click external links to sign up/login
|
||||
✅ User can enter API key and validate it
|
||||
✅ Valid API key shows success and site details
|
||||
✅ Invalid API key shows clear error
|
||||
✅ Admin notices guide user through setup
|
||||
✅ Connected state shows all site information
|
||||
✅ Plugin can be enabled/disabled after connection
|
||||
|
||||
## Next Steps After Testing
|
||||
|
||||
Once basic authentication flow is verified:
|
||||
1. Test with production backend URL
|
||||
2. Implement content indexing features
|
||||
3. Add search widget functionality
|
||||
4. Build web dashboard for account/site management
|
||||
145
native/wordpress/maplepress-plugin/Taskfile.yml
Normal file
145
native/wordpress/maplepress-plugin/Taskfile.yml
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
version: '3'
|
||||
|
||||
tasks:
|
||||
dev:setup:
|
||||
desc: Set up local development environment
|
||||
cmds:
|
||||
- task create-local-config
|
||||
- task inject-wp-config
|
||||
- echo "Local development environment configured"
|
||||
- echo "API URL pre-configured to http://maplepress-backend-dev:8000"
|
||||
- echo "Go to Settings > MaplePress to enter your API key"
|
||||
|
||||
create-local-config:
|
||||
desc: Create local development config file
|
||||
cmds:
|
||||
- |
|
||||
cat > wp-config.local.php <<'EOF'
|
||||
<?php
|
||||
/**
|
||||
* MaplePress Local Development Configuration
|
||||
*
|
||||
* This file is for LOCAL DEVELOPMENT ONLY.
|
||||
* DO NOT commit this file to git.
|
||||
* DO NOT include in production builds.
|
||||
*/
|
||||
|
||||
// MaplePress Local Development Settings
|
||||
// Uses Docker container hostname for inter-container communication
|
||||
define('MAPLEPRESS_API_URL', 'http://maplepress-backend-dev:8000');
|
||||
EOF
|
||||
- echo "Created wp-config.local.php"
|
||||
|
||||
inject-wp-config:
|
||||
desc: Add local config loader to WordPress wp-config.php
|
||||
cmds:
|
||||
- |
|
||||
docker exec maple-wordpress-dev bash -c '
|
||||
CONFIG_FILE="/var/www/html/wp-config.php"
|
||||
LOADER_LINE="// MaplePress local development config\nif (file_exists(__DIR__ . '\''/wp-content/plugins/maplepress-plugin/wp-config.local.php'\'')) { require_once __DIR__ . '\''/wp-content/plugins/maplepress-plugin/wp-config.local.php'\''; }"
|
||||
|
||||
# Check if already added
|
||||
if grep -q "maplepress-plugin/wp-config.local.php" "$CONFIG_FILE"; then
|
||||
echo "wp-config.php already configured"
|
||||
else
|
||||
# Insert before "That'"'"'s all, stop editing!"
|
||||
sed -i "/That.*s all, stop editing/i $LOADER_LINE" "$CONFIG_FILE"
|
||||
echo "Added loader to wp-config.php"
|
||||
fi
|
||||
'
|
||||
|
||||
dev:reset:
|
||||
desc: Reset to production configuration (remove local dev settings)
|
||||
cmds:
|
||||
- rm -f wp-config.local.php
|
||||
- |
|
||||
docker exec maple-wordpress-dev bash -c '
|
||||
CONFIG_FILE="/var/www/html/wp-config.php"
|
||||
# Remove MaplePress local config lines
|
||||
sed -i "/MaplePress local development config/d" "$CONFIG_FILE"
|
||||
sed -i "/maplepress-plugin\/wp-config.local.php/d" "$CONFIG_FILE"
|
||||
'
|
||||
- echo "Reset to production configuration"
|
||||
- echo "Plugin will now use empty API URL (production behavior)"
|
||||
|
||||
sync:
|
||||
desc: Sync plugin to local WordPress container
|
||||
cmds:
|
||||
- docker cp . maple-wordpress-dev:/var/www/html/wp-content/plugins/maplepress-plugin/
|
||||
- echo "Plugin synced to WordPress container"
|
||||
|
||||
watch:
|
||||
desc: Watch for changes and auto-sync to WordPress
|
||||
cmds:
|
||||
- |
|
||||
echo "Watching for changes... (Press Ctrl+C to stop)"
|
||||
fswatch -o . | while read; do
|
||||
task sync
|
||||
done
|
||||
|
||||
logs:
|
||||
desc: View WordPress debug logs
|
||||
cmds:
|
||||
- docker exec -it maple-wordpress-dev tail -f /var/www/html/wp-content/debug.log
|
||||
|
||||
shell:
|
||||
desc: Open shell in WordPress container
|
||||
cmds:
|
||||
- docker exec -it maple-wordpress-dev /bin/bash
|
||||
|
||||
lint:
|
||||
desc: Run PHP CodeSniffer (requires phpcs)
|
||||
cmds:
|
||||
- phpcs --standard=WordPress --extensions=php .
|
||||
|
||||
lint:fix:
|
||||
desc: Auto-fix PHP CodeSniffer issues (requires phpcbf)
|
||||
cmds:
|
||||
- phpcbf --standard=WordPress --extensions=php .
|
||||
|
||||
test:
|
||||
desc: Run PHPUnit tests (requires phpunit)
|
||||
cmds:
|
||||
- phpunit
|
||||
|
||||
build:
|
||||
desc: Build plugin zip for distribution
|
||||
cmds:
|
||||
- rm -rf dist
|
||||
- mkdir -p dist
|
||||
- |
|
||||
rsync -av \
|
||||
--exclude='dist' \
|
||||
--exclude='.git' \
|
||||
--exclude='node_modules' \
|
||||
--exclude='Taskfile.yml' \
|
||||
--exclude='.phpcs.xml.dist' \
|
||||
--exclude='wp-config.local.php' \
|
||||
--exclude='composer.json' \
|
||||
--exclude='composer.lock' \
|
||||
--exclude='vendor' \
|
||||
--exclude='.gitignore' \
|
||||
. dist/maplepress-plugin/
|
||||
- cd dist && zip -r maplepress-plugin.zip maplepress-plugin
|
||||
- echo "Plugin built at dist/maplepress-plugin.zip"
|
||||
- echo "Local dev files excluded from build"
|
||||
|
||||
clean:
|
||||
desc: Clean build artifacts
|
||||
cmds:
|
||||
- rm -rf dist
|
||||
- rm -rf vendor
|
||||
- rm -rf node_modules
|
||||
- echo "Cleaned build artifacts"
|
||||
|
||||
install:
|
||||
desc: Install Composer dependencies
|
||||
cmds:
|
||||
- composer install
|
||||
- echo "Composer dependencies installed"
|
||||
|
||||
update:
|
||||
desc: Update Composer dependencies
|
||||
cmds:
|
||||
- composer update
|
||||
- echo "Composer dependencies updated"
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* MaplePress Admin Styles
|
||||
*
|
||||
* @package MaplePress
|
||||
*/
|
||||
|
||||
.maplepress-admin {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.maplepress-status-active {
|
||||
color: #46b450;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.maplepress-status-inactive {
|
||||
color: #dc3232;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* MaplePress Public Styles
|
||||
*
|
||||
* @package MaplePress
|
||||
*/
|
||||
|
||||
.maplepress-search-widget {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.maplepress-search-results {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.maplepress-search-result-item {
|
||||
margin-bottom: 15px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.maplepress-search-result-item h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,440 @@
|
|||
/**
|
||||
* MaplePress Search Speed Test - Admin Styles
|
||||
*/
|
||||
|
||||
.mpss-wrap {
|
||||
max-width: 1200px;
|
||||
margin: 20px auto 20px 0;
|
||||
}
|
||||
|
||||
.mpss-card {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mpss-card h2 {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mpss-card h3 {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* Mode Selection */
|
||||
.mpss-mode-selection {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.mpss-radio-label {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.mpss-radio-label:hover {
|
||||
border-color: #2271b1;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.mpss-radio-label input[type="radio"] {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mpss-radio-label input[type="radio"]:checked + .mpss-radio-title {
|
||||
color: #2271b1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mpss-radio-label input[type="radio"]:checked ~ .mpss-radio-title {
|
||||
color: #2271b1;
|
||||
}
|
||||
|
||||
.mpss-radio-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.mpss-radio-desc {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
/* Test Info */
|
||||
.mpss-test-info ul {
|
||||
list-style: none;
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mpss-test-info li {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.mpss-test-info li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Warning */
|
||||
.mpss-warning {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 12px 15px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mpss-warning .dashicons {
|
||||
color: #856404;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Progress */
|
||||
.mpss-progress {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.mpss-progress-bar {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.mpss-progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
transition: width 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mpss-progress-fill.pulsing::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
left: -100%;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.mpss-progress-fill.pulsing {
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test Meta */
|
||||
.mpss-test-meta {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
margin: 15px 0 25px 0;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mpss-test-meta span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mpss-test-meta strong {
|
||||
color: #2271b1;
|
||||
}
|
||||
|
||||
/* Summary Cards */
|
||||
.mpss-summary-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.mpss-profile-card {
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.mpss-profile-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mpss-profile-card h4 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.mpss-profile-metrics {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.mpss-metric {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mpss-metric-label {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.mpss-metric-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mpss-status-badge {
|
||||
display: inline-block;
|
||||
padding: 5px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mpss-status-excellent {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border-color: #46b450;
|
||||
}
|
||||
|
||||
.mpss-status-good {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border-color: #00a0d2;
|
||||
}
|
||||
|
||||
.mpss-status-fair {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
border-color: #ffc107;
|
||||
}
|
||||
|
||||
.mpss-status-poor {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
.mpss-status-critical {
|
||||
background: #dc3232;
|
||||
color: #fff;
|
||||
border-color: #dc3232;
|
||||
}
|
||||
|
||||
.mpss-profile-details {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Chart Container */
|
||||
.mpss-chart-container {
|
||||
margin: 30px 0;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#mpss-chart {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
/* Results Table */
|
||||
.mpss-table-container {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
#mpss-results-table {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
#mpss-results-table th {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#mpss-results-table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Recommendations */
|
||||
.mpss-recommendations {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.mpss-recommendation {
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
border-left: 4px solid;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mpss-recommendation h4 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mpss-recommendation p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mpss-recommendation-success {
|
||||
background: #d4edda;
|
||||
border-color: #46b450;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.mpss-recommendation-info {
|
||||
background: #d1ecf1;
|
||||
border-color: #00a0d2;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.mpss-recommendation-warning {
|
||||
background: #fff3cd;
|
||||
border-color: #ffc107;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.mpss-recommendation-error {
|
||||
background: #f8d7da;
|
||||
border-color: #dc3545;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.mpss-recommendation-critical {
|
||||
background: #dc3232;
|
||||
border-color: #dc3232;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Site Info */
|
||||
.mpss-site-info {
|
||||
margin: 30px 0;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mpss-site-info ul {
|
||||
list-style: none;
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mpss-site-info li {
|
||||
padding: 8px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mpss-site-info strong {
|
||||
color: #2271b1;
|
||||
min-width: 150px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
.mpss-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.mpss-actions button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.mpss-summary-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.mpss-test-meta {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mpss-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mpss-actions button {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* MaplePress Admin JavaScript
|
||||
*
|
||||
* @package MaplePress
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready(function() {
|
||||
// Admin-specific JavaScript can be added here
|
||||
console.log('MaplePress Admin JS loaded');
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* MaplePress Public JavaScript
|
||||
*
|
||||
* @package MaplePress
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready(function() {
|
||||
// Public-facing JavaScript can be added here
|
||||
console.log('MaplePress Public JS loaded');
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
894
native/wordpress/maplepress-plugin/assets/js/speedtest-simple.js
Normal file
894
native/wordpress/maplepress-plugin/assets/js/speedtest-simple.js
Normal file
|
|
@ -0,0 +1,894 @@
|
|||
/**
|
||||
* MaplePress Search Speed Test - Simple Admin JavaScript
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var testResults = null;
|
||||
var performanceChart = null;
|
||||
|
||||
$(document).ready(function() {
|
||||
// Note: Notification permission will be requested when user runs first test
|
||||
// (requestNotificationPermission() can only be called from user gesture)
|
||||
|
||||
// Run test button
|
||||
$('#mpss-run-test').on('click', function() {
|
||||
runSpeedTest();
|
||||
});
|
||||
|
||||
// Cancel test button
|
||||
$(document).on('click', '#mpss-cancel-test', function() {
|
||||
if (confirm('Are you sure you want to cancel the test?')) {
|
||||
cancelTest();
|
||||
}
|
||||
});
|
||||
|
||||
// Run again button
|
||||
$(document).on('click', '#mpss-run-again', function() {
|
||||
resetTest();
|
||||
});
|
||||
|
||||
// Export results button
|
||||
$(document).on('click', '#mpss-export-results', function() {
|
||||
exportResults();
|
||||
});
|
||||
|
||||
|
||||
// Share results button
|
||||
$(document).on('click', '#mpss-share-results', function() {
|
||||
showShareModal();
|
||||
});
|
||||
|
||||
// Close share modal
|
||||
$(document).on('click', '#mpss-close-share-modal', function() {
|
||||
closeShareModal();
|
||||
});
|
||||
|
||||
// Share buttons
|
||||
$(document).on('click', '.mpss-share-twitter', function() {
|
||||
shareToTwitter();
|
||||
});
|
||||
|
||||
$(document).on('click', '.mpss-share-facebook', function() {
|
||||
shareToFacebook();
|
||||
});
|
||||
|
||||
$(document).on('click', '.mpss-share-linkedin', function() {
|
||||
shareToLinkedIn();
|
||||
});
|
||||
|
||||
$(document).on('click', '.mpss-share-copy', function() {
|
||||
copyShareLink();
|
||||
});
|
||||
|
||||
// Handle mode card selection
|
||||
$('.mpss-mode-card').on('click', function() {
|
||||
var $radio = $(this).find('input[type="radio"]');
|
||||
$radio.prop('checked', true);
|
||||
|
||||
// Update visual state of cards
|
||||
$('.mpss-mode-card-inner').css({
|
||||
'border-color': '#e0e0e0',
|
||||
'box-shadow': '0 1px 3px rgba(0, 0, 0, 0.05)',
|
||||
'transform': 'scale(1)'
|
||||
});
|
||||
|
||||
$(this).find('.mpss-mode-card-inner').css({
|
||||
'border-color': '#667eea',
|
||||
'box-shadow': '0 4px 15px rgba(102, 126, 234, 0.3)',
|
||||
'transform': 'scale(1.02)'
|
||||
});
|
||||
});
|
||||
|
||||
// Add hover effects for mode cards
|
||||
$('.mpss-mode-card').hover(
|
||||
function() {
|
||||
if (!$(this).find('input[type="radio"]').is(':checked')) {
|
||||
$(this).find('.mpss-mode-card-inner').css({
|
||||
'transform': 'translateY(-2px)',
|
||||
'box-shadow': '0 4px 10px rgba(0, 0, 0, 0.1)'
|
||||
});
|
||||
}
|
||||
},
|
||||
function() {
|
||||
if (!$(this).find('input[type="radio"]').is(':checked')) {
|
||||
$(this).find('.mpss-mode-card-inner').css({
|
||||
'transform': 'scale(1)',
|
||||
'box-shadow': '0 1px 3px rgba(0, 0, 0, 0.05)'
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Add hover effect for run button
|
||||
$('#mpss-run-test').hover(
|
||||
function() {
|
||||
$(this).css({
|
||||
'transform': 'translateY(-2px)',
|
||||
'box-shadow': '0 6px 20px rgba(102, 126, 234, 0.5)'
|
||||
});
|
||||
},
|
||||
function() {
|
||||
$(this).css({
|
||||
'transform': 'translateY(0)',
|
||||
'box-shadow': '0 4px 15px rgba(102, 126, 234, 0.4)'
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Add focus effect for select dropdown
|
||||
$('#mpss-query-count').on('focus', function() {
|
||||
$(this).css({
|
||||
'border-color': '#667eea',
|
||||
'box-shadow': '0 0 0 3px rgba(102, 126, 234, 0.1)'
|
||||
});
|
||||
}).on('blur', function() {
|
||||
$(this).css({
|
||||
'border-color': '#e0e0e0',
|
||||
'box-shadow': 'none'
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize the first card as selected
|
||||
$('.mpss-mode-card').first().find('.mpss-mode-card-inner').css({
|
||||
'border-color': '#667eea',
|
||||
'box-shadow': '0 4px 15px rgba(102, 126, 234, 0.3)',
|
||||
'transform': 'scale(1.02)'
|
||||
});
|
||||
});
|
||||
|
||||
var progressInterval = null;
|
||||
var currentAjaxRequest = null;
|
||||
|
||||
function runSpeedTest() {
|
||||
// Request notification permission on first test run
|
||||
requestNotificationPermission();
|
||||
|
||||
var queryCount = parseInt($('#mpss-query-count').val()) || 10;
|
||||
var executionMode = $('input[name="execution_mode"]:checked').val() || 'serial';
|
||||
|
||||
console.log('Starting speed test with ' + queryCount + ' queries in ' + executionMode + ' mode');
|
||||
console.log('Execution mode value:', executionMode);
|
||||
console.log('Radio button checked:', $('input[name="execution_mode"]:checked').length);
|
||||
|
||||
// Hide config, show progress
|
||||
$('.mpss-test-config').fadeOut();
|
||||
$('#mpss-results').hide();
|
||||
$('#mpss-progress').fadeIn();
|
||||
|
||||
var modeText = executionMode === 'parallel' ? 'many at once' : 'one at a time';
|
||||
$('#mpss-progress-text').text('Testing ' + queryCount + ' searches (' + modeText + ')...');
|
||||
$('#mpss-progress-fill').css('width', '10%');
|
||||
|
||||
// Initialize counter
|
||||
$('#mpss-total-count').text(queryCount);
|
||||
$('#mpss-completed-count').text(0);
|
||||
|
||||
// Calculate and display estimated time
|
||||
var estimatedSeconds = Math.ceil((queryCount * 200) / 1000); // 200ms per query
|
||||
if (executionMode === 'parallel') {
|
||||
estimatedSeconds = Math.ceil(estimatedSeconds * 0.1); // Parallel is much faster
|
||||
}
|
||||
updateTimeEstimate(estimatedSeconds);
|
||||
|
||||
// Start animated progress counter
|
||||
startProgressAnimation(queryCount, estimatedSeconds);
|
||||
|
||||
// Make AJAX request
|
||||
currentAjaxRequest = $.ajax({
|
||||
url: mpssAjax.ajaxurl,
|
||||
method: 'POST',
|
||||
data: {
|
||||
action: 'mpss_run_test',
|
||||
nonce: mpssAjax.nonce,
|
||||
query_count: queryCount,
|
||||
execution_mode: executionMode
|
||||
},
|
||||
success: function(response) {
|
||||
currentAjaxRequest = null;
|
||||
console.log('Response received:', response);
|
||||
|
||||
// Stop the progress animation
|
||||
stopProgressAnimation();
|
||||
|
||||
if (response.success) {
|
||||
testResults = response.data;
|
||||
console.log('Test results:', testResults);
|
||||
|
||||
// Complete the progress
|
||||
$('#mpss-completed-count').text(queryCount);
|
||||
$('#mpss-progress-fill').css('width', '100%');
|
||||
$('#mpss-progress-text').text('Test complete!');
|
||||
|
||||
setTimeout(function() {
|
||||
displayResults(testResults);
|
||||
}, 500);
|
||||
} else {
|
||||
showError('Test failed: ' + (response.data ? response.data.message : 'Unknown error'));
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
currentAjaxRequest = null;
|
||||
|
||||
// Don't show error if request was aborted (cancelled by user)
|
||||
if (status === 'abort') {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('AJAX error:', error);
|
||||
stopProgressAnimation();
|
||||
showError('Network error: ' + error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cancelTest() {
|
||||
// Abort the AJAX request
|
||||
if (currentAjaxRequest) {
|
||||
currentAjaxRequest.abort();
|
||||
currentAjaxRequest = null;
|
||||
}
|
||||
|
||||
// Stop the animation
|
||||
stopProgressAnimation();
|
||||
|
||||
// Hide progress and show config
|
||||
$('#mpss-progress').fadeOut(function() {
|
||||
$('.mpss-test-config').fadeIn();
|
||||
});
|
||||
|
||||
// Show cancellation message
|
||||
alert('Test cancelled');
|
||||
}
|
||||
|
||||
function updateTimeEstimate(secondsRemaining) {
|
||||
if (secondsRemaining <= 0) {
|
||||
$('#mpss-time-estimate').text('Almost done...');
|
||||
return;
|
||||
}
|
||||
|
||||
var minutes = Math.floor(secondsRemaining / 60);
|
||||
var seconds = secondsRemaining % 60;
|
||||
|
||||
var timeText = 'Estimated time: ';
|
||||
if (minutes > 0) {
|
||||
timeText += minutes + ' min ' + seconds + ' sec';
|
||||
} else {
|
||||
timeText += seconds + ' sec';
|
||||
}
|
||||
|
||||
$('#mpss-time-estimate').text(timeText);
|
||||
}
|
||||
|
||||
function startProgressAnimation(totalCount, estimatedSeconds) {
|
||||
// Clear any existing interval
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval);
|
||||
}
|
||||
|
||||
var currentCount = 0;
|
||||
var estimatedTime = totalCount * 200; // Rough estimate: 200ms per search
|
||||
var updateInterval = 100; // Update every 100ms
|
||||
var incrementPerUpdate = totalCount / (estimatedTime / updateInterval);
|
||||
var reachedNinetyFive = false;
|
||||
var timeRemaining = estimatedSeconds;
|
||||
var lastTimeUpdate = Date.now();
|
||||
|
||||
progressInterval = setInterval(function() {
|
||||
// Update time estimate every second
|
||||
var now = Date.now();
|
||||
if (now - lastTimeUpdate >= 1000) {
|
||||
timeRemaining--;
|
||||
updateTimeEstimate(timeRemaining);
|
||||
lastTimeUpdate = now;
|
||||
}
|
||||
|
||||
// After reaching 95%, slow down significantly
|
||||
if (currentCount >= totalCount * 0.95 && !reachedNinetyFive) {
|
||||
reachedNinetyFive = true;
|
||||
incrementPerUpdate = incrementPerUpdate * 0.1; // Slow down to 10% speed
|
||||
// Update the message and add pulsing animation
|
||||
$('#mpss-progress-text').html('Almost done, finalizing results...');
|
||||
$('#mpss-progress-fill').addClass('pulsing');
|
||||
}
|
||||
|
||||
currentCount += incrementPerUpdate;
|
||||
|
||||
// Cap at 99% until we get the real result
|
||||
var displayCount = Math.min(Math.floor(currentCount), Math.floor(totalCount * 0.99));
|
||||
var percentage = Math.min((displayCount / totalCount) * 100, 99);
|
||||
|
||||
$('#mpss-completed-count').text(displayCount);
|
||||
$('#mpss-progress-fill').css('width', percentage + '%');
|
||||
|
||||
// Stop at 99%
|
||||
if (displayCount >= totalCount * 0.99) {
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
}
|
||||
}, updateInterval);
|
||||
}
|
||||
|
||||
function stopProgressAnimation() {
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function displayResults(data) {
|
||||
console.log('Displaying results...');
|
||||
|
||||
// Calculate average time for notification
|
||||
var avgTime = data.total_time / data.total_queries;
|
||||
|
||||
// Play completion sound and show notification
|
||||
playCompletionSound();
|
||||
showTestCompleteNotification(avgTime, data.total_queries);
|
||||
|
||||
// Hide progress, show results
|
||||
$('#mpss-progress').fadeOut();
|
||||
$('#mpss-results').fadeIn();
|
||||
|
||||
// Display recommendations
|
||||
displayRecommendations(data.total_queries, data.mode);
|
||||
|
||||
// Display summary stats (only total queries and total time)
|
||||
$('#mpss-total-queries').text(data.total_queries.toLocaleString());
|
||||
$('#mpss-total-time').text(data.total_time + ' sec');
|
||||
|
||||
// Add mode-specific explanation
|
||||
var $chart = $('#mpss-chart');
|
||||
var existingExplanation = $chart.parent().find('.mpss-mode-explanation');
|
||||
if (existingExplanation.length) {
|
||||
existingExplanation.remove();
|
||||
}
|
||||
|
||||
var explanation = '';
|
||||
if (data.mode === 'parallel') {
|
||||
explanation = '<div class="mpss-mode-explanation" style="padding: 15px; margin-bottom: 20px; background: linear-gradient(to right, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)); border-left: 4px solid #667eea; border-radius: 8px;">' +
|
||||
'<strong style="color: #667eea; font-size: 14px;">👥 Stress Test Mode:</strong><br>' +
|
||||
'<span style="font-size: 13px; color: #666; line-height: 1.6;">' +
|
||||
'We simulated <strong>' + data.total_queries + ' people</strong> searching your site at the same time. It took <strong>' + data.total_time + ' seconds</strong> to handle everyone.' +
|
||||
'</span>' +
|
||||
'</div>';
|
||||
} else {
|
||||
explanation = '<div class="mpss-mode-explanation" style="padding: 15px; margin-bottom: 20px; background: linear-gradient(to right, rgba(240, 147, 251, 0.1), rgba(245, 87, 108, 0.1)); border-left: 4px solid #f093fb; border-radius: 8px;">' +
|
||||
'<strong style="color: #f093fb; font-size: 14px;">👤 Normal Mode:</strong><br>' +
|
||||
'<span style="font-size: 13px; color: #666; line-height: 1.6;">' +
|
||||
'We tested <strong>' + data.total_queries + ' searches</strong> one at a time. The total test took <strong>' + data.total_time + ' seconds</strong> to complete.' +
|
||||
'</span>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
$chart.parent().prepend(explanation);
|
||||
|
||||
// Display chart
|
||||
displayChart(data);
|
||||
}
|
||||
|
||||
function displayChart(data) {
|
||||
var chartContainer = $('#mpss-chart').parent();
|
||||
|
||||
// Destroy existing chart
|
||||
if (performanceChart) {
|
||||
performanceChart.destroy();
|
||||
performanceChart = null;
|
||||
}
|
||||
|
||||
// Restore the canvas if needed
|
||||
if (!chartContainer.find('canvas').length) {
|
||||
chartContainer.html('<canvas id="mpss-chart"></canvas>');
|
||||
}
|
||||
|
||||
var ctx = document.getElementById('mpss-chart').getContext('2d');
|
||||
|
||||
// Prepare data
|
||||
var labels = data.results.map(function(r, i) { return 'Query ' + (i + 1); });
|
||||
var times = data.results.map(function(r) { return r.duration_ms; });
|
||||
|
||||
// Determine title based on mode
|
||||
var chartTitle = 'How Long Each Search Took';
|
||||
var chartSubtitle = '';
|
||||
|
||||
if (data.mode === 'parallel') {
|
||||
chartSubtitle = 'Each dot shows how long one search took (all happening at the same time)';
|
||||
} else {
|
||||
chartSubtitle = 'Each dot shows how long one search took (one after another)';
|
||||
}
|
||||
|
||||
// Create chart
|
||||
performanceChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Response Time (ms)',
|
||||
data: times,
|
||||
backgroundColor: 'rgba(102, 126, 234, 0.3)',
|
||||
borderColor: 'rgba(102, 126, 234, 1)',
|
||||
borderWidth: 2,
|
||||
pointRadius: 3,
|
||||
tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: chartTitle
|
||||
},
|
||||
subtitle: {
|
||||
display: true,
|
||||
text: chartSubtitle,
|
||||
font: {
|
||||
size: 11,
|
||||
style: 'italic'
|
||||
},
|
||||
color: '#666'
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: false
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Response Time (ms)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exportResults() {
|
||||
if (!testResults) {
|
||||
alert('No results to export');
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate metrics
|
||||
var avgTime = testResults.total_time / testResults.total_queries;
|
||||
var avgMs = (avgTime * 1000).toFixed(0);
|
||||
var modeLabel = testResults.mode === 'parallel' ? 'Stress Test - Many People at Once' : 'Normal - One Person at a Time';
|
||||
var date = new Date();
|
||||
var dateStr = date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
||||
|
||||
// Calculate statistics
|
||||
var times = testResults.results.map(function(r) { return r.duration_ms; });
|
||||
var minTime = Math.min.apply(null, times);
|
||||
var maxTime = Math.max.apply(null, times);
|
||||
var medianTime = calculateMedian(times);
|
||||
|
||||
// Build HTML report
|
||||
var html = '<!DOCTYPE html>\n';
|
||||
html += '<html lang="en">\n';
|
||||
html += '<head>\n';
|
||||
html += ' <meta charset="UTF-8">\n';
|
||||
html += ' <meta name="viewport" content="width=device-width, initial-scale=1.0">\n';
|
||||
html += ' <title>Search Speed Test Report - ' + dateStr + '</title>\n';
|
||||
html += ' <style>\n';
|
||||
html += ' * { margin: 0; padding: 0; box-sizing: border-box; }\n';
|
||||
html += ' body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #f5f5f5; padding: 40px 20px; color: #333; line-height: 1.6; }\n';
|
||||
html += ' .container { max-width: 1000px; margin: 0 auto; background: white; padding: 50px; border-radius: 20px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); }\n';
|
||||
html += ' .header { text-align: center; margin-bottom: 50px; padding-bottom: 30px; border-bottom: 3px solid #667eea; }\n';
|
||||
html += ' .header h1 { font-size: 42px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 15px; }\n';
|
||||
html += ' .header .date { font-size: 16px; color: #666; }\n';
|
||||
html += ' .summary-section { text-align: center; padding: 40px; background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)); border-radius: 16px; margin-bottom: 40px; border: 3px solid #667eea; }\n';
|
||||
html += ' .summary-icon { font-size: 80px; margin-bottom: 20px; }\n';
|
||||
html += ' .summary-title { font-size: 32px; font-weight: 700; color: #667eea; margin-bottom: 15px; }\n';
|
||||
html += ' .summary-message { font-size: 18px; color: #666; }\n';
|
||||
html += ' .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 40px; }\n';
|
||||
html += ' .metric { background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)); padding: 25px; border-radius: 12px; text-align: center; }\n';
|
||||
html += ' .metric-label { font-size: 13px; color: #666; text-transform: uppercase; font-weight: 600; margin-bottom: 10px; }\n';
|
||||
html += ' .metric-value { font-size: 32px; font-weight: 800; color: #667eea; }\n';
|
||||
html += ' .section { margin-bottom: 40px; }\n';
|
||||
html += ' .section h2 { font-size: 24px; color: #333; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px solid #e0e0e0; }\n';
|
||||
html += ' .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; }\n';
|
||||
html += ' .info-item { padding: 15px; background: #f9f9f9; border-radius: 8px; }\n';
|
||||
html += ' .info-label { font-size: 12px; color: #666; text-transform: uppercase; margin-bottom: 5px; }\n';
|
||||
html += ' .info-value { font-size: 18px; font-weight: 600; color: #333; }\n';
|
||||
html += ' .footer { text-align: center; padding-top: 30px; margin-top: 50px; border-top: 2px solid #e0e0e0; color: #999; font-size: 14px; }\n';
|
||||
html += ' @media print { body { background: white; padding: 0; } .container { box-shadow: none; padding: 30px; } }\n';
|
||||
html += ' </style>\n';
|
||||
html += '</head>\n';
|
||||
html += '<body>\n';
|
||||
html += ' <div class="container">\n';
|
||||
html += ' <div class="header">\n';
|
||||
html += ' <h1>Search Speed Test Report</h1>\n';
|
||||
html += ' <div class="date">Generated on ' + escapeHtml(dateStr) + '</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' \n';
|
||||
html += ' <div class="summary-section">\n';
|
||||
html += ' <div class="summary-icon">🚀</div>\n';
|
||||
html += ' <div class="summary-title">Search Speed Test Results</div>\n';
|
||||
html += ' <div class="summary-message">Average response time: ' + avgMs + ' ms</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' \n';
|
||||
html += ' <div class="metrics">\n';
|
||||
html += ' <div class="metric">\n';
|
||||
html += ' <div class="metric-label">Average Time</div>\n';
|
||||
html += ' <div class="metric-value">' + avgMs + ' ms</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' <div class="metric">\n';
|
||||
html += ' <div class="metric-label">Total Tests</div>\n';
|
||||
html += ' <div class="metric-value">' + testResults.total_queries + '</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' <div class="metric">\n';
|
||||
html += ' <div class="metric-label">Total Time</div>\n';
|
||||
html += ' <div class="metric-value">' + testResults.total_time + ' s</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' <div class="metric">\n';
|
||||
html += ' <div class="metric-label">Test Mode</div>\n';
|
||||
html += ' <div class="metric-value" style="font-size: 18px;">' + escapeHtml(modeLabel.split(' - ')[0]) + '</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' \n';
|
||||
html += ' <div class="section">\n';
|
||||
html += ' <h2>Detailed Statistics</h2>\n';
|
||||
html += ' <div class="info-grid">\n';
|
||||
html += ' <div class="info-item">\n';
|
||||
html += ' <div class="info-label">Fastest Response</div>\n';
|
||||
html += ' <div class="info-value">' + minTime.toFixed(0) + ' ms</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' <div class="info-item">\n';
|
||||
html += ' <div class="info-label">Slowest Response</div>\n';
|
||||
html += ' <div class="info-value">' + maxTime.toFixed(0) + ' ms</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' <div class="info-item">\n';
|
||||
html += ' <div class="info-label">Median Response</div>\n';
|
||||
html += ' <div class="info-value">' + medianTime.toFixed(0) + ' ms</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' <div class="info-item">\n';
|
||||
html += ' <div class="info-label">Test Mode</div>\n';
|
||||
html += ' <div class="info-value">' + escapeHtml(modeLabel) + '</div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' \n';
|
||||
html += ' <div class="section">\n';
|
||||
html += ' <h2>General Recommendations</h2>\n';
|
||||
html += ' <div style="background: rgba(102, 126, 234, 0.1); padding: 25px; border-radius: 12px; border-left: 4px solid #667eea;">\n';
|
||||
html += ' <p><strong>💡 Optimization Tips</strong></p>\n';
|
||||
html += ' <p>Consider these strategies to improve search performance:</p>\n';
|
||||
html += ' <ul style="margin-top: 15px; margin-left: 20px;">\n';
|
||||
html += ' <li>Install and configure a caching plugin (e.g., WP Super Cache, W3 Total Cache)</li>\n';
|
||||
html += ' <li>Optimize your database with plugins like WP-Optimize</li>\n';
|
||||
html += ' <li>Review and optimize database indexes</li>\n';
|
||||
html += ' <li>Consider upgrading your hosting plan for better resources</li>\n';
|
||||
html += ' <li>Monitor performance regularly as your site grows</li>\n';
|
||||
html += ' </ul>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' \n';
|
||||
html += ' <div class="footer">\n';
|
||||
html += ' <p>Generated by MaplePress Search Speed Test Plugin</p>\n';
|
||||
html += ' <p>Website: ' + escapeHtml(window.location.hostname) + '</p>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' </div>\n';
|
||||
html += '</body>\n';
|
||||
html += '</html>';
|
||||
|
||||
// Create and download the HTML file
|
||||
var htmlBlob = new Blob([html], {type: 'text/html'});
|
||||
var url = URL.createObjectURL(htmlBlob);
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'search-speed-test-report-' + Date.now() + '.html';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
// Show success message
|
||||
alert('✅ Report exported! Open the HTML file in your browser and use "Print to PDF" to create a PDF version.');
|
||||
}
|
||||
|
||||
function calculateMedian(arr) {
|
||||
var sorted = arr.slice().sort(function(a, b) { return a - b; });
|
||||
var mid = Math.floor(sorted.length / 2);
|
||||
if (sorted.length % 2 === 0) {
|
||||
return (sorted[mid - 1] + sorted[mid]) / 2;
|
||||
}
|
||||
return sorted[mid];
|
||||
}
|
||||
|
||||
function resetTest() {
|
||||
$('#mpss-results').fadeOut();
|
||||
$('.mpss-test-config').fadeIn();
|
||||
testResults = null;
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
$('#mpss-progress').fadeOut();
|
||||
$('.mpss-test-config').fadeIn();
|
||||
alert('Error: ' + message);
|
||||
}
|
||||
|
||||
function displayRecommendations(totalQueries, mode) {
|
||||
var recommendations = [];
|
||||
|
||||
// Query count recommendations
|
||||
if (totalQueries < 100) {
|
||||
recommendations.push({
|
||||
type: 'info',
|
||||
icon: '📊',
|
||||
title: 'Try more searches',
|
||||
message: 'Testing with ' + totalQueries + ' searches gives you a basic idea. Try 500-1000 searches for more accurate results.'
|
||||
});
|
||||
}
|
||||
|
||||
// Mode-based recommendations
|
||||
if (mode === 'serial') {
|
||||
recommendations.push({
|
||||
type: 'info',
|
||||
icon: '🔄',
|
||||
title: 'Try Stress Test mode',
|
||||
message: 'You tested in Normal mode. Try "Stress Test - Many People at Once" to see how your site handles high traffic.'
|
||||
});
|
||||
} else {
|
||||
recommendations.push({
|
||||
type: 'info',
|
||||
icon: '👥',
|
||||
title: 'Consider traffic patterns',
|
||||
message: 'Stress Test mode shows worst-case performance. If your site rarely gets simultaneous searches, Normal mode results matter more.'
|
||||
});
|
||||
}
|
||||
|
||||
// Build HTML
|
||||
var html = '<div style="margin: 30px 0;">';
|
||||
html += '<h3 style="margin-bottom: 20px; font-size: 18px; color: #333;">💡 Quick Tips</h3>';
|
||||
|
||||
for (var i = 0; i < recommendations.length; i++) {
|
||||
var rec = recommendations[i];
|
||||
var bgColor, borderColor;
|
||||
|
||||
if (rec.type === 'success') {
|
||||
bgColor = '#d1fae5';
|
||||
borderColor = '#10b981';
|
||||
} else if (rec.type === 'info') {
|
||||
bgColor = '#dbeafe';
|
||||
borderColor = '#3b82f6';
|
||||
} else if (rec.type === 'warning') {
|
||||
bgColor = '#fef3c7';
|
||||
borderColor = '#f59e0b';
|
||||
} else {
|
||||
bgColor = '#fee2e2';
|
||||
borderColor = '#ef4444';
|
||||
}
|
||||
|
||||
html += '<div style="padding: 15px; margin: 15px 0; background: ' + bgColor + '; border-left: 4px solid ' + borderColor + '; border-radius: 8px;">';
|
||||
html += '<div style="font-size: 16px; font-weight: 600; color: #333; margin-bottom: 8px;">' + rec.icon + ' ' + rec.title + '</div>';
|
||||
html += '<div style="font-size: 14px; color: #666; line-height: 1.6;">' + rec.message + '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
$('#mpss-recommendations').html(html);
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
var map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
function showShareModal() {
|
||||
if (!testResults) {
|
||||
alert('No results to share');
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate metrics
|
||||
var avgTime = testResults.total_time / testResults.total_queries;
|
||||
var avgMs = (avgTime * 1000).toFixed(0);
|
||||
var modeLabel = testResults.mode === 'parallel' ? 'Stress Test' : 'Normal';
|
||||
|
||||
// Build share graphic
|
||||
var graphicHtml = '<div style="text-align: center;">';
|
||||
graphicHtml += '<div style="font-size: 64px; margin-bottom: 15px;">🚀</div>';
|
||||
graphicHtml += '<div style="font-size: 28px; font-weight: 700; margin-bottom: 10px;">My Search Speed Test Results</div>';
|
||||
graphicHtml += '<div style="font-size: 48px; font-weight: 800; margin: 20px 0; text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);">' + avgMs + ' ms</div>';
|
||||
graphicHtml += '<div style="font-size: 18px; opacity: 0.9; margin-bottom: 10px;">Average Response Time</div>';
|
||||
graphicHtml += '<div style="font-size: 14px; opacity: 0.8; margin-top: 20px;">Tested ' + testResults.total_queries + ' searches in ' + modeLabel + ' mode</div>';
|
||||
graphicHtml += '</div>';
|
||||
|
||||
$('#mpss-share-graphic').html(graphicHtml);
|
||||
|
||||
// Show modal with flex display
|
||||
$('#mpss-share-modal').css('display', 'flex').hide().fadeIn(300);
|
||||
}
|
||||
|
||||
function closeShareModal() {
|
||||
$('#mpss-share-modal').fadeOut(300);
|
||||
}
|
||||
|
||||
function shareToTwitter() {
|
||||
if (!testResults) return;
|
||||
|
||||
var avgTime = testResults.total_time / testResults.total_queries;
|
||||
var avgMs = (avgTime * 1000).toFixed(0);
|
||||
|
||||
var text = 'I just tested my website\'s search speed! 🚀 Average response time: ' + avgMs + 'ms #WebPerformance #SiteSpeed';
|
||||
var url = window.location.href;
|
||||
|
||||
var twitterUrl = 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(text) + '&url=' + encodeURIComponent(url);
|
||||
window.open(twitterUrl, '_blank', 'width=550,height=420');
|
||||
}
|
||||
|
||||
function shareToFacebook() {
|
||||
if (!testResults) return;
|
||||
|
||||
var url = window.location.href;
|
||||
var facebookUrl = 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(url);
|
||||
window.open(facebookUrl, '_blank', 'width=550,height=420');
|
||||
}
|
||||
|
||||
function shareToLinkedIn() {
|
||||
if (!testResults) return;
|
||||
|
||||
var avgTime = testResults.total_time / testResults.total_queries;
|
||||
var avgMs = (avgTime * 1000).toFixed(0);
|
||||
|
||||
var url = window.location.href;
|
||||
var title = 'Website Search Speed Test Results';
|
||||
var summary = 'I tested my website\'s search performance. Average response time: ' + avgMs + 'ms';
|
||||
|
||||
var linkedinUrl = 'https://www.linkedin.com/sharing/share-offsite/?url=' + encodeURIComponent(url);
|
||||
window.open(linkedinUrl, '_blank', 'width=550,height=420');
|
||||
}
|
||||
|
||||
function copyShareLink() {
|
||||
if (!testResults) return;
|
||||
|
||||
var avgTime = testResults.total_time / testResults.total_queries;
|
||||
var avgMs = (avgTime * 1000).toFixed(0);
|
||||
|
||||
var text = 'I just tested my website\'s search speed! 🚀 Average response time: ' + avgMs + 'ms\n' + window.location.href;
|
||||
|
||||
// Try to use modern clipboard API
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
alert('✅ Link copied to clipboard!');
|
||||
}).catch(function() {
|
||||
fallbackCopyText(text);
|
||||
});
|
||||
} else {
|
||||
fallbackCopyText(text);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopyText(text) {
|
||||
// Fallback for older browsers
|
||||
var textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.top = '0';
|
||||
textArea.style.left = '0';
|
||||
textArea.style.width = '2em';
|
||||
textArea.style.height = '2em';
|
||||
textArea.style.padding = '0';
|
||||
textArea.style.border = 'none';
|
||||
textArea.style.outline = 'none';
|
||||
textArea.style.boxShadow = 'none';
|
||||
textArea.style.background = 'transparent';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
alert('✅ Link copied to clipboard!');
|
||||
} catch (err) {
|
||||
alert('❌ Could not copy to clipboard. Please copy manually:\n\n' + text);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
function requestNotificationPermission() {
|
||||
// Check if browser supports notifications
|
||||
if (!('Notification' in window)) {
|
||||
console.log('This browser does not support desktop notifications');
|
||||
return;
|
||||
}
|
||||
|
||||
// Request permission if not already granted or denied
|
||||
if (Notification.permission === 'default') {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
}
|
||||
|
||||
function showTestCompleteNotification(avgTime, totalQueries) {
|
||||
// Check if notifications are supported and permitted
|
||||
if (!('Notification' in window) || Notification.permission !== 'granted') {
|
||||
return;
|
||||
}
|
||||
|
||||
var avgMs = (avgTime * 1000).toFixed(0);
|
||||
var title = 'Search Speed Test Complete!';
|
||||
var body = 'Tested ' + totalQueries + ' searches. Average response time: ' + avgMs + 'ms';
|
||||
var icon = ''; // WordPress doesn't have a default icon, but we could add one
|
||||
|
||||
try {
|
||||
var notification = new Notification(title, {
|
||||
body: body,
|
||||
icon: icon,
|
||||
badge: icon,
|
||||
tag: 'mpss-test-complete',
|
||||
requireInteraction: false
|
||||
});
|
||||
|
||||
// Auto-close notification after 5 seconds
|
||||
setTimeout(function() {
|
||||
notification.close();
|
||||
}, 5000);
|
||||
|
||||
// Focus window when notification is clicked
|
||||
notification.onclick = function() {
|
||||
window.focus();
|
||||
notification.close();
|
||||
};
|
||||
} catch (e) {
|
||||
console.log('Could not show notification:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function playCompletionSound() {
|
||||
// Create a simple success sound using Web Audio API
|
||||
try {
|
||||
var audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
var oscillator = audioContext.createOscillator();
|
||||
var gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
// Set sound parameters for a pleasant "ding" sound
|
||||
oscillator.frequency.value = 800; // Start frequency
|
||||
oscillator.type = 'sine';
|
||||
|
||||
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
|
||||
|
||||
oscillator.start(audioContext.currentTime);
|
||||
oscillator.stop(audioContext.currentTime + 0.5);
|
||||
|
||||
// Add a second tone for a pleasant chord
|
||||
setTimeout(function() {
|
||||
var oscillator2 = audioContext.createOscillator();
|
||||
var gainNode2 = audioContext.createGain();
|
||||
|
||||
oscillator2.connect(gainNode2);
|
||||
gainNode2.connect(audioContext.destination);
|
||||
|
||||
oscillator2.frequency.value = 1000;
|
||||
oscillator2.type = 'sine';
|
||||
|
||||
gainNode2.gain.setValueAtTime(0.2, audioContext.currentTime);
|
||||
gainNode2.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.4);
|
||||
|
||||
oscillator2.start(audioContext.currentTime);
|
||||
oscillator2.stop(audioContext.currentTime + 0.4);
|
||||
}, 100);
|
||||
} catch (e) {
|
||||
console.log('Could not play sound:', e);
|
||||
}
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
35
native/wordpress/maplepress-plugin/composer.json
Normal file
35
native/wordpress/maplepress-plugin/composer.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "mapleopentech/maplepress-plugin",
|
||||
"description": "Cloud services platform for WordPress - offloads computationally intensive tasks to improve performance. Features search, with uploads, metrics, and more coming soon",
|
||||
"type": "wordpress-plugin",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maple Open Technologies",
|
||||
"email": "hello@mapleopentech.io"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"wp-coding-standards/wpcs": "^3.0",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MaplePress\\": "includes/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "phpcs --standard=WordPress --extensions=php .",
|
||||
"lint:fix": "phpcbf --standard=WordPress --extensions=php .",
|
||||
"test": "phpunit"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,342 @@
|
|||
# MaplePress Performance Optimization Guide
|
||||
|
||||
## Understanding WordPress Performance Bottlenecks
|
||||
|
||||
MaplePress offloads search processing to our cloud infrastructure, but your WordPress site's ability to **handle concurrent requests** determines the actual user experience. Even with our lightning-fast backend (< 20ms processing time), users may experience slow response times if WordPress itself is the bottleneck.
|
||||
|
||||
---
|
||||
|
||||
## The #1 Bottleneck: PHP-FPM Worker Starvation
|
||||
|
||||
### What is PHP-FPM?
|
||||
|
||||
**PHP-FPM** (FastCGI Process Manager) is the process manager that handles PHP requests in WordPress. It uses a pool of "worker" processes to execute PHP code.
|
||||
|
||||
**Think of it like a restaurant:**
|
||||
- **Chef (PHP-FPM worker)** = processes one request at a time
|
||||
- **Order (HTTP request)** = incoming search/page load
|
||||
|
||||
If you have **5 chefs** and **25 orders arrive simultaneously**, only 5 orders get processed immediately. The other 20 orders **wait in line** (queue), even if each order only takes 200ms to prepare.
|
||||
|
||||
### Real-World Impact
|
||||
|
||||
**Common Default Configuration:**
|
||||
```ini
|
||||
pm.max_children = 5 # Only 5 workers!
|
||||
```
|
||||
|
||||
**What This Means:**
|
||||
- **Concurrent users**: 25 people search simultaneously
|
||||
- **Workers available**: Only 5
|
||||
- **Result**: 20 requests wait in queue
|
||||
- **User experience**: 8-10 second response times (even though backend is fast!)
|
||||
|
||||
**Performance Comparison (25 concurrent searches):**
|
||||
|
||||
| Configuration | Workers | Response Time | Queue Time |
|
||||
|--------------|---------|---------------|------------|
|
||||
| **Default** | 5 | 8,000-9,000ms | ~7,800ms waiting |
|
||||
| **Optimized** | 50 | 300-500ms | No waiting ✅ |
|
||||
|
||||
**52x faster** just by increasing workers!
|
||||
|
||||
---
|
||||
|
||||
## Solution: Increase PHP-FPM Workers
|
||||
|
||||
### Step 1: Check Current Configuration
|
||||
|
||||
SSH into your WordPress server:
|
||||
|
||||
```bash
|
||||
# For standard PHP-FPM installations
|
||||
sudo cat /etc/php/*/fpm/pool.d/www.conf | grep -E "^pm\.|^pm "
|
||||
|
||||
# For Docker (check inside container)
|
||||
docker exec your-wordpress-container cat /usr/local/etc/php-fpm.d/www.conf | grep -E "^pm\.|^pm "
|
||||
```
|
||||
|
||||
Look for these values:
|
||||
```ini
|
||||
pm = dynamic
|
||||
pm.max_children = 5 # ← This is usually the problem!
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
```
|
||||
|
||||
### Step 2: Calculate Optimal Worker Count
|
||||
|
||||
**Formula:**
|
||||
```
|
||||
pm.max_children = (Available RAM) / (Average PHP Memory per Process)
|
||||
```
|
||||
|
||||
**Example Calculations:**
|
||||
|
||||
**2GB Server:**
|
||||
- Total RAM: 2048 MB
|
||||
- System RAM: ~512 MB
|
||||
- Available: 1536 MB
|
||||
- PHP Memory per Process: ~70 MB
|
||||
- **Recommended**: `pm.max_children = 20-25`
|
||||
|
||||
**4GB Server:**
|
||||
- Total RAM: 4096 MB
|
||||
- System RAM: ~512 MB
|
||||
- Available: 3584 MB
|
||||
- PHP Memory per Process: ~70 MB
|
||||
- **Recommended**: `pm.max_children = 50`
|
||||
|
||||
**8GB Server:**
|
||||
- Total RAM: 8192 MB
|
||||
- System RAM: ~512 MB
|
||||
- Available: 7680 MB
|
||||
- PHP Memory per Process: ~70 MB
|
||||
- **Recommended**: `pm.max_children = 100`
|
||||
|
||||
### Step 3: Apply Configuration
|
||||
|
||||
**For Docker Deployments** (Recommended):
|
||||
|
||||
Create `/opt/wordpress/php-fpm-custom.conf`:
|
||||
```ini
|
||||
[www]
|
||||
pm = dynamic
|
||||
pm.max_children = 50 ; Adjust based on your RAM
|
||||
pm.start_servers = 10 ; Start with 10 workers
|
||||
pm.min_spare_servers = 5 ; Keep at least 5 idle
|
||||
pm.max_spare_servers = 20 ; Keep at most 20 idle
|
||||
pm.max_requests = 500 ; Recycle workers after 500 requests
|
||||
|
||||
; Logging for debugging
|
||||
pm.status_path = /status
|
||||
ping.path = /ping
|
||||
ping.response = pong
|
||||
```
|
||||
|
||||
Mount it in `docker-compose.yml`:
|
||||
```yaml
|
||||
wordpress:
|
||||
volumes:
|
||||
- ./php-fpm-custom.conf:/usr/local/etc/php-fpm.d/zz-custom.conf:ro
|
||||
```
|
||||
|
||||
Restart: `docker compose restart wordpress`
|
||||
|
||||
**For Standard PHP-FPM Installations**:
|
||||
|
||||
```bash
|
||||
# Edit PHP-FPM config (adjust PHP version as needed)
|
||||
sudo nano /etc/php/8.2/fpm/pool.d/www.conf
|
||||
|
||||
# Find and modify:
|
||||
pm = dynamic
|
||||
pm.max_children = 50
|
||||
pm.start_servers = 10
|
||||
pm.min_spare_servers = 5
|
||||
pm.max_spare_servers = 20
|
||||
pm.max_requests = 500
|
||||
|
||||
# Restart PHP-FPM
|
||||
sudo systemctl restart php8.2-fpm
|
||||
|
||||
# Verify
|
||||
sudo systemctl status php8.2-fpm
|
||||
```
|
||||
|
||||
### Step 4: Verify Changes
|
||||
|
||||
```bash
|
||||
# Check running PHP-FPM processes
|
||||
ps aux | grep php-fpm | wc -l
|
||||
# Should show 10+ processes initially
|
||||
|
||||
# During load test, monitor scaling
|
||||
watch -n 1 "ps aux | grep php-fpm | wc -l"
|
||||
# Should scale up to match traffic
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Optimizations
|
||||
|
||||
### 1. Enable OpCache (PHP Bytecode Cache)
|
||||
|
||||
OpCache compiles PHP code once and caches it, avoiding recompilation on every request.
|
||||
|
||||
**Configuration** (`/etc/php/8.2/fpm/php.ini`):
|
||||
```ini
|
||||
opcache.enable = 1
|
||||
opcache.memory_consumption = 256 ; 256MB for cache
|
||||
opcache.max_accelerated_files = 20000 ; Cache up to 20k files
|
||||
opcache.validate_timestamps = 0 ; Don't check for file changes (production)
|
||||
opcache.revalidate_freq = 0
|
||||
opcache.fast_shutdown = 1
|
||||
```
|
||||
|
||||
**Expected Impact**: 30-50% faster PHP execution
|
||||
|
||||
### 2. Optimize Nginx (if applicable)
|
||||
|
||||
```nginx
|
||||
worker_processes auto; # Use all CPU cores
|
||||
worker_connections 1024; # Handle 1024 connections per worker
|
||||
|
||||
# Enable gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript;
|
||||
```
|
||||
|
||||
### 3. Increase MySQL Connections
|
||||
|
||||
If you see "too many connections" errors:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/mysql/my.cnf
|
||||
|
||||
[mysqld]
|
||||
max_connections = 200
|
||||
|
||||
sudo systemctl restart mysql
|
||||
```
|
||||
|
||||
### 4. Monitor Resource Usage
|
||||
|
||||
```bash
|
||||
# CPU and memory usage
|
||||
htop
|
||||
|
||||
# PHP-FPM status (if enabled)
|
||||
curl http://localhost/status?full
|
||||
|
||||
# Disk I/O
|
||||
iostat -x 1
|
||||
|
||||
# Network usage
|
||||
iftop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Optimization
|
||||
|
||||
### Before and After Comparison
|
||||
|
||||
**Test Tool**: MaplePress CLI `wptest` command
|
||||
|
||||
```bash
|
||||
cd native/desktop/maplepress-cli
|
||||
|
||||
# Run concurrent test (25 simultaneous requests)
|
||||
go run main.go wptest --url https://your-site.com --mode parallel --concurrency 25
|
||||
```
|
||||
|
||||
**Expected Results:**
|
||||
|
||||
| Metric | Before | After (Optimized) | Improvement |
|
||||
|--------|--------|-------------------|-------------|
|
||||
| Average Response | 8,000ms | 300-500ms | **16-27x faster** |
|
||||
| P95 Latency | 9,000ms | < 1,000ms | **9x faster** |
|
||||
| Concurrent Handling | Queues 20/25 | Handles all 25 | **No queuing** |
|
||||
|
||||
### In-Plugin Speed Test
|
||||
|
||||
Use the built-in **MaplePress → Speed Test** page in WordPress admin:
|
||||
1. Go to **MaplePress** → **Speed Test**
|
||||
2. Select number of trials (10-20 recommended)
|
||||
3. Click **Run Speed Test**
|
||||
4. Review results and recommendations
|
||||
|
||||
---
|
||||
|
||||
## Common Performance Issues
|
||||
|
||||
### Issue: High PHP Memory Usage
|
||||
|
||||
**Symptoms**:
|
||||
- PHP workers crash with "out of memory"
|
||||
- WordPress shows white screen/500 errors
|
||||
|
||||
**Solutions**:
|
||||
```ini
|
||||
# Reduce worker count
|
||||
pm.max_children = 25 # Instead of 50
|
||||
|
||||
# Or increase PHP memory limit
|
||||
memory_limit = 256M # In php.ini
|
||||
```
|
||||
|
||||
### Issue: Still Slow After Optimization
|
||||
|
||||
**Check**:
|
||||
1. **Database queries**: Slow plugins, missing indexes
|
||||
```bash
|
||||
# Enable MySQL slow query log
|
||||
slow_query_log = 1
|
||||
slow_query_log_file = /var/log/mysql/slow.log
|
||||
long_query_time = 1
|
||||
```
|
||||
|
||||
2. **Caching**: Aggressive caching can hide PHP-FPM issues
|
||||
- Clear all caches and test
|
||||
- Disable page caching temporarily
|
||||
|
||||
3. **Network latency**: Test from different locations
|
||||
```bash
|
||||
curl -w "@curl-format.txt" -o /dev/null -s https://your-site.com
|
||||
```
|
||||
|
||||
4. **Resource limits**: Check if hitting CPU/RAM limits
|
||||
```bash
|
||||
# Monitor during load test
|
||||
top
|
||||
free -h
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Production Deployment Checklist
|
||||
|
||||
- [ ] PHP-FPM `pm.max_children` increased (≥ 25 for 2GB, ≥ 50 for 4GB)
|
||||
- [ ] OpCache enabled and configured
|
||||
- [ ] Nginx worker_processes set to `auto`
|
||||
- [ ] MySQL max_connections increased (≥ 200)
|
||||
- [ ] Caching strategy reviewed (not hiding problems)
|
||||
- [ ] Monitoring tools installed (htop, iotop, iftop)
|
||||
- [ ] Load testing performed (wptest with concurrency 25+)
|
||||
- [ ] Resource usage monitored under load
|
||||
- [ ] Backup/rollback plan in place
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### WordPress Documentation
|
||||
- [PHP-FPM Configuration](https://www.php.net/manual/en/install.fpm.configuration.php)
|
||||
- [OpCache Configuration](https://www.php.net/manual/en/opcache.configuration.php)
|
||||
|
||||
### MaplePress Resources
|
||||
- **Production Deployment Guide**: See `cloud/infrastructure/production/setup/08_wordpress.md` in the MaplePress repository
|
||||
- **MaplePress CLI**: Speed testing and diagnostics tool
|
||||
- **Support**: Contact MaplePress support for optimization assistance
|
||||
|
||||
### Performance Tools
|
||||
- **htop**: Interactive process viewer
|
||||
- **iotop**: Disk I/O monitoring
|
||||
- **iftop**: Network bandwidth monitoring
|
||||
- **MySQL Tuner**: `mysqltuner.pl` for database optimization
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**The #1 performance issue is PHP-FPM worker starvation, not backend speed.**
|
||||
|
||||
MaplePress backend processes searches in < 20ms, but if your WordPress site only has 5 PHP-FPM workers and receives 25 concurrent requests, users will wait 8-10 seconds while requests queue.
|
||||
|
||||
**Quick Win**: Increase `pm.max_children` from 5 to 50 (on 4GB server) for **16-27x faster response times**.
|
||||
|
||||
Your MaplePress subscription includes a lightning-fast backend. Ensure your WordPress frontend can keep up! ⚡
|
||||
362
native/wordpress/maplepress-plugin/includes/admin-dashboard.php
Normal file
362
native/wordpress/maplepress-plugin/includes/admin-dashboard.php
Normal 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' ); ?>
|
||||
|
|
||||
<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>'
|
||||
);
|
||||
?>
|
||||
|
|
||||
<a href="https://getmaplepress.com" target="_blank" rel="noopener noreferrer" style="color: #2271b1; text-decoration: none;">
|
||||
<?php esc_html_e( 'Documentation', 'maplepress' ); ?>
|
||||
</a>
|
||||
|
|
||||
<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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 ); ?>
|
||||
|
||||
<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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
115
native/wordpress/maplepress-plugin/includes/class-maplepress.php
Normal file
115
native/wordpress/maplepress-plugin/includes/class-maplepress.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
82
native/wordpress/maplepress-plugin/maplepress-plugin.php
Normal file
82
native/wordpress/maplepress-plugin/maplepress-plugin.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: MaplePress
|
||||
* Plugin URI: https://codeberg.org/mapleopentech/monorepo/src/branch/main/native/wordpress/maplepress-plugin
|
||||
* Description: Fast, scalable search for WordPress - Offloads search processing to the cloud for lightning-fast results without slowing down your server
|
||||
* Version: 1.0.0
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 7.4
|
||||
* Author: Maple Open Technologies
|
||||
* Author URI: https://mapleopentech.io
|
||||
* License: GPL-2.0+
|
||||
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
||||
* Text Domain: maplepress
|
||||
* Domain Path: /languages
|
||||
*
|
||||
* @package MaplePress
|
||||
*/
|
||||
|
||||
// If this file is called directly, abort.
|
||||
if (!defined("WPINC")) {
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current plugin version.
|
||||
*/
|
||||
define("MAPLEPRESS_VERSION", "1.0.0");
|
||||
|
||||
/**
|
||||
* Plugin file path.
|
||||
*/
|
||||
define("MAPLEPRESS_PLUGIN_FILE", __FILE__);
|
||||
|
||||
/**
|
||||
* Plugin directory path.
|
||||
*/
|
||||
define("MAPLEPRESS_PLUGIN_DIR", plugin_dir_path(__FILE__));
|
||||
|
||||
/**
|
||||
* Plugin directory URL.
|
||||
* Use set_url_scheme() to ensure correct protocol (HTTP/HTTPS) based on current request.
|
||||
* This prevents mixed content issues when site is served over HTTPS.
|
||||
*/
|
||||
define("MAPLEPRESS_PLUGIN_URL", set_url_scheme(plugin_dir_url(__FILE__)));
|
||||
|
||||
/**
|
||||
* The code that runs during plugin activation.
|
||||
*/
|
||||
function activate_maplepress()
|
||||
{
|
||||
require_once MAPLEPRESS_PLUGIN_DIR .
|
||||
"includes/class-maplepress-activator.php";
|
||||
MaplePress_Activator::activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* The code that runs during plugin deactivation.
|
||||
*/
|
||||
function deactivate_maplepress()
|
||||
{
|
||||
require_once MAPLEPRESS_PLUGIN_DIR .
|
||||
"includes/class-maplepress-deactivator.php";
|
||||
MaplePress_Deactivator::deactivate();
|
||||
}
|
||||
|
||||
register_activation_hook(__FILE__, "activate_maplepress");
|
||||
register_deactivation_hook(__FILE__, "deactivate_maplepress");
|
||||
|
||||
/**
|
||||
* The core plugin class.
|
||||
*/
|
||||
require MAPLEPRESS_PLUGIN_DIR . "includes/class-maplepress.php";
|
||||
|
||||
/**
|
||||
* Begins execution of the plugin.
|
||||
*/
|
||||
function run_maplepress()
|
||||
{
|
||||
$plugin = new MaplePress();
|
||||
$plugin->run();
|
||||
}
|
||||
run_maplepress();
|
||||
BIN
native/wordpress/maplepress-plugin/maplepress-plugin.zip
Normal file
BIN
native/wordpress/maplepress-plugin/maplepress-plugin.zip
Normal file
Binary file not shown.
92
native/wordpress/maplepress-plugin/readme.txt
Normal file
92
native/wordpress/maplepress-plugin/readme.txt
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
=== MaplePress ===
|
||||
Contributors: mapleopentech
|
||||
Tags: search, cloud services, performance, scalable, cloud search, offload processing
|
||||
Requires at least: 6.0
|
||||
Tested up to: 6.4
|
||||
Requires PHP: 7.4
|
||||
Stable tag: 1.0.0
|
||||
License: GPLv2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Cloud services platform for WordPress - Offloads computationally intensive tasks to cloud infrastructure. Currently features advanced search, with more services coming soon.
|
||||
|
||||
== Description ==
|
||||
|
||||
MaplePress connects your WordPress site to a cloud services platform that handles computationally intensive tasks remotely. Instead of burdening your WordPress server, MaplePress offloads processing to dedicated cloud infrastructure for better performance and scalability.
|
||||
|
||||
**Current Features:**
|
||||
|
||||
* **Cloud-powered search** - Lightning-fast search with advanced indexing
|
||||
* **Scalable infrastructure** - Handles high-traffic effortlessly
|
||||
* **Offloaded processing** - Frees up your WordPress server resources
|
||||
* **Automatic content indexing** - Content synced to the cloud automatically
|
||||
* **Centralized management** - Manage multiple sites from one dashboard
|
||||
* **Modern technology** - Better performance than default WordPress search
|
||||
|
||||
**Coming Soon:**
|
||||
|
||||
* Cloud-powered file uploads and storage
|
||||
* Advanced metrics and analytics
|
||||
* Performance monitoring
|
||||
* And more cloud services to enhance your WordPress site
|
||||
|
||||
**How It Works:**
|
||||
|
||||
1. Sign up for a MaplePress account
|
||||
2. Install and activate this plugin
|
||||
3. Enter your API key in the settings
|
||||
4. MaplePress begins handling cloud services for your site
|
||||
5. Your visitors enjoy better performance and enhanced features
|
||||
|
||||
**Requirements:**
|
||||
|
||||
* WordPress 6.0 or higher
|
||||
* PHP 7.4 or higher
|
||||
* MaplePress account (sign up at https://mapleopentech.io)
|
||||
|
||||
== Installation ==
|
||||
|
||||
1. Upload the plugin files to `/wp-content/plugins/maplepress-plugin/`, or install through the WordPress plugins screen
|
||||
2. Activate the plugin through the 'Plugins' screen in WordPress
|
||||
3. Go to Settings > MaplePress to configure the plugin
|
||||
4. Enter your MaplePress API URL and API Key
|
||||
5. Enable the plugin and save settings
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= Do I need a MaplePress account? =
|
||||
|
||||
Yes, you need a MaplePress account to use this plugin. Sign up at https://mapleopentech.io
|
||||
|
||||
= Is my data secure? =
|
||||
|
||||
Yes, all communication between your WordPress site and MaplePress backend is encrypted. Your API key is stored securely.
|
||||
|
||||
= How often is content indexed? =
|
||||
|
||||
Content is indexed automatically whenever you create, update, or delete posts and pages.
|
||||
|
||||
= Can I customize the search results? =
|
||||
|
||||
Yes, the plugin provides filters and hooks for developers to customize search behavior and results display.
|
||||
|
||||
== Screenshots ==
|
||||
|
||||
1. MaplePress settings page with connection status
|
||||
2. Cloud services dashboard showing usage metrics
|
||||
3. Search results powered by cloud infrastructure
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.0.0 =
|
||||
* Initial release
|
||||
* Cloud services platform integration
|
||||
* Cloud-powered search with automatic indexing
|
||||
* Settings page for API configuration
|
||||
* Connection verification and usage monitoring
|
||||
* Foundation for future cloud services (uploads, metrics, etc.)
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 1.0.0 =
|
||||
Initial release of MaplePress plugin.
|
||||
23
native/wordpress/maplepress-plugin/uninstall.php
Normal file
23
native/wordpress/maplepress-plugin/uninstall.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
/**
|
||||
* Fired when the plugin is uninstalled.
|
||||
*
|
||||
* @package MaplePress
|
||||
* @subpackage MaplePress/includes
|
||||
*/
|
||||
|
||||
// If uninstall not called from WordPress, then exit.
|
||||
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Delete all plugin options
|
||||
delete_option( 'maplepress_settings' );
|
||||
|
||||
// Delete any transients
|
||||
delete_transient( 'maplepress_activation_redirect' );
|
||||
delete_transient( 'maplepress_redirect_to_sync' );
|
||||
delete_transient( 'maplepress_pending_deletion' );
|
||||
|
||||
// Log the uninstall
|
||||
error_log( 'MaplePress: Plugin uninstalled and all data removed' );
|
||||
Loading…
Add table
Add a link
Reference in a new issue