Sync::tick()); add_action('flx_security_disconnect', [Disconnect::class, 'tick']); add_action('flx_security_resume', [self::class, 'resume']); add_action('flx_security_connection_change', [ConnectionChanges::class, 'process']); add_action('admin_init', [self::class, 'ensure']); foreach (['activated_plugin', 'deactivated_plugin', 'deleted_plugin'] as $hook) add_action($hook, [Inventory::class, 'changed']); add_action('upgrader_process_complete', [Inventory::class, 'upgraded'], 10, 2); if (is_admin()) Admin::register(); } public static function settings(): array { $stored = Store::get('settings'); return ['automatic' => (bool) ($stored['automatic'] ?? true), 'hours' => max(6, min(8760, (int) ($stored['hours'] ?? 6))), 'delete_on_uninstall' => (bool) ($stored['delete_on_uninstall'] ?? false)]; } public static function activate(): void { try { Store::install(); self::resume(); self::ensure(); } catch (\Throwable $error) { wp_die(esc_html(I18n::t('error_'.I18n::errorCode($error->getMessage())))); } } public static function resume(): void { $connection = Store::get('connection'); if (empty($connection['paused']) || strtotime($connection['expires_at'] ?? '') <= time() || ($connection['url'] ?? '') !== rtrim(is_multisite() ? network_site_url() : site_url(), '/')) return; $owner = Store::lock(); if ($owner === false) { if (! wp_next_scheduled('flx_security_resume')) wp_schedule_single_event(time() + 5, 'flx_security_resume'); return; } try { $connection = Store::get('connection'); if (empty($connection['paused'])) return; Client::connect(Store::decrypt($connection['secret'])); Store::remove('resume_attempts'); if (self::settings()['automatic']) wp_schedule_single_event(time() + 2, 'flx_security_refresh'); } catch (\Throwable $error) { $code = I18n::errorCode($error->getMessage()); Store::set('error', $code); if (! in_array($code, ['invalid_token', 'token_expired', 'site_disconnected', 'insufficient_scope', 'connection_required', 'crypto_unavailable'], true)) { $attempt = min(7, (int) Store::get('resume_attempts', 0) + 1); Store::set('resume_attempts', $attempt); $due = max(time() + min(HOUR_IN_SECONDS, 30 * (2 ** $attempt)), (int) Store::get('retry_after', 0)); if (! wp_next_scheduled('flx_security_resume')) wp_schedule_single_event($due, 'flx_security_resume'); } } finally { Store::unlock($owner); } self::ensure(); } public static function deactivate(): void { if (current_filter() === 'deactivate_'.plugin_basename(FLX_SECURITY_FILE)) { try { Store::remove('connection_change'); wp_clear_scheduled_hook('flx_security_connection_change'); $connection = Store::get('connection'); if ($connection) { $connection['paused'] = true; Store::set('connection', $connection); } Disconnect::request(); } catch (\Throwable) { // Plugin deactivation must still succeed during a service or storage outage. } } wp_clear_scheduled_hook('flx_security_resume'); wp_clear_scheduled_hook('flx_security_refresh'); wp_clear_scheduled_hook('flx_security_continue'); } public static function schedules(array $schedules): array { $schedules['flx_security_interval'] = ['interval' => self::settings()['hours'] * HOUR_IN_SECONDS, 'display' => 'FluxCDN Security']; return $schedules; } public static function ensure(): void { try { self::ensureReady(); } catch (\Throwable $error) { try { Store::set('error', I18n::errorCode($error->getMessage())); } catch (\Throwable) { /* A storage outage must not break unrelated administration pages. */ } } } private static function ensureReady(): void { if (! current_user_can(is_multisite() ? 'manage_network_options' : 'manage_options')) { return; } if ((int) Store::get('schema', 0) !== 2) { Store::install(); } if (is_multisite() && get_current_blog_id() !== get_main_site_id()) { return; } Disconnect::ensure(); if (ConnectionChanges::pending()) { ConnectionChanges::schedule(); return; } if (($error = Client::connectionError()) !== '') Store::set('error', $error); $enabled = self::settings()['automatic'] && Client::connected(); if ($enabled && ! wp_next_scheduled('flx_security_refresh')) { wp_schedule_event(time() + self::settings()['hours'] * HOUR_IN_SECONDS + wp_rand(0, 300), 'flx_security_interval', 'flx_security_refresh'); } elseif (! $enabled) { wp_clear_scheduled_hook('flx_security_refresh'); } if (Store::get('sync')) { Sync::schedule(2); } } public static function refresh(): void { if (! self::settings()['automatic'] || ! Client::connected()) { return; } try { Sync::start(); Sync::tick(); } catch (\Throwable $error) { Store::set('error', I18n::errorCode($error->getMessage())); } } }