diff --git a/native/wordpress/maple-performance-wp/README.md b/native/wordpress/maple-performance-wp/README.md
new file mode 100644
index 0000000..c7df4b8
--- /dev/null
+++ b/native/wordpress/maple-performance-wp/README.md
@@ -0,0 +1,228 @@
+# Maple Performance WP 🍁
+
+A lightweight, privacy-focused WordPress performance plugin. No external dependencies, no tracking, no upsells.
+
+**Built by [Maple Open Tech](https://mapleopentech.ca) for Canadian businesses who care about data sovereignty.**
+
+## Features
+
+### Page Caching
+- Static HTML file generation
+- Gzip pre-compression
+- Brotli pre-compression (if PHP extension installed)
+- Smart cache invalidation on content updates
+- Automatic exclusion for logged-in users
+
+### Asset Optimization
+- **HTML**: Minification, comment removal
+- **CSS**: Minification, aggregation, optional async loading
+- **JavaScript**: Minification, aggregation (disabled by default for safety)
+- **Google Fonts**: Combine multiple requests, add display:swap, optional deferred loading
+
+### Lazy Loading
+- Native `loading="lazy"` for images
+- Native `loading="lazy"` for iframes
+- Exclude LCP/hero images from lazy loading
+
+### Extra Optimizations
+- Remove WordPress emoji scripts
+- Remove query strings from static resources
+- DNS prefetch hints
+- Preconnect to third-party domains
+
+### Smart Plugin Compatibility
+
+Maple Performance automatically detects these plugins and applies safe exclusions:
+
+| Plugin | Automatic Protections |
+|--------|----------------------|
+| **WooCommerce** | Cart/checkout/account pages excluded from cache, WooCommerce cookies bypass cache, cart fragments and checkout scripts protected |
+| **LearnDash** | Lesson/topic/quiz pages excluded, progress tracking AJAX protected, quiz scripts excluded from aggregation |
+| **WPForms** | Form validation scripts excluded, AJAX submissions protected |
+| **Wordfence** | Security scripts excluded, login pages not cached, firewall bypass cookies respected |
+| **Gravity Forms** | Form scripts excluded from aggregation |
+| **Contact Form 7** | Form scripts excluded from aggregation |
+| **Elementor** | Builder scripts excluded from aggregation |
+
+## Site Modes
+
+Select your site type and Maple Performance automatically applies safe defaults:
+
+| Site Type | JS Aggregate | CSS Defer | Expected Score |
+|-----------|--------------|-----------|----------------|
+| Brochure/Blog | ✅ Available | ✅ Available | 80-95 |
+| WooCommerce | ❌ Disabled | ❌ Disabled | 70-85 |
+| LearnDash | ❌ Disabled | ❌ Disabled | 70-85 |
+| WooCommerce + LearnDash | ❌ Disabled | ❌ Disabled | 65-80 |
+
+## Installation
+
+### From GitHub
+1. Download or clone this repository
+2. Upload the `maple-performance-wp` folder to `/wp-content/plugins/`
+3. Activate through WordPress admin
+4. Go to Settings > Maple Performance
+5. Select your site type and configure
+
+### From WordPress Admin
+1. Go to Plugins > Add New
+2. Upload the zip file
+3. Activate and configure
+
+## Configuration
+
+### Brochure Sites (Maximum Performance)
+
+```
+Site Mode: Brochure
+Cache: Enabled
+HTML Minify: On
+CSS Minify: On
+CSS Aggregate: On
+CSS Defer: On (optional)
+JS Minify: On
+JS Aggregate: On
+Lazy Load: On
+```
+
+### WooCommerce Sites (Safe Defaults)
+
+```
+Site Mode: WooCommerce
+Cache: Enabled (cart/checkout excluded)
+HTML Minify: On
+CSS Minify: On
+CSS Aggregate: On
+CSS Defer: Off
+JS Minify: On
+JS Aggregate: Off (protects checkout)
+Lazy Load: On
+```
+
+### LearnDash Sites (Safe Defaults)
+
+```
+Site Mode: LearnDash
+Cache: Enabled (lessons excluded for logged-in)
+HTML Minify: On
+CSS Minify: On
+CSS Aggregate: On
+CSS Defer: Off
+JS Minify: On
+JS Aggregate: Off (protects tracking)
+Lazy Load: On
+```
+
+## Privacy
+
+This plugin:
+- ✅ Processes everything locally on your server
+- ✅ Makes zero external API calls
+- ✅ Sends no data to third parties
+- ✅ Has no tracking or analytics
+- ✅ Has no premium upsells or nags
+- ✅ Is fully open source (GPL-2.0)
+
+## Privacy & GDPR Compliance
+
+Maple Performance WP is designed with privacy as a core principle:
+
+| Aspect | Status |
+|--------|--------|
+| Personal data collection | ❌ None |
+| Cookies set by plugin | ❌ None |
+| External connections | ❌ None |
+| Tracking/analytics | ❌ None |
+| Third-party services | ❌ None |
+
+### What the plugin stores
+
+- **Page cache**: Static HTML copies of publicly-visible pages (same content any visitor sees)
+- **Asset cache**: Aggregated/minified CSS and JS files
+- **Settings**: Your plugin configuration (no personal data)
+
+### Cookie behavior
+
+The plugin **reads** existing cookies (WordPress login, WooCommerce cart) only to determine whether to serve cached content. It never sets, modifies, or transmits cookie data.
+
+### Privacy Policy
+
+The plugin automatically registers suggested privacy policy text with WordPress (Settings → Privacy) that you can include in your site's privacy policy.
+
+## Requirements
+
+- WordPress 5.9+
+- PHP 7.4+
+- Write access to `wp-content/cache/`
+
+## Cache Location
+
+Cached files are stored in:
+```
+wp-content/cache/maple-performance/
+├── assets/ # Aggregated CSS/JS files
+├── {domain}/ # Page cache by domain
+│ └── {path}/ # Page cache by URL path
+│ ├── https-index.html
+│ ├── https-index.html.gz
+│ └── https-index.html.br
+```
+
+## Hooks & Filters
+
+### Actions
+
+```php
+// Clear all cache
+do_action( 'maple_performance_clear_cache' );
+
+// After cache is cleared
+do_action( 'maple_performance_cache_cleared' );
+```
+
+### Filters
+
+```php
+// Modify settings programmatically
+add_filter( 'maple_performance_settings', function( $settings ) {
+ $settings['cache_enabled'] = false;
+ return $settings;
+});
+
+// Exclude specific URLs from caching
+add_filter( 'maple_performance_exclude_url', function( $exclude, $url ) {
+ if ( strpos( $url, '/members/' ) !== false ) {
+ return true;
+ }
+ return $exclude;
+}, 10, 2 );
+```
+
+## Comparison
+
+| Feature | Maple Performance | Autoptimize | Cache Enabler | WP Rocket |
+|---------|------------------|-------------|---------------|-----------|
+| Page Cache | ✅ | ❌ | ✅ | ✅ |
+| CSS/JS Minify | ✅ | ✅ | ❌ | ✅ |
+| CSS/JS Aggregate | ✅ | ✅ | ❌ | ✅ |
+| Google Fonts Optimization | ✅ | ✅ | ❌ | ✅ |
+| External Dependencies | ❌ None | ⚠️ Optional | ⚠️ CDN promo | ✅ Required |
+| Tracking | ❌ None | ⚠️ News feed | ❌ None | ✅ License |
+| Upsells | ❌ None | ✅ Yes | ✅ Yes | N/A (paid) |
+| WooCommerce Safe Mode | ✅ | ❌ | ❌ | ✅ |
+| LearnDash Safe Mode | ✅ | ❌ | ❌ | ❌ |
+| Price | Free | Free | Free | $59/year |
+
+## Contributing
+
+Contributions are welcome! Please feel free to submit issues and pull requests.
+
+## License
+
+GPL-2.0-or-later - see [LICENSE](LICENSE) for details.
+
+## Credits
+
+Built by [Maple Open Tech](https://mapleopentech.ca) 🍁
+
+Inspired by the core concepts from Autoptimize and Cache Enabler, rebuilt from scratch with privacy and safety in mind.
diff --git a/native/wordpress/maple-performance-wp/css/index.php b/native/wordpress/maple-performance-wp/css/index.php
new file mode 100644
index 0000000..4f5fef0
--- /dev/null
+++ b/native/wordpress/maple-performance-wp/css/index.php
@@ -0,0 +1,11 @@
+init_hooks();
+ }
+
+ /**
+ * Initialize hooks
+ */
+ private function init_hooks() {
+ // Add admin menu
+ add_action( 'admin_menu', array( $this, 'add_menu' ) );
+
+ // Register settings
+ add_action( 'admin_init', array( $this, 'register_settings' ) );
+
+ // Admin bar cache clear button
+ add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_button' ), 100 );
+
+ // Handle cache clear action
+ add_action( 'admin_init', array( $this, 'handle_cache_clear' ) );
+
+ // Admin notices
+ add_action( 'admin_notices', array( $this, 'admin_notices' ) );
+
+ // Admin styles
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_styles' ) );
+
+ // Plugin action links
+ add_filter( 'plugin_action_links_' . plugin_basename( MAPLE_PERF_FILE ), array( $this, 'action_links' ) );
+
+ // Privacy policy
+ add_action( 'admin_init', array( $this, 'add_privacy_policy_content' ) );
+ }
+
+ /**
+ * Add privacy policy content
+ */
+ public function add_privacy_policy_content() {
+ if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
+ return;
+ }
+
+ $content = sprintf(
+ '
%s %s
%s %s
%s %s
%s %s
',
+ __( 'Maple Performance WP', 'maple-performance' ),
+ __( 'This site uses the Maple Performance WP plugin to improve page load times through caching and asset optimization.', 'maple-performance' ),
+ __( 'What personal data we collect and why', 'maple-performance' ),
+ __( 'Maple Performance WP does not collect, store, or process any personal data. The plugin caches publicly-visible page content to improve performance. By default, pages are not cached for logged-in users, ensuring that no user-specific content is stored in the cache.', 'maple-performance' ),
+ __( 'Cookies', 'maple-performance' ),
+ __( 'Maple Performance WP does not set any cookies. The plugin may read existing cookies (such as WordPress login cookies or WooCommerce cart cookies) solely to determine whether to serve a cached page or bypass the cache for dynamic content. No cookie data is stored or transmitted.', 'maple-performance' ),
+ __( 'Third-party services', 'maple-performance' ),
+ __( 'Maple Performance WP does not connect to any external services or transmit any data to third parties. All caching and optimization is performed locally on your web server.', 'maple-performance' )
+ );
+
+ wp_add_privacy_policy_content(
+ 'Maple Performance WP',
+ wp_kses_post( $content )
+ );
+ }
+
+ /**
+ * Add admin menu
+ */
+ public function add_menu() {
+ add_options_page(
+ __( 'Maple Performance', 'maple-performance' ),
+ __( 'Maple Performance', 'maple-performance' ),
+ 'manage_options',
+ 'maple-performance',
+ array( $this, 'settings_page' )
+ );
+ }
+
+ /**
+ * Register settings
+ */
+ public function register_settings() {
+ register_setting(
+ 'maple_performance_settings',
+ 'maple_performance_settings',
+ array( $this, 'sanitize_settings' )
+ );
+ }
+
+ /**
+ * Sanitize settings
+ */
+ public function sanitize_settings( $input ) {
+ $sanitized = array();
+
+ // Site mode
+ $sanitized['site_mode'] = sanitize_text_field( $input['site_mode'] ?? 'brochure' );
+
+ // Google Fonts mode
+ $valid_font_modes = array( 'leave', 'remove', 'combine', 'defer' );
+ $sanitized['google_fonts'] = in_array( $input['google_fonts'] ?? 'defer', $valid_font_modes )
+ ? $input['google_fonts']
+ : 'defer';
+
+ // Plugin compatibility
+ $sanitized['compat_auto_detect'] = ! empty( $input['compat_auto_detect'] );
+
+ // Manual plugin selection
+ $valid_compat_plugins = array( 'woocommerce', 'learndash', 'wordfence', 'wpforms', 'gutenberg_fse' );
+ $sanitized['compat_plugins'] = array();
+ if ( ! empty( $input['compat_plugins'] ) && is_array( $input['compat_plugins'] ) ) {
+ foreach ( $input['compat_plugins'] as $plugin ) {
+ if ( in_array( $plugin, $valid_compat_plugins ) ) {
+ $sanitized['compat_plugins'][] = $plugin;
+ }
+ }
+ }
+
+ // Boolean settings
+ $booleans = array(
+ 'cache_enabled', 'cache_logged_in', 'cache_gzip', 'cache_brotli',
+ 'html_minify', 'html_remove_comments',
+ 'css_minify', 'css_aggregate', 'css_inline_aggregate', 'css_defer',
+ 'js_minify', 'js_aggregate', 'js_defer', 'js_exclude_jquery',
+ 'remove_emojis', 'remove_query_strings', 'dns_prefetch',
+ 'lazyload_images', 'lazyload_iframes',
+ 'local_font_display',
+ );
+
+ foreach ( $booleans as $key ) {
+ $sanitized[ $key ] = ! empty( $input[ $key ] );
+ }
+
+ // Integer settings
+ $sanitized['cache_expiry'] = absint( $input['cache_expiry'] ?? 0 );
+
+ // Array settings (textarea, one per line)
+ $arrays = array(
+ 'exclude_paths', 'exclude_cookies', 'exclude_js', 'exclude_css',
+ 'preconnect_domains', 'lazyload_exclude', 'preload_fonts',
+ );
+
+ foreach ( $arrays as $key ) {
+ $value = $input[ $key ] ?? '';
+ if ( is_string( $value ) ) {
+ $lines = explode( "\n", $value );
+ $sanitized[ $key ] = array_filter( array_map( function( $line ) {
+ // Sanitize each line - remove potentially dangerous characters
+ $line = trim( $line );
+ $line = str_replace( array( "\0", "\r" ), '', $line );
+ // For URLs/paths, use esc_url_raw for domains, sanitize_text_field for others
+ if ( strpos( $line, 'http' ) === 0 || strpos( $line, '//' ) === 0 ) {
+ return esc_url_raw( $line );
+ }
+ return sanitize_text_field( $line );
+ }, $lines ) );
+ } else {
+ $sanitized[ $key ] = array();
+ }
+ }
+
+ // Clear cache when settings change
+ maple_performance()->clear_cache();
+
+ return $sanitized;
+ }
+
+ /**
+ * Add admin bar button
+ */
+ public function add_admin_bar_button( $wp_admin_bar ) {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+
+ $wp_admin_bar->add_node( array(
+ 'id' => 'maple-performance',
+ 'title' => ' ' . __( 'Maple Cache', 'maple-performance' ),
+ 'href' => '#',
+ ) );
+
+ $wp_admin_bar->add_node( array(
+ 'parent' => 'maple-performance',
+ 'id' => 'maple-clear-cache',
+ 'title' => __( 'Clear All Cache', 'maple-performance' ),
+ 'href' => wp_nonce_url( admin_url( 'admin.php?action=maple_clear_cache' ), 'maple_clear_cache' ),
+ ) );
+
+ $wp_admin_bar->add_node( array(
+ 'parent' => 'maple-performance',
+ 'id' => 'maple-settings',
+ 'title' => __( 'Settings', 'maple-performance' ),
+ 'href' => admin_url( 'options-general.php?page=maple-performance' ),
+ ) );
+
+ // Show cache stats - use transient to avoid filesystem scan on every page load
+ $stats = get_transient( 'maple_perf_cache_stats' );
+ if ( false === $stats ) {
+ // Only calculate if not cached, with a 5-minute expiry
+ $stats = array(
+ 'count' => Maple_Performance_Cache::get_cache_count(),
+ 'size' => Maple_Performance_Cache::get_cache_size(),
+ );
+ set_transient( 'maple_perf_cache_stats', $stats, 5 * MINUTE_IN_SECONDS );
+ }
+
+ $wp_admin_bar->add_node( array(
+ 'parent' => 'maple-performance',
+ 'id' => 'maple-cache-stats',
+ 'title' => sprintf( __( 'Cache: %d pages (%s)', 'maple-performance' ), $stats['count'], size_format( $stats['size'] ) ),
+ 'href' => admin_url( 'options-general.php?page=maple-performance' ),
+ ) );
+ }
+
+ /**
+ * Handle cache clear action
+ */
+ public function handle_cache_clear() {
+ if ( ! isset( $_GET['action'] ) || $_GET['action'] !== 'maple_clear_cache' ) {
+ return;
+ }
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_die( __( 'Unauthorized', 'maple-performance' ) );
+ }
+
+ if ( ! wp_verify_nonce( $_GET['_wpnonce'] ?? '', 'maple_clear_cache' ) ) {
+ wp_die( __( 'Invalid nonce', 'maple-performance' ) );
+ }
+
+ maple_performance()->clear_cache();
+
+ // Redirect back with notice
+ $redirect = remove_query_arg( array( 'action', '_wpnonce' ), wp_get_referer() );
+ $redirect = add_query_arg( 'maple_cache_cleared', '1', $redirect );
+
+ wp_safe_redirect( $redirect );
+ exit;
+ }
+
+ /**
+ * Admin notices
+ */
+ public function admin_notices() {
+ // Show activation conflict notice (only once, immediately after activation)
+ $activation_conflicts = get_transient( 'maple_perf_activation_conflict' );
+ if ( false !== $activation_conflicts && is_array( $activation_conflicts ) ) {
+ delete_transient( 'maple_perf_activation_conflict' );
+
+ echo '';
+ echo '
' . esc_html__( '⚠️ Maple Performance WP - Important!', 'maple-performance' ) . '
';
+ echo '
' . sprintf(
+ esc_html__( 'Another caching plugin is already active: %s', 'maple-performance' ),
+ '' . esc_html( implode( ', ', $activation_conflicts ) ) . ' '
+ ) . '
';
+ echo '
' . esc_html__( 'Running multiple caching plugins can cause site errors, blank pages, or performance issues.', 'maple-performance' ) . '
';
+ echo '
';
+ echo '' . esc_html__( 'Choose which plugin to keep', 'maple-performance' ) . ' ';
+ echo '' . esc_html__( 'Go to Settings', 'maple-performance' ) . ' ';
+ echo '
';
+ echo '
';
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display check
+ if ( isset( $_GET['maple_cache_cleared'] ) ) {
+ echo '';
+ echo '
' . esc_html__( 'Maple Performance: Cache cleared successfully.', 'maple-performance' ) . '
';
+ echo '
';
+ }
+
+ // Check if settings were saved
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display check
+ $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
+ if ( isset( $_GET['settings-updated'] ) && $page === 'maple-performance' ) {
+ echo '';
+ echo '
' . esc_html__( 'Maple Performance: Settings saved and cache cleared.', 'maple-performance' ) . '
';
+ echo '
';
+ }
+ }
+
+ /**
+ * Admin styles
+ */
+ public function admin_styles( $hook ) {
+ if ( $hook !== 'settings_page_maple-performance' ) {
+ return;
+ }
+
+ wp_add_inline_style( 'common', '
+ .maple-settings { max-width: 900px; }
+ .maple-settings h2 { border-bottom: 1px solid #ccc; padding-bottom: 10px; margin-top: 30px; }
+ .maple-settings h2:first-of-type { margin-top: 10px; }
+ .maple-settings table { margin-bottom: 20px; }
+ .maple-settings .description { color: #666; font-style: italic; }
+ .maple-settings textarea { width: 100%; max-width: 400px; }
+ .maple-settings .site-mode-card {
+ border: 2px solid #ddd;
+ padding: 15px;
+ margin: 10px 0;
+ border-radius: 5px;
+ cursor: pointer;
+ }
+ .maple-settings .site-mode-card:hover { border-color: #2271b1; }
+ .maple-settings .site-mode-card.selected { border-color: #2271b1; background: #f0f7fc; }
+ .maple-settings .site-mode-card h4 { margin: 0 0 5px; }
+ .maple-settings .warning { color: #d63638; }
+ .maple-settings .safe { color: #00a32a; }
+ .maple-cache-stats {
+ background: #f0f0f1;
+ padding: 15px;
+ border-radius: 5px;
+ margin-bottom: 20px;
+ }
+ .maple-cache-stats strong { font-size: 1.2em; }
+ .maple-detected-plugins {
+ background: #f9f9f9;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ padding: 15px 20px;
+ margin-bottom: 25px;
+ }
+ .maple-detected-plugins h3 { margin-top: 0; }
+ .maple-detected-plugins table { margin-top: 15px; }
+ .maple-detected-plugins ul li { font-size: 13px; color: #555; }
+ ' );
+ }
+
+ /**
+ * Plugin action links
+ */
+ public function action_links( $links ) {
+ $settings_link = '' . __( 'Settings', 'maple-performance' ) . ' ';
+ $clear_link = '' . __( 'Clear Cache', 'maple-performance' ) . ' ';
+
+ array_unshift( $links, $settings_link, $clear_link );
+
+ return $links;
+ }
+
+ /**
+ * Settings page
+ */
+ public function settings_page() {
+ $settings = maple_performance()->settings;
+
+ // Get cache stats
+ $cache_count = Maple_Performance_Cache::get_cache_count();
+ $cache_size = Maple_Performance_Cache::get_cache_size();
+ ?>
+
+
+
+
+
+
+
+ get_detected() : array();
+ ?>
+
+
+
+
+
+ v |
+ Maple Open Tech |
+ 🍁
+
+
+ settings = maple_performance()->settings;
+ $this->init_hooks();
+ }
+
+ /**
+ * Initialize hooks
+ */
+ private function init_hooks() {
+ // Start output buffering early
+ add_action( 'template_redirect', array( $this, 'start_buffering' ), -999 );
+
+ // Cache clearing hooks
+ add_action( 'save_post', array( $this, 'clear_post_cache' ), 10, 1 );
+ add_action( 'delete_post', array( $this, 'clear_post_cache' ), 10, 1 );
+ add_action( 'wp_trash_post', array( $this, 'clear_post_cache' ), 10, 1 );
+ add_action( 'comment_post', array( $this, 'clear_post_cache_by_comment' ), 10, 2 );
+ add_action( 'edit_comment', array( $this, 'clear_post_cache_by_comment' ), 10, 1 );
+ add_action( 'switch_theme', array( __CLASS__, 'clear_all' ) );
+ add_action( 'activated_plugin', array( __CLASS__, 'clear_all' ) );
+ add_action( 'deactivated_plugin', array( __CLASS__, 'clear_all' ) );
+ add_action( 'update_option_permalink_structure', array( __CLASS__, 'clear_all' ) );
+
+ // WooCommerce hooks
+ if ( class_exists( 'WooCommerce' ) ) {
+ add_action( 'woocommerce_product_set_stock', array( $this, 'clear_product_cache' ) );
+ add_action( 'woocommerce_variation_set_stock', array( $this, 'clear_product_cache' ) );
+ }
+ }
+
+ /**
+ * Start output buffering
+ */
+ public function start_buffering() {
+ // Check if we should cache this request
+ if ( maple_performance()->is_excluded() ) {
+ return;
+ }
+
+ // Check if cached version exists
+ $cache_file = $this->get_cache_file_path();
+
+ if ( $this->serve_cache( $cache_file ) ) {
+ exit;
+ }
+
+ // Start buffering for cache creation
+ ob_start( array( $this, 'process_buffer' ) );
+ }
+
+ /**
+ * Process output buffer and create cache
+ */
+ public function process_buffer( $buffer ) {
+ // Don't cache empty or error pages
+ if ( empty( $buffer ) || http_response_code() !== 200 ) {
+ return $buffer;
+ }
+
+ // Don't cache if contains certain markers
+ if ( $this->should_skip_caching( $buffer ) ) {
+ return $buffer;
+ }
+
+ // Write cache file
+ $this->write_cache( $buffer );
+
+ return $buffer;
+ }
+
+ /**
+ * Check if page should skip caching based on content
+ */
+ private function should_skip_caching( $buffer ) {
+ // Skip if contains no-cache markers
+ $skip_markers = array(
+ '',
+ '',
+ 'woocommerce-cart',
+ 'woocommerce-checkout',
+ );
+
+ foreach ( $skip_markers as $marker ) {
+ if ( strpos( $buffer, $marker ) !== false ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get cache file path for current request
+ */
+ private function get_cache_file_path( $url = null ) {
+ if ( null === $url ) {
+ $url = $this->get_current_url();
+ }
+
+ $parsed = parse_url( $url );
+ $host = $parsed['host'] ?? '';
+ $path = $parsed['path'] ?? '/';
+
+ // Sanitize host - only allow alphanumeric, dots, and hyphens
+ $host = preg_replace( '/[^a-zA-Z0-9.-]/', '', $host );
+
+ // Sanitize path - remove any path traversal attempts
+ $path = str_replace( array( '..', "\0" ), '', $path );
+ $path = preg_replace( '/[^a-zA-Z0-9\/_-]/', '', $path );
+ $path = preg_replace( '#/+#', '/', $path ); // Collapse multiple slashes
+ $path = rtrim( $path, '/' );
+
+ if ( empty( $path ) ) {
+ $path = '/index';
+ }
+
+ // Limit path depth to prevent excessive directory creation
+ $path_parts = explode( '/', trim( $path, '/' ) );
+ if ( count( $path_parts ) > 10 ) {
+ $path_parts = array_slice( $path_parts, 0, 10 );
+ $path = '/' . implode( '/', $path_parts );
+ }
+
+ // Build cache directory structure
+ $cache_dir = MAPLE_PERF_CACHE_DIR . $host . $path . '/';
+
+ // Verify the resolved path is within cache directory
+ $real_cache_base = realpath( MAPLE_PERF_CACHE_DIR );
+ if ( $real_cache_base ) {
+ // Use dirname to check parent since $cache_dir may not exist yet
+ $parent_dir = dirname( $cache_dir );
+ while ( ! is_dir( $parent_dir ) && $parent_dir !== dirname( $parent_dir ) ) {
+ $parent_dir = dirname( $parent_dir );
+ }
+
+ if ( is_dir( $parent_dir ) ) {
+ $real_parent = realpath( $parent_dir );
+ if ( $real_parent && strpos( $real_parent, $real_cache_base ) !== 0 ) {
+ // Path escapes cache directory - return safe fallback
+ return MAPLE_PERF_CACHE_DIR . 'fallback/https-index.html';
+ }
+ }
+ }
+
+ // Determine cache file name
+ $is_https = is_ssl() ? 'https' : 'http';
+ $cache_file = $cache_dir . $is_https . '-index.html';
+
+ return $cache_file;
+ }
+
+ /**
+ * Get current URL
+ */
+ private function get_current_url() {
+ $scheme = is_ssl() ? 'https' : 'http';
+
+ // Sanitize HTTP_HOST
+ $host = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '';
+ $host = preg_replace( '/[^a-zA-Z0-9.-]/', '', $host );
+
+ // Sanitize REQUEST_URI
+ $uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/';
+ $uri = filter_var( $uri, FILTER_SANITIZE_URL );
+
+ // Remove query strings for cache key
+ $uri = strtok( $uri, '?' );
+
+ // Remove any null bytes or path traversal
+ $uri = str_replace( array( "\0", '..' ), '', $uri );
+
+ return $scheme . '://' . $host . $uri;
+ }
+
+ /**
+ * Serve cached file if exists
+ */
+ private function serve_cache( $cache_file ) {
+ // Check for gzipped version first
+ if ( $this->settings['cache_gzip'] && $this->client_accepts_gzip() ) {
+ $gzip_file = $cache_file . '.gz';
+ if ( file_exists( $gzip_file ) && $this->is_cache_valid( $gzip_file ) ) {
+ header( 'Content-Encoding: gzip' );
+ header( 'Content-Type: text/html; charset=UTF-8' );
+ header( 'X-Maple-Cache: HIT (gzip)' );
+ readfile( $gzip_file );
+ return true;
+ }
+ }
+
+ // Check for brotli version
+ if ( $this->settings['cache_brotli'] && $this->client_accepts_brotli() ) {
+ $br_file = $cache_file . '.br';
+ if ( file_exists( $br_file ) && $this->is_cache_valid( $br_file ) ) {
+ header( 'Content-Encoding: br' );
+ header( 'Content-Type: text/html; charset=UTF-8' );
+ header( 'X-Maple-Cache: HIT (brotli)' );
+ readfile( $br_file );
+ return true;
+ }
+ }
+
+ // Serve uncompressed
+ if ( file_exists( $cache_file ) && $this->is_cache_valid( $cache_file ) ) {
+ header( 'Content-Type: text/html; charset=UTF-8' );
+ header( 'X-Maple-Cache: HIT' );
+ readfile( $cache_file );
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if cache file is still valid
+ */
+ private function is_cache_valid( $cache_file ) {
+ // No expiry set
+ if ( empty( $this->settings['cache_expiry'] ) || $this->settings['cache_expiry'] === 0 ) {
+ return true;
+ }
+
+ $file_age = time() - filemtime( $cache_file );
+ $max_age = $this->settings['cache_expiry'] * HOUR_IN_SECONDS;
+
+ return $file_age < $max_age;
+ }
+
+ /**
+ * Check if client accepts gzip
+ */
+ private function client_accepts_gzip() {
+ $encoding = $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '';
+ return strpos( $encoding, 'gzip' ) !== false;
+ }
+
+ /**
+ * Check if client accepts brotli
+ */
+ private function client_accepts_brotli() {
+ $encoding = $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '';
+ return strpos( $encoding, 'br' ) !== false;
+ }
+
+ /**
+ * Write cache file
+ */
+ private function write_cache( $content ) {
+ $cache_file = $this->get_cache_file_path();
+ $cache_dir = dirname( $cache_file );
+
+ // Create directory if needed
+ if ( ! file_exists( $cache_dir ) ) {
+ wp_mkdir_p( $cache_dir );
+ }
+
+ // Verify we're still within cache directory (paranoid check)
+ $real_cache_base = realpath( MAPLE_PERF_CACHE_DIR );
+ $real_cache_dir = realpath( $cache_dir );
+
+ if ( false === $real_cache_base || false === $real_cache_dir ) {
+ return;
+ }
+
+ if ( strpos( $real_cache_dir, $real_cache_base ) !== 0 ) {
+ return;
+ }
+
+ // Add cache signature
+ $timestamp = gmdate( 'D, d M Y H:i:s' ) . ' GMT';
+ $signature = "\n";
+ $content .= $signature;
+
+ // Write HTML file with proper permissions
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
+ file_put_contents( $cache_file, $content );
+ if ( file_exists( $cache_file ) ) {
+ chmod( $cache_file, 0644 );
+ }
+
+ // Create gzipped version
+ if ( $this->settings['cache_gzip'] && function_exists( 'gzencode' ) ) {
+ $gzip_content = gzencode( $content, 9 );
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
+ file_put_contents( $cache_file . '.gz', $gzip_content );
+ if ( file_exists( $cache_file . '.gz' ) ) {
+ chmod( $cache_file . '.gz', 0644 );
+ }
+ }
+
+ // Create brotli version
+ if ( $this->settings['cache_brotli'] && function_exists( 'brotli_compress' ) ) {
+ $br_content = brotli_compress( $content );
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
+ file_put_contents( $cache_file . '.br', $br_content );
+ if ( file_exists( $cache_file . '.br' ) ) {
+ chmod( $cache_file . '.br', 0644 );
+ }
+ }
+ }
+
+ /**
+ * Clear cache for a specific post
+ */
+ public function clear_post_cache( $post_id ) {
+ // Don't clear for autosaves or revisions
+ if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
+ return;
+ }
+
+ $post = get_post( $post_id );
+ if ( ! $post || $post->post_status !== 'publish' ) {
+ return;
+ }
+
+ // Clear post URL
+ $url = get_permalink( $post_id );
+ $this->clear_url_cache( $url );
+
+ // Clear home page
+ $this->clear_url_cache( home_url( '/' ) );
+
+ // Clear archive pages
+ $post_type = get_post_type( $post_id );
+ $archive_url = get_post_type_archive_link( $post_type );
+ if ( $archive_url ) {
+ $this->clear_url_cache( $archive_url );
+ }
+
+ // Clear category/tag archives - limit to prevent performance issues
+ $taxonomies = get_object_taxonomies( $post_type );
+ $terms_cleared = 0;
+ $max_terms = 50; // Limit term cache clearing to prevent slowdown
+
+ foreach ( $taxonomies as $taxonomy ) {
+ if ( $terms_cleared >= $max_terms ) {
+ break;
+ }
+
+ $terms = get_the_terms( $post_id, $taxonomy );
+ if ( $terms && ! is_wp_error( $terms ) ) {
+ foreach ( $terms as $term ) {
+ if ( $terms_cleared >= $max_terms ) {
+ break;
+ }
+
+ $term_url = get_term_link( $term );
+ if ( ! is_wp_error( $term_url ) ) {
+ $this->clear_url_cache( $term_url );
+ $terms_cleared++;
+ }
+ }
+ }
+ }
+
+ // Clear stats transient since cache changed
+ delete_transient( 'maple_perf_cache_stats' );
+ }
+
+ /**
+ * Clear cache by comment
+ */
+ public function clear_post_cache_by_comment( $comment_id, $comment_approved = null ) {
+ $comment = get_comment( $comment_id );
+ if ( $comment ) {
+ $this->clear_post_cache( $comment->comment_post_ID );
+ }
+ }
+
+ /**
+ * Clear product cache (WooCommerce)
+ */
+ public function clear_product_cache( $product ) {
+ if ( is_numeric( $product ) ) {
+ $product_id = $product;
+ } else {
+ $product_id = $product->get_id();
+ }
+
+ $this->clear_post_cache( $product_id );
+ }
+
+ /**
+ * Clear cache for a specific URL
+ */
+ public function clear_url_cache( $url ) {
+ $cache_file = $this->get_cache_file_path( $url );
+
+ if ( file_exists( $cache_file ) ) {
+ @unlink( $cache_file );
+ }
+ if ( file_exists( $cache_file . '.gz' ) ) {
+ @unlink( $cache_file . '.gz' );
+ }
+ if ( file_exists( $cache_file . '.br' ) ) {
+ @unlink( $cache_file . '.br' );
+ }
+
+ // Also clear https version if http was cleared and vice versa
+ $alt_file = str_replace(
+ array( '/http-index.html', '/https-index.html' ),
+ array( '/https-index.html', '/http-index.html' ),
+ $cache_file
+ );
+
+ if ( file_exists( $alt_file ) ) {
+ @unlink( $alt_file );
+ }
+ if ( file_exists( $alt_file . '.gz' ) ) {
+ @unlink( $alt_file . '.gz' );
+ }
+ if ( file_exists( $alt_file . '.br' ) ) {
+ @unlink( $alt_file . '.br' );
+ }
+ }
+
+ /**
+ * Clear all cache
+ */
+ public static function clear_all() {
+ self::recursive_delete( MAPLE_PERF_CACHE_DIR );
+
+ // Recreate directories
+ wp_mkdir_p( MAPLE_PERF_CACHE_DIR );
+ wp_mkdir_p( MAPLE_PERF_CACHE_DIR . 'assets/' );
+
+ // Add index.php files with ABSPATH check
+ $index = " $max_iterations ) {
+ break; // Safety limit reached
+ }
+
+ $file_path = $file->getRealPath();
+
+ // Verify each file is within allowed directory
+ if ( strpos( $file_path, $real_dir ) !== 0 ) {
+ continue;
+ }
+
+ if ( $file->isDir() ) {
+ @rmdir( $file_path );
+ } else {
+ @unlink( $file_path );
+ }
+ }
+ } catch ( Exception $e ) {
+ // Handle iterator exceptions gracefully
+ return;
+ }
+ }
+
+ /**
+ * Get cache size
+ */
+ public static function get_cache_size() {
+ $size = 0;
+
+ if ( ! is_dir( MAPLE_PERF_CACHE_DIR ) ) {
+ return $size;
+ }
+
+ // Limit iterations to prevent runaway on huge cache directories
+ $max_iterations = 10000;
+ $iterations = 0;
+
+ try {
+ $files = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator( MAPLE_PERF_CACHE_DIR, RecursiveDirectoryIterator::SKIP_DOTS )
+ );
+
+ foreach ( $files as $file ) {
+ if ( ++$iterations > $max_iterations ) {
+ break; // Safety limit reached
+ }
+
+ if ( $file->isFile() ) {
+ $size += $file->getSize();
+ }
+ }
+ } catch ( Exception $e ) {
+ // Handle iterator exceptions gracefully
+ return $size;
+ }
+
+ return $size;
+ }
+
+ /**
+ * Get number of cached files
+ */
+ public static function get_cache_count() {
+ $count = 0;
+
+ if ( ! is_dir( MAPLE_PERF_CACHE_DIR ) ) {
+ return $count;
+ }
+
+ // Limit iterations to prevent runaway on huge cache directories
+ $max_iterations = 10000;
+ $iterations = 0;
+
+ try {
+ $files = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator( MAPLE_PERF_CACHE_DIR, RecursiveDirectoryIterator::SKIP_DOTS )
+ );
+
+ foreach ( $files as $file ) {
+ if ( ++$iterations > $max_iterations ) {
+ break; // Safety limit reached
+ }
+
+ if ( $file->isFile() && pathinfo( $file, PATHINFO_EXTENSION ) === 'html' ) {
+ $count++;
+ }
+ }
+ } catch ( Exception $e ) {
+ // Handle iterator exceptions gracefully
+ return $count;
+ }
+
+ return $count;
+ }
+}
diff --git a/native/wordpress/maple-performance-wp/inc/class-maple-compat.php b/native/wordpress/maple-performance-wp/inc/class-maple-compat.php
new file mode 100644
index 0000000..42d425f
--- /dev/null
+++ b/native/wordpress/maple-performance-wp/inc/class-maple-compat.php
@@ -0,0 +1,801 @@
+settings = maple_performance()->settings;
+ $this->determine_enabled_plugins();
+ $this->init_hooks();
+ }
+
+ /**
+ * Determine which plugins should have compatibility rules applied
+ */
+ private function determine_enabled_plugins() {
+ // If auto-detect is enabled, detect plugins
+ if ( ! empty( $this->settings['compat_auto_detect'] ) ) {
+ $this->detect_plugins();
+ $this->enabled = $this->detected;
+ } else {
+ // Use manual selection
+ $manual = $this->settings['compat_plugins'] ?? array();
+ foreach ( $manual as $plugin ) {
+ $this->enabled[ $plugin ] = true;
+ }
+ }
+ }
+
+ /**
+ * Detect active plugins (used when auto-detect is enabled)
+ */
+ private function detect_plugins() {
+ // WooCommerce
+ if ( class_exists( 'WooCommerce' ) || defined( 'WC_PLUGIN_FILE' ) ) {
+ $this->detected['woocommerce'] = true;
+ }
+
+ // LearnDash
+ if ( defined( 'LEARNDASH_VERSION' ) || class_exists( 'SFWD_LMS' ) ) {
+ $this->detected['learndash'] = true;
+ }
+
+ // WPForms
+ if ( defined( 'WPFORMS_VERSION' ) || class_exists( 'WPForms' ) ) {
+ $this->detected['wpforms'] = true;
+ }
+
+ // Wordfence
+ if ( defined( 'WORDFENCE_VERSION' ) || class_exists( 'wordfence' ) ) {
+ $this->detected['wordfence'] = true;
+ }
+
+ // Gutenberg FSE / Block Themes
+ if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
+ $this->detected['gutenberg_fse'] = true;
+ }
+
+ // Gravity Forms
+ if ( class_exists( 'GFForms' ) || defined( 'GF_MIN_WP_VERSION' ) ) {
+ $this->detected['gravityforms'] = true;
+ }
+
+ // Contact Form 7
+ if ( defined( 'WPCF7_VERSION' ) || class_exists( 'WPCF7' ) ) {
+ $this->detected['cf7'] = true;
+ }
+
+ // Elementor
+ if ( defined( 'ELEMENTOR_VERSION' ) ) {
+ $this->detected['elementor'] = true;
+ }
+
+ // === Caching plugin conflict detection ===
+
+ // WP Rocket
+ if ( defined( 'WP_ROCKET_VERSION' ) ) {
+ $this->detected['wp_rocket'] = true;
+ }
+
+ // W3 Total Cache
+ if ( defined( 'W3TC' ) || class_exists( 'W3_Plugin_TotalCache' ) ) {
+ $this->detected['w3tc'] = true;
+ }
+
+ // LiteSpeed Cache
+ if ( defined( 'LSCWP_V' ) || class_exists( 'LiteSpeed_Cache' ) ) {
+ $this->detected['litespeed'] = true;
+ }
+
+ // WP Super Cache
+ if ( defined( 'WPCACHEHOME' ) || function_exists( 'wp_cache_phase2' ) ) {
+ $this->detected['wp_super_cache'] = true;
+ }
+
+ // WP Fastest Cache
+ if ( class_exists( 'WpFastestCache' ) || defined( 'WPFC_WP_CONTENT_BASENAME' ) ) {
+ $this->detected['wp_fastest_cache'] = true;
+ }
+
+ // Autoptimize
+ if ( class_exists( 'autoptimizeMain' ) || defined( 'AUTOPTIMIZE_PLUGIN_VERSION' ) ) {
+ $this->detected['autoptimize'] = true;
+ }
+
+ // Cache Enabler
+ if ( class_exists( 'Cache_Enabler' ) || defined( 'CACHE_ENABLER_VERSION' ) ) {
+ $this->detected['cache_enabler'] = true;
+ }
+
+ // Hummingbird
+ if ( class_exists( 'WP_Hummingbird' ) || defined( 'WPHB_VERSION' ) ) {
+ $this->detected['hummingbird'] = true;
+ }
+
+ // Breeze (Cloudways)
+ if ( class_exists( 'Breeze_Admin' ) || defined( 'BREEZE_VERSION' ) ) {
+ $this->detected['breeze'] = true;
+ }
+
+ // SG Optimizer (SiteGround)
+ if ( class_exists( 'SiteGround_Optimizer' ) || defined( 'SG_OPTIMIZER_VERSION' ) ) {
+ $this->detected['sg_optimizer'] = true;
+ }
+
+ // Swift Performance
+ if ( class_exists( 'Swift_Performance' ) || defined( 'FLAVOR_FLAVOR' ) ) {
+ $this->detected['swift_performance'] = true;
+ }
+
+ // Comet Cache
+ if ( class_exists( 'WebSharks\\CometCache\\Classes\\Plugin' ) || defined( 'COMET_CACHE_VERSION' ) ) {
+ $this->detected['comet_cache'] = true;
+ }
+
+ // Powered Cache
+ if ( defined( 'POWERED_CACHE_VERSION' ) ) {
+ $this->detected['powered_cache'] = true;
+ }
+
+ // Perfmatters
+ if ( class_exists( 'Perfmatters' ) || defined( 'PERFMATTERS_VERSION' ) ) {
+ $this->detected['perfmatters'] = true;
+ }
+ }
+
+ /**
+ * Initialize hooks
+ */
+ private function init_hooks() {
+ // Only apply filters if at least one plugin is enabled
+ if ( empty( $this->enabled ) ) {
+ // Still check for caching conflicts in admin
+ add_action( 'admin_notices', array( $this, 'conflict_notices' ) );
+ return;
+ }
+
+ // Filter exclusions based on enabled plugins
+ add_filter( 'maple_performance_js_exclusions', array( $this, 'add_js_exclusions' ) );
+ add_filter( 'maple_performance_css_exclusions', array( $this, 'add_css_exclusions' ) );
+ add_filter( 'maple_performance_path_exclusions', array( $this, 'add_path_exclusions' ) );
+ add_filter( 'maple_performance_cookie_exclusions', array( $this, 'add_cookie_exclusions' ) );
+
+ // Add admin notice for conflicts
+ add_action( 'admin_notices', array( $this, 'conflict_notices' ) );
+
+ // WooCommerce specific hooks
+ if ( $this->is_enabled( 'woocommerce' ) ) {
+ $this->init_woocommerce_compat();
+ }
+
+ // LearnDash specific hooks
+ if ( $this->is_enabled( 'learndash' ) ) {
+ $this->init_learndash_compat();
+ }
+
+ // WPForms specific hooks
+ if ( $this->is_enabled( 'wpforms' ) ) {
+ $this->init_wpforms_compat();
+ }
+
+ // Wordfence specific hooks
+ if ( $this->is_enabled( 'wordfence' ) ) {
+ $this->init_wordfence_compat();
+ }
+
+ // Gutenberg FSE specific hooks
+ if ( $this->is_enabled( 'gutenberg_fse' ) ) {
+ $this->init_gutenberg_fse_compat();
+ }
+ }
+
+ /**
+ * Check if plugin compatibility is enabled (either via auto-detect or manual)
+ */
+ public function is_enabled( $plugin ) {
+ return ! empty( $this->enabled[ $plugin ] );
+ }
+
+ /**
+ * Check if plugin was detected (for display purposes)
+ */
+ public function is_detected( $plugin ) {
+ return ! empty( $this->detected[ $plugin ] );
+ }
+
+ /**
+ * Get all detected plugins
+ */
+ public function get_detected() {
+ // Always run detection for display in admin
+ if ( empty( $this->detected ) ) {
+ $this->detect_plugins();
+ }
+ return $this->detected;
+ }
+
+ /**
+ * Get all enabled plugins
+ */
+ public function get_enabled() {
+ return $this->enabled;
+ }
+
+ /**
+ * Add JS exclusions for enabled plugins
+ */
+ public function add_js_exclusions( $exclusions ) {
+ // WooCommerce
+ if ( $this->is_enabled( 'woocommerce' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'woocommerce',
+ 'wc-',
+ 'wc_',
+ 'jquery-blockui',
+ 'selectWoo',
+ 'select2',
+ 'js-cookie',
+ 'cart-fragments',
+ 'checkout',
+ 'add-to-cart',
+ 'payment',
+ 'stripe',
+ 'paypal',
+ 'square',
+ 'braintree',
+ ) );
+ }
+
+ // LearnDash
+ if ( $this->is_enabled( 'learndash' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'learndash',
+ 'sfwd-',
+ 'sfwd_',
+ 'ld-',
+ 'ld_',
+ 'ldlms',
+ 'quiz',
+ 'wpProQuiz',
+ ) );
+ }
+
+ // WPForms
+ if ( $this->is_enabled( 'wpforms' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'wpforms',
+ 'wpforms-',
+ 'jquery-validation',
+ 'mailcheck',
+ 'inputmask',
+ ) );
+ }
+
+ // Wordfence
+ if ( $this->is_enabled( 'wordfence' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'wordfence',
+ 'wf-',
+ 'wfls-',
+ ) );
+ }
+
+ // Gutenberg FSE / Block Themes
+ if ( $this->is_enabled( 'gutenberg_fse' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'wp-block-',
+ 'wp-edit-',
+ ) );
+ }
+
+ // Gravity Forms
+ if ( $this->is_enabled( 'gravityforms' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'gform',
+ 'gravityforms',
+ 'gf_',
+ ) );
+ }
+
+ // Contact Form 7
+ if ( $this->is_enabled( 'cf7' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'contact-form-7',
+ 'wpcf7',
+ ) );
+ }
+
+ // Elementor
+ if ( $this->is_enabled( 'elementor' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'elementor-',
+ 'elementor_',
+ ) );
+ }
+
+ return array_unique( $exclusions );
+ }
+
+ /**
+ * Add CSS exclusions for enabled plugins
+ */
+ public function add_css_exclusions( $exclusions ) {
+ // LearnDash - Focus Mode CSS should load normally
+ if ( $this->is_enabled( 'learndash' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'learndash-front',
+ 'sfwd-',
+ ) );
+ }
+
+ // WPForms
+ if ( $this->is_enabled( 'wpforms' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'wpforms',
+ ) );
+ }
+
+ // Gutenberg FSE / Block Themes - protect global styles
+ if ( $this->is_enabled( 'gutenberg_fse' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'global-styles',
+ 'wp-block-',
+ 'core-block-',
+ ) );
+ }
+
+ return array_unique( $exclusions );
+ }
+
+ /**
+ * Add path exclusions for enabled plugins
+ */
+ public function add_path_exclusions( $exclusions ) {
+ // WooCommerce
+ if ( $this->is_enabled( 'woocommerce' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ '/cart/',
+ '/cart',
+ '/checkout/',
+ '/checkout',
+ '/my-account/',
+ '/my-account',
+ '/wc-api/',
+ '/order-received/',
+ '/order-pay/',
+ '/view-order/',
+ '/add-to-cart=',
+ '?add-to-cart=',
+ '?remove_item=',
+ '?removed_item=',
+ ) );
+ }
+
+ // LearnDash
+ if ( $this->is_enabled( 'learndash' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ '/lessons/',
+ '/topic/',
+ '/quiz/',
+ '/quizzes/',
+ '/certificates/',
+ '/sfwd-',
+ ) );
+ }
+
+ // Wordfence
+ if ( $this->is_enabled( 'wordfence' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ '/wp-login.php',
+ '?wfls-',
+ ) );
+ }
+
+ return array_unique( $exclusions );
+ }
+
+ /**
+ * Add cookie exclusions for enabled plugins
+ */
+ public function add_cookie_exclusions( $exclusions ) {
+ // WooCommerce
+ if ( $this->is_enabled( 'woocommerce' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'woocommerce_items_in_cart',
+ 'woocommerce_cart_hash',
+ 'wp_woocommerce_session_',
+ 'woocommerce_recently_viewed',
+ ) );
+ }
+
+ // Wordfence
+ if ( $this->is_enabled( 'wordfence' ) ) {
+ $exclusions = array_merge( $exclusions, array(
+ 'wfCBLBypass',
+ 'wf_loginalerted_',
+ ) );
+ }
+
+ return array_unique( $exclusions );
+ }
+
+ /**
+ * Initialize WooCommerce compatibility
+ */
+ private function init_woocommerce_compat() {
+ // Don't cache cart fragments AJAX
+ add_action( 'wc_ajax_get_refreshed_fragments', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wc_ajax_add_to_cart', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wc_ajax_remove_from_cart', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wc_ajax_apply_coupon', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wc_ajax_remove_coupon', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wc_ajax_update_shipping_method', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wc_ajax_checkout', array( $this, 'disable_caching' ), 1 );
+
+ // Clear cache on stock changes
+ add_action( 'woocommerce_product_set_stock', array( $this, 'clear_product_cache' ) );
+ add_action( 'woocommerce_variation_set_stock', array( $this, 'clear_product_cache' ) );
+
+ // Clear cache on order status changes (affects stock)
+ add_action( 'woocommerce_order_status_changed', array( $this, 'clear_on_order_status' ), 10, 3 );
+
+ // Exclude WooCommerce pages from optimization
+ add_filter( 'maple_performance_exclude_optimization', array( $this, 'exclude_woo_pages' ) );
+ }
+
+ /**
+ * Initialize LearnDash compatibility
+ */
+ private function init_learndash_compat() {
+ // Don't cache any LearnDash AJAX
+ add_action( 'wp_ajax_learndash_mark_complete', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wp_ajax_ld_adv_quiz_result', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wp_ajax_wpProQuiz_admin_ajax', array( $this, 'disable_caching' ), 1 );
+
+ // Clear cache on course enrollment/completion
+ add_action( 'learndash_course_completed', array( $this, 'clear_learndash_user_cache' ), 10, 1 );
+ add_action( 'learndash_lesson_completed', array( $this, 'clear_learndash_user_cache' ), 10, 1 );
+ add_action( 'learndash_topic_completed', array( $this, 'clear_learndash_user_cache' ), 10, 1 );
+ add_action( 'learndash_quiz_completed', array( $this, 'clear_learndash_user_cache' ), 10, 2 );
+
+ // Clear cache when user is enrolled
+ add_action( 'learndash_update_course_access', array( $this, 'clear_course_cache' ), 10, 4 );
+ }
+
+ /**
+ * Initialize WPForms compatibility
+ */
+ private function init_wpforms_compat() {
+ // WPForms AJAX is generally fine, but ensure it's not cached
+ add_action( 'wp_ajax_wpforms_submit', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wp_ajax_nopriv_wpforms_submit', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wp_ajax_wpforms_file_upload_speed_test', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wp_ajax_nopriv_wpforms_file_upload_speed_test', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wp_ajax_wpforms_restricted_email', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wp_ajax_nopriv_wpforms_restricted_email', array( $this, 'disable_caching' ), 1 );
+ }
+
+ /**
+ * Initialize Wordfence compatibility
+ */
+ private function init_wordfence_compat() {
+ // Don't interfere with Wordfence login security
+ add_action( 'wp_ajax_nopriv_wordfence_ls_authenticate', array( $this, 'disable_caching' ), 1 );
+ add_action( 'wp_ajax_wordfence_ls_authenticate', array( $this, 'disable_caching' ), 1 );
+
+ // Don't cache Wordfence blocked pages
+ if ( defined( 'WORDFENCE_BLOCKED' ) && WORDFENCE_BLOCKED ) {
+ add_filter( 'maple_performance_should_cache', '__return_false' );
+ }
+
+ // Respect Wordfence's caching headers
+ add_filter( 'maple_performance_should_cache', array( $this, 'check_wordfence_bypass' ) );
+ }
+
+ /**
+ * Initialize Gutenberg FSE / Block Theme compatibility
+ */
+ private function init_gutenberg_fse_compat() {
+ // Don't aggregate global styles inline CSS
+ add_filter( 'maple_performance_aggregate_inline_css', '__return_false' );
+
+ // Protect block editor assets on frontend
+ add_filter( 'maple_performance_exclude_optimization', array( $this, 'exclude_block_editor_frontend' ) );
+ }
+
+ /**
+ * Exclude block editor frontend from aggressive optimization
+ */
+ public function exclude_block_editor_frontend( $exclude ) {
+ // If viewing a page with blocks that need JS interaction
+ // This is conservative - most block content is static
+ return $exclude;
+ }
+
+ /**
+ * Disable caching for current request
+ */
+ public function disable_caching() {
+ if ( ! defined( 'DONOTCACHEPAGE' ) ) {
+ define( 'DONOTCACHEPAGE', true );
+ }
+ }
+
+ /**
+ * Clear product cache
+ */
+ public function clear_product_cache( $product ) {
+ if ( ! class_exists( 'Maple_Performance_Cache' ) ) {
+ return;
+ }
+
+ $product_id = is_numeric( $product ) ? $product : $product->get_id();
+ $url = get_permalink( $product_id );
+
+ if ( $url ) {
+ $cache = Maple_Performance_Cache::get_instance();
+ $cache->clear_url_cache( $url );
+ }
+
+ // Also clear shop page
+ $shop_url = function_exists( 'wc_get_page_permalink' ) ? wc_get_page_permalink( 'shop' ) : '';
+ if ( $shop_url ) {
+ $cache->clear_url_cache( $shop_url );
+ }
+ }
+
+ /**
+ * Clear cache on order status change
+ */
+ public function clear_on_order_status( $order_id, $old_status, $new_status ) {
+ // Only clear when status changes might affect stock
+ $stock_statuses = array( 'completed', 'processing', 'cancelled', 'refunded' );
+
+ if ( in_array( $new_status, $stock_statuses ) || in_array( $old_status, $stock_statuses ) ) {
+ $order = wc_get_order( $order_id );
+ if ( $order ) {
+ foreach ( $order->get_items() as $item ) {
+ $product_id = $item->get_product_id();
+ $this->clear_product_cache( $product_id );
+ }
+ }
+ }
+ }
+
+ /**
+ * Exclude WooCommerce pages from JS/CSS optimization
+ */
+ public function exclude_woo_pages( $exclude ) {
+ if ( function_exists( 'is_cart' ) && is_cart() ) {
+ return true;
+ }
+ if ( function_exists( 'is_checkout' ) && is_checkout() ) {
+ return true;
+ }
+ if ( function_exists( 'is_account_page' ) && is_account_page() ) {
+ return true;
+ }
+ return $exclude;
+ }
+
+ /**
+ * Clear LearnDash user-related cache
+ */
+ public function clear_learndash_user_cache( $data ) {
+ // LearnDash pages are user-specific and already excluded for logged-in users
+ // But we can clear the course archive pages
+ if ( ! class_exists( 'Maple_Performance_Cache' ) ) {
+ return;
+ }
+
+ $cache = Maple_Performance_Cache::get_instance();
+
+ // Clear course archive
+ $course_archive = get_post_type_archive_link( 'sfwd-courses' );
+ if ( $course_archive ) {
+ $cache->clear_url_cache( $course_archive );
+ }
+
+ // Clear home page (might show course counts)
+ $cache->clear_url_cache( home_url( '/' ) );
+ }
+
+ /**
+ * Clear course cache when enrollment changes
+ */
+ public function clear_course_cache( $user_id, $course_id, $access_list, $remove ) {
+ if ( ! class_exists( 'Maple_Performance_Cache' ) ) {
+ return;
+ }
+
+ $cache = Maple_Performance_Cache::get_instance();
+
+ // Clear the course page (might show enrollment count)
+ $course_url = get_permalink( $course_id );
+ if ( $course_url ) {
+ $cache->clear_url_cache( $course_url );
+ }
+ }
+
+ /**
+ * Check Wordfence bypass
+ */
+ public function check_wordfence_bypass( $should_cache ) {
+ // If Wordfence has set bypass cookie, don't cache
+ if ( isset( $_COOKIE['wfCBLBypass'] ) ) {
+ return false;
+ }
+ return $should_cache;
+ }
+
+ /**
+ * Show admin notices for plugin conflicts
+ */
+ public function conflict_notices() {
+ // Build list of conflicting caching plugins
+ $caching_conflicts = $this->get_caching_conflicts();
+
+ // Show caching conflict warning on all admin pages (dismissible)
+ if ( ! empty( $caching_conflicts ) && ! get_transient( 'maple_perf_conflict_dismissed' ) ) {
+ $this->show_conflict_warning( $caching_conflicts );
+ }
+
+ // Show detailed info only on Maple Performance settings page
+ $screen = get_current_screen();
+ if ( ! $screen || $screen->id !== 'settings_page_maple-performance' ) {
+ return;
+ }
+
+ // If conflicts exist but were dismissed, show a subtle reminder on settings page
+ if ( ! empty( $caching_conflicts ) && get_transient( 'maple_perf_conflict_dismissed' ) ) {
+ echo '';
+ echo '
' . esc_html__( 'Reminder:', 'maple-performance' ) . ' ';
+ echo sprintf(
+ esc_html__( 'Other caching plugin(s) detected: %s. For best results, use only one caching solution.', 'maple-performance' ),
+ '' . implode( ', ', array_map( 'esc_html', $caching_conflicts ) ) . ''
+ );
+ echo '
';
+ }
+
+ // Show detected compatible plugins (info)
+ $compatible = array();
+ if ( $this->is_detected( 'woocommerce' ) ) $compatible[] = 'WooCommerce';
+ if ( $this->is_detected( 'learndash' ) ) $compatible[] = 'LearnDash';
+ if ( $this->is_detected( 'wpforms' ) ) $compatible[] = 'WPForms';
+ if ( $this->is_detected( 'wordfence' ) ) $compatible[] = 'Wordfence';
+ if ( $this->is_detected( 'gravityforms' ) ) $compatible[] = 'Gravity Forms';
+ if ( $this->is_detected( 'cf7' ) ) $compatible[] = 'Contact Form 7';
+ if ( $this->is_detected( 'elementor' ) ) $compatible[] = 'Elementor';
+
+ if ( ! empty( $compatible ) ) {
+ echo '';
+ echo '
' . esc_html__( 'Maple Performance - Detected Plugins:', 'maple-performance' ) . ' ';
+ echo sprintf(
+ esc_html__( 'Compatibility rules available for: %s. Enable them in the Plugin Compatibility section below.', 'maple-performance' ),
+ '' . implode( ', ', array_map( 'esc_html', $compatible ) ) . ''
+ );
+ echo '
';
+ }
+ }
+
+ /**
+ * Get list of detected caching plugin conflicts
+ */
+ public function get_caching_conflicts() {
+ $conflicts = array();
+
+ $caching_plugins = array(
+ 'wp_rocket' => 'WP Rocket',
+ 'w3tc' => 'W3 Total Cache',
+ 'litespeed' => 'LiteSpeed Cache',
+ 'wp_super_cache' => 'WP Super Cache',
+ 'wp_fastest_cache' => 'WP Fastest Cache',
+ 'autoptimize' => 'Autoptimize',
+ 'cache_enabler' => 'Cache Enabler',
+ 'hummingbird' => 'Hummingbird',
+ 'breeze' => 'Breeze',
+ 'sg_optimizer' => 'SG Optimizer',
+ 'swift_performance'=> 'Swift Performance',
+ 'comet_cache' => 'Comet Cache',
+ 'powered_cache' => 'Powered Cache',
+ 'perfmatters' => 'Perfmatters',
+ );
+
+ foreach ( $caching_plugins as $key => $name ) {
+ if ( $this->is_detected( $key ) ) {
+ $conflicts[] = $name;
+ }
+ }
+
+ return $conflicts;
+ }
+
+ /**
+ * Check if any caching conflicts exist
+ */
+ public function has_caching_conflicts() {
+ return ! empty( $this->get_caching_conflicts() );
+ }
+
+ /**
+ * Show conflict warning notice
+ */
+ private function show_conflict_warning( $conflicts ) {
+ // Handle dismiss action
+ if ( isset( $_GET['maple_dismiss_conflict'] ) && wp_verify_nonce( $_GET['_wpnonce'] ?? '', 'maple_dismiss_conflict' ) ) {
+ set_transient( 'maple_perf_conflict_dismissed', true, 7 * DAY_IN_SECONDS );
+ return;
+ }
+
+ $dismiss_url = wp_nonce_url( add_query_arg( 'maple_dismiss_conflict', '1' ), 'maple_dismiss_conflict' );
+
+ echo '';
+ echo '
' . esc_html__( '⚠️ Maple Performance - Caching Conflict Detected', 'maple-performance' ) . '
';
+ echo '
' . sprintf(
+ esc_html__( 'The following caching/optimization plugin(s) are also active: %s', 'maple-performance' ),
+ '' . implode( ', ', array_map( 'esc_html', $conflicts ) ) . ' '
+ ) . '
';
+ echo '
' . esc_html__( 'Running multiple caching plugins simultaneously can cause:', 'maple-performance' ) . '
';
+ echo '
';
+ echo '' . esc_html__( 'Blank pages or site errors', 'maple-performance' ) . ' ';
+ echo '' . esc_html__( 'Stale/outdated content being served', 'maple-performance' ) . ' ';
+ echo '' . esc_html__( 'Slower performance (double processing)', 'maple-performance' ) . ' ';
+ echo '' . esc_html__( 'Cache invalidation failures', 'maple-performance' ) . ' ';
+ echo ' ';
+ echo '
' . esc_html__( 'Recommended action:', 'maple-performance' ) . ' ';
+ echo esc_html__( 'Deactivate the other caching plugin(s), or deactivate Maple Performance if you prefer to keep your existing solution.', 'maple-performance' );
+ echo '
';
+ echo '
';
+ echo '' . esc_html__( 'Go to Plugins', 'maple-performance' ) . ' ';
+ echo '' . esc_html__( 'Dismiss for 7 days', 'maple-performance' ) . ' ';
+ echo '
';
+ echo '
';
+ }
+}
diff --git a/native/wordpress/maple-performance-wp/inc/class-maple-optimize.php b/native/wordpress/maple-performance-wp/inc/class-maple-optimize.php
new file mode 100644
index 0000000..d845b2c
--- /dev/null
+++ b/native/wordpress/maple-performance-wp/inc/class-maple-optimize.php
@@ -0,0 +1,1234 @@
+settings = maple_performance()->settings;
+ $this->init_hooks();
+ }
+
+ /**
+ * Initialize hooks
+ */
+ private function init_hooks() {
+ // HTML processing - needed for minification, local font display, or iframe lazyload
+ $needs_html_processing = $this->settings['html_minify']
+ || $this->settings['local_font_display']
+ || $this->settings['lazyload_iframes'];
+
+ if ( $needs_html_processing ) {
+ add_action( 'template_redirect', array( $this, 'start_html_buffer' ), -998 );
+ }
+
+ // Remove query strings
+ if ( $this->settings['remove_query_strings'] ) {
+ add_filter( 'script_loader_src', array( $this, 'remove_query_string' ), 15 );
+ add_filter( 'style_loader_src', array( $this, 'remove_query_string' ), 15 );
+ }
+
+ // DNS prefetch
+ if ( $this->settings['dns_prefetch'] ) {
+ add_action( 'wp_head', array( $this, 'add_dns_prefetch' ), 1 );
+ }
+
+ // Preconnect
+ if ( ! empty( $this->settings['preconnect_domains'] ) ) {
+ add_action( 'wp_head', array( $this, 'add_preconnect' ), 1 );
+ }
+
+ // CSS optimization
+ if ( $this->settings['css_minify'] || $this->settings['css_aggregate'] ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'collect_styles' ), 9999 );
+ }
+
+ // JS optimization
+ if ( $this->settings['js_minify'] || $this->settings['js_aggregate'] ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'collect_scripts' ), 9999 );
+ }
+
+ // Lazy loading
+ if ( $this->settings['lazyload_images'] ) {
+ add_filter( 'the_content', array( $this, 'add_lazyload' ), 99 );
+ add_filter( 'post_thumbnail_html', array( $this, 'add_lazyload' ), 99 );
+ add_filter( 'widget_text', array( $this, 'add_lazyload' ), 99 );
+ }
+
+ // Google Fonts optimization
+ if ( $this->settings['google_fonts'] !== 'leave' ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'process_google_fonts' ), 9999 );
+
+ // Also process fonts in HTML output for hardcoded fonts
+ if ( $this->settings['html_minify'] ) {
+ add_filter( 'maple_performance_html_output', array( $this, 'process_google_fonts_html' ), 10 );
+ }
+ }
+
+ // Local font optimization - add font-display: swap to @font-face rules
+ if ( $this->settings['local_font_display'] ) {
+ add_filter( 'maple_performance_html_output', array( $this, 'add_font_display_swap' ), 5 );
+ }
+
+ // Font preloading
+ if ( ! empty( $this->settings['preload_fonts'] ) ) {
+ add_action( 'wp_head', array( $this, 'output_font_preloads' ), 1 );
+ }
+ }
+
+ /**
+ * Start HTML buffer for minification
+ */
+ public function start_html_buffer() {
+ if ( maple_performance()->is_excluded() ) {
+ return;
+ }
+
+ ob_start( array( $this, 'process_html' ) );
+ }
+
+ /**
+ * Process HTML output
+ */
+ public function process_html( $html ) {
+ if ( empty( $html ) ) {
+ return $html;
+ }
+
+ // Skip if not HTML
+ if ( strpos( $html, ' $max_size ) {
+ return $html;
+ }
+
+ // Process Google Fonts in HTML (for hardcoded fonts)
+ $html = apply_filters( 'maple_performance_html_output', $html );
+
+ // Minify HTML
+ if ( $this->settings['html_minify'] ) {
+ $html = $this->minify_html( $html );
+ }
+
+ // Add lazy loading to iframes
+ if ( $this->settings['lazyload_iframes'] ) {
+ $html = $this->add_iframe_lazyload( $html );
+ }
+
+ return $html;
+ }
+
+ /**
+ * Minify HTML
+ */
+ private function minify_html( $html ) {
+ // Additional size check for safety
+ if ( strlen( $html ) > 2 * 1024 * 1024 ) {
+ return $html;
+ }
+
+ // Preserve pre, script, style, textarea content
+ $preserve = array();
+ $preserve_tags = array( 'pre', 'script', 'style', 'textarea', 'svg' );
+
+ foreach ( $preserve_tags as $tag ) {
+ // Use atomic grouping concept with possessive quantifier simulation
+ // Limit matches to prevent catastrophic backtracking
+ $pattern = '/<' . $tag . '[^>]*>.*?<\/' . $tag . '>/is';
+
+ // Set a reasonable limit on preg operations
+ $match_count = preg_match_all( $pattern, $html, $matches );
+
+ // Limit to 500 preserved blocks per tag type to prevent memory issues
+ if ( $match_count > 500 ) {
+ $matches[0] = array_slice( $matches[0], 0, 500 );
+ }
+
+ foreach ( $matches[0] as $i => $match ) {
+ $placeholder = '';
+ $preserve[ $placeholder ] = $match;
+ $html = str_replace( $match, $placeholder, $html );
+ }
+ }
+
+ // Remove HTML comments (except IE conditionals and preserved)
+ if ( $this->settings['html_remove_comments'] ) {
+ $html = preg_replace( '//s', '', $html );
+ }
+
+ // Remove whitespace between tags
+ $html = preg_replace( '/>\s+', '><', $html );
+
+ // Collapse multiple spaces
+ $html = preg_replace( '/\s{2,}/', ' ', $html );
+
+ // Remove spaces around attributes
+ $html = preg_replace( '/\s+([a-zA-Z-]+)=/', ' $1=', $html );
+
+ // Restore preserved content
+ $html = str_replace( array_keys( $preserve ), array_values( $preserve ), $html );
+
+ return trim( $html );
+ }
+
+ /**
+ * Remove query strings from static resources
+ */
+ public function remove_query_string( $src ) {
+ if ( strpos( $src, '?ver=' ) !== false ) {
+ $src = remove_query_arg( 'ver', $src );
+ }
+ return $src;
+ }
+
+ /**
+ * Add DNS prefetch hints
+ */
+ public function add_dns_prefetch() {
+ $domains = array(
+ 'fonts.googleapis.com',
+ 'fonts.gstatic.com',
+ );
+
+ // Add from preconnect settings
+ if ( ! empty( $this->settings['preconnect_domains'] ) ) {
+ foreach ( $this->settings['preconnect_domains'] as $domain ) {
+ $parsed = parse_url( $domain );
+ if ( ! empty( $parsed['host'] ) ) {
+ $domains[] = $parsed['host'];
+ }
+ }
+ }
+
+ $domains = array_unique( $domains );
+
+ foreach ( $domains as $domain ) {
+ echo ' ' . "\n";
+ }
+ }
+
+ /**
+ * Add preconnect hints
+ */
+ public function add_preconnect() {
+ foreach ( $this->settings['preconnect_domains'] as $domain ) {
+ if ( ! empty( $domain ) ) {
+ // Ensure URL has scheme
+ if ( strpos( $domain, '//' ) === false ) {
+ $domain = 'https://' . $domain;
+ }
+ echo ' ' . "\n";
+ }
+ }
+ }
+
+ /**
+ * Collect enqueued styles
+ */
+ public function collect_styles() {
+ if ( ! $this->settings['css_aggregate'] ) {
+ return;
+ }
+
+ global $wp_styles;
+
+ if ( empty( $wp_styles->queue ) ) {
+ return;
+ }
+
+ $to_aggregate = array();
+
+ foreach ( $wp_styles->queue as $handle ) {
+ // Skip if excluded
+ if ( $this->is_excluded_css( $handle ) ) {
+ continue;
+ }
+
+ $style = $wp_styles->registered[ $handle ] ?? null;
+ if ( ! $style || empty( $style->src ) ) {
+ continue;
+ }
+
+ // Only aggregate local files
+ $src = $style->src;
+ if ( $this->is_external_url( $src ) ) {
+ continue;
+ }
+
+ $to_aggregate[] = array(
+ 'handle' => $handle,
+ 'src' => $src,
+ 'deps' => $style->deps,
+ 'ver' => $style->ver,
+ 'media' => $style->args ?? 'all',
+ );
+
+ // Dequeue original
+ wp_dequeue_style( $handle );
+ }
+
+ if ( ! empty( $to_aggregate ) ) {
+ $this->create_aggregated_css( $to_aggregate );
+ }
+ }
+
+ /**
+ * Check if style should be excluded
+ */
+ private function is_excluded_css( $handle ) {
+ $exclude = array_merge(
+ array( 'admin-bar', 'dashicons' ),
+ $this->settings['exclude_css']
+ );
+
+ // Apply compat filter for detected plugins
+ $exclude = apply_filters( 'maple_performance_css_exclusions', $exclude );
+
+ foreach ( $exclude as $pattern ) {
+ if ( ! empty( $pattern ) && strpos( $handle, $pattern ) !== false ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Create aggregated CSS file
+ */
+ private function create_aggregated_css( $styles ) {
+ $combined = '';
+ $total_size = 0;
+ $max_total_size = 2 * 1024 * 1024; // 2MB max combined size
+ $max_file_size = 500 * 1024; // 500KB max per file
+ $max_files = 50; // Max files to aggregate
+ $file_count = 0;
+
+ foreach ( $styles as $style ) {
+ // Limit number of files to aggregate
+ if ( ++$file_count > $max_files ) {
+ break;
+ }
+
+ $file_path = $this->url_to_path( $style['src'] );
+
+ if ( ! $file_path || ! file_exists( $file_path ) ) {
+ continue;
+ }
+
+ // Check file size before reading
+ $file_size = filesize( $file_path );
+ if ( $file_size > $max_file_size ) {
+ continue; // Skip files that are too large
+ }
+
+ // Check if adding this file would exceed total limit
+ if ( $total_size + $file_size > $max_total_size ) {
+ break;
+ }
+
+ $content = file_get_contents( $file_path );
+ if ( false === $content ) {
+ continue;
+ }
+
+ $total_size += strlen( $content );
+
+ // Fix relative URLs in CSS
+ $content = $this->fix_css_urls( $content, dirname( $style['src'] ) );
+
+ // Minify if enabled
+ if ( $this->settings['css_minify'] ) {
+ $content = $this->minify_css( $content );
+ }
+
+ $combined .= "/* {$style['handle']} */\n" . $content . "\n";
+ }
+
+ if ( empty( $combined ) ) {
+ return;
+ }
+
+ // Generate hash for filename
+ $hash = substr( md5( $combined ), 0, 12 );
+ $filename = 'maple-css-' . $hash . '.css';
+ $filepath = MAPLE_PERF_CACHE_DIR . 'assets/' . $filename;
+ $fileurl = MAPLE_PERF_CACHE_URL . 'assets/' . $filename;
+
+ // Verify filepath is within allowed directory
+ $assets_dir = realpath( MAPLE_PERF_CACHE_DIR . 'assets/' );
+ if ( false === $assets_dir ) {
+ // Assets directory doesn't exist yet, create it
+ wp_mkdir_p( MAPLE_PERF_CACHE_DIR . 'assets/' );
+ $assets_dir = realpath( MAPLE_PERF_CACHE_DIR . 'assets/' );
+ }
+
+ if ( false === $assets_dir ) {
+ return;
+ }
+
+ // Write file if doesn't exist
+ if ( ! file_exists( $filepath ) ) {
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
+ file_put_contents( $filepath, $combined );
+ if ( file_exists( $filepath ) ) {
+ chmod( $filepath, 0644 );
+ }
+
+ // Create gzipped version
+ if ( function_exists( 'gzencode' ) ) {
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
+ file_put_contents( $filepath . '.gz', gzencode( $combined, 9 ) );
+ if ( file_exists( $filepath . '.gz' ) ) {
+ chmod( $filepath . '.gz', 0644 );
+ }
+ }
+ }
+
+ // Enqueue aggregated file
+ wp_enqueue_style( 'maple-aggregated-css', $fileurl, array(), null, 'all' );
+
+ // Add defer if enabled
+ if ( $this->settings['css_defer'] ) {
+ add_filter( 'style_loader_tag', array( $this, 'defer_css' ), 10, 2 );
+ }
+ }
+
+ /**
+ * Defer CSS loading
+ */
+ public function defer_css( $tag, $handle ) {
+ if ( $handle !== 'maple-aggregated-css' ) {
+ return $tag;
+ }
+
+ // Convert to preload + onload pattern
+ $tag = str_replace(
+ "rel='stylesheet'",
+ "rel='preload' as='style' onload=\"this.onload=null;this.rel='stylesheet'\"",
+ $tag
+ );
+
+ // Add noscript fallback
+ $noscript = '' . str_replace(
+ array( "rel='preload' as='style' onload=\"this.onload=null;this.rel='stylesheet'\"" ),
+ array( "rel='stylesheet'" ),
+ $tag
+ ) . ' ';
+
+ return $tag . $noscript;
+ }
+
+ /**
+ * Fix relative URLs in CSS
+ */
+ private function fix_css_urls( $css, $base_url ) {
+ // Ensure base URL has trailing slash
+ $base_url = trailingslashit( $base_url );
+
+ // Fix url() references
+ $css = preg_replace_callback(
+ '/url\s*\(\s*[\'"]?\s*(?!data:|https?:|\/\/)(.*?)\s*[\'"]?\s*\)/i',
+ function( $matches ) use ( $base_url ) {
+ $url = $matches[1];
+
+ // Handle relative paths
+ if ( strpos( $url, '../' ) === 0 ) {
+ // Go up directories
+ $parts = explode( '../', $url );
+ $up_count = count( $parts ) - 1;
+ $base_parts = explode( '/', rtrim( $base_url, '/' ) );
+
+ for ( $i = 0; $i < $up_count; $i++ ) {
+ array_pop( $base_parts );
+ }
+
+ $new_base = implode( '/', $base_parts ) . '/';
+ $url = $new_base . end( $parts );
+ } else {
+ $url = $base_url . ltrim( $url, './' );
+ }
+
+ return 'url(' . $url . ')';
+ },
+ $css
+ );
+
+ return $css;
+ }
+
+ /**
+ * Minify CSS
+ */
+ private function minify_css( $css ) {
+ // Skip minification for very large CSS files to prevent performance issues
+ // 500KB is a reasonable limit for inline minification
+ if ( strlen( $css ) > 500 * 1024 ) {
+ return $css;
+ }
+
+ // Remove comments - use simpler pattern to avoid catastrophic backtracking
+ $css = preg_replace( '#/\*[^*]*\*+(?:[^/*][^*]*\*+)*/#', '', $css );
+
+ // Remove whitespace
+ $css = preg_replace( '/\s+/', ' ', $css );
+
+ // Remove spaces around special characters
+ $css = preg_replace( '/\s*([\{\}\:\;\,])\s*/', '$1', $css );
+
+ // Remove trailing semicolons before closing braces
+ $css = str_replace( ';}', '}', $css );
+
+ // Remove empty rules - simplified pattern
+ $css = preg_replace( '/[^\{\}]+\{\}/', '', $css );
+
+ return trim( $css );
+ }
+
+ /**
+ * Collect enqueued scripts
+ */
+ public function collect_scripts() {
+ if ( ! $this->settings['js_aggregate'] ) {
+ // Just minify individual scripts if enabled
+ if ( $this->settings['js_minify'] ) {
+ add_filter( 'script_loader_tag', array( $this, 'maybe_minify_script' ), 10, 3 );
+ }
+ return;
+ }
+
+ global $wp_scripts;
+
+ if ( empty( $wp_scripts->queue ) ) {
+ return;
+ }
+
+ $to_aggregate = array();
+
+ foreach ( $wp_scripts->queue as $handle ) {
+ // Skip if excluded
+ if ( $this->is_excluded_js( $handle ) ) {
+ continue;
+ }
+
+ $script = $wp_scripts->registered[ $handle ] ?? null;
+ if ( ! $script || empty( $script->src ) ) {
+ continue;
+ }
+
+ // Only aggregate local files
+ $src = $script->src;
+ if ( $this->is_external_url( $src ) ) {
+ continue;
+ }
+
+ $to_aggregate[] = array(
+ 'handle' => $handle,
+ 'src' => $src,
+ 'deps' => $script->deps,
+ 'ver' => $script->ver,
+ );
+
+ // Dequeue original
+ wp_dequeue_script( $handle );
+ }
+
+ if ( ! empty( $to_aggregate ) ) {
+ $this->create_aggregated_js( $to_aggregate );
+ }
+ }
+
+ /**
+ * Check if script should be excluded
+ */
+ private function is_excluded_js( $handle ) {
+ $exclude = $this->settings['exclude_js'];
+
+ // Always exclude jQuery if setting enabled
+ if ( $this->settings['js_exclude_jquery'] ) {
+ $exclude = array_merge( $exclude, array( 'jquery', 'jquery-core', 'jquery-migrate' ) );
+ }
+
+ // Add admin/core scripts
+ $exclude = array_merge( $exclude, array( 'admin-bar', 'wp-embed' ) );
+
+ // Apply compat filter for detected plugins
+ $exclude = apply_filters( 'maple_performance_js_exclusions', $exclude );
+
+ foreach ( $exclude as $pattern ) {
+ if ( ! empty( $pattern ) && strpos( $handle, $pattern ) !== false ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Create aggregated JS file
+ */
+ private function create_aggregated_js( $scripts ) {
+ $combined = '';
+ $total_size = 0;
+ $max_total_size = 2 * 1024 * 1024; // 2MB max combined size
+ $max_file_size = 500 * 1024; // 500KB max per file
+ $max_files = 50; // Max files to aggregate
+ $file_count = 0;
+
+ foreach ( $scripts as $script ) {
+ // Limit number of files to aggregate
+ if ( ++$file_count > $max_files ) {
+ break;
+ }
+
+ $file_path = $this->url_to_path( $script['src'] );
+
+ if ( ! $file_path || ! file_exists( $file_path ) ) {
+ continue;
+ }
+
+ // Check file size before reading
+ $file_size = filesize( $file_path );
+ if ( $file_size > $max_file_size ) {
+ continue; // Skip files that are too large
+ }
+
+ // Check if adding this file would exceed total limit
+ if ( $total_size + $file_size > $max_total_size ) {
+ break;
+ }
+
+ $content = file_get_contents( $file_path );
+ if ( false === $content ) {
+ continue;
+ }
+
+ $total_size += strlen( $content );
+
+ // Minify if enabled
+ if ( $this->settings['js_minify'] ) {
+ $content = $this->minify_js( $content );
+ }
+
+ // Ensure semicolon at end
+ $content = rtrim( $content, "; \n\r" ) . ";\n";
+
+ $combined .= "/* {$script['handle']} */\n" . $content . "\n";
+ }
+
+ if ( empty( $combined ) ) {
+ return;
+ }
+
+ // Generate hash for filename
+ $hash = substr( md5( $combined ), 0, 12 );
+ $filename = 'maple-js-' . $hash . '.js';
+ $filepath = MAPLE_PERF_CACHE_DIR . 'assets/' . $filename;
+ $fileurl = MAPLE_PERF_CACHE_URL . 'assets/' . $filename;
+
+ // Verify filepath is within allowed directory
+ $assets_dir = realpath( MAPLE_PERF_CACHE_DIR . 'assets/' );
+ if ( false === $assets_dir ) {
+ // Assets directory doesn't exist yet, create it
+ wp_mkdir_p( MAPLE_PERF_CACHE_DIR . 'assets/' );
+ $assets_dir = realpath( MAPLE_PERF_CACHE_DIR . 'assets/' );
+ }
+
+ if ( false === $assets_dir ) {
+ return;
+ }
+
+ // Write file if doesn't exist
+ if ( ! file_exists( $filepath ) ) {
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
+ file_put_contents( $filepath, $combined );
+ if ( file_exists( $filepath ) ) {
+ chmod( $filepath, 0644 );
+ }
+
+ // Create gzipped version
+ if ( function_exists( 'gzencode' ) ) {
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
+ file_put_contents( $filepath . '.gz', gzencode( $combined, 9 ) );
+ if ( file_exists( $filepath . '.gz' ) ) {
+ chmod( $filepath . '.gz', 0644 );
+ }
+ }
+ }
+
+ // Enqueue aggregated file in footer
+ wp_enqueue_script( 'maple-aggregated-js', $fileurl, array( 'jquery' ), null, true );
+
+ // Add defer if enabled
+ if ( $this->settings['js_defer'] ) {
+ add_filter( 'script_loader_tag', array( $this, 'defer_js' ), 10, 2 );
+ }
+ }
+
+ /**
+ * Defer JS loading
+ */
+ public function defer_js( $tag, $handle ) {
+ if ( $handle !== 'maple-aggregated-js' ) {
+ return $tag;
+ }
+
+ return str_replace( ' src', ' defer src', $tag );
+ }
+
+ /**
+ * Basic JS minification
+ */
+ private function minify_js( $js ) {
+ // Skip minification for very large JS files to prevent performance issues
+ // 500KB is a reasonable limit for inline minification
+ if ( strlen( $js ) > 500 * 1024 ) {
+ return $js;
+ }
+
+ // Remove single-line comments (but not URLs)
+ $js = preg_replace( '#(?is_excluded_js( $handle ) ) {
+ return $tag;
+ }
+
+ // Skip external scripts
+ if ( $this->is_external_url( $src ) ) {
+ return $tag;
+ }
+
+ return $tag;
+ }
+
+ /**
+ * Add lazy loading to images
+ */
+ public function add_lazyload( $content ) {
+ if ( empty( $content ) ) {
+ return $content;
+ }
+
+ // Don't lazyload in admin
+ if ( is_admin() ) {
+ return $content;
+ }
+
+ // Find all img tags
+ $content = preg_replace_callback(
+ '/ ]+)>/i',
+ array( $this, 'process_image_tag' ),
+ $content
+ );
+
+ return $content;
+ }
+
+ /**
+ * Process individual image tag for lazy loading
+ */
+ private function process_image_tag( $matches ) {
+ $img = $matches[0];
+ $attrs = $matches[1];
+
+ // Skip if already has loading attribute
+ if ( strpos( $attrs, 'loading=' ) !== false ) {
+ return $img;
+ }
+
+ // Skip if has fetchpriority="high" (LCP image)
+ if ( strpos( $attrs, 'fetchpriority="high"' ) !== false ) {
+ return $img;
+ }
+
+ // Check exclusions
+ foreach ( $this->settings['lazyload_exclude'] as $exclude ) {
+ if ( ! empty( $exclude ) && strpos( $attrs, $exclude ) !== false ) {
+ return $img;
+ }
+ }
+
+ // Add loading="lazy"
+ return str_replace( ' ]+)>/i',
+ function( $matches ) {
+ $iframe = $matches[0];
+ $attrs = $matches[1];
+
+ // Skip if already has loading attribute
+ if ( strpos( $attrs, 'loading=' ) !== false ) {
+ return $iframe;
+ }
+
+ return str_replace( '