` in whatever context it was called from, // and a programmatic activation (a Playground Blueprint, a provisioning // script) need not look like an admin request at all. Registering the // runner's `admin_init` hook on a frontend request costs nothing: it // never fires there. require_once OPENSTATION_DIR . 'includes/migrations.php'; require_once OPENSTATION_DIR . 'includes/portal.php'; require_once OPENSTATION_DIR . 'includes/default-window.php'; // Solo window rendering mode (`?openstation_solo=`). Unconditional // because `includes/render/` reads its flag, and because extensions // (the Electron adapter) call its helpers from their own hooks. require_once OPENSTATION_DIR . 'includes/solo-window.php'; require_once OPENSTATION_DIR . 'includes/themes-tabs.php'; require_once OPENSTATION_DIR . 'includes/media-query.php'; require_once OPENSTATION_DIR . 'includes/accents.php'; require_once OPENSTATION_DIR . 'includes/toast-types.php'; require_once OPENSTATION_DIR . 'includes/registries/native-windows.php'; require_once OPENSTATION_DIR . 'includes/registries/window-tabs.php'; require_once OPENSTATION_DIR . 'includes/registries/icons.php'; require_once OPENSTATION_DIR . 'includes/registries/wallpapers.php'; require_once OPENSTATION_DIR . 'includes/registries/widgets.php'; require_once OPENSTATION_DIR . 'includes/components.php'; require_once OPENSTATION_DIR . 'includes/commands.php'; require_once OPENSTATION_DIR . 'includes/settings-tabs.php'; require_once OPENSTATION_DIR . 'includes/dock-rail-renderer.php'; require_once OPENSTATION_DIR . 'includes/title-bar-buttons.php'; require_once OPENSTATION_DIR . 'includes/window-actions.php'; require_once OPENSTATION_DIR . 'includes/unfocus-effects.php'; require_once OPENSTATION_DIR . 'includes/window-links.php'; require_once OPENSTATION_DIR . 'includes/window-chrome.php'; require_once OPENSTATION_DIR . 'includes/window-notices.php'; require_once OPENSTATION_DIR . 'includes/wallpapers.php'; require_once OPENSTATION_DIR . 'includes/mio.php'; require_once OPENSTATION_DIR . 'includes/widgets/heartbeat.php'; require_once OPENSTATION_DIR . 'includes/widgets/widget-comments.php'; require_once OPENSTATION_DIR . 'includes/widgets/widget-post-stats.php'; require_once OPENSTATION_DIR . 'includes/widgets/widget-site-views.php'; require_once OPENSTATION_DIR . 'includes/widgets/widget-jazz-quote.php'; require_once OPENSTATION_DIR . 'includes/widgets/widget-starter.php'; require_once OPENSTATION_DIR . 'includes/widgets/widget-notes.php'; require_once OPENSTATION_DIR . 'includes/widgets/widget-drafts.php'; require_once OPENSTATION_DIR . 'includes/widgets/widget-focus-timer.php'; require_once OPENSTATION_DIR . 'includes/extended-options.php'; require_once OPENSTATION_DIR . 'includes/oauth-relay.php'; require_once OPENSTATION_DIR . 'includes/ai-copilot/bootstrap.php'; // Content-changes must load before the recycle bin — the bin's // changelog delegates into the generic recorder. require_once OPENSTATION_DIR . 'includes/content-changes.php'; require_once OPENSTATION_DIR . 'includes/recycle-bin/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/desktop-files/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/desktop-themes/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/notes/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/posts-window/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/pages-window/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/users-window/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/user-edit-window/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/plugins-window/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/comments-window/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/my-wordpress/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/content-graph/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/living-tree/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/games/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/agents/bootstrap.php'; require_once OPENSTATION_DIR . 'includes/pwa.php'; require_once OPENSTATION_DIR . 'includes/compat/divi.php'; // Admin-rendering modules — every hook these register (`admin_init`, // `admin_enqueue_scripts`, `in_admin_header`, `admin_head`, // `admin_footer`, `admin_body_class`, `wp_ajax_*`) only fires inside // wp-admin, so pure frontend page views skip them entirely. REST, // cron, WP-CLI, admin-ajax, and the PHPUnit environment all load // them (see openstation_request_needs_admin_modules()). Relative // order preserved from the historical unconditional list. if ( openstation_request_needs_admin_modules() ) { require_once OPENSTATION_DIR . 'includes/ajax.php'; require_once OPENSTATION_DIR . 'includes/welcome-dialog.php'; require_once OPENSTATION_DIR . 'includes/update-notice.php'; require_once OPENSTATION_DIR . 'includes/core-notices.php'; require_once OPENSTATION_DIR . 'includes/plugin-notices.php'; require_once OPENSTATION_DIR . 'includes/render.php'; require_once OPENSTATION_DIR . 'includes/devtools.php'; } /** * Cascade-deactivate plugins that declare `Requires Plugins: desktop-mode` * when OpenStation itself is being deactivated. * * Without this, a third-party plugin like `os-messages` keeps * its `init` hook armed after OpenStation is turned off — the next * admin page load fires `init`, the hook calls * `openstation_register_window()`, and the request fatals because the * function lives in the plugin we just deactivated. The user lands on * a white screen instead of the classic admin we redirected them to. * * WordPress's plugin dependency feature (6.5+) blocks ACTIVATION of a * plugin whose declared `Requires Plugins` aren't all active, but it * does NOT auto-deactivate dependents when their requirement is * deactivated through any path — the user is expected to do that * themselves. The classic plugins.php list shows a warning; the REST * controller (`PUT /wp/v2/plugins/desktop-mode/openstation`) we route * through has no such gate. So we cascade ourselves. * * Timing: `deactivate_plugins()` writes `active_plugins` to the DB * AFTER its hook loop finishes. If we call `deactivate_plugins()` * from inside our `register_deactivation_hook` callback, our nested * write-back is overwritten the moment the outer call runs its own * `update_option`. So we register the cascade to fire on * `shutdown` — by then the outer `deactivate_plugins` has finished * and our write-back sticks. * * Matching uses `WP_Plugin_Dependencies::get_dependents()` keyed on * our own directory slug (`dirname( plugin_basename( __FILE__ ) )`), * which is the same string the dependent's `Requires Plugins` header * resolves against. The filter below lets sites widen or narrow the * cascade — e.g. add a plugin that wraps `openstation_*` calls in * `function_exists` guards and shouldn't be torn down with us. * * Silent deactivation (`$silent = true`) skips the dependent * plugins' own deactivation hooks. Those callbacks routinely call * `openstation_*` helpers expecting OpenStation to be wired up; * firing them mid-cascade can trigger the same fatal we're trying * to prevent. Skipping them is the safer default. */ function openstation_cascade_deactivate_dependents() { // Defer to `shutdown` so we run AFTER the outer // `deactivate_plugins()` has flushed its own option update. // Inline cascade gets clobbered by the outer's write-back. add_action( 'shutdown', 'openstation_do_cascade_deactivate', 0 ); } register_deactivation_hook( OPENSTATION_FILE, 'openstation_cascade_deactivate_dependents' ); /** * The deferred cascade body — runs on `shutdown` after the * outer `deactivate_plugins()` has persisted its update. * * Split out from the `register_deactivation_hook` callback because * that callback must register, not execute, the cascade. See * {@see openstation_cascade_deactivate_dependents} for the timing * rationale. */ function openstation_do_cascade_deactivate() { if ( ! class_exists( 'WP_Plugin_Dependencies' ) ) { // Pre-6.5 — no native dependency tracking. Sites running an // old Core won't have dependents declared via the header // either; nothing to cascade. return; } WP_Plugin_Dependencies::initialize(); $slug = dirname( plugin_basename( OPENSTATION_FILE ) ); if ( '' === $slug || '.' === $slug ) { return; } $dependents = (array) WP_Plugin_Dependencies::get_dependents( $slug ); /** * Filter the list of plugin files to cascade-deactivate when * OpenStation is deactivated. Defaults to every plugin whose * `Requires Plugins` header lists our directory slug. * * @param string[] $dependents Plugin files (e.g. "foo/foo.php"). * @param string $slug OpenStation's directory slug. */ $dependents = (array) apply_filters( 'openstation_cascade_deactivate_dependents', $dependents, $slug ); if ( empty( $dependents ) ) { return; } $active = (array) get_option( 'active_plugins', array() ); $targets = array_values( array_intersect( $active, $dependents ) ); if ( empty( $targets ) ) { return; } if ( ! function_exists( 'deactivate_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } deactivate_plugins( $targets, true ); }