<?php
/**
 * Plugin Name:       Blaminhor Essentials – All-in-One: GEO, SEO, Redirect, SMTP, Cache & More
 * Plugin URI:        https://wp.blaminhor.com/
 * Description:       A modular toolkit for WordPress with activatable features. Lightweight, secure, and reliable.
 * Version:           1.11.1
 * Requires at least: 6.2
 * Requires PHP:      7.4
 * Author:            blaminhor
 * Author URI:        https://blaminhor.com
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       blaminhor-essentials
 * Domain Path:       /languages
 *
 * @package BlaminhorEssentials
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

// Plugin constants
define('BLAMINHOR_ESSENTIALS_VERSION', '1.11.1');
define('BLAMINHOR_ESSENTIALS_PLUGIN_FILE', __FILE__);
define('BLAMINHOR_ESSENTIALS_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('BLAMINHOR_ESSENTIALS_PLUGIN_URL', plugin_dir_url(__FILE__));
define('BLAMINHOR_ESSENTIALS_PLUGIN_BASENAME', plugin_basename(__FILE__));

/*
 * Translatable strings for modules (used for i18n extraction tools).
 * These strings are translated dynamically at runtime to avoid loading
 * translations before the 'init' hook (WordPress 6.7+ requirement).
 *
 * __( 'Content Duplicator', 'blaminhor-essentials' );
 * __( 'Duplicate any post type or taxonomy with all metadata.', 'blaminhor-essentials' );
 * __( 'Classic Editor & Widgets', 'blaminhor-essentials' );
 * __( 'Restores the classic post editor and/or the classic widgets screen.', 'blaminhor-essentials' );
 * __( 'Image Sizes', 'blaminhor-essentials' );
 * __( 'Manage WordPress image sizes and disable unused ones.', 'blaminhor-essentials' );
 * __( 'Favicon Generator', 'blaminhor-essentials' );
 * __( 'Generate all favicon formats from a single image.', 'blaminhor-essentials' );
 * __( 'Post Types Order', 'blaminhor-essentials' );
 * __( 'Reorder any post type or taxonomy with drag and drop.', 'blaminhor-essentials' );
 * __( 'SEO/GEO', 'blaminhor-essentials' );
 * __( 'Complete SEO toolkit: meta tags, indexing control, social sharing, and XML sitemaps.', 'blaminhor-essentials' );
 * __( 'Broken Links Checker', 'blaminhor-essentials' );
 * __( 'Scan your site for broken internal and external links.', 'blaminhor-essentials' );
 * __( 'Redirections 301', 'blaminhor-essentials' );
 * __( 'Manage 301 redirections with simple rules or regex patterns.', 'blaminhor-essentials' );
 * __( 'Backup & Restore', 'blaminhor-essentials' );
 * __( 'Create and restore local backups of your site files and database.', 'blaminhor-essentials' );
 * __( 'Database Optimizer', 'blaminhor-essentials' );
 * __( 'Optimize and clean your WordPress database for better performance.', 'blaminhor-essentials' );
 * __( 'Domain Changer', 'blaminhor-essentials' );
 * __( 'Change your site domain and update all database references in one click.', 'blaminhor-essentials' );
 * __( 'HTTPS Redirect', 'blaminhor-essentials' );
 * __( 'Force HTTPS redirection for your site.', 'blaminhor-essentials' );
 * __( 'Fatal Error Recovery', 'blaminhor-essentials' );
 * __( 'Recover from fatal errors by disabling problematic plugins without FTP access.', 'blaminhor-essentials' );
 * __( 'Coming soon / Maintenance', 'blaminhor-essentials' );
 * __( 'Enable Coming Soon or Maintenance mode for your site.', 'blaminhor-essentials' );
 * __( 'SMTP Mailer', 'blaminhor-essentials' );
 * __( 'Configure SMTP relay with fallback support and email logging.', 'blaminhor-essentials' );
 * __( 'Mute Core Emails', 'blaminhor-essentials' );
 * __( 'Mute automatic emails from WordPress core.', 'blaminhor-essentials' );
 * __( 'User Role Editor', 'blaminhor-essentials' );
 * __( 'Manage user roles and capabilities.', 'blaminhor-essentials' );
 * __( 'Disable Comments', 'blaminhor-essentials' );
 * __( 'Disable comments on your site.', 'blaminhor-essentials' );
 * __( 'Content & Media', 'blaminhor-essentials' );
 * __( 'SEO & Links', 'blaminhor-essentials' );
 * __( 'Database & Migration', 'blaminhor-essentials' );
 * __( 'Security & Maintenance', 'blaminhor-essentials' );
 * __( 'Hide Login Page', 'blaminhor-essentials' );
 * __( 'Change the login URL and block access to wp-login.php.', 'blaminhor-essentials' );
 * __( 'Emails', 'blaminhor-essentials' );
 * __( 'Cache & Performance', 'blaminhor-essentials' );
 * __( 'Page cache, asset optimization, lazy load, performance audit, and Web Vitals dashboard.', 'blaminhor-essentials' );
 * __( 'Performance', 'blaminhor-essentials' );
 */

/**
 * STANDALONE RECOVERY HANDLER
 * This runs BEFORE anything else to ensure recovery works even during fatal errors.
 * It doesn't depend on any module or class.
 *
 * Note: Nonce verification is not used here because:
 * 1. This is a recovery mechanism that must work even when WordPress is broken
 * 2. Authentication is done via a secret recovery key stored in DB/file
 * 3. The recovery key acts as a CSRF token for form submissions
 */
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Recovery mode uses secret key authentication instead of nonce
if ( isset( $_GET['be_recovery'] ) && ! empty( $_GET['be_recovery'] ) ) {
	blaminhor_essentials_standalone_recovery( sanitize_text_field( wp_unslash( $_GET['be_recovery'] ) ) );
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended

/**
 * Standalone recovery function - completely independent of the plugin
 *
 * @param string $provided_key The recovery key from URL.
 */
function blaminhor_essentials_standalone_recovery( $provided_key ) {
	// Get recovery key from options (may fail if DB issue, so also check file)
	$stored_key = get_option( 'blaminhor_essentials_module_fatal-error-recovery_settings', array() );
	$stored_key = isset( $stored_key['recovery_key'] ) ? $stored_key['recovery_key'] : '';

	// Also check file-based key as fallback
	$upload_dir = wp_upload_dir();
	$key_file   = $upload_dir['basedir'] . '/blaminhor-essentials/.recovery-key';
	if ( empty( $stored_key ) && file_exists( $key_file ) ) {
		$stored_key = trim( file_get_contents( $key_file ) );
	}

	// Verify key
	if ( empty( $stored_key ) || ! hash_equals( $stored_key, $provided_key ) ) {
		return; // Invalid key, continue normal plugin load
	}

	// We're in recovery mode - handle actions and display page
	blaminhor_essentials_recovery_handle_actions( $stored_key );
	blaminhor_essentials_recovery_display_page( $stored_key );
	exit; // Stop all further execution
}

/**
 * Handle recovery actions (enable/disable plugins)
 * Uses simple token verification since wp_create_nonce() may not be available yet
 *
 * Note: This function intentionally uses WP_Filesystem because:
 * 1. It's the WordPress-recommended way to handle file operations
 * 2. Recovery mode initializes filesystem in direct mode for reliability
 *
 * @param string $recovery_key The recovery key for CSRF protection.
 */
function blaminhor_essentials_recovery_handle_actions( $recovery_key ) {
	// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Recovery mode uses secret key authentication instead of nonce
	if ( ! isset( $_POST['be_fer_token'] ) ) {
		return;
	}

	// Use recovery key as CSRF token since it's already secret
	// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Recovery mode uses secret key as CSRF token
	$token = sanitize_text_field( wp_unslash( $_POST['be_fer_token'] ) );
	if ( ! hash_equals( $recovery_key, $token ) ) {
		return;
	}

	// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified via recovery key above
	$action = isset( $_POST['be_fer_action'] ) ? sanitize_key( wp_unslash( $_POST['be_fer_action'] ) ) : '';
	// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified via recovery key above
	$plugin = isset( $_POST['be_fer_plugin'] ) ? sanitize_file_name( wp_unslash( $_POST['be_fer_plugin'] ) ) : '';

	if ( empty( $action ) || empty( $plugin ) ) {
		return;
	}

	// Additional validation: only allow specific actions
	if ( ! in_array( $action, array( 'enable', 'disable', 'disable_https' ), true ) ) {
		return;
	}

	// Handle HTTPS disable action
	if ( 'disable_https' === $action ) {
		$https_settings = get_option( 'blaminhor_essentials_module_https-redirect_settings', array() );
		$https_settings['enabled'] = false;
		update_option( 'blaminhor_essentials_module_https-redirect_settings', $https_settings );
		return;
	}

	// Get active plugins from database
	$active_plugins = get_option( 'active_plugins', array() );

	if ( 'disable' === $action ) {
		// Find and remove plugin from active_plugins
		$plugin_file = blaminhor_essentials_find_plugin_file( $plugin );
		if ( $plugin_file ) {
			$key = array_search( $plugin_file, $active_plugins, true );
			if ( false !== $key ) {
				unset( $active_plugins[ $key ] );
				update_option( 'active_plugins', array_values( $active_plugins ) );
			}
		}
	} elseif ( 'enable' === $action ) {
		// Find and add plugin to active_plugins
		$plugin_file = blaminhor_essentials_find_plugin_file( $plugin );
		if ( $plugin_file && ! in_array( $plugin_file, $active_plugins, true ) ) {
			$active_plugins[] = $plugin_file;
			update_option( 'active_plugins', $active_plugins );
		}
	}
}

/**
 * Find the main plugin file for a plugin folder
 *
 * @param string $plugin_folder Plugin folder name.
 * @return string|false Plugin file path (folder/file.php) or false.
 */
function blaminhor_essentials_find_plugin_file( $plugin_folder ) {
	$plugins_dir = WP_PLUGIN_DIR;
	$plugin_path = $plugins_dir . '/' . $plugin_folder;

	if ( ! is_dir( $plugin_path ) ) {
		return false;
	}

	// Check for plugin-name.php
	$main_file = $plugin_path . '/' . $plugin_folder . '.php';
	if ( file_exists( $main_file ) ) {
		$content = file_get_contents( $main_file, false, null, 0, 8192 );
		if ( strpos( $content, 'Plugin Name:' ) !== false ) {
			return $plugin_folder . '/' . $plugin_folder . '.php';
		}
	}

	// Scan for any PHP file with Plugin Name header
	$files = glob( $plugin_path . '/*.php' );
	if ( $files ) {
		foreach ( $files as $file ) {
			$content = file_get_contents( $file, false, null, 0, 8192 );
			if ( strpos( $content, 'Plugin Name:' ) !== false ) {
				return $plugin_folder . '/' . basename( $file );
			}
		}
	}

	return false;
}

/**
 * Display standalone recovery page
 *
 * @param string $recovery_key The recovery key for CSRF protection.
 */
function blaminhor_essentials_recovery_display_page( $recovery_key ) {
	$plugins_dir    = WP_PLUGIN_DIR;
	$plugins        = array();
	$active_plugins = get_option( 'active_plugins', array() );

	// Scan plugins directory
	$dirs = @scandir( $plugins_dir );
	if ( $dirs ) {
		foreach ( $dirs as $dir ) {
			if ( in_array( $dir, array( '.', '..', 'index.php' ), true ) ) {
				continue;
			}

			// Skip folders starting with underscore (legacy disabled plugins)
			if ( strpos( $dir, '_' ) === 0 ) {
				continue;
			}

			$plugin_path = $plugins_dir . '/' . $dir;

			if ( ! is_dir( $plugin_path ) ) {
				continue;
			}

			// Find the main plugin file
			$plugin_file = blaminhor_essentials_find_plugin_file( $dir );
			if ( ! $plugin_file ) {
				continue;
			}

			// Check if plugin is active
			$is_active = in_array( $plugin_file, $active_plugins, true );

			// Try to get plugin name from main file
			$plugin_name = $dir;
			$main_file   = $plugins_dir . '/' . $plugin_file;
			if ( file_exists( $main_file ) ) {
				$plugin_data = get_file_data( $main_file, array( 'Name' => 'Plugin Name' ) );
				if ( ! empty( $plugin_data['Name'] ) ) {
					$plugin_name = $plugin_data['Name'];
				}
			}

			$plugins[] = array(
				'name'     => $plugin_name,
				'folder'   => $dir,
				'active'   => $is_active,
				'is_self'  => ( strpos( $dir, 'blaminhor-essentials' ) !== false ),
			);
		}
	}

	// Sort: active first, then inactive
	usort( $plugins, function( $a, $b ) {
		if ( $a['active'] === $b['active'] ) {
			return strcasecmp( $a['name'], $b['name'] );
		}
		return $a['active'] ? -1 : 1;
	} );

	$token = $recovery_key; // Use recovery key as CSRF token

	// Check if HTTPS redirect is enabled
	$https_settings = get_option( 'blaminhor_essentials_module_https-redirect_settings', array() );
	$https_enabled  = isset( $https_settings['enabled'] ) && $https_settings['enabled'];

	// Output the recovery page
	?>
	<!DOCTYPE html>
	<html>
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<meta name="robots" content="noindex, nofollow">
		<title><?php echo esc_html__( 'Recovery Mode', 'blaminhor-essentials' ); ?> - Blaminhor Essentials</title>
		<style>
			* { box-sizing: border-box; margin: 0; padding: 0; }
			body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f0f0f1; padding: 40px 20px; }
			.recovery-container { max-width: 800px; margin: 0 auto; }
			.recovery-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; padding: 30px; border-radius: 8px 8px 0 0; }
			.recovery-header h1 { font-size: 24px; margin-bottom: 8px; }
			.recovery-header p { opacity: 0.9; font-size: 14px; }
			.recovery-content { background: #fff; padding: 30px; border-radius: 0 0 8px 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
			.warning-box { background: #fff3cd; border: 1px solid #ffc107; padding: 15px; border-radius: 4px; margin-bottom: 20px; }
			.warning-box strong { color: #856404; }
			.plugin-list { list-style: none; }
			.plugin-item { display: flex; align-items: center; justify-content: space-between; padding: 15px; border-bottom: 1px solid #e0e0e0; }
			.plugin-item:last-child { border-bottom: none; }
			.plugin-item.disabled { background: #f8f8f8; }
			.plugin-name { font-weight: 500; }
			.plugin-name.disabled { color: #666; text-decoration: line-through; }
			.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; }
			.btn-disable { background: #dc3545; color: #fff; }
			.btn-enable { background: #28a745; color: #fff; }
			.btn-warning { background: #ffc107; color: #000; }
			.btn:hover { opacity: 0.9; }
			.self-warning { font-size: 11px; color: #dc3545; margin-top: 4px; }
			.https-box { background: #fff3cd; border: 1px solid #ffc107; padding: 20px; border-radius: 4px; margin-bottom: 20px; }
			.https-box h3 { color: #856404; margin-bottom: 10px; font-size: 16px; }
			.https-box p { margin-bottom: 15px; color: #856404; }
			.https-box .btn { margin-top: 5px; }
			.success-box { background: #d4edda; border: 1px solid #28a745; padding: 15px; border-radius: 4px; margin-bottom: 20px; color: #155724; }
		</style>
	</head>
	<body>
		<div class="recovery-container">
			<div class="recovery-header">
				<h1><?php echo esc_html__( 'Recovery Mode', 'blaminhor-essentials' ); ?></h1>
				<p><?php echo esc_html__( 'Disable problematic plugins to recover your site.', 'blaminhor-essentials' ); ?></p>
			</div>
			<div class="recovery-content">
				<?php if ( $https_enabled ) : ?>
				<div class="https-box">
					<h3><?php echo esc_html__( 'HTTPS Redirect is enabled', 'blaminhor-essentials' ); ?></h3>
					<p><?php echo esc_html__( 'If you are having trouble accessing your site due to HTTPS issues, you can disable the HTTPS redirect here.', 'blaminhor-essentials' ); ?></p>
					<form method="post" style="display: inline;">
						<input type="hidden" name="be_fer_token" value="<?php echo esc_attr( $token ); ?>">
						<input type="hidden" name="be_fer_action" value="disable_https">
						<input type="hidden" name="be_fer_plugin" value="https-redirect">
						<button type="submit" class="btn btn-warning"><?php echo esc_html__( 'Disable HTTPS Redirect', 'blaminhor-essentials' ); ?></button>
					</form>
				</div>
				<?php endif; ?>

				<?php
				// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Recovery mode uses secret key
				if ( isset( $_POST['be_fer_action'] ) && 'disable_https' === sanitize_key( wp_unslash( $_POST['be_fer_action'] ) ) ) : ?>
				<div class="success-box">
					<?php echo esc_html__( 'HTTPS Redirect has been disabled. You can now access your site via HTTP.', 'blaminhor-essentials' ); ?>
				</div>
				<?php endif; ?>

				<div class="warning-box">
					<strong><?php echo esc_html__( 'Warning:', 'blaminhor-essentials' ); ?></strong>
					<?php echo esc_html__( 'Disabling a plugin deactivates it in the database. Re-enable via this page or the WordPress admin.', 'blaminhor-essentials' ); ?>
				</div>

				<ul class="plugin-list">
					<?php foreach ( $plugins as $plugin ) : ?>
						<li class="plugin-item <?php echo ! $plugin['active'] ? 'disabled' : ''; ?>">
							<div>
								<span class="plugin-name <?php echo ! $plugin['active'] ? 'disabled' : ''; ?>">
									<?php echo esc_html( $plugin['name'] ); ?>
								</span>
								<?php if ( $plugin['is_self'] && $plugin['active'] ) : ?>
									<div class="self-warning"><?php echo esc_html__( 'Disabling this will disable recovery mode too!', 'blaminhor-essentials' ); ?></div>
								<?php endif; ?>
							</div>
							<div>
								<?php if ( ! $plugin['active'] ) : ?>
									<form method="post" style="display: inline;">
										<input type="hidden" name="be_fer_token" value="<?php echo esc_attr( $token ); ?>">
										<input type="hidden" name="be_fer_action" value="enable">
										<input type="hidden" name="be_fer_plugin" value="<?php echo esc_attr( $plugin['folder'] ); ?>">
										<button type="submit" class="btn btn-enable"><?php echo esc_html__( 'Activate', 'blaminhor-essentials' ); ?></button>
									</form>
								<?php elseif ( $plugin['is_self'] ) : ?>
									<form method="post" style="display: inline;" onsubmit="return confirm('<?php echo esc_js( __( 'This will disable Blaminhor Essentials and this recovery page. You will need to re-enable it from WordPress admin. Continue?', 'blaminhor-essentials' ) ); ?>');">
										<input type="hidden" name="be_fer_token" value="<?php echo esc_attr( $token ); ?>">
										<input type="hidden" name="be_fer_action" value="disable">
										<input type="hidden" name="be_fer_plugin" value="<?php echo esc_attr( $plugin['folder'] ); ?>">
										<button type="submit" class="btn btn-disable"><?php echo esc_html__( 'Deactivate (Self)', 'blaminhor-essentials' ); ?></button>
									</form>
								<?php else : ?>
									<form method="post" style="display: inline;">
										<input type="hidden" name="be_fer_token" value="<?php echo esc_attr( $token ); ?>">
										<input type="hidden" name="be_fer_action" value="disable">
										<input type="hidden" name="be_fer_plugin" value="<?php echo esc_attr( $plugin['folder'] ); ?>">
										<button type="submit" class="btn btn-disable"><?php echo esc_html__( 'Deactivate', 'blaminhor-essentials' ); ?></button>
									</form>
								<?php endif; ?>
							</div>
						</li>
					<?php endforeach; ?>
				</ul>
			</div>
		</div>
	</body>
	</html>
	<?php
}

/**
 * Main plugin class
 */
final class Blaminhor_Essentials
{

    /**
     * Single instance of the class
     *
     * @var Blaminhor_Essentials|null
     */
    private static $instance = null;

    /**
     * Active modules
     *
     * @var array
     */
    private $active_modules = array();

    /**
     * Available modules
     *
     * @var array
     */
    private $available_modules = array();

    /**
     * Get single instance
     *
     * @return Blaminhor_Essentials
     */
    public static function get_instance()
    {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor
     */
    private function __construct()
    {
        $this->load_dependencies();
        $this->set_locale();
        $this->define_admin_hooks();
        $this->register_modules();
        $this->cleanup_orphan_cache_dropin();
        // Modules must be instantiated BEFORE the version-bump cleanup so it
        // can call into the cache module's drop-in installer to ship the
        // latest drop-in alongside the page-cache purge.
        $this->load_active_modules();
        $this->maybe_purge_cache_on_version_bump();
    }

    /**
     * After an FTP/manual upload of a new plugin ZIP, the cache drop-in keeps
     * serving the HTML baked under the previous version — fine for cosmetic
     * fixes, broken for upgrades that change which optimisations run (e.g.
     * 1.9.8.18 turning Critical CSS off on Avada). To prevent visitors seeing
     * stale broken HTML, every detected version bump wipes the static page
     * cache once. The marker option is updated immediately so concurrent
     * requests do not re-purge.
     */
    private function maybe_purge_cache_on_version_bump()
    {
        $current = defined('BLAMINHOR_ESSENTIALS_VERSION') ? BLAMINHOR_ESSENTIALS_VERSION : '';
        $stored  = get_option('blaminhor_essentials_installed_version', '');
        if ($current === '' || $current === $stored) {
            return;
        }
        // Wipe the static HTML page cache: the drop-in serves these files
        // before WordPress fully boots, so we cannot rely on PHP-side hooks
        // to invalidate after an FTP/manual upload.
        $base = WP_CONTENT_DIR . '/uploads/blaminhor-essentials/cache/pages';
        if (is_dir($base)) {
            try {
                $it = new RecursiveIteratorIterator(
                    new RecursiveDirectoryIterator($base, RecursiveDirectoryIterator::SKIP_DOTS),
                    RecursiveIteratorIterator::CHILD_FIRST
                );
                foreach ($it as $entry) {
                    if ($entry->isFile()) {
                        $ext = strtolower(pathinfo((string) $entry->getPathname(), PATHINFO_EXTENSION));
                        if (in_array($ext, array('html', 'gz', 'meta', 'redirect'), true)) {
                            @unlink((string) $entry->getPathname());
                        }
                    }
                }
            } catch (Exception $e) {
                // Filesystem race / permission glitch — nothing to do here, the
                // safe-defaults run later in the request will catch up.
            }
        }

        // Reinstall the drop-in if it carries an outdated version: the new
        // file shipped with this release may include logic the on-disk one
        // doesn't (e.g. the per-file mtime invalidation introduced in 1.3).
        // This must succeed even when the user uploads a fresh ZIP via FTP
        // — the drop-in needs to learn the new tricks before the next
        // anonymous request hits.
        $this->reinstall_cache_dropin_if_outdated();

        // Belt-and-braces: bump the drop-in's mtime so any cache file the
        // plugin writes between this hook firing and the next request gets
        // invalidated by the drop-in's mtime check on the next anonymous
        // request.
        $this->touch_cache_dropin_for_invalidation();

        update_option('blaminhor_essentials_installed_version', $current, false);
        if (function_exists('blaminhor_essentials_log')) {
            blaminhor_essentials_log(
                sprintf('Plugin upgraded from "%s" to "%s"; static page cache purged and drop-in mtime bumped so anonymous visitors see the new build immediately.', $stored, $current),
                'info',
                'cache'
            );
        }
    }

    /**
     * Bump the on-disk advanced-cache.php drop-in so its mtime is fresher than
     * any cached HTML page baked under a previous build. The drop-in itself
     * compares its mtime against each cache file's mtime and self-invalidates
     * older files on the next anonymous request — the only path that lets us
     * dodge a stale page when WordPress never gets to fully bootstrap.
     *
     * Implementation: a touch() is enough to bump mtime atomically; if it
     * fails (read-only FS, suhosin, etc.) we fall back to a re-write of the
     * file so the upgrade still propagates.
     */
    /**
     * Reinstall the bundled advanced-cache.php drop-in if the on-disk version
     * marker is older than the constant shipped with the cache module. Used
     * during version-bump handling so an FTP upload immediately picks up the
     * new drop-in logic without waiting for the user to visit wp-admin.
     */
    private function reinstall_cache_dropin_if_outdated()
    {
        if (!isset($this->active_modules['cache']) || !is_object($this->active_modules['cache'])) {
            return;
        }
        $module = $this->active_modules['cache'];
        if (!isset($module->dropin_installer) || !is_object($module->dropin_installer)) {
            return;
        }
        if (!method_exists($module, 'dropin_install_params')) {
            return;
        }
        // Force-reinstall on every version bump — not just when the version
        // marker is older. The drop-in file on disk can drift away from its
        // marker (corruption, half-applied upgrade, manual edit) and the safe
        // default is to ship the bundled template again. install() with
        // $force=true also takes over a foreign drop-in.
        $module->dropin_installer->install($module->dropin_install_params(), true);
    }

    private function touch_cache_dropin_for_invalidation()
    {
        if (!defined('WP_CONTENT_DIR')) {
            return;
        }
        $dropin = WP_CONTENT_DIR . '/advanced-cache.php';
        if (!file_exists($dropin)) {
            return;
        }
        $head = @file_get_contents($dropin, false, null, 0, 2048);
        if (false === $head || false === strpos($head, 'BLAMINHOR_ESSENTIALS_DROPIN')) {
            // Foreign drop-in, never touch.
            return;
        }
        if (@touch($dropin, time())) {
            return;
        }
        // touch() refused — rewrite the file in place. We read+write the same
        // bytes; mtime is bumped as a side-effect.
        $body = @file_get_contents($dropin);
        if (false !== $body && strlen($body) > 100) {
            @file_put_contents($dropin, $body, LOCK_EX);
        }
    }

    /**
     * Auto-cleanup of an orphaned cache drop-in.
     *
     * Scenario: the user removed the plugin folder (or the Cache module) without
     * letting the deactivation hook run. The drop-in keeps serving cached HTML
     * — potentially stale or generated with optimisations that no longer match
     * the live config. This guard removes our drop-in when its owning module is
     * no longer in the active list. We never touch a foreign drop-in (we check
     * the BLAMINHOR_ESSENTIALS_DROPIN marker before deleting).
     */
    private function cleanup_orphan_cache_dropin()
    {
        if (!defined('WP_CONTENT_DIR')) {
            return;
        }
        $dropin_path = WP_CONTENT_DIR . '/advanced-cache.php';
        if (!file_exists($dropin_path)) {
            return;
        }
        $head = @file_get_contents($dropin_path, false, null, 0, 2048);
        if (false === $head || false === strpos($head, 'BLAMINHOR_ESSENTIALS_DROPIN')) {
            return;
        }
        $active = get_option('blaminhor_essentials_active_modules', array());
        if (is_array($active) && in_array('cache', $active, true)) {
            return;
        }
        @unlink($dropin_path);
        if (function_exists('blaminhor_essentials_log')) {
            blaminhor_essentials_log('Cache: orphaned drop-in removed (Cache module not in active list).', 'warning');
        }
    }

    /**
     * Load required dependencies
     */
    private function load_dependencies()
    {
        require_once BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'includes/functions.php';
        require_once BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'includes/upgrade-notices.php';
        require_once BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'includes/class-blaminhor-essentials-logs.php';
        require_once BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'includes/class-blaminhor-essentials-module.php';
        require_once BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'includes/class-blaminhor-essentials-admin.php';
    }

    /**
     * Set locale
     *
     * Note: Since WordPress 4.6, translations are loaded automatically from WordPress.org
     * for plugins hosted there. No need to call load_plugin_textdomain().
     * This method is kept as a placeholder for any future locale-related needs.
     */
    private function set_locale()
    {
        // Translations are automatically loaded by WordPress.org for hosted plugins.
        // No action needed here for WordPress 5.8+.
    }

    /**
     * Register admin hooks
     */
    private function define_admin_hooks()
    {
        if (is_admin()) {
            $admin = new Blaminhor_Essentials_Admin($this);
            add_action('admin_menu', array($admin, 'add_admin_menu'));
            add_action('admin_enqueue_scripts', array($admin, 'enqueue_assets'));
            add_action('admin_init', array($admin, 'register_settings'));
        }
    }

    /**
     * Register all available modules
     *
     * Note: Module names and descriptions are stored as translation keys (not translated)
     * to avoid loading translations too early. Use get_available_modules() to get
     * translated module data.
     */
    private function register_modules()
    {
        $this->available_modules = array(
            // Content & Media
            'duplicator' => array(
                'name' => 'Content Duplicator',
                'description' => 'Duplicate any post type or taxonomy with all metadata.',
                'icon' => 'dashicons-admin-page',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/duplicator/class-module-duplicator.php',
                'class' => 'Blaminhor_Essentials_Module_Duplicator',
                'category' => 'content-media',
            ),
            'classic-editor' => array(
                'name' => 'Classic Editor & Widgets',
                'description' => 'Restores the classic post editor and/or the classic widgets screen.',
                'icon' => 'dashicons-edit',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/classic-editor/class-module-classic-editor.php',
                'class' => 'Blaminhor_Essentials_Module_Classic_Editor',
                'category' => 'content-media',
            ),
            'image-sizes' => array(
                'name' => 'Image Sizes',
                'description' => 'Manage WordPress image sizes and disable unused ones.',
                'icon' => 'dashicons-format-image',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/image-sizes/class-module-image-sizes.php',
                'class' => 'Blaminhor_Essentials_Module_Image_Sizes',
                'category' => 'content-media',
            ),
            'favicon' => array(
                'name' => 'Favicon Generator',
                'description' => 'Generate all favicon formats from a single image.',
                'icon' => 'dashicons-star-filled',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/favicon/class-module-favicon.php',
                'class' => 'Blaminhor_Essentials_Module_Favicon',
                'category' => 'content-media',
            ),
            'post-types-order' => array(
                'name' => 'Post Types Order',
                'description' => 'Reorder any post type or taxonomy with drag and drop.',
                'icon' => 'dashicons-sort',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/post-types-order/class-module-post-types-order.php',
                'class' => 'Blaminhor_Essentials_Module_Post_Types_Order',
                'category' => 'content-media',
            ),
            'disable-comments' => array(
                'name' => 'Disable Comments',
                'description' => 'Disable comments on your site.',
                'icon' => 'dashicons-admin-comments',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/disable-comments/class-module-disable-comments.php',
                'class' => 'Blaminhor_Essentials_Module_Disable_Comments',
                'category' => 'content-media',
            ),
            'cookies' => array(
                'name' => 'Cookie Banner',
                'description' => 'Show a consent banner and block tracking scripts until visitors agree.',
                'icon' => 'dashicons-yes-alt',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/cookies/class-module-cookies.php',
                'class' => 'Blaminhor_Essentials_Module_Cookies',
                'category' => 'security-maintenance',
            ),
            // SEO & Links
            'seo-manager' => array(
                'name' => 'SEO/GEO',
                'description' => 'Complete SEO toolkit: meta tags, indexing control, social sharing, and XML sitemaps.',
                'icon' => 'dashicons-chart-line',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/seo-manager/class-module-seo-manager.php',
                'class' => 'Blaminhor_Essentials_Module_SEO_Manager',
                'category' => 'seo-links',
            ),
            'broken-links' => array(
                'name' => 'Broken Links Checker',
                'description' => 'Scan your site for broken internal and external links.',
                'icon' => 'dashicons-admin-links',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/broken-links/class-module-broken-links.php',
                'class' => 'Blaminhor_Essentials_Module_Broken_Links',
                'category' => 'seo-links',
            ),
            'redirections' => array(
                'name' => 'Redirections 301',
                'description' => 'Manage 301 redirections with simple rules or regex patterns.',
                'icon' => 'dashicons-randomize',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/redirections/class-module-redirections.php',
                'class' => 'Blaminhor_Essentials_Module_Redirections',
                'category' => 'seo-links',
            ),
            // Database & Migration
            'backup' => array(
                'name' => 'Backup & Restore',
                'description' => 'Create and restore local backups of your site files and database.',
                'icon' => 'dashicons-cloud-saved',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/backup/class-module-backup.php',
                'class' => 'Blaminhor_Essentials_Module_Backup',
                'category' => 'database-migration',
            ),
            'db-optimizer' => array(
                'name' => 'Database Optimizer',
                'description' => 'Optimize and clean your WordPress database for better performance.',
                'icon' => 'dashicons-database',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/db-optimizer/class-module-db-optimizer.php',
                'class' => 'Blaminhor_Essentials_Module_Db_Optimizer',
                'category' => 'database-migration',
            ),
            'domain-changer' => array(
                'name' => 'Domain Changer',
                'description' => 'Change your site domain and update all database references in one click.',
                'icon' => 'dashicons-admin-site-alt3',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/domain-changer/class-module-domain-changer.php',
                'class' => 'Blaminhor_Essentials_Module_Domain_Changer',
                'category' => 'database-migration',
            ),
            // Security & Maintenance
            'https-redirect' => array(
                'name' => 'HTTPS Redirect',
                'description' => 'Force HTTPS redirection for your site.',
                'icon' => 'dashicons-lock',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/https-redirect/class-module-https-redirect.php',
                'class' => 'Blaminhor_Essentials_Module_HTTPS_Redirect',
                'category' => 'security-maintenance',
            ),
            'hide-login' => array(
                'name' => 'Hide Login Page',
                'description' => 'Change the login URL and block access to wp-login.php.',
                'icon' => 'dashicons-hidden',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/hide-login/class-module-hide-login.php',
                'class' => 'Blaminhor_Essentials_Module_Hide_Login',
                'category' => 'security-maintenance',
            ),
            'fatal-error-recovery' => array(
                'name' => 'Fatal Error Recovery',
                'description' => 'Recover from fatal errors by disabling problematic plugins without FTP access.',
                'icon' => 'dashicons-shield',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/fatal-error-recovery/class-module-fatal-error-recovery.php',
                'class' => 'Blaminhor_Essentials_Module_Fatal_Error_Recovery',
                'category' => 'security-maintenance',
            ),
            'maintenance' => array(
                'name' => 'Coming soon / Maintenance',
                'description' => 'Enable Coming Soon or Maintenance mode for your site.',
                'icon' => 'dashicons-hammer',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/maintenance/class-module-maintenance.php',
                'class' => 'Blaminhor_Essentials_Module_Maintenance',
                'category' => 'security-maintenance',
            ),
            'user-role-editor' => array(
                'name' => 'User Role Editor',
                'description' => 'Manage user roles and capabilities.',
                'icon' => 'dashicons-admin-users',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/user-role-editor/class-module-user-role-editor.php',
                'class' => 'Blaminhor_Essentials_Module_User_Role_Editor',
                'category' => 'security-maintenance',
            ),
            // Performance
            'cache' => array(
                'name' => 'Cache & Performance',
                'description' => 'Page cache, asset optimization, lazy load, performance audit, and Web Vitals dashboard.',
                'icon' => 'dashicons-performance',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/cache/class-module-cache.php',
                'class' => 'Blaminhor_Essentials_Module_Cache',
                'category' => 'performance',
            ),
            // Emails
            'smtp' => array(
                'name' => 'SMTP Mailer',
                'description' => 'Configure SMTP relay with fallback support and email logging.',
                'icon' => 'dashicons-email-alt',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/smtp/class-module-smtp.php',
                'class' => 'Blaminhor_Essentials_Module_SMTP',
                'category' => 'emails',
            ),
            'mute-core-emails' => array(
                'name' => 'Mute Core Emails',
                'description' => 'Mute automatic emails from WordPress core.',
                'icon' => 'dashicons-controls-volumeoff',
                'file' => BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'modules/mute-core-emails/class-module-mute-core-emails.php',
                'class' => 'Blaminhor_Essentials_Module_Mute_Core_Emails',
                'category' => 'emails',
            ),
        );

        $this->available_modules = apply_filters('blaminhor_essentials_available_modules', $this->available_modules);
    }

    /**
     * Load active modules
     */
    private function load_active_modules()
    {
        $active = get_option('blaminhor_essentials_active_modules', array());

        foreach ($active as $module_id) {
            if (isset($this->available_modules[$module_id])) {
                $module = $this->available_modules[$module_id];
                if (file_exists($module['file'])) {
                    require_once $module['file'];
                    if (class_exists($module['class'])) {
                        $this->active_modules[$module_id] = new $module['class']();
                    }
                }
            }
        }
    }

    /**
     * Get available modules with translated names and descriptions
     *
     * @return array
     */
    public function get_available_modules()
    {
        $modules = $this->available_modules;

        // Translate module names and descriptions.
        foreach ( $modules as $id => $module ) {
            // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText -- Dynamic translation for registered modules.
            $modules[ $id ]['name'] = __( $module['name'], 'blaminhor-essentials' );
            // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText -- Dynamic translation for registered modules.
            $modules[ $id ]['description'] = __( $module['description'], 'blaminhor-essentials' );
        }

        return $modules;
    }

    /**
     * Get available modules without translation (for internal use)
     *
     * @return array
     */
    public function get_available_modules_raw()
    {
        return $this->available_modules;
    }

    /**
     * Get module categories
     *
     * @return array
     */
    public function get_module_categories()
    {
        return array(
            'content-media' => array(
                'name' => __( 'Content & Media', 'blaminhor-essentials' ),
                'icon' => 'dashicons-admin-media',
            ),
            'seo-links' => array(
                'name' => __( 'SEO & Links', 'blaminhor-essentials' ),
                'icon' => 'dashicons-search',
            ),
            'database-migration' => array(
                'name' => __( 'Database & Migration', 'blaminhor-essentials' ),
                'icon' => 'dashicons-database',
            ),
            'security-maintenance' => array(
                'name' => __( 'Security & Maintenance', 'blaminhor-essentials' ),
                'icon' => 'dashicons-shield',
            ),
            'emails' => array(
                'name' => __( 'Emails', 'blaminhor-essentials' ),
                'icon' => 'dashicons-email',
            ),
            'performance' => array(
                'name' => __( 'Performance', 'blaminhor-essentials' ),
                'icon' => 'dashicons-performance',
            ),
        );
    }

    /**
     * Get modules grouped by category
     *
     * @return array
     */
    public function get_modules_by_category()
    {
        $categories = $this->get_module_categories();
        $grouped = array();

        foreach ($categories as $cat_id => $cat_data) {
            $grouped[$cat_id] = array(
                'name' => $cat_data['name'],
                'icon' => $cat_data['icon'],
                'modules' => array(),
            );
        }

        foreach ($this->available_modules as $module_id => $module) {
            $category = isset($module['category']) ? $module['category'] : 'content-media';
            if (isset($grouped[$category])) {
                // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText -- Dynamic translation for registered modules.
                $module['name'] = __( $module['name'], 'blaminhor-essentials' );
                // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText -- Dynamic translation for registered modules.
                $module['description'] = __( $module['description'], 'blaminhor-essentials' );
                $grouped[$category]['modules'][$module_id] = $module;
            }
        }

        return $grouped;
    }

    /**
     * Get active modules
     *
     * @return array
     */
    public function get_active_modules()
    {
        return $this->active_modules;
    }

    /**
     * Check if module is active
     *
     * @param string $module_id Module ID.
     * @return bool
     */
    public function is_module_active($module_id)
    {
        return isset($this->active_modules[$module_id]);
    }

    /**
     * Activate module
     *
     * Triggers an optional `on_activate()` hook on the module class so it can
     * perform setup tasks (drop-in install, .htaccess writes, table creation, etc.).
     * The hook is invoked AFTER the module is added to the active list so that
     * any hooks registered by the module's constructor are also primed for the
     * next request.
     *
     * @param string $module_id Module ID.
     * @return bool|WP_Error True on success, WP_Error if the module's on_activate refused.
     */
    public function activate_module($module_id)
    {
        if (!isset($this->available_modules[$module_id])) {
            return false;
        }

        $module_def = $this->available_modules[$module_id];

        // Allow the module to refuse activation BEFORE we persist the change.
        // Useful for conflict detection (e.g. another cache plugin already active).
        if (file_exists($module_def['file'])) {
            require_once $module_def['file'];
            if (class_exists($module_def['class']) && method_exists($module_def['class'], 'pre_activation_check')) {
                $check = call_user_func(array($module_def['class'], 'pre_activation_check'));
                if (is_wp_error($check)) {
                    return $check;
                }
            }
        }

        $active = get_option('blaminhor_essentials_active_modules', array());
        if (!in_array($module_id, $active, true)) {
            $active[] = $module_id;
            update_option('blaminhor_essentials_active_modules', $active);
        }

        // Fire the on_activate() hook on a fresh instance so the module can
        // perform its setup (drop-in install, etc.). The instance is discarded;
        // the proper instance will be created on the next request by load_active_modules().
        if (file_exists($module_def['file']) && class_exists($module_def['class'])) {
            if (method_exists($module_def['class'], 'on_activate')) {
                $instance = new $module_def['class']();
                $instance->on_activate();
            }
        }

        return true;
    }

    /**
     * Deactivate module
     *
     * Triggers an optional `on_deactivate()` hook on the active module instance
     * BEFORE removing it from the active list, so the module can clean up its
     * artifacts (drop-in, .htaccess sections, transients, etc.).
     *
     * @param string $module_id Module ID.
     * @return bool
     */
    public function deactivate_module($module_id)
    {
        // Run cleanup hook on the live instance if available.
        if (isset($this->active_modules[$module_id])) {
            $instance = $this->active_modules[$module_id];
            if (method_exists($instance, 'on_deactivate')) {
                $instance->on_deactivate();
            }
        } elseif (isset($this->available_modules[$module_id])) {
            // Fallback: load class on demand to allow cleanup even if not currently loaded.
            $module_def = $this->available_modules[$module_id];
            if (file_exists($module_def['file'])) {
                require_once $module_def['file'];
                if (class_exists($module_def['class']) && method_exists($module_def['class'], 'on_deactivate')) {
                    $instance = new $module_def['class']();
                    $instance->on_deactivate();
                }
            }
        }

        $active = get_option('blaminhor_essentials_active_modules', array());
        $active = array_diff($active, array($module_id));
        update_option('blaminhor_essentials_active_modules', array_values($active));

        return true;
    }
}

/**
 * Initialize the plugin
 *
 * @return Blaminhor_Essentials
 */
function blaminhor_essentials()
{
    return Blaminhor_Essentials::get_instance();
}

// Start the plugin on 'init' to ensure translations are loaded properly (WordPress 6.7+ requirement)
add_action('init', 'blaminhor_essentials', 0);

// One-shot migration: strip backslashes accumulated by pre-1.9.10 versions
// that sanitized $_POST without wp_unslash(). Runs once per site (flag option).
// We require the helper file early because plugins_loaded fires before the
// main plugin class (instantiated on 'init') has loaded its dependencies.
require_once BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'includes/functions.php';
add_action('plugins_loaded', 'blaminhor_essentials_migrate_strip_accumulated_slashes', 1);

// Register WP-CLI commands. Loaded on plugins_loaded priority 5 so the
// migration helper (priority 1) and theme-compat constants (priority 0) run
// first, and the singleton (init priority 0) is ready by the time a user
// actually invokes `wp blaminhor ...`. Conditional on WP_CLI to keep zero
// overhead on web requests.
if ( defined( 'WP_CLI' ) && WP_CLI ) {
    add_action(
        'plugins_loaded',
        function () {
            require_once BLAMINHOR_ESSENTIALS_PLUGIN_DIR . 'includes/cli/class-blaminhor-essentials-cli.php';
        },
        5
    );
}

/**
 * Theme-compat constants must be defined BEFORE the target theme runs its
 * setup (which happens on 'after_setup_theme', i.e. between 'plugins_loaded'
 * and 'init'). The Cache module's full Theme Compat Manager instantiates on
 * 'init' — too late for these constants. We therefore plant the strict
 * minimum here, on 'plugins_loaded' priority 0, which is the earliest hook
 * that runs after this plugin file has been parsed.
 */
add_action('plugins_loaded', 'blaminhor_essentials_pose_theme_compat_constants', 0);
function blaminhor_essentials_pose_theme_compat_constants()
{
    $active = get_option('blaminhor_essentials_active_modules', array());
    if (!is_array($active) || !in_array('cache', $active, true)) {
        return;
    }
    $cache_settings = get_option('blaminhor_essentials_module_cache', array());
    $disabled       = is_array($cache_settings) && isset($cache_settings['theme_compat_disabled']) && is_array($cache_settings['theme_compat_disabled'])
        ? $cache_settings['theme_compat_disabled']
        : array();

    if (!function_exists('wp_get_theme')) {
        return;
    }
    $theme      = wp_get_theme();
    $template   = strtolower((string) $theme->get_template());
    $stylesheet = strtolower((string) $theme->get_stylesheet());

    // Avada / Fusion: FUSION_DISABLE_COMPILERS forces the theme to enqueue
    // the full CSS instead of a downgraded bundle, but it ALSO disables the
    // JS compiler that defines `window.fusion` — without which Fusion's own
    // scripts throw `ReferenceError: fusion is not defined` and the hero
    // background, animations, scrollspy and mobile menu break.
    //
    // WP Rocket sets this constant when their server-side Remove-Unused-CSS
    // pipeline needs the full CSS bundle to scan it. Our RUCSS pipeline runs
    // browser-side inside a sandboxed iframe — the constant is therefore
    // never required for our cache to work correctly on Avada, and posing it
    // breaks every Fusion-driven script. We expose a filter for the rare
    // case where an integrator does need it (bypass adapter explicitly).
    $is_avada = ('avada' === $template || 'avada' === $stylesheet || false !== strpos($stylesheet, 'avada'));
    $force_constant = (bool) apply_filters(
        'blaminhor_essentials_cache_force_disable_fusion_compilers',
        false,
        $cache_settings
    );
    if (!in_array('avada', $disabled, true)
        && $is_avada
        && $force_constant
        && !defined('FUSION_DISABLE_COMPILERS')) {
        define('FUSION_DISABLE_COMPILERS', true);
    }
}

/**
 * Activation hook
 */
register_activation_hook(__FILE__, function () {
    // Set default options
    if (false === get_option('blaminhor_essentials_active_modules')) {
        add_option('blaminhor_essentials_active_modules', array());
    }

    // Flush rewrite rules
    flush_rewrite_rules();

    // Set transient for redirect to dashboard (single site only)
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Not processing form data, just checking activation context.
    if ( ! is_network_admin() && ! isset( $_GET['activate-multi'] ) ) {
        set_transient( 'blaminhor_essentials_activation_redirect', true, 30 );
    }
});

/**
 * Deactivation hook
 */
register_deactivation_hook(__FILE__, function () {
    flush_rewrite_rules();
});
