0, 'started_at' => time(), 'updated_at' => time(), ) ); self::activate_site(); self::schedule_network_activation_batch(); return; } self::activate_site(); } private static function activate_site() { if ( version_compare( PHP_VERSION, '7.4', '<' ) ) { deactivate_plugins( NBH_PERFORMANCE_BASENAME ); wp_die( esc_html__( 'NBH Performance requires PHP 7.4 or newer.', 'nbh-performance' ) ); } // First public release: initialize only the current schema and defaults. // No abandoned-feature migrations or filesystem changes on activation. self::ensure_persistent_defaults(); $index_ready = CacheIndex::install(); $jobs_ready = JobStore::install(); if ( ! $index_ready || ! $jobs_ready ) { update_option( 'nbh_performance_activation_schema_error', 1, false ); return; } delete_option( 'nbh_performance_activation_schema_error' ); update_option( 'nbh_performance_settings_schema_version', self::SETTINGS_SCHEMA_VERSION, false ); update_option( 'nbh_performance_version', NBH_PERFORMANCE_VERSION, false ); } /** * Add every known persistent option without changing an existing value. * * Used during the first install; avoids overwriting site settings. * * @return void */ private static function ensure_persistent_defaults() { $defaults = array( 'nbh_performance_safe_mode' => 1, 'nbh_performance_cache_enabled' => 0, 'nbh_performance_cache_ttl' => HOUR_IN_SECONDS, 'nbh_performance_cache_stale_while_revalidate' => 300, 'nbh_performance_cache_stale_if_error' => DAY_IN_SECONDS, 'nbh_performance_cache_gzip_enabled' => 1, 'nbh_performance_cache_brotli_enabled' => 1, 'nbh_performance_cache_lock_timeout' => 45, 'nbh_performance_cache_verify_integrity' => 0, 'nbh_performance_cache_404_enabled' => 0, 'nbh_performance_cache_redirects_enabled' => 0, 'nbh_performance_cache_allowed_query_args' => '', 'nbh_performance_cache_vary_cookies' => '', 'nbh_performance_language_cache_variant' => 0, 'nbh_performance_language_variant_cookies' => '', 'nbh_performance_cache_max_html_mb' => 8, 'nbh_performance_cache_min_html_bytes' => 256, 'nbh_performance_cache_min_free_disk_mb' => 128, 'nbh_performance_last_cache_maintenance' => array(), 'nbh_performance_mobile_cache_enabled' => 0, 'nbh_performance_logged_in_cache_enabled' => 0, 'nbh_performance_debug_headers' => 0, 'nbh_performance_cache_index' => array(), 'nbh_performance_preload_queue' => array(), 'nbh_performance_sitemap_preload_enabled' => 1, 'nbh_performance_preload_batch_size' => 3, 'nbh_performance_preload_max_urls' => 500, 'nbh_performance_preload_request_timeout' => 12, 'nbh_performance_preload_retry_limit' => 3, 'nbh_performance_preload_excluded_urls' => '', 'nbh_performance_preload_paused' => 0, 'nbh_performance_preload_mobile_variant' => 0, 'nbh_performance_preload_slow_request_ms' => 5000, 'nbh_performance_preload_run' => array(), 'nbh_performance_preload_history' => array(), 'nbh_performance_preload_recent_results' => array(), 'nbh_performance_lcp_preload_limit' => 1, 'nbh_performance_font_preload_limit' => 2, 'nbh_performance_priority_preload_urls' => '', 'nbh_performance_font_preload_urls' => '', 'nbh_performance_cache_stats' => array( 'files' => 0, 'bytes' => 0, 'updated_at' => 0, ), 'nbh_performance_css_minify_enabled' => 0, 'nbh_performance_js_minify_enabled' => 0, 'nbh_performance_combine_assets_enabled' => 0, 'nbh_performance_asset_failures' => array(), 'nbh_performance_defer_js_enabled' => 0, 'nbh_performance_delay_js_enabled' => 0, 'nbh_performance_async_js_handles' => array(), 'nbh_performance_delay_js_handles' => array(), 'nbh_performance_script_manager_enabled' => 0, 'nbh_performance_script_manager_testing_mode' => 1, 'nbh_performance_script_manager_rules' => array(), 'nbh_performance_script_manager_warnings' => array(), 'nbh_performance_delay_timeout' => 8000, 'nbh_performance_async_css_enabled' => 0, 'nbh_performance_preconnect_limit' => 3, 'nbh_performance_regression_score_drop' => 5, 'nbh_performance_regression_lcp_percent' => 12, 'nbh_performance_regression_tbt_percent' => 20, 'nbh_performance_regression_ttfb_percent' => 15, 'nbh_performance_regression_cls_delta' => 0.05, 'nbh_performance_regression_email_alerts' => 0, 'nbh_performance_performance_regression_alerts' => array(), 'nbh_performance_asset_inventory_enabled' => 0, 'nbh_performance_performance_budgets_enabled' => 0, 'nbh_performance_budget_script_count' => 30, 'nbh_performance_budget_style_count' => 20, 'nbh_performance_performance_change_impacts' => array(), 'nbh_performance_smart_optimize_last_run' => array(), 'nbh_performance_smart_rollback_enabled' => 1, 'nbh_performance_smart_rollback_production_enabled' => 0, 'nbh_performance_smart_rollback_window_minutes' => 360, 'nbh_performance_automatic_rollback_history' => array(), 'nbh_performance_image_conversion_enabled' => 0, 'nbh_performance_nextgen_image_serving_enabled' => 0, 'nbh_performance_generate_webp' => 1, 'nbh_performance_generate_avif' => 1, 'nbh_performance_convert_on_upload' => 1, 'nbh_performance_webp_quality' => 82, 'nbh_performance_avif_quality' => 70, 'nbh_performance_image_batch_size' => 10, 'nbh_performance_image_optimization_profile' => 'balanced', 'nbh_performance_image_minimum_saving_percent' => 1, 'nbh_performance_lcp_image_optimization_enabled' => 1, 'nbh_performance_image_batch_state_v2' => array(), 'nbh_performance_image_max_source_mb' => 25, 'nbh_performance_compress_original_images' => 0, 'nbh_performance_jpeg_quality' => 82, 'nbh_performance_png_quality' => 88, 'nbh_performance_image_conversion_exclusions' => '', 'nbh_performance_image_last_batch' => array(), 'nbh_performance_lazy_load_enabled' => 0, 'nbh_performance_lazyload_eager_images' => 2, 'nbh_performance_lazyload_background_images' => 0, 'nbh_performance_video_preload' => 'metadata', 'nbh_performance_responsive_images_enabled' => 0, 'nbh_performance_youtube_facade_enabled' => 0, 'nbh_performance_youtube_autoload_after_consent' => 0, 'nbh_performance_chat_facade_enabled' => 0, 'nbh_performance_consent_mode_enabled' => 0, 'nbh_performance_edge_provider' => '', 'nbh_performance_edge_auto_purge_enabled' => 0, 'nbh_performance_cdn_rewrite_enabled' => 0, 'nbh_performance_cdn_base_url' => '', 'nbh_performance_resource_hints_enabled' => 0, 'nbh_performance_cloudflare_zone_id' => '', 'nbh_performance_cloudflare_api_token' => '', 'nbh_performance_bunny_pull_zone_id' => 0, 'nbh_performance_bunny_api_key' => '', 'nbh_performance_keycdn_zone_id' => 0, 'nbh_performance_keycdn_api_key' => '', 'nbh_performance_disable_emojis' => 0, 'nbh_performance_disable_embeds' => 0, 'nbh_performance_disable_jquery_migrate' => 0, 'nbh_performance_disable_dashicons_guests' => 0, 'nbh_performance_remove_query_strings' => 0, 'nbh_performance_disable_xmlrpc' => 0, 'nbh_performance_remove_head_links' => 0, 'nbh_performance_disable_rest_api_links' => 0, 'nbh_performance_disable_rss_links' => 0, 'nbh_performance_remove_comment_author_urls' => 0, 'nbh_performance_disable_self_pingbacks' => 0, 'nbh_performance_disable_block_library_css' => 0, 'nbh_performance_disable_global_styles' => 0, 'nbh_performance_disable_woocommerce_cart_fragments' => 0, 'nbh_performance_disable_woocommerce_scripts_non_shop' => 0, 'nbh_performance_disable_password_strength_meter' => 0, 'nbh_performance_woocommerce_currency_variant' => 0, 'nbh_performance_woocommerce_currency_cookies' => '', 'nbh_performance_heartbeat_control_enabled' => 0, 'nbh_performance_heartbeat_frontend_mode' => 'reduce', 'nbh_performance_heartbeat_frontend_interval' => 120, 'nbh_performance_heartbeat_admin_mode' => 'reduce', 'nbh_performance_heartbeat_admin_interval' => 60, 'nbh_performance_heartbeat_editor_mode' => 'reduce', 'nbh_performance_heartbeat_editor_interval' => 30, 'nbh_performance_safe_mode_snapshot' => array(), 'nbh_performance_last_readiness_scan' => array(), 'nbh_performance_uninstall_data_policy' => 'retain', 'nbh_performance_uninstall_remove_cache' => 1, 'nbh_performance_uninstall_remove_dropin' => 1, 'nbh_performance_browser_cache_ttl' => MONTH_IN_SECONDS, 'nbh_performance_browser_cache_rules_enabled' => 1, 'nbh_performance_compression_rules_enabled' => 1, 'nbh_performance_static_cache_max_age' => YEAR_IN_SECONDS, 'nbh_performance_onboarding_completed' => 0, 'nbh_performance_onboarding_profile' => '', 'nbh_performance_onboarding_completed_at' => 0, 'nbh_performance_job_worker_batch_size' => 5, 'nbh_performance_job_queue_paused' => 0, 'nbh_performance_job_worker_last_run' => 0, 'nbh_performance_job_worker_last_heartbeat' => 0, 'nbh_performance_job_worker_time_budget' => 20, 'nbh_performance_job_worker_memory_reserve_mb' => 16, 'nbh_performance_job_worker_last_fatal' => array(), 'nbh_performance_runtime_reliability' => array(), 'nbh_performance_runtime_degraded' => array(), 'nbh_performance_runtime_watchdog_last_run' => 0, 'nbh_performance_staging_auto_validation' => 1, 'nbh_performance_force_staging_validation' => 0, 'nbh_performance_external_cron_configured' => 0, 'nbh_performance_validation_urls' => '', 'nbh_performance_configuration_transactions' => array(), 'nbh_performance_per_page_runtime_rules' => array( 'excluded_paths' => array(), 'mobile_variant_disabled_paths' => array(), 'ttl_overrides' => array(), 'updated_at' => 0, ), 'nbh_performance_last_staging_validation' => array(), 'nbh_performance_compatibility_matrix' => array(), 'nbh_performance_compatibility_matrix_checked_at' => 0, 'nbh_performance_edge_last_purge' => array(), ); foreach ( $defaults as $option => $default_value ) { add_option( $option, $default_value, '', false ); } if ( ! self::option_exists( 'nbh_performance_edge_purge_secret' ) ) { add_option( 'nbh_performance_edge_purge_secret', wp_generate_password( 64, false, false ), '', false ); } if ( ! self::option_exists( 'nbh_performance_activated_at' ) ) { add_option( 'nbh_performance_activated_at', time(), '', false ); } } /** * Determine whether an option exists without confusing a stored false value. * * @param string $option Option name. * @return bool */ private static function option_exists( $option ) { $sentinel = '__nbh_performance_option_missing__'; return get_option( $option, $sentinel ) !== $sentinel; } public static function deactivate( $network_wide = false ) { if ( is_multisite() && $network_wide && function_exists( 'get_sites' ) ) { $site_ids = get_sites( array( 'fields' => 'ids', 'number' => 0, 'deleted' => 0, 'archived' => 0, 'spam' => 0, ) ); foreach ( (array) $site_ids as $site_id ) { switch_to_blog( absint( $site_id ) ); self::deactivate_site(); restore_current_blog(); } delete_site_option( 'nbh_performance_network_activation_pending' ); return; } self::deactivate_site(); } private static function deactivate_site() { // Fail closed before WordPress deactivates the plugin: the early drop-in // runs without WordPress hooks and must not serve stale pages afterwards. update_option( 'nbh_performance_safe_mode', 1, false ); update_option( 'nbh_performance_cache_enabled', 0, false ); $runtime = WP_CONTENT_DIR . '/cache/nbh-performance/runtime.json'; if ( is_file( $runtime ) && ! is_link( $runtime ) ) { wp_delete_file( $runtime ); } $dropin = WP_CONTENT_DIR . '/advanced-cache.php'; $installer = new DropInInstaller(); if ( 'owned' === $installer->inspect( $dropin ) ) { wp_delete_file( $dropin ); } foreach ( DataOwnershipRegistry::cron_hooks() as $hook ) { wp_clear_scheduled_hook( $hook ); } delete_option( 'nbh_performance_job_worker_lock' ); } /** Register multisite initialization hooks while the plugin is loaded. */ public static function register_runtime_hooks() { if ( ! is_multisite() ) { return; } add_action( 'wp_initialize_site', array( __CLASS__, 'initialize_new_site' ), 30, 1 ); add_action( 'nbh_performance_network_activation_batch', array( __CLASS__, 'process_network_activation_batch' ) ); } /** Initialize tables and defaults for a newly created site when network-active. */ public static function initialize_new_site( $new_site ) { if ( ! self::is_network_active() ) { return; } $site_id = is_object( $new_site ) && isset( $new_site->blog_id ) ? absint( $new_site->blog_id ) : 0; if ( ! $site_id ) { return; } switch_to_blog( $site_id ); try { self::activate_site(); } finally { restore_current_blog(); } } /** Process network activation in bounded batches to avoid request timeouts. */ public static function process_network_activation_batch() { if ( ! is_multisite() || ! self::is_network_active() ) { return; } global $wpdb; $state = get_site_option( 'nbh_performance_network_activation_pending', array() ); if ( ! is_array( $state ) ) { return; } $last = isset( $state['last_blog_id'] ) ? absint( $state['last_blog_id'] ) : 0; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Activation processes the current multisite/schema state in batches; a cached cursor can skip sites. $ids = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM {$wpdb->blogs} WHERE blog_id > %d AND deleted = 0 AND archived = 0 AND spam = 0 ORDER BY blog_id ASC LIMIT 25", $last ) ); foreach ( (array) $ids as $site_id ) { $site_id = absint( $site_id ); if ( ! $site_id ) { continue; } switch_to_blog( $site_id ); try { self::activate_site(); } finally { restore_current_blog(); } $last = $site_id; } if ( count( (array) $ids ) < 25 ) { delete_site_option( 'nbh_performance_network_activation_pending' ); return; } update_site_option( 'nbh_performance_network_activation_pending', array( 'last_blog_id' => $last, 'started_at' => isset( $state['started_at'] ) ? absint( $state['started_at'] ) : time(), 'updated_at' => time(), ) ); self::schedule_network_activation_batch(); } private static function schedule_network_activation_batch() { if ( ! wp_next_scheduled( 'nbh_performance_network_activation_batch' ) ) { wp_schedule_single_event( time() + 5, 'nbh_performance_network_activation_batch' ); } } private static function is_network_active() { $active = get_site_option( 'active_sitewide_plugins', array() ); return is_array( $active ) && isset( $active[ NBH_PERFORMANCE_BASENAME ] ); } }