*/ private array $container = array(); /** * Private constructor, use Plugin::instance(). */ private function __construct() {} /** * Get a shared object, e.g. Plugin::instance()->get( SearchService::class ). * * @param string $class_name Fully qualified class name. * @return object|null Instance of $class_name, or null when unknown. */ public function get( string $class_name ): ?object { return $this->container[ $class_name ] ?? null; } /** * Get the shared plugin instance. * * @return Plugin */ public static function instance(): self { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Boot the plugin: run pending database upgrades and register all services. */ public function boot(): void { if ( $this->booted ) { return; } $this->booted = true; // Covers plugin updates and network activation, where the activation hook does not run per site. self::installer()->maybe_upgrade(); $this->services = $this->create_services(); foreach ( $this->services as $service ) { $service->register_hooks(); } /** * Fires after Asorto has registered all its services. * * @since 1.0.0 * * @param Plugin $plugin Plugin instance. */ do_action( 'asorto_loaded', $this ); } /** * Activation callback. */ public static function activate(): void { self::installer()->install(); } /** * Deactivation callback. Stops background jobs but keeps all data. */ public static function deactivate(): void { if ( function_exists( 'as_unschedule_all_actions' ) ) { as_unschedule_all_actions( '', array(), self::ACTION_GROUP ); } } /** * Absolute path to a file or directory inside the plugin. * * @param string $relative Path relative to the plugin root. * @return string */ public function path( string $relative = '' ): string { return plugin_dir_path( PLUGIN_FILE ) . ltrim( $relative, '/' ); } /** * Public URL of a file inside the plugin. * * @param string $relative Path relative to the plugin root. * @return string */ public function url( string $relative = '' ): string { return plugin_dir_url( PLUGIN_FILE ) . ltrim( $relative, '/' ); } /** * Create all services. * * @return Hookable[] */ private function create_services(): array { global $wpdb; $secrets = new Secrets(); $settings = new Settings( $secrets ); $logger = new Logger( $settings ); $usage = new UsageMeter( $settings ); $factory = new ServiceFactory( $settings, $secrets, $usage, $logger, $wpdb ); $tester = new ConnectionTester( $factory, $settings, $secrets, $wpdb ); $cleaner = new TextCleaner(); $detector = new BoilerplateDetector( $wpdb, $cleaner ); $languages = new Languages(); $builder = new DocumentBuilder( $settings, $cleaner, $detector, $languages ); $state = new IndexState(); $docs = new DocumentTable( $wpdb ); $indexer = new Indexer( $factory, $builder, $usage, $settings, $docs, $state, new SyncTable( $wpdb ) ); $bulk = new BulkIndexer( $indexer, $state, $builder, $factory, $usage, $settings, $logger, $wpdb, $detector ); $health = new HealthMonitor( $settings ); $tokenizer = new Tokenizer(); $search = new SearchService( $factory, $settings, $usage, $state, $docs, new IntentParser(), $tokenizer, $logger, $health, new Speller(), new Synonyms( $tokenizer ), new GiftRecipient(), $languages ); $limiter = new AiRateLimiter(); $cards = new ProductPresenter( $builder ); $managed = new ManagedAi( $factory->http(), $secrets ); $box = new SearchBox( $this, $settings ); $this->container = array( Settings::class => $settings, ServiceFactory::class => $factory, IndexState::class => $state, BulkIndexer::class => $bulk, SearchService::class => $search, ); $stats = new SearchStatsTable( $wpdb ); $this->container[ SearchStatsTable::class ] = $stats; $appearance = new AppearanceTab( $settings, $box, $cards, $languages ); $steps = new NextSteps( $settings, $state, $stats, $appearance ); $services = array( $settings, new ProductSync( $indexer, $bulk, $state, $logger, $settings, $languages ), new SiteSearchRouter( $settings ), new QueryInterceptor( $search, $limiter, $logger ), new SearchTracker( $stats ), new LiveSearch( $this, $settings, $search, $languages ), $box, new SearchController( $search, $settings, $limiter, $cards, $languages ), new Notices( $health, $state, $secrets, $settings ), new Privacy(), new AdminPage( $this, $settings, $tester, new Assets( $this ), new ScreenData( $settings, $secrets, $usage, $factory, $bulk, $state, $stats, $docs, $steps, $appearance, $managed, $health ), $steps ), new AdminController( $secrets, $tester, $bulk, $state, $factory, $search, $cards, $settings, $docs, $managed ), $managed, ); /** * Filters the services registered by the plugin. * * @since 1.0.0 * * @param Hookable[] $services Service instances. */ $services = apply_filters( 'asorto_services', $services ); return array_filter( $services, static fn( $service ): bool => $service instanceof Hookable ); } /** * Create the database installer. * * @return Installer */ private static function installer(): Installer { global $wpdb; return new Installer( $wpdb ); } }