register_hooks(); } /** * The booted instance, or null before {@see self::bootstrap()} ran. */ public static function instance(): ?self { return self::$instance; } /** * Registers the hooks the plugin needs before any dependency is known. */ public function register_hooks(): void { add_action( 'before_woocommerce_init', array( $this, 'declare_woocommerce_compatibility' ) ); add_action( 'plugins_loaded', array( $this, 'boot' ) ); } /** * Declares compatibility with WooCommerce's High-Performance Order Storage. * * The plugin never reads orders from the posts table, so HPOS is supported. */ public function declare_woocommerce_compatibility(): void { if ( class_exists( FeaturesUtil::class ) ) { FeaturesUtil::declare_compatibility( 'custom_order_tables', $this->plugin_file, true ); } } /** * Checks dependencies once all plugins are loaded and reports problems to admins. */ public function boot(): void { $this->status = $this->dependencies->check(); if ( array() !== $this->status->problems() ) { add_action( 'admin_notices', array( $this, 'render_dependency_notice' ) ); } if ( $this->status->can_register_abilities() ) { $this->registrar = new Registrar( $this->create_abilities( $this->status ) ); $this->registrar->register_hooks(); } } /** * Ability registrar, or null when dependencies block registration. */ public function registrar(): ?Registrar { return $this->registrar; } /** * Creates every ability the plugin provides. * * @param DependencyStatus $status Detected dependencies. * @return list */ private function create_abilities( DependencyStatus $status ): array { $audit = new AuditLogger( new WooCommerceLogSink() ); return array( new LanguagesOverview( $this->gateway, $audit, $status ), new TranslationCompleteness( $this->gateway, $this->woo, $audit, $status ), new CrossLanguageConsistency( $this->gateway, $this->woo, $audit, $status ), new UntranslatedVariations( $this->gateway, $this->woo, $audit, $status ), new CurrencyPriceGaps( $this->gateway, $this->woo, $audit, $status ), new TranslationWorkQueue( $this->gateway, $this->woo, $audit, $status ), ); } /** * Dependency status, or null before `plugins_loaded`. */ public function status(): ?DependencyStatus { return $this->status; } /** * Prints one admin notice listing every unmet dependency. */ public function render_dependency_notice(): void { if ( null === $this->status || ! current_user_can( 'activate_plugins' ) ) { return; } $blocking = ! $this->status->can_register_abilities(); $summary = $blocking ? __( 'LinguaPilot is inactive: no abilities are registered until the following requirements are met.', 'linguapilot' ) : __( 'LinguaPilot runs with reduced functionality: abilities that need WooCommerce Multilingual are unavailable.', 'linguapilot' ); printf( '

%2$s

'; } /** * Human-readable description of a dependency problem. * * @param DependencyProblem $problem Problem to describe. */ private static function describe( DependencyProblem $problem ): string { $names = array( DependencyProblem::ABILITIES_API => __( 'WordPress Abilities API (WordPress 6.9 or newer)', 'linguapilot' ), DependencyProblem::WOOCOMMERCE => 'WooCommerce', DependencyProblem::WPML => 'WPML Multilingual CMS', DependencyProblem::WPML_SETUP => 'WPML Multilingual CMS', DependencyProblem::WCML => 'WooCommerce Multilingual & Multicurrency', ); $name = $names[ $problem->dependency ] ?? $problem->dependency; if ( DependencyProblem::WPML_SETUP === $problem->dependency ) { return __( 'The WPML setup wizard has not been completed (WPML → Setup).', 'linguapilot' ); } if ( $problem->is_missing() ) { /* translators: %s: Name of the required plugin or API. */ return sprintf( __( '%s is not active.', 'linguapilot' ), $name ); } return sprintf( /* translators: 1: Plugin name, 2: Installed version, 3: Minimum required version. */ __( '%1$s %2$s is active, but version %3$s or newer is required.', 'linguapilot' ), $name, (string) $problem->installed_version, (string) $problem->required_version ); } }