container = new Container(); } /** * Initialize the plugin */ public function init(): void { if ($this->initialized) { return; } try { // Register service providers first $this->registerServiceProviders(); // Load helper functions (must be loaded after autoloader) $this->loadHelperFunctions(); // Initialize cache hooks if (class_exists('\Yatra\Hooks\CacheHooks')) { \Yatra\Hooks\CacheHooks::init(); } if (class_exists('\Yatra\Hooks\AvailabilityInventoryHooks')) { \Yatra\Hooks\AvailabilityInventoryHooks::init(); } if (class_exists('\Yatra\Hooks\MigrationAdminNoticeHooks')) { \Yatra\Hooks\MigrationAdminNoticeHooks::init(); } // Initialize Setup Wizard Service if (class_exists('\Yatra\Services\SetupWizardService')) { \Yatra\Services\SetupWizardService::init(); } // Centralized notices (React UI + WP admin notices) if (class_exists('\Yatra\Services\NoticeService')) { \Yatra\Services\NoticeService::init(); } if (class_exists('\Yatra\Services\StatsUsage')) { \Yatra\Services\StatsUsage::instance()->init(); } // Initialize Dynamic Pricing Service // DISABLED: Automatic dynamic pricing was adding 15% markup for trips with ≤5 spots // if (class_exists('\Yatra\Services\DynamicPricingService')) { // \Yatra\Services\DynamicPricingService::init(); // } // Initialize SEO Manager if (class_exists('\Yatra\Managers\SEOManager')) { \Yatra\Managers\SEOManager::init(); } // Register Setup Service activation hook if (class_exists('\Yatra\Services\SetupService')) { \Yatra\Services\SetupService::registerActivationHook(); } // Initialize Action Scheduler $this->initializeActionScheduler(); // Initialize REST API hooks if (class_exists('\Yatra\Hooks\RestApiHooks')) { \Yatra\Hooks\RestApiHooks::init(); } // Initialize core components $this->initializeCore(); // Set up WordPress hooks $this->setupWordPressHooks(); // Load text domain on `init` — calling load_(plugin_)textdomain before `init` // triggers WP 6.7+ _doing_it_wrong notices (and Loco's "premature text domain" // warning). Use priority 1 so it runs before code that translates on `init`. if (did_action('init')) { $this->loadTextDomain(); } else { add_action('init', [$this, 'loadTextDomain'], 1); } } catch (\Throwable $e) { // Show admin notice if in admin area if (is_admin()) { add_action('admin_notices', function() use ($e) { echo '

Yatra: ' . esc_html($e->getMessage()) . '

'; }); } return; } $this->initialized = true; } /** * Load helper functions */ private function loadHelperFunctions(): void { $helpersPath = YATRA_PLUGIN_PATH . 'includes/helpers.php'; if (file_exists($helpersPath)) { require_once $helpersPath; } $seoHelperPath = YATRA_PLUGIN_PATH . 'includes/seo-helper.php'; if (file_exists($seoHelperPath)) { require_once $seoHelperPath; } } /** * Register and boot service providers. * * Each provider is instantiated once. The same instance is used for both * register() and boot() so that any hooks added during register() remain * attached to the object that boot() later acts on. */ private function registerServiceProviders(): void { // Register third-party compatibility hooks (Elementor, etc.) // Must run on `plugins_loaded` so that other plugins (Elementor, etc.) are // guaranteed to have loaded their classes before we check class_exists(). // Calling Compatibility::register() directly here runs before plugins_loaded // and class_exists('\Elementor\Plugin') will always be false at that point. if (!is_admin() && class_exists('Yatra\\Compatibility\\Compatibility')) { add_action('plugins_loaded', ['Yatra\\Compatibility\\Compatibility', 'register'], 20); } $providerClasses = []; // Core providers — always loaded if (class_exists('Yatra\Providers\AppServiceProvider')) { $providerClasses[] = 'Yatra\Providers\AppServiceProvider'; } if (class_exists('Yatra\Providers\RouteServiceProvider')) { $providerClasses[] = 'Yatra\Providers\RouteServiceProvider'; } // Admin-only providers if (is_admin() && class_exists('Yatra\Providers\AdminServiceProvider')) { $providerClasses[] = 'Yatra\Providers\AdminServiceProvider'; } // Frontend-only providers if (!is_admin() && class_exists('Yatra\Providers\FrontendAssetsProvider')) { $providerClasses[] = 'Yatra\Providers\FrontendAssetsProvider'; } // Shortcode provider (frontend + admin) if (class_exists('Yatra\Providers\ShortcodeServiceProvider')) { $providerClasses[] = 'Yatra\Providers\ShortcodeServiceProvider'; } // Block provider (Gutenberg) if (class_exists('Yatra\Providers\BlockServiceProvider')) { $providerClasses[] = 'Yatra\Providers\BlockServiceProvider'; } // Instantiate and register all providers, keeping a map of the instances // so boot() runs on the exact same object that register() did. $instances = []; foreach ($providerClasses as $class) { try { $instance = new $class($this->container); if (method_exists($instance, 'register')) { $instance->register(); } $instances[$class] = $instance; } catch (\Throwable $e) { continue; } } // Boot each successfully registered provider foreach ($instances as $class => $instance) { try { if (method_exists($instance, 'boot')) { $instance->boot(); } } catch (\Throwable $e) { continue; } } } /** * Initialize Action Scheduler */ private function initializeActionScheduler(): void { $actionSchedulerPath = YATRA_PLUGIN_PATH . 'vendor/woocommerce/action-scheduler/action-scheduler.php'; if (file_exists($actionSchedulerPath)) { require_once $actionSchedulerPath; } } /** * Initialize core components */ private function initializeCore(): void { // Check and create database tables if they don't exist $this->ensureDatabaseTables(); } /** * Setup WordPress hooks */ private function setupWordPressHooks(): void { // Register activation/deactivation hooks register_activation_hook(YATRA_PLUGIN_FILE, [$this, 'activate']); register_deactivation_hook(YATRA_PLUGIN_FILE, [$this, 'deactivate']); if (class_exists(\Yatra\Upgrades\FreeUpgradeRunner::class)) { \Yatra\Upgrades\FreeUpgradeRunner::register(); } // On user registration, link any guest bookings made under // the same email. Otherwise a customer who booked as guest // first and then created an account would lose visibility of // that earlier booking from My Account (the rows live with // user_id=0 and the customer page filters by user_id). if (class_exists('\\Yatra\\Services\\CustomerService')) { add_action('user_register', static function ($user_id): void { try { (new \Yatra\Services\CustomerService())->linkGuestBookingsToUser((int) $user_id); } catch (\Throwable $e) { // Never block registration on a reconciliation // failure. The booking remains accessible via the // confirmation email link either way. } }, 20, 1); } } /** * Ensure database tables exist */ private function ensureDatabaseTables(): void { // Register migration routes add_action('rest_api_init', function() { // Temporarily manually require migration files until autoloader is fixed $migrationFiles = [ __DIR__ . '/Migrations/MigrationController.php', __DIR__ . '/Migrations/MigrationProgress.php', __DIR__ . '/Migrations/MigrationDetector.php', ]; foreach ($migrationFiles as $file) { if (file_exists($file)) { require_once $file; } } if (class_exists('\Yatra\Migration\MigrationController')) { $migrationController = new \Yatra\Migration\MigrationController(); $migrationController->registerRoutes(); } }); // Register Action Scheduler hook for background migration processing add_action('yatra_migrate_data_type', function($dataType, $force = false) { if (class_exists('\Yatra\Migration\MigrationProgress')) { $migrationService = new \Yatra\Migration\MigrationProgress(); $result = $migrationService->processMigration($dataType, (bool) $force); } }, 10, 2); // Register background hook for all data types migration via cron add_action('yatra_migration_background_run', function($force = false) { if (class_exists('\Yatra\Migration\MigrationProgress')) { $migrationService = new \Yatra\Migration\MigrationProgress(); $migrationService->migrateAllDirect((bool) $force); } }, 10, 1); } /** * Plugin activation */ public function activate(): void { // Run centralized installer for all one-time actions (tables + settings) \Yatra\Services\InstallerService::install(); // Set default options (only version tracking - other defaults handled by InstallerService) if (get_option('yatra_version') === false) { add_option('yatra_version', YATRA_VERSION); } // Set up setup wizard redirect for first-time activation if (get_option('yatra_setup_wizard_ran') !== '1') { set_transient('yatra_setup_wizard_redirect', 1, 30); } // Flush rewrite rules flush_rewrite_rules(); } /** * Plugin deactivation */ public function deactivate(): void { // Clean up if needed flush_rewrite_rules(); } /** * Load plugin text domain */ public function loadTextDomain(): void { $locale = determine_locale(); // Unload any existing text domain unload_textdomain('yatra'); // Load from WordPress languages directory first (where Loco Translate saves files) load_textdomain('yatra', WP_LANG_DIR . '/plugins/yatra-' . $locale . '.mo'); // Also check loco subdirectory if (file_exists(WP_LANG_DIR . '/loco/plugins/yatra-' . $locale . '.mo')) { load_textdomain('yatra', WP_LANG_DIR . '/loco/plugins/yatra-' . $locale . '.mo'); } // Load from the plugin's own i18n/languages folder (fallback). The 3rd // arg is relative to WP_PLUGIN_DIR, so it MUST include the plugin folder // name — a bare 'i18n/languages' resolves to wp-content/plugins/i18n/ // languages (which doesn't exist), so bundled translations never load. load_plugin_textdomain('yatra', false, dirname(YATRA_PLUGIN_BASENAME) . '/i18n/languages'); } /** * Get container instance */ public function getContainer(): Container { return $this->container; } }