PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / app / Bootstrap.php

Bootstrap.php in Yatra – Travel Booking & Tour Operator Software trunk, at app/Bootstrap.php

390 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra;
6
7 use Yatra\Core\Container;
8 use Yatra\Providers\AppServiceProvider;
9 use Yatra\Providers\RouteServiceProvider;
10 use Yatra\Providers\AdminServiceProvider;
11 use Yatra\Providers\FrontendAssetsProvider;
12 use Yatra\Compatibility\Compatibility;
13 use Yatra\Providers\BlockServiceProvider;
14
15 /**
16 * Main Bootstrap class for Yatra plugin
17 */
18 class Bootstrap
19 {
20
21 /**
22 * @var Container
23 */
24 private Container $container;
25
26 /**
27 * @var bool
28 */
29 private bool $initialized = false;
30
31 /**
32 * Bootstrap constructor
33 */
34 public function __construct()
35 {
36 $this->container = new Container();
37 }
38
39 /**
40 * Initialize the plugin
41 */
42 public function init(): void
43 {
44 if ($this->initialized) {
45 return;
46 }
47
48 try {
49 // Register service providers first
50 $this->registerServiceProviders();
51
52 // Load helper functions (must be loaded after autoloader)
53 $this->loadHelperFunctions();
54
55 // Initialize cache hooks
56 if (class_exists('\Yatra\Hooks\CacheHooks')) {
57 \Yatra\Hooks\CacheHooks::init();
58 }
59
60 if (class_exists('\Yatra\Hooks\AvailabilityInventoryHooks')) {
61 \Yatra\Hooks\AvailabilityInventoryHooks::init();
62 }
63
64 if (class_exists('\Yatra\Hooks\MigrationAdminNoticeHooks')) {
65 \Yatra\Hooks\MigrationAdminNoticeHooks::init();
66 }
67
68 // Initialize Setup Wizard Service
69 if (class_exists('\Yatra\Services\SetupWizardService')) {
70 \Yatra\Services\SetupWizardService::init();
71 }
72
73 // Centralized notices (React UI + WP admin notices)
74 if (class_exists('\Yatra\Services\NoticeService')) {
75 \Yatra\Services\NoticeService::init();
76 }
77
78 if (class_exists('\Yatra\Services\StatsUsage')) {
79 \Yatra\Services\StatsUsage::instance()->init();
80 }
81
82 // Initialize Dynamic Pricing Service
83 // DISABLED: Automatic dynamic pricing was adding 15% markup for trips with ≤5 spots
84 // if (class_exists('\Yatra\Services\DynamicPricingService')) {
85 // \Yatra\Services\DynamicPricingService::init();
86 // }
87
88 // Initialize SEO Manager
89 if (class_exists('\Yatra\Managers\SEOManager')) {
90 \Yatra\Managers\SEOManager::init();
91 }
92
93 // Register Setup Service activation hook
94 if (class_exists('\Yatra\Services\SetupService')) {
95 \Yatra\Services\SetupService::registerActivationHook();
96 }
97
98 // Initialize Action Scheduler
99 $this->initializeActionScheduler();
100
101 // Initialize REST API hooks
102 if (class_exists('\Yatra\Hooks\RestApiHooks')) {
103 \Yatra\Hooks\RestApiHooks::init();
104 }
105
106 // Initialize core components
107 $this->initializeCore();
108
109 // Set up WordPress hooks
110 $this->setupWordPressHooks();
111
112 // Load text domain on `init` — calling load_(plugin_)textdomain before `init`
113 // triggers WP 6.7+ _doing_it_wrong notices (and Loco's "premature text domain"
114 // warning). Use priority 1 so it runs before code that translates on `init`.
115 if (did_action('init')) {
116 $this->loadTextDomain();
117 } else {
118 add_action('init', [$this, 'loadTextDomain'], 1);
119 }
120
121 } catch (\Throwable $e) {
122 // Show admin notice if in admin area
123 if (is_admin()) {
124 add_action('admin_notices', function() use ($e) {
125 echo '<div class="notice notice-error"><p><strong>Yatra:</strong> ' .
126 esc_html($e->getMessage()) . '</p></div>';
127 });
128 }
129
130 return;
131 }
132
133 $this->initialized = true;
134 }
135
136 /**
137 * Load helper functions
138 */
139 private function loadHelperFunctions(): void
140 {
141 $helpersPath = YATRA_PLUGIN_PATH . 'includes/helpers.php';
142 if (file_exists($helpersPath)) {
143 require_once $helpersPath;
144 }
145
146 $seoHelperPath = YATRA_PLUGIN_PATH . 'includes/seo-helper.php';
147 if (file_exists($seoHelperPath)) {
148 require_once $seoHelperPath;
149 }
150 }
151
152 /**
153 * Register and boot service providers.
154 *
155 * Each provider is instantiated once. The same instance is used for both
156 * register() and boot() so that any hooks added during register() remain
157 * attached to the object that boot() later acts on.
158 */
159 private function registerServiceProviders(): void
160 {
161 // Register third-party compatibility hooks (Elementor, etc.)
162 // Must run on `plugins_loaded` so that other plugins (Elementor, etc.) are
163 // guaranteed to have loaded their classes before we check class_exists().
164 // Calling Compatibility::register() directly here runs before plugins_loaded
165 // and class_exists('\Elementor\Plugin') will always be false at that point.
166 if (!is_admin() && class_exists('Yatra\\Compatibility\\Compatibility')) {
167 add_action('plugins_loaded', ['Yatra\\Compatibility\\Compatibility', 'register'], 20);
168 }
169
170 $providerClasses = [];
171
172 // Core providers — always loaded
173 if (class_exists('Yatra\Providers\AppServiceProvider')) {
174 $providerClasses[] = 'Yatra\Providers\AppServiceProvider';
175 }
176
177 if (class_exists('Yatra\Providers\RouteServiceProvider')) {
178 $providerClasses[] = 'Yatra\Providers\RouteServiceProvider';
179 }
180
181 // Admin-only providers
182 if (is_admin() && class_exists('Yatra\Providers\AdminServiceProvider')) {
183 $providerClasses[] = 'Yatra\Providers\AdminServiceProvider';
184 }
185
186 // Frontend-only providers
187 if (!is_admin() && class_exists('Yatra\Providers\FrontendAssetsProvider')) {
188 $providerClasses[] = 'Yatra\Providers\FrontendAssetsProvider';
189 }
190
191 // Shortcode provider (frontend + admin)
192 if (class_exists('Yatra\Providers\ShortcodeServiceProvider')) {
193 $providerClasses[] = 'Yatra\Providers\ShortcodeServiceProvider';
194 }
195
196 // Block provider (Gutenberg)
197 if (class_exists('Yatra\Providers\BlockServiceProvider')) {
198 $providerClasses[] = 'Yatra\Providers\BlockServiceProvider';
199 }
200
201 // Instantiate and register all providers, keeping a map of the instances
202 // so boot() runs on the exact same object that register() did.
203 $instances = [];
204
205 foreach ($providerClasses as $class) {
206 try {
207 $instance = new $class($this->container);
208 if (method_exists($instance, 'register')) {
209 $instance->register();
210 }
211 $instances[$class] = $instance;
212 } catch (\Throwable $e) {
213 continue;
214 }
215 }
216
217 // Boot each successfully registered provider
218 foreach ($instances as $class => $instance) {
219 try {
220 if (method_exists($instance, 'boot')) {
221 $instance->boot();
222 }
223 } catch (\Throwable $e) {
224 continue;
225 }
226 }
227 }
228
229 /**
230 * Initialize Action Scheduler
231 */
232 private function initializeActionScheduler(): void
233 {
234 $actionSchedulerPath = YATRA_PLUGIN_PATH . 'vendor/woocommerce/action-scheduler/action-scheduler.php';
235 if (file_exists($actionSchedulerPath)) {
236 require_once $actionSchedulerPath;
237 }
238 }
239
240 /**
241 * Initialize core components
242 */
243 private function initializeCore(): void
244 {
245 // Check and create database tables if they don't exist
246 $this->ensureDatabaseTables();
247 }
248
249 /**
250 * Setup WordPress hooks
251 */
252 private function setupWordPressHooks(): void
253 {
254 // Register activation/deactivation hooks
255 register_activation_hook(YATRA_PLUGIN_FILE, [$this, 'activate']);
256 register_deactivation_hook(YATRA_PLUGIN_FILE, [$this, 'deactivate']);
257
258 if (class_exists(\Yatra\Upgrades\FreeUpgradeRunner::class)) {
259 \Yatra\Upgrades\FreeUpgradeRunner::register();
260 }
261
262 // On user registration, link any guest bookings made under
263 // the same email. Otherwise a customer who booked as guest
264 // first and then created an account would lose visibility of
265 // that earlier booking from My Account (the rows live with
266 // user_id=0 and the customer page filters by user_id).
267 if (class_exists('\\Yatra\\Services\\CustomerService')) {
268 add_action('user_register', static function ($user_id): void {
269 try {
270 (new \Yatra\Services\CustomerService())->linkGuestBookingsToUser((int) $user_id);
271 } catch (\Throwable $e) {
272 // Never block registration on a reconciliation
273 // failure. The booking remains accessible via the
274 // confirmation email link either way.
275 }
276 }, 20, 1);
277 }
278 }
279
280 /**
281 * Ensure database tables exist
282 */
283 private function ensureDatabaseTables(): void
284 {
285 // Register migration routes
286 add_action('rest_api_init', function() {
287 // Temporarily manually require migration files until autoloader is fixed
288 $migrationFiles = [
289 __DIR__ . '/Migrations/MigrationController.php',
290 __DIR__ . '/Migrations/MigrationProgress.php',
291 __DIR__ . '/Migrations/MigrationDetector.php',
292 ];
293
294 foreach ($migrationFiles as $file) {
295 if (file_exists($file)) {
296 require_once $file;
297 }
298 }
299
300 if (class_exists('\Yatra\Migration\MigrationController')) {
301 $migrationController = new \Yatra\Migration\MigrationController();
302 $migrationController->registerRoutes();
303 }
304 });
305
306 // Register Action Scheduler hook for background migration processing
307 add_action('yatra_migrate_data_type', function($dataType, $force = false) {
308 if (class_exists('\Yatra\Migration\MigrationProgress')) {
309 $migrationService = new \Yatra\Migration\MigrationProgress();
310 $result = $migrationService->processMigration($dataType, (bool) $force);
311
312
313 }
314 }, 10, 2);
315
316 // Register background hook for all data types migration via cron
317 add_action('yatra_migration_background_run', function($force = false) {
318 if (class_exists('\Yatra\Migration\MigrationProgress')) {
319 $migrationService = new \Yatra\Migration\MigrationProgress();
320 $migrationService->migrateAllDirect((bool) $force);
321 }
322 }, 10, 1);
323 }
324
325 /**
326 * Plugin activation
327 */
328 public function activate(): void
329 {
330 // Run centralized installer for all one-time actions (tables + settings)
331 \Yatra\Services\InstallerService::install();
332
333 // Set default options (only version tracking - other defaults handled by InstallerService)
334 if (get_option('yatra_version') === false) {
335 add_option('yatra_version', YATRA_VERSION);
336 }
337
338 // Set up setup wizard redirect for first-time activation
339 if (get_option('yatra_setup_wizard_ran') !== '1') {
340 set_transient('yatra_setup_wizard_redirect', 1, 30);
341 }
342
343 // Flush rewrite rules
344 flush_rewrite_rules();
345 }
346
347 /**
348 * Plugin deactivation
349 */
350 public function deactivate(): void
351 {
352 // Clean up if needed
353 flush_rewrite_rules();
354 }
355
356 /**
357 * Load plugin text domain
358 */
359 public function loadTextDomain(): void
360 {
361 $locale = determine_locale();
362
363 // Unload any existing text domain
364 unload_textdomain('yatra');
365
366 // Load from WordPress languages directory first (where Loco Translate saves files)
367 load_textdomain('yatra', WP_LANG_DIR . '/plugins/yatra-' . $locale . '.mo');
368
369 // Also check loco subdirectory
370 if (file_exists(WP_LANG_DIR . '/loco/plugins/yatra-' . $locale . '.mo')) {
371 load_textdomain('yatra', WP_LANG_DIR . '/loco/plugins/yatra-' . $locale . '.mo');
372 }
373
374 // Load from the plugin's own i18n/languages folder (fallback). The 3rd
375 // arg is relative to WP_PLUGIN_DIR, so it MUST include the plugin folder
376 // name — a bare 'i18n/languages' resolves to wp-content/plugins/i18n/
377 // languages (which doesn't exist), so bundled translations never load.
378 load_plugin_textdomain('yatra', false, dirname(YATRA_PLUGIN_BASENAME) . '/i18n/languages');
379 }
380
381 /**
382 * Get container instance
383 */
384 public function getContainer(): Container
385 {
386 return $this->container;
387 }
388 }
389
390