Simple service container. */ private array $services = []; /* ------------------------------------------------------------------ Boot ------------------------------------------------------------------ */ public function boot(): void { // Once per request, even if a second copy of the plugin file got loaded. static $booted = false; if ( $booted ) return; $booted = true; // Load core dependencies explicitly. require_once CLUSTERLANE_DIR . 'intelligence/core/class-installer.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-capabilities.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-edition.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-brand.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-legacy.php'; // ClusterIQ (the earlier name): take over its data once, before the // capability mapping and DB version checks below read their options. // While ClusterIQ is still active Clusterlane waits — only a notice // asks the user to deactivate it. if ( ! Legacy::maybe_migrate() ) return; // Capabilities: lock-out safety net + grant role caps on first boot // (also covers existing installs and multisite sub-sites, where the // activation hook never ran). Capabilities::register_fallback(); Capabilities::maybe_install(); // Multisite: install on new sites, drop tables on site deletion. require_once CLUSTERLANE_DIR . 'intelligence/core/class-multisite.php'; Multisite::register_hooks(); require_once CLUSTERLANE_DIR . 'intelligence/core/class-compat.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-scope.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-relevance.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-seo-meta.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-html-places.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-cluster-sections.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-lang.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-shop.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-thumbs.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-page-reader.php'; // published pages (0.98.177) require_once CLUSTERLANE_DIR . 'intelligence/core/class-cluster-types.php'; // clusters from any taxonomy (0.98.177) require_once CLUSTERLANE_DIR . 'intelligence/core/class-hub-clusters.php'; // pages as clusters (0.98.180) // Premium code (Pro/Agency) — a folder that is NOT in the free build. // It hooks itself in via the clusterlane_* actions below; Edition caps the // tier at 'free' when it is absent. $premium = CLUSTERLANE_DIR . 'intelligence/pro__premium_only/bootstrap.php'; if ( is_readable( $premium ) ) { require_once $premium; } // Run DB migrations if needed. Installer::maybe_upgrade(); Brand::register_hooks(); // white label: translated texts follow the brand name // Guard: if the tables still don't exist after maybe_upgrade() // (e.g. the activation ran into a MySQL error), show a clear admin // notice and bail out early instead of crashing with a DB error // somewhere deep inside a REST callback. if ( ! Installer::tables_exist() ) { add_action( 'admin_notices', function () { echo '

' . 'Clusterlane: ' . esc_html__( 'Database tables are missing. Please deactivate and reactivate the plugin to repair the installation.', 'clusterlane' ) . '

'; } ); return; } // Register services. $this->register_services(); // History (daily snapshots) and the activity log — every edition, // because history can't be recreated afterwards. require_once CLUSTERLANE_DIR . 'intelligence/core/class-history.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-activity.php'; History::register_hooks( $this ); PageReader::register_hooks( $this ); // published pages read after a scan (0.98.178) Activity::register_hooks(); // Clusterlane in the toolbar — on the site too, for "Inspect this article". require_once CLUSTERLANE_DIR . 'intelligence/admin/class-toolbar.php'; \Clusterlane\Admin\Toolbar::register(); // Admin-only hooks. if ( is_admin() ) { $this->boot_admin(); } // REST API endpoints (available to authenticated requests always). add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] ); } /* ------------------------------------------------------------------ Services ------------------------------------------------------------------ */ private function register_services(): void { // Explicit requires — more reliable than autoloader during initial setup. require_once CLUSTERLANE_DIR . 'intelligence/core/class-settings.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-link-graph.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-scanner.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-cluster-engine.php'; require_once CLUSTERLANE_DIR . 'intelligence/core/class-text-match.php'; $this->services['settings'] = new \Clusterlane\Core\Settings(); $this->services['link_graph'] = new \Clusterlane\Core\LinkGraph(); $this->services['scanner'] = new \Clusterlane\Core\Scanner( $this->services['link_graph'] ); $this->services['cluster_engine'] = new \Clusterlane\Core\ClusterEngine(); $this->services['text_match'] = new \Clusterlane\Core\TextMatch(); // Premium adds its services here (e.g. ai_client). do_action( 'clusterlane_register_services', $this ); } public function get( string $key ): ?object { return $this->services[ $key ] ?? null; } /** Add a service — used by premium code from clusterlane_register_services. */ public function set( string $key, object $service ): void { $this->services[ $key ] = $service; } /* ------------------------------------------------------------------ Admin ------------------------------------------------------------------ */ private function boot_admin(): void { require_once CLUSTERLANE_DIR . 'intelligence/admin/class-inline.php'; // page scripts via wp_add_inline_script (1.3.0) require_once CLUSTERLANE_DIR . 'intelligence/admin/class-subnav.php'; require_once CLUSTERLANE_DIR . 'intelligence/admin/class-help.php'; require_once CLUSTERLANE_DIR . 'intelligence/admin/class-admin.php'; $admin = new \Clusterlane\Admin\Admin( $this ); $admin->register(); // Premium admin screens (Network Admin page, AI settings, …). do_action( 'clusterlane_boot_admin', $this ); } /* ------------------------------------------------------------------ REST ------------------------------------------------------------------ */ public function register_rest_routes(): void { require_once CLUSTERLANE_DIR . 'intelligence/api/class-rest-clusters.php'; require_once CLUSTERLANE_DIR . 'intelligence/api/class-rest-scanner.php'; require_once CLUSTERLANE_DIR . 'intelligence/api/class-rest-text-match.php'; require_once CLUSTERLANE_DIR . 'intelligence/api/class-rest-graph.php'; require_once CLUSTERLANE_DIR . 'intelligence/api/class-rest-article-inspector.php'; require_once CLUSTERLANE_DIR . 'intelligence/api/class-rest-cluster-inspector.php'; require_once CLUSTERLANE_DIR . 'intelligence/api/class-rest-seo.php'; ( new \Clusterlane\Api\RestClusters( $this ) )->register_routes(); ( new \Clusterlane\Api\RestScanner( $this ) )->register_routes(); ( new \Clusterlane\Api\RestTextMatch( $this ) )->register_routes(); ( new \Clusterlane\Api\RestGraph( $this ) )->register_routes(); ( new \Clusterlane\Api\RestArticleInspector( $this ) )->register_routes(); ( new \Clusterlane\Api\RestClusterInspector( $this ) )->register_routes(); ( new \Clusterlane\Api\RestSeo( $this ) )->register_routes(); // Premium REST routes (AI, link suggestions, network AI test, …). do_action( 'clusterlane_register_rest_routes', $this ); } }