539 lines
18 KiB
PHP
539 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: Maple Performance WP
|
|
* Plugin URI: https://mapleopentech.ca/maple-performance-wp
|
|
* Description: A lightweight, privacy-focused WordPress performance plugin. No external dependencies, no tracking, no upsells.
|
|
* Version: 1.0.0
|
|
* Author: Maple Open Tech
|
|
* Author URI: https://mapleopentech.ca
|
|
* License: GPL-2.0-or-later
|
|
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
|
* Text Domain: maple-performance
|
|
* Domain Path: /languages
|
|
* Requires at least: 5.9
|
|
* Requires PHP: 7.4
|
|
*
|
|
* @package MaplePerformance
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
// Plugin constants
|
|
define( 'MAPLE_PERF_VERSION', '1.0.0' );
|
|
define( 'MAPLE_PERF_FILE', __FILE__ );
|
|
define( 'MAPLE_PERF_PATH', plugin_dir_path( __FILE__ ) );
|
|
define( 'MAPLE_PERF_URL', plugin_dir_url( __FILE__ ) );
|
|
define( 'MAPLE_PERF_CACHE_DIR', WP_CONTENT_DIR . '/cache/maple-performance/' );
|
|
define( 'MAPLE_PERF_CACHE_URL', content_url( '/cache/maple-performance/' ) );
|
|
|
|
/**
|
|
* Autoloader for plugin classes
|
|
*/
|
|
spl_autoload_register( function( $class ) {
|
|
$prefix = 'Maple_Performance_';
|
|
|
|
if ( strpos( $class, $prefix ) !== 0 ) {
|
|
return;
|
|
}
|
|
|
|
$class_name = str_replace( $prefix, '', $class );
|
|
$class_name = strtolower( str_replace( '_', '-', $class_name ) );
|
|
$file = MAPLE_PERF_PATH . 'inc/class-maple-' . $class_name . '.php';
|
|
|
|
if ( file_exists( $file ) ) {
|
|
require_once $file;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Main plugin class
|
|
*/
|
|
final class Maple_Performance {
|
|
|
|
/**
|
|
* Single instance
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Plugin settings
|
|
*/
|
|
public $settings = array();
|
|
|
|
/**
|
|
* Get instance
|
|
*/
|
|
public static function get_instance() {
|
|
if ( null === self::$instance ) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
$this->load_settings();
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Load settings from database
|
|
*/
|
|
private function load_settings() {
|
|
$defaults = array(
|
|
// Site mode
|
|
'site_mode' => 'brochure', // brochure, woocommerce, learndash, woo_learndash
|
|
|
|
// Page caching
|
|
'cache_enabled' => true,
|
|
'cache_expiry' => 0, // 0 = never expires
|
|
'cache_logged_in' => false,
|
|
'cache_gzip' => true,
|
|
'cache_brotli' => false,
|
|
|
|
// HTML optimization
|
|
'html_minify' => true,
|
|
'html_remove_comments' => true,
|
|
|
|
// CSS optimization
|
|
'css_minify' => true,
|
|
'css_aggregate' => true,
|
|
'css_inline_aggregate' => false,
|
|
'css_defer' => false, // Disabled by default for safety
|
|
|
|
// JS optimization
|
|
'js_minify' => true,
|
|
'js_aggregate' => false, // Disabled by default for safety
|
|
'js_defer' => false,
|
|
'js_exclude_jquery' => true,
|
|
|
|
// Exclusions
|
|
'exclude_paths' => array(),
|
|
'exclude_cookies' => array(),
|
|
'exclude_js' => array(),
|
|
'exclude_css' => array(),
|
|
|
|
// Extra
|
|
'remove_emojis' => true,
|
|
'remove_query_strings' => true,
|
|
'preconnect_domains' => array(),
|
|
'dns_prefetch' => true,
|
|
|
|
// Lazy loading
|
|
'lazyload_images' => true,
|
|
'lazyload_iframes' => true,
|
|
'lazyload_exclude' => array(),
|
|
|
|
// Google Fonts
|
|
'google_fonts' => 'defer', // 'leave', 'remove', 'combine', 'defer'
|
|
|
|
// Local Font Optimization
|
|
'local_font_display' => true, // Add font-display: swap to all @font-face rules
|
|
'preload_fonts' => array(), // Font URLs to preload (woff2 recommended)
|
|
|
|
// Plugin Compatibility
|
|
'compat_auto_detect' => false, // Auto-detect plugins (can be fragile)
|
|
'compat_plugins' => array(), // Manually selected: woocommerce, learndash, wordfence, wpforms, gutenberg_fse
|
|
);
|
|
|
|
$saved = get_option( 'maple_performance_settings', array() );
|
|
$this->settings = wp_parse_args( $saved, $defaults );
|
|
|
|
// Apply site mode presets
|
|
$this->apply_site_mode();
|
|
}
|
|
|
|
/**
|
|
* Apply safe defaults based on site mode
|
|
*/
|
|
private function apply_site_mode() {
|
|
switch ( $this->settings['site_mode'] ) {
|
|
case 'woocommerce':
|
|
$this->settings['js_aggregate'] = false;
|
|
$this->settings['css_defer'] = false;
|
|
$this->settings['cache_logged_in'] = false;
|
|
break;
|
|
|
|
case 'learndash':
|
|
$this->settings['js_aggregate'] = false;
|
|
$this->settings['css_defer'] = false;
|
|
$this->settings['cache_logged_in'] = false;
|
|
break;
|
|
|
|
case 'woo_learndash':
|
|
$this->settings['js_aggregate'] = false;
|
|
$this->settings['js_defer'] = false;
|
|
$this->settings['css_aggregate'] = true;
|
|
$this->settings['css_inline_aggregate'] = false;
|
|
$this->settings['css_defer'] = false;
|
|
$this->settings['cache_logged_in'] = false;
|
|
break;
|
|
}
|
|
|
|
// Ensure arrays are unique
|
|
$this->settings['exclude_paths'] = array_unique( array_filter( $this->settings['exclude_paths'] ) );
|
|
$this->settings['exclude_cookies'] = array_unique( array_filter( $this->settings['exclude_cookies'] ) );
|
|
$this->settings['exclude_js'] = array_unique( array_filter( $this->settings['exclude_js'] ) );
|
|
$this->settings['exclude_css'] = array_unique( array_filter( $this->settings['exclude_css'] ) );
|
|
}
|
|
|
|
/**
|
|
* Initialize hooks
|
|
*/
|
|
private function init_hooks() {
|
|
// Activation/Deactivation
|
|
register_activation_hook( MAPLE_PERF_FILE, array( $this, 'activate' ) );
|
|
register_deactivation_hook( MAPLE_PERF_FILE, array( $this, 'deactivate' ) );
|
|
|
|
// Load components
|
|
add_action( 'plugins_loaded', array( $this, 'load_components' ) );
|
|
|
|
// Admin
|
|
if ( is_admin() ) {
|
|
add_action( 'plugins_loaded', array( $this, 'load_admin' ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load plugin components
|
|
*/
|
|
public function load_components() {
|
|
// Compatibility layer (always load to detect plugins)
|
|
require_once MAPLE_PERF_PATH . 'inc/class-maple-compat.php';
|
|
Maple_Performance_Compat::get_instance();
|
|
|
|
// Cache component
|
|
if ( $this->settings['cache_enabled'] && ! is_admin() ) {
|
|
require_once MAPLE_PERF_PATH . 'inc/class-maple-cache.php';
|
|
Maple_Performance_Cache::get_instance();
|
|
}
|
|
|
|
// Optimize component (CSS/JS/HTML)
|
|
if ( ! is_admin() ) {
|
|
require_once MAPLE_PERF_PATH . 'inc/class-maple-optimize.php';
|
|
Maple_Performance_Optimize::get_instance();
|
|
}
|
|
|
|
// Extra optimizations
|
|
if ( $this->settings['remove_emojis'] ) {
|
|
$this->disable_emojis();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load admin component
|
|
*/
|
|
public function load_admin() {
|
|
require_once MAPLE_PERF_PATH . 'inc/class-maple-admin.php';
|
|
Maple_Performance_Admin::get_instance();
|
|
}
|
|
|
|
/**
|
|
* Disable WordPress emojis
|
|
*/
|
|
private function disable_emojis() {
|
|
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
|
|
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
|
|
remove_action( 'wp_print_styles', 'print_emoji_styles' );
|
|
remove_action( 'admin_print_styles', 'print_emoji_styles' );
|
|
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
|
|
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
|
|
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
|
|
|
|
add_filter( 'tiny_mce_plugins', function( $plugins ) {
|
|
return is_array( $plugins ) ? array_diff( $plugins, array( 'wpemoji' ) ) : array();
|
|
});
|
|
|
|
add_filter( 'wp_resource_hints', function( $urls, $relation_type ) {
|
|
if ( 'dns-prefetch' === $relation_type ) {
|
|
$urls = array_filter( $urls, function( $url ) {
|
|
return strpos( $url, 'https://s.w.org/images/core/emoji/' ) === false;
|
|
});
|
|
}
|
|
return $urls;
|
|
}, 10, 2 );
|
|
}
|
|
|
|
/**
|
|
* Plugin activation
|
|
*/
|
|
public function activate() {
|
|
// Create cache directory
|
|
if ( ! file_exists( MAPLE_PERF_CACHE_DIR ) ) {
|
|
wp_mkdir_p( MAPLE_PERF_CACHE_DIR );
|
|
}
|
|
|
|
// Create assets cache directory
|
|
if ( ! file_exists( MAPLE_PERF_CACHE_DIR . 'assets/' ) ) {
|
|
wp_mkdir_p( MAPLE_PERF_CACHE_DIR . 'assets/' );
|
|
}
|
|
|
|
// Add index.php for security
|
|
$index_content = "<?php\n// Silence is golden.\nif ( ! defined( 'ABSPATH' ) ) { exit; }";
|
|
if ( ! file_exists( MAPLE_PERF_CACHE_DIR . 'index.php' ) ) {
|
|
file_put_contents( MAPLE_PERF_CACHE_DIR . 'index.php', $index_content );
|
|
}
|
|
if ( ! file_exists( MAPLE_PERF_CACHE_DIR . 'assets/index.php' ) ) {
|
|
file_put_contents( MAPLE_PERF_CACHE_DIR . 'assets/index.php', $index_content );
|
|
}
|
|
|
|
// Set default settings
|
|
if ( ! get_option( 'maple_performance_settings' ) ) {
|
|
add_option( 'maple_performance_settings', array() );
|
|
}
|
|
|
|
// Check for caching conflicts and set flag to show notice
|
|
$conflicts = $this->detect_caching_conflicts();
|
|
if ( ! empty( $conflicts ) ) {
|
|
set_transient( 'maple_perf_activation_conflict', $conflicts, 60 );
|
|
}
|
|
|
|
// Clear any existing cache
|
|
$this->clear_cache();
|
|
|
|
// Clear any previously dismissed conflict notice
|
|
delete_transient( 'maple_perf_conflict_dismissed' );
|
|
|
|
flush_rewrite_rules();
|
|
}
|
|
|
|
/**
|
|
* Detect caching plugin conflicts at activation time
|
|
*/
|
|
private function detect_caching_conflicts() {
|
|
$conflicts = array();
|
|
|
|
// Check for common caching plugins
|
|
if ( defined( 'WP_ROCKET_VERSION' ) ) {
|
|
$conflicts[] = 'WP Rocket';
|
|
}
|
|
if ( defined( 'W3TC' ) || class_exists( 'W3_Plugin_TotalCache' ) ) {
|
|
$conflicts[] = 'W3 Total Cache';
|
|
}
|
|
if ( defined( 'LSCWP_V' ) || class_exists( 'LiteSpeed_Cache' ) ) {
|
|
$conflicts[] = 'LiteSpeed Cache';
|
|
}
|
|
if ( defined( 'WPCACHEHOME' ) || function_exists( 'wp_cache_phase2' ) ) {
|
|
$conflicts[] = 'WP Super Cache';
|
|
}
|
|
if ( class_exists( 'WpFastestCache' ) || defined( 'WPFC_WP_CONTENT_BASENAME' ) ) {
|
|
$conflicts[] = 'WP Fastest Cache';
|
|
}
|
|
if ( class_exists( 'autoptimizeMain' ) || defined( 'AUTOPTIMIZE_PLUGIN_VERSION' ) ) {
|
|
$conflicts[] = 'Autoptimize';
|
|
}
|
|
if ( class_exists( 'Cache_Enabler' ) || defined( 'CACHE_ENABLER_VERSION' ) ) {
|
|
$conflicts[] = 'Cache Enabler';
|
|
}
|
|
if ( class_exists( 'WP_Hummingbird' ) || defined( 'WPHB_VERSION' ) ) {
|
|
$conflicts[] = 'Hummingbird';
|
|
}
|
|
if ( class_exists( 'Breeze_Admin' ) || defined( 'BREEZE_VERSION' ) ) {
|
|
$conflicts[] = 'Breeze';
|
|
}
|
|
if ( class_exists( 'SiteGround_Optimizer' ) || defined( 'SG_OPTIMIZER_VERSION' ) ) {
|
|
$conflicts[] = 'SG Optimizer';
|
|
}
|
|
|
|
return $conflicts;
|
|
}
|
|
|
|
/**
|
|
* Plugin deactivation
|
|
*/
|
|
public function deactivate() {
|
|
$this->clear_cache();
|
|
flush_rewrite_rules();
|
|
}
|
|
|
|
/**
|
|
* Clear all caches
|
|
*/
|
|
public function clear_cache() {
|
|
// Clear page cache
|
|
if ( class_exists( 'Maple_Performance_Cache' ) ) {
|
|
Maple_Performance_Cache::clear_all();
|
|
} else {
|
|
$this->recursive_delete( MAPLE_PERF_CACHE_DIR );
|
|
}
|
|
|
|
// Recreate directories
|
|
if ( ! file_exists( MAPLE_PERF_CACHE_DIR ) ) {
|
|
wp_mkdir_p( MAPLE_PERF_CACHE_DIR );
|
|
}
|
|
if ( ! file_exists( MAPLE_PERF_CACHE_DIR . 'assets/' ) ) {
|
|
wp_mkdir_p( MAPLE_PERF_CACHE_DIR . 'assets/' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively delete directory contents
|
|
*/
|
|
private function recursive_delete( $dir ) {
|
|
if ( ! is_dir( $dir ) ) {
|
|
return;
|
|
}
|
|
|
|
// Safety check - only delete within wp-content/cache/maple-performance
|
|
$real_dir = realpath( $dir );
|
|
$allowed_base = realpath( WP_CONTENT_DIR );
|
|
|
|
if ( false === $real_dir || false === $allowed_base ) {
|
|
return;
|
|
}
|
|
|
|
if ( strpos( $real_dir, $allowed_base ) !== 0 ) {
|
|
return;
|
|
}
|
|
|
|
// Additional safety - must contain 'maple-performance' in path
|
|
if ( strpos( $real_dir, 'maple-performance' ) === false ) {
|
|
return;
|
|
}
|
|
|
|
// Limit iterations to prevent runaway deletion
|
|
$max_iterations = 50000;
|
|
$iterations = 0;
|
|
|
|
try {
|
|
$files = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ),
|
|
RecursiveIteratorIterator::CHILD_FIRST
|
|
);
|
|
|
|
foreach ( $files as $file ) {
|
|
if ( ++$iterations > $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 setting value
|
|
*/
|
|
public function get_setting( $key, $default = null ) {
|
|
return isset( $this->settings[ $key ] ) ? $this->settings[ $key ] : $default;
|
|
}
|
|
|
|
/**
|
|
* Check if current request should be excluded from caching
|
|
*/
|
|
public function is_excluded() {
|
|
// Admin requests
|
|
if ( is_admin() ) {
|
|
return true;
|
|
}
|
|
|
|
// AJAX requests
|
|
if ( wp_doing_ajax() ) {
|
|
return true;
|
|
}
|
|
|
|
// Cron requests
|
|
if ( wp_doing_cron() ) {
|
|
return true;
|
|
}
|
|
|
|
// REST API
|
|
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
|
|
return true;
|
|
}
|
|
|
|
// POST requests
|
|
if ( 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
|
|
return true;
|
|
}
|
|
|
|
// DONOTCACHEPAGE constant (set by compat class or other plugins)
|
|
if ( defined( 'DONOTCACHEPAGE' ) && DONOTCACHEPAGE ) {
|
|
return true;
|
|
}
|
|
|
|
// Logged-in users (unless enabled)
|
|
if ( ! $this->settings['cache_logged_in'] && is_user_logged_in() ) {
|
|
return true;
|
|
}
|
|
|
|
// Get cookie exclusions with compat filter
|
|
$exclude_cookies = apply_filters( 'maple_performance_cookie_exclusions', $this->settings['exclude_cookies'] );
|
|
|
|
// Check excluded cookies
|
|
foreach ( $exclude_cookies as $cookie ) {
|
|
if ( ! empty( $cookie ) ) {
|
|
// Support partial cookie name matching (for session cookies with dynamic suffixes)
|
|
foreach ( $_COOKIE as $cookie_name => $cookie_value ) {
|
|
if ( strpos( $cookie_name, $cookie ) !== false ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get path exclusions with compat filter
|
|
$exclude_paths = apply_filters( 'maple_performance_path_exclusions', $this->settings['exclude_paths'] );
|
|
|
|
// Check excluded paths
|
|
$request_uri = $_SERVER['REQUEST_URI'] ?? '';
|
|
foreach ( $exclude_paths as $path ) {
|
|
if ( ! empty( $path ) && strpos( $request_uri, $path ) !== false ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Allow other plugins/code to exclude
|
|
if ( apply_filters( 'maple_performance_exclude_optimization', false ) ) {
|
|
return true;
|
|
}
|
|
|
|
// WooCommerce specific
|
|
if ( function_exists( 'is_cart' ) && ( is_cart() || is_checkout() || is_account_page() ) ) {
|
|
return true;
|
|
}
|
|
|
|
// LearnDash specific - always exclude when user is enrolled
|
|
if ( function_exists( 'learndash_user_get_enrolled_courses' ) && is_user_logged_in() ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize plugin
|
|
*/
|
|
function maple_performance() {
|
|
return Maple_Performance::get_instance();
|
|
}
|
|
|
|
// Start the plugin
|
|
maple_performance();
|
|
|
|
/**
|
|
* Template tag to clear cache
|
|
*/
|
|
function maple_clear_cache() {
|
|
maple_performance()->clear_cache();
|
|
}
|