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 );
}
}