modules = new Module_Registry(); } /** * The shared instance. * * @return Plugin */ public static function instance(): Plugin { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Hook everything up. * * @return void */ public function boot(): void { $this->discover_modules(); /** * Fires after auto-discovery so third parties can register their own. * * @param Module_Registry $modules The module registry. */ do_action( 'transtrix_register_modules', $this->modules ); if ( defined( 'WP_CLI' ) && WP_CLI ) { \WP_CLI::add_command( 'transtrix', Cli_Command::class ); } add_action( 'rest_api_init', array( $this, 'register_routes' ) ); add_action( self::CRON_HOOK, array( $this, 'collect_garbage' ) ); if ( ! wp_next_scheduled( self::CRON_HOOK ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', self::CRON_HOOK ); } if ( is_admin() ) { ( new Admin_Page() )->register(); } } /** * Daily sweep: stalled runs, old full-site packages, stray package files. * * Stalled runs are also failed whenever History is read, so the list never * shows a run as 'running' after its tab went away. * * @return void */ public function collect_garbage(): void { Job_Store::collect_garbage( array( Tasks::class, 'for_job' ) ); // Staging a restore left behind when its job was failed before anything // could clean it: temp tables and -transtrix-new folders, never the site. $restoring = array_filter( Job_Store::recent( Job_Store::LIMIT ), static function ( $record ) { return 'full-site-restore' === ( $record['task'] ?? '' ) && ! in_array( $record['status'], Job_Store::TERMINAL, true ); } ); if ( ! $restoring ) { ( new Full_Site_Restorer( '' ) )->cleanup( array() ); } Restore_Point::expire( (int) Settings::get( 'restore_retention_days' ) ); Job_Store::prune_full_site( (int) Settings::get( 'full_site_packages' ) ); Package_Store::sweep_parts(); Package_Reader::sweep_stale(); Package_Store::sweep_unused( Job_Store::package_ids() ); } /** * Register the REST surface the admin app consumes. * * The engine's only server contract (invariant A9): every controller is a * thin adapter over an engine call WP-CLI can make too. * * @return void */ public function register_routes(): void { $controllers = array( new Controllers\Modules(), new Controllers\Objects(), new Controllers\Export(), new Controllers\Import(), new Controllers\Jobs(), new Controllers\History(), new Controllers\Settings(), new Controllers\Fullsite(), ); foreach ( $controllers as $controller ) { $controller->register_routes(); } } /** * The module registry. * * @return Module_Registry */ public function modules(): Module_Registry { return $this->modules; } /** * Register every module shipped under includes/Modules. * * A module is any class extending Contracts\Module that lives at * includes/Modules/