*/ private array $services = []; public static function instance(): self { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } /** * Register hooks only. Everything here must stay cheap: it runs on every request. */ public static function boot(): void { add_filter('cron_schedules', [self::class, 'cron_schedules']); add_action('init', [self::class, 'ensure_heartbeat']); add_action(self::HEARTBEAT_HOOK, static function (): void { $plugin = self::instance(); $plugin->scheduler()->maybe_run(); $plugin->runner()->heartbeat(); }); foreach (['wp_ajax_', 'wp_ajax_nopriv_'] as $prefix) { add_action($prefix . self::WORKER_ACTION, static function (): void { self::instance()->runner()->handle_worker_request(); }); add_action($prefix . self::PING_ACTION, static function (): void { self::instance()->runner()->handle_ping(); }); } add_action('rest_api_init', static function (): void { self::instance()->rest()->register_routes(); }); if (is_admin()) { self::boot_admin(); } } private static function boot_admin(): void { add_action('admin_menu', static function (): void { add_menu_page( 'seljeBackup', 'seljeBackup', 'manage_options', SWPB_ADMIN_SLUG, static function (): void { self::instance()->admin()->render_page(); }, 'dashicons-backup', 80 ); }); // Only build the admin object when the request is actually for it. add_action('admin_init', static function (): void { // phpcs:disable WordPress.Security.NonceVerification -- only checks whether a handler is needed; the handlers verify. $state = isset($_GET['state']) && is_string($_GET['state']) ? sanitize_text_field(wp_unslash($_GET['state'])) : ''; if (strpos($state, 'swpb') === 0) { self::instance()->admin()->handle_oauth_callback(); } if (isset($_POST['swpb_action'])) { self::instance()->admin()->handle_postbacks(); } // phpcs:enable }); add_action('admin_enqueue_scripts', static function (string $hook): void { if ($hook === 'toplevel_page_' . SWPB_ADMIN_SLUG) { self::instance()->admin()->enqueue_assets($hook); } }); add_action('admin_post_swpb_download', static function (): void { self::instance()->admin()->handle_download(); }); } public static function cron_schedules($schedules): array { $schedules = is_array($schedules) ? $schedules : []; $schedules['swpb_five_minutes'] = [ 'interval' => 5 * MINUTE_IN_SECONDS, 'display' => __('Every 5 minutes (seljeBackup)', 'seljebackup'), ]; return $schedules; } public static function ensure_heartbeat(): void { if (!wp_next_scheduled(self::HEARTBEAT_HOOK)) { wp_schedule_event(time() + MINUTE_IN_SECONDS, 'swpb_five_minutes', self::HEARTBEAT_HOOK); } } public static function activate(): void { (new SWPB_Fs())->base_dir(); } public static function deactivate(): void { wp_clear_scheduled_hook(self::HEARTBEAT_HOOK); } /* ── Services (built on first use) ─────────────────────────────── */ /** * @template T of object * @param callable():T $factory * @return T */ private function service(string $name, callable $factory) { if (!isset($this->services[$name])) { $this->services[$name] = $factory(); } return $this->services[$name]; } public function crypto(): SWPB_Crypto { return $this->service('crypto', static fn () => new SWPB_Crypto()); } public function settings(): SWPB_Settings { return $this->service('settings', static fn () => new SWPB_Settings()); } public function fs(): SWPB_Fs { return $this->service('fs', static fn () => new SWPB_Fs()); } public function registry(): SWPB_Registry { return $this->service('registry', static fn () => new SWPB_Registry()); } public function destinations(): SWPB_Destinations { return $this->service('destinations', fn () => new SWPB_Destinations($this->crypto())); } public function remote_auth(): SWPB_Remote_Auth { return $this->service('remote_auth', fn () => new SWPB_Remote_Auth($this->crypto())); } public function notifier(): SWPB_Notifier { return $this->service('notifier', fn () => new SWPB_Notifier($this->settings(), $this->remote_auth())); } public function runner(): SWPB_Runner { return $this->service('runner', fn () => new SWPB_Runner( $this->settings(), $this->fs(), $this->registry(), $this->destinations(), $this->notifier(), new SWPB_Job_Store() )); } public function scheduler(): SWPB_Scheduler { return $this->service('scheduler', fn () => new SWPB_Scheduler($this->settings(), $this->runner())); } public function preflight(): SWPB_Preflight { return $this->service('preflight', fn () => new SWPB_Preflight($this->settings(), $this->fs())); } public function size_report(): SWPB_Size_Report { return $this->service('size_report', fn () => new SWPB_Size_Report($this->settings(), $this->fs())); } public function admin(): SWPB_Admin { return $this->service('admin', fn () => new SWPB_Admin( $this->settings(), $this->fs(), $this->registry(), $this->destinations(), $this->remote_auth(), $this->runner(), $this->scheduler(), $this->preflight() )); } public function rest(): SWPB_Rest { return $this->service('rest', fn () => new SWPB_Rest( $this->settings(), $this->registry(), $this->destinations(), $this->remote_auth(), $this->runner(), $this->scheduler(), $this->preflight(), $this->size_report() )); } }