PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.10
Yatra – Travel Booking & Tour Operator Software v3.0.10
3.0.15 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 All 83 releases
yatra / app / Core / Modules / ModuleManager.php

ModuleManager.php in Yatra – Travel Booking & Tour Operator Software 3.0.10, at app/Core/Modules/ModuleManager.php

584 lines 28.3 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\Core\Modules;
6
7 /**
8 * Module Manager
9 * Handles module definitions and persistence via wp_options
10 */
11 class ModuleManager
12 {
13 private const OPTION_KEY = 'yatra_modules';
14 private const DEFAULT_VIDEO_URL = 'https://youtu.be/cHmC-x7y0TQ';
15
16 /**
17 * Ensure option exists with defaults
18 */
19 public static function initializeDefaults(): void
20 {
21 if (get_option(self::OPTION_KEY) === false) {
22 $defaults = [];
23 $timestamp = current_time('mysql');
24
25 foreach (self::getDefaultModules() as $module) {
26 $defaults[$module['slug']] = [
27 'enabled' => !empty($module['enabled']),
28 'updated_at' => $timestamp,
29 ];
30 }
31
32 add_option(self::OPTION_KEY, $defaults);
33 }
34 }
35
36 /**
37 * Get default module definitions
38 */
39 public static function getDefaultModules(): array
40 {
41 $modules = [
42 [
43 'slug' => 'google_calendar',
44 'name' => __('Google Calendar Integration', 'yatra'),
45 'description' => __('Automatically sync bookings and departures to Google Calendar. Keep travelers informed with calendar invitations. Perfect integration for managing your travel schedule.', 'yatra'),
46 'category' => __('Integrations', 'yatra'),
47 'docs_url' => 'https://docs.wpyatra.com/modules/google-calendar',
48 'is_premium' => true,
49 'purchase_url' => 'https://wpyatra.com/pricing?module=google-calendar',
50 'is_core' => false,
51 'enabled' => false,
52 'tags' => ['integrations', 'calendar', 'automation'],
53 'video_url' => self::DEFAULT_VIDEO_URL,
54 'requires_pro' => true,
55 'settings_page' => 'yatra-google-calendar',
56 ],
57 [
58 'slug' => 'additional_services',
59 'name' => __('Additional Services', 'yatra'),
60 'description' => __('Offer optional add-ons like airport transfers, travel insurance, and equipment rental. Increase revenue per booking with valuable extras travelers want. Enhance complete trip experience for your customers.', 'yatra'),
61 'category' => __('Sales', 'yatra'),
62 'docs_url' => 'https://docs.wpyatra.com/modules/additional-services',
63 'is_premium' => true,
64 'purchase_url' => 'https://wpyatra.com/pricing?module=additional-services',
65 'is_core' => false,
66 'enabled' => false,
67 'tags' => ['upsell', 'services', 'add-ons', 'extras'],
68 'video_url' => self::DEFAULT_VIDEO_URL,
69 'requires_pro' => true,
70 'settings_page' => 'yatra-additional-services',
71 ],
72 [
73 'slug' => 'trip_consent',
74 'name' => __('Trip Consent', 'yatra'),
75 'description' => __('Collect digital consent forms and signatures from travelers before their trip. Includes liability waivers and health declarations. Ensure legal compliance and protect your business.', 'yatra'),
76 'category' => __('Operations', 'yatra'),
77 'docs_url' => 'https://docs.wpyatra.com/modules/trip-consent',
78 'is_premium' => true,
79 'purchase_url' => 'https://wpyatra.com/pricing?module=trip-consent',
80 'is_core' => false,
81 'enabled' => false,
82 'tags' => ['consent', 'waiver', 'signature', 'forms', 'legal'],
83 'video_url' => self::DEFAULT_VIDEO_URL,
84 'requires_pro' => true,
85 'settings_page' => 'yatra-trip-consent',
86 ],
87 [
88 'slug' => 'email_automation',
89 'name' => __('Email Automation', 'yatra'),
90 'description' => __('Adds the full automation template library (database), sequences, and send logs on the Email screen. Everyone already gets Delivery settings plus the four core customer templates (booking, payment, cancellation, reminder) without this module.', 'yatra'),
91 'category' => __('Marketing', 'yatra'),
92 'docs_url' => 'https://docs.wpyatra.com/modules/email-automation',
93 'is_premium' => true,
94 'purchase_url' => 'https://wpyatra.com/pricing?module=email-automation',
95 'is_core' => false,
96 'enabled' => false,
97 'tags' => ['email', 'automation', 'templates', 'marketing', 'notifications'],
98 'video_url' => self::DEFAULT_VIDEO_URL,
99 'requires_pro' => true,
100 'settings_page' => 'email-automation',
101 ],
102 [
103 'slug' => 'dynamic_form_field',
104 'name' => __('Dynamic Form Field', 'yatra'),
105 'description' => __('Customize booking form fields with drag-and-drop builder. Add custom fields for traveler information and emergency contacts. Create the perfect booking experience for your customers.', 'yatra'),
106 'category' => __('Bookings', 'yatra'),
107 'docs_url' => 'https://docs.wpyatra.com/modules/dynamic-form-field',
108 'is_premium' => true,
109 'purchase_url' => 'https://wpyatra.com/pricing?module=dynamic-form-field',
110 'is_core' => false,
111 'enabled' => false,
112 'tags' => ['form', 'booking', 'customization', 'fields', 'builder'],
113 'video_url' => self::DEFAULT_VIDEO_URL,
114 'requires_pro' => true,
115 'settings_page' => 'yatra-settings',
116 ],
117 [
118 'slug' => 'advanced_discount',
119 'name' => __('Advanced Discount', 'yatra'),
120 'description' => __('Create group discounts that auto-apply based on traveler count. Combine promo codes with group savings for maximum flexibility and conversion optimization.', 'yatra'),
121 'category' => __('Marketing', 'yatra'),
122 'docs_url' => 'https://docs.wpyatra.com/modules/advanced-discount',
123 'is_premium' => true,
124 'purchase_url' => 'https://wpyatra.com/pricing?module=advanced-discount',
125 'is_core' => false,
126 'enabled' => false,
127 'tags' => ['discount', 'group', 'promo', 'coupon', 'marketing'],
128 'video_url' => self::DEFAULT_VIDEO_URL,
129 'requires_pro' => true,
130 ],
131 [
132 'slug' => 'mailchimp',
133 'name' => __('Mailchimp Integration', 'yatra'),
134 'description' => __('Automatically sync customers to Mailchimp lists when they book. Add tags based on trips booked. Build targeted email campaigns and nurture leads effectively.', 'yatra'),
135 'category' => __('Marketing', 'yatra'),
136 'docs_url' => 'https://docs.wpyatra.com/modules/mailchimp',
137 'is_premium' => true,
138 'purchase_url' => 'https://wpyatra.com/pricing?module=mailchimp',
139 'is_core' => false,
140 'enabled' => false,
141 'tags' => ['email', 'marketing', 'mailchimp', 'automation', 'integration'],
142 'video_url' => self::DEFAULT_VIDEO_URL,
143 'requires_pro' => true,
144 'settings_page' => 'yatra-mailchimp',
145 ],
146 [
147 'slug' => 'facebook_pixel',
148 'name' => __('Facebook Pixel', 'yatra'),
149 'description' => __('Track booking conversions with Facebook Pixel. Retarget visitors who viewed trips. Optimize ad campaigns with accurate conversion data and build lookalike audiences.', 'yatra'),
150 'category' => __('Marketing', 'yatra'),
151 'docs_url' => 'https://docs.wpyatra.com/modules/facebook-pixel',
152 'is_premium' => true,
153 'purchase_url' => 'https://wpyatra.com/pricing?module=facebook-pixel',
154 'is_core' => false,
155 'enabled' => false,
156 'tags' => ['facebook', 'pixel', 'tracking', 'ads', 'retargeting', 'marketing'],
157 'video_url' => self::DEFAULT_VIDEO_URL,
158 'requires_pro' => true,
159 'settings_page' => 'yatra-facebook-pixel',
160 ],
161 [
162 'slug' => 'google_analytics',
163 'name' => __('Google Analytics 4 Enhanced', 'yatra'),
164 'description' => __('Enhanced e-commerce tracking for GA4. Track view_item, begin_checkout, and purchase events. Server-side tracking via Measurement Protocol for accurate conversion data.', 'yatra'),
165 'category' => __('Marketing', 'yatra'),
166 'docs_url' => 'https://docs.wpyatra.com/modules/google-analytics',
167 'is_premium' => true,
168 'purchase_url' => 'https://wpyatra.com/pricing?module=google-analytics',
169 'is_core' => false,
170 'enabled' => false,
171 'tags' => ['google', 'analytics', 'ga4', 'tracking', 'ecommerce', 'marketing'],
172 'video_url' => self::DEFAULT_VIDEO_URL,
173 'requires_pro' => true,
174 'settings_page' => 'yatra-google-analytics',
175 ],
176 [
177 'slug' => 'flexible_payments',
178 'name' => __('Flexible Payments', 'yatra'),
179 'description' => __('Enable deposit and partial payment options for bookings. Allow customers to pay a percentage upfront and the rest later. Perfect for high-value trips and improving conversion rates.', 'yatra'),
180 'category' => __('Payments', 'yatra'),
181 'docs_url' => 'https://docs.wpyatra.com/modules/flexible-payments',
182 'is_premium' => true,
183 'purchase_url' => 'https://wpyatra.com/pricing?module=flexible-payments',
184 'is_core' => false,
185 'enabled' => false,
186 'tags' => ['payments', 'deposit', 'partial', 'installments', 'booking'],
187 'video_url' => self::DEFAULT_VIDEO_URL,
188 'requires_pro' => true,
189 'settings_page' => 'yatra-settings',
190 ],
191 [
192 'slug' => 'scheduled_payments',
193 'name' => __('Scheduled Payments', 'yatra'),
194 'description' => __('Automatically schedule the remaining booking balance after a partial or deposit payment. Uses your payment gateway (e.g. Stripe) to invoice or charge on the dates you configure.', 'yatra'),
195 'category' => __('Payments', 'yatra'),
196 'docs_url' => 'https://docs.wpyatra.com/modules/scheduled-payments',
197 'is_premium' => true,
198 'purchase_url' => 'https://wpyatra.com/pricing?module=scheduled-payments',
199 'is_core' => false,
200 'enabled' => false,
201 'tags' => ['payments', 'installments', 'balance', 'stripe', 'automation'],
202 'video_url' => self::DEFAULT_VIDEO_URL,
203 'requires_pro' => true,
204 'settings_page' => 'yatra-settings',
205 ],
206 [
207 'slug' => 'dynamic_pricing',
208 'name' => __('Dynamic Pricing', 'yatra'),
209 'description' => __('Automatically adjust trip prices based on demand, seasonality, early bird discounts, and last-minute deals. Maximize revenue with intelligent pricing rules that respond to booking patterns.', 'yatra'),
210 'category' => __('Sales', 'yatra'),
211 'docs_url' => 'https://docs.wpyatra.com/modules/dynamic-pricing',
212 'is_premium' => true,
213 'purchase_url' => 'https://wpyatra.com/pricing?module=dynamic-pricing',
214 'is_core' => false,
215 'enabled' => false,
216 'tags' => ['pricing', 'revenue', 'automation', 'discounts', 'sales'],
217 'video_url' => self::DEFAULT_VIDEO_URL,
218 'requires_pro' => true,
219 'settings_page' => 'dynamic-pricing',
220 ],
221 [
222 'slug' => 'custom_landing_pages',
223 'name' => __('Custom Landing Pages', 'yatra'),
224 'description' => __('Point destinations, activities, and trip types to any WordPress page. Use your page builder and Yatra shortcodes on that page; catalog links and redirects use the page URL when the module is enabled.', 'yatra'),
225 'category' => __('Marketing', 'yatra'),
226 'docs_url' => 'https://docs.wpyatra.com/modules/custom-landing-pages',
227 'is_premium' => true,
228 'purchase_url' => 'https://wpyatra.com/pricing?module=custom-landing-pages',
229 'is_core' => false,
230 'enabled' => false,
231 'tags' => ['seo', 'landing', 'page-builder', 'destinations', 'taxonomy'],
232 'video_url' => self::DEFAULT_VIDEO_URL,
233 'requires_pro' => true,
234 'settings_page' => 'yatra-settings',
235 ],
236 [
237 'slug' => 'abandoned_booking_recovery',
238 'name' => __('Abandoned Booking Recovery', 'yatra'),
239 'description' => __('Recover lost sales by automatically tracking abandoned bookings and sending targeted email sequences. Win back customers who left before completing their purchase.', 'yatra'),
240 'category' => __('Marketing', 'yatra'),
241 'docs_url' => 'https://docs.wpyatra.com/modules/abandoned-booking-recovery',
242 'is_premium' => true,
243 'purchase_url' => 'https://wpyatra.com/pricing?module=abandoned-booking-recovery',
244 'is_core' => false,
245 'enabled' => false,
246 'tags' => ['recovery', 'email', 'automation', 'marketing', 'conversion'],
247 'video_url' => self::DEFAULT_VIDEO_URL,
248 'requires_pro' => true,
249 'settings_page' => 'abandoned-recovery',
250 ],
251 [
252 'slug' => 'white_label',
253 'name' => __('White Label', 'yatra'),
254 'description' => __('Fully rebrand Yatra as your own product. Replace the plugin name, logo, author, support URL, and email/PDF branding shown to clients. Available exclusively on the Scale plan.', 'yatra'),
255 'category' => __('Branding', 'yatra'),
256 'docs_url' => 'https://docs.wpyatra.com/modules/white-label',
257 'is_premium' => true,
258 'purchase_url' => 'https://wpyatra.com/pricing?module=white-label',
259 'is_core' => false,
260 'enabled' => false,
261 'tags' => ['branding', 'agency', 'whitelabel', 'rebrand'],
262 'video_url' => self::DEFAULT_VIDEO_URL,
263 'requires_pro' => true,
264 'requires_agency' => true,
265 'settings_page' => 'white-label',
266 ],
267 [
268 'slug' => 'ai_assistant',
269 'name' => __('AI Assistant', 'yatra'),
270 'description' => __('Generate trip descriptions, itineraries, SEO meta, and email copy without leaving the editor. Inline AI affordances on every long-text field — write less, finish more. Bring your own OpenAI or Anthropic key.', 'yatra'),
271 'category' => __('Productivity', 'yatra'),
272 'docs_url' => 'https://docs.wpyatra.com/modules/ai-assistant',
273 'is_premium' => true,
274 'purchase_url' => 'https://wpyatra.com/pricing?module=ai-assistant',
275 'is_core' => false,
276 'enabled' => false,
277 'tags' => ['ai', 'assistant', 'content', 'seo', 'productivity'],
278 'video_url' => self::DEFAULT_VIDEO_URL,
279 'requires_pro' => true,
280 'requires_growth_or_agency' => true,
281 'settings_page' => 'ai-assistant',
282 ],
283 [
284 'slug' => 'whatsapp',
285 'name' => __('WhatsApp Notifications', 'yatra'),
286 'description' => __('Send booking confirmations, payment alerts, departure reminders, and review requests over WhatsApp — the channel customers actually read. Inbound messages route into your Yatra Enquiries inbox. Bring your own WhatsApp Business / Cloud API credentials; messages are billed by Meta, not by the plugin.', 'yatra'),
287 'category' => __('Communications', 'yatra'),
288 'docs_url' => 'https://docs.wpyatra.com/modules/whatsapp',
289 'is_premium' => true,
290 'purchase_url' => 'https://wpyatra.com/pricing?module=whatsapp',
291 'is_core' => false,
292 'enabled' => false,
293 'tags' => ['whatsapp', 'notifications', 'messaging', 'communications', 'reminders'],
294 'video_url' => self::DEFAULT_VIDEO_URL,
295 'requires_pro' => true,
296 'requires_growth_or_agency' => true,
297 'settings_page' => 'whatsapp',
298 ],
299 [
300 'slug' => 'channel_manager',
301 'name' => __('Channel Manager', 'yatra'),
302 'description' => __('Sync your trips + departures + inventory to Viator, GetYourGuide, Klook, and TripAdvisor Experiences. Pull bookings back into Yatra as native bookings. Real-time inventory sync prevents oversells across channels. Per-channel pricing offsets cover OTA commission. The single biggest revenue lever for tour operators on multiple platforms.', 'yatra'),
303 'category' => __('Distribution', 'yatra'),
304 'docs_url' => 'https://docs.wpyatra.com/modules/channel-manager',
305 'is_premium' => true,
306 'purchase_url' => 'https://wpyatra.com/pricing?module=channel-manager',
307 'is_core' => false,
308 'enabled' => false,
309 'tags' => ['ota', 'channel', 'viator', 'getyourguide', 'distribution', 'agency'],
310 'video_url' => self::DEFAULT_VIDEO_URL,
311 'requires_pro' => true,
312 'requires_agency' => true,
313 'settings_page' => 'channel-manager',
314 ],
315 [
316 'slug' => 'webhooks',
317 'name' => __('Webhooks', 'yatra'),
318 'description' => __('Push every Yatra event (booking, payment, refund, customer, enquiry, trip, departure) to your CRM, accounting tool, Zapier, Make, Slack, or any HTTPS endpoint. HMAC-SHA256 signed payloads, automatic retry with exponential backoff, full delivery log with payload inspection + replay, per-endpoint encrypted secrets. The integration backbone for agencies wiring Yatra into a custom tech stack.', 'yatra'),
319 'category' => __('Integrations', 'yatra'),
320 'docs_url' => 'https://docs.wpyatra.com/modules/webhooks',
321 'is_premium' => true,
322 'purchase_url' => 'https://wpyatra.com/pricing?module=webhooks',
323 'is_core' => false,
324 'enabled' => false,
325 'tags' => ['webhook', 'integration', 'zapier', 'crm', 'api', 'agency', 'automation'],
326 'video_url' => self::DEFAULT_VIDEO_URL,
327 'requires_pro' => true,
328 'requires_agency' => true,
329 'settings_page' => 'webhooks',
330 ],
331 [
332 'slug' => 'team',
333 'name' => __('Team & Access', 'yatra'),
334 'description' => __('Granular roles + capability-based access control for multi-staff agencies. Eight shipped roles (Owner, Manager, Sales Agent, Front Desk, Guide, Accountant, Marketing, Auditor) plus custom role builder. Per-user capability grants/revokes, row-level scope assignment (destination / activity / trip), magic-link invitations, append-only audit log with 180-day retention, and 2FA enforcement per role. Defense-in-depth: every REST route enforces capabilities server-side; the React UI mirrors via <Can/> for UX only.', 'yatra'),
335 'category' => __('Team', 'yatra'),
336 'docs_url' => 'https://docs.wpyatra.com/modules/team-access',
337 'is_premium' => true,
338 'purchase_url' => 'https://wpyatra.com/pricing?module=team',
339 'is_core' => false,
340 'enabled' => false,
341 'tags' => ['team', 'access', 'roles', 'permissions', 'rbac', 'audit-log', 'agency', 'sso', 'invitations'],
342 'video_url' => self::DEFAULT_VIDEO_URL,
343 'requires_pro' => true,
344 'requires_agency' => true,
345 'settings_page' => 'team',
346 ],
347 ];
348
349 return apply_filters('yatra_default_modules', $modules);
350 }
351
352 /**
353 * Get modules merged with stored option state
354 */
355 public static function getModules(): array
356 {
357 // Get enable/disable status ONLY from database
358 $stored_status = get_option(self::OPTION_KEY, []);
359 $stored_status = is_array($stored_status) ? $stored_status : [];
360
361 // Get module definitions ONLY from hardcoded array
362 $modules = self::getDefaultModules();
363
364 $result = [];
365 foreach ($modules as $module) {
366 $slug = $module['slug'];
367
368 // Get enable/disable status from database only
369 $enabled = isset($stored_status[$slug]['enabled'])
370 ? (bool) $stored_status[$slug]['enabled']
371 : (bool) ($module['enabled'] ?? false);
372
373 // Check if module is available (can be enabled)
374 $is_available = true;
375 if (!empty($module['requires_pro'])) {
376 $pro_active = apply_filters('yatra_is_pro_active', false);
377 if ($pro_active) {
378 // Check if this module is available in Pro
379 $available_modules = apply_filters('yatra_pro_available_modules', []);
380 $is_available = in_array($slug, $available_modules, true);
381 } else {
382 $is_available = false;
383 }
384 }
385
386 // Agency-tier-only modules require both Pro AND an Agency license.
387 if ($is_available && !empty($module['requires_agency'])) {
388 $is_available = (bool) apply_filters('yatra_is_agency_active', false);
389 }
390
391 // Growth-or-Agency tier modules (e.g. AI Assistant) — require Pro
392 // AND a license tier at or above Growth.
393 if ($is_available && !empty($module['requires_growth_or_agency'])) {
394 $is_available = (bool) apply_filters('yatra_is_ai_eligible', false);
395 }
396
397 // Plan badge: 'agency' for Agency-only, 'growth' for the
398 // Growth-or-Agency band, 'personal' for any other Pro module,
399 // 'free' for core. Surfaced on the Modules page.
400 if (!empty($module['requires_agency'])) {
401 $plan = 'agency';
402 } elseif (!empty($module['requires_growth_or_agency'])) {
403 $plan = 'growth';
404 } elseif (!empty($module['requires_pro'])) {
405 $plan = 'personal';
406 } else {
407 $plan = 'free';
408 }
409
410 $result[] = array_merge($module, [
411 'enabled' => $enabled,
412 'updated_at' => $stored_status[$slug]['updated_at'] ?? null,
413 'is_available' => $is_available,
414 'plan' => $plan,
415 ]);
416 }
417
418 return $result;
419 }
420
421 /**
422 * Check if module is enabled
423 */
424 public static function isModuleEnabled(string $slug): bool
425 {
426 // Allow direct override for specific modules
427 $override = apply_filters('yatra_module_enabled_status', null, $slug);
428 if ($override !== null) {
429 return (bool) $override;
430 }
431
432 $modules = self::getModules();
433 foreach ($modules as $module) {
434 if ($module['slug'] === $slug) {
435 // Check if module requires Pro and if Pro is active
436 if (!empty($module['requires_pro'])) {
437 $pro_active = apply_filters('yatra_is_pro_active', false);
438 if (!$pro_active) {
439 return false;
440 }
441 }
442
443 // Agency-tier-only modules also need an Agency license — a
444 // toggle stuck "on" must not keep working after a downgrade.
445 if (!empty($module['requires_agency'])) {
446 if (!apply_filters('yatra_is_agency_active', false)) {
447 return false;
448 }
449 }
450
451 // Growth-or-Agency modules need at least a Growth license.
452 if (!empty($module['requires_growth_or_agency'])) {
453 if (!apply_filters('yatra_is_ai_eligible', false)) {
454 return false;
455 }
456 }
457
458 return (bool) $module['enabled'];
459 }
460 }
461
462 return false;
463 }
464
465 /**
466 * Check if module is available (can be enabled)
467 */
468 public static function isModuleAvailable(string $slug): bool
469 {
470 // Allow direct override for specific modules
471 $override = apply_filters('yatra_module_is_available_' . $slug, null);
472 if ($override !== null) {
473 return (bool) $override;
474 }
475
476 $modules = self::getModules();
477 foreach ($modules as $module) {
478 if ($module['slug'] === $slug) {
479 // If module requires Pro, check if Pro is active
480 if (!empty($module['requires_pro'])) {
481 // Check if Pro is active
482 $pro_active = apply_filters('yatra_is_pro_active', false);
483 if (!$pro_active) {
484 return false;
485 }
486
487 // Check if this module is available in Pro
488 $available_modules = apply_filters('yatra_pro_available_modules', []);
489 if (!in_array($slug, $available_modules, true)) {
490 return false;
491 }
492 }
493
494 if (!empty($module['requires_agency'])) {
495 return (bool) apply_filters('yatra_is_agency_active', false);
496 }
497
498 if (!empty($module['requires_growth_or_agency'])) {
499 return (bool) apply_filters('yatra_is_ai_eligible', false);
500 }
501
502 return true;
503 }
504 }
505
506 return false;
507 }
508
509 /**
510 * Update module enabled state
511 */
512 public static function setModuleStatus(string $slug, bool $enabled): array
513 {
514 $modules = get_option(self::OPTION_KEY, []);
515 $modules = is_array($modules) ? $modules : [];
516
517 $modules[$slug] = array_merge($modules[$slug] ?? [], [
518 'enabled' => $enabled,
519 'updated_at' => current_time('mysql'),
520 ]);
521
522 update_option(self::OPTION_KEY, $modules);
523
524 // Trigger module activation/deactivation hook
525 if ($enabled) {
526 do_action('yatra_module_active', $slug);
527 } else {
528 do_action('yatra_module_deactive', $slug);
529 }
530
531 return self::getModules();
532 }
533
534 /**
535 * Update multiple modules at once
536 *
537 * @param array<array{slug:string, enabled:bool}> $items
538 */
539 public static function setMultipleStatuses(array $items): array
540 {
541 if (empty($items)) {
542 return self::getModules();
543 }
544
545 $modules = get_option(self::OPTION_KEY, []);
546 $modules = is_array($modules) ? $modules : [];
547 $timestamp = current_time('mysql');
548
549 foreach ($items as $item) {
550 if (empty($item['slug'])) {
551 continue;
552 }
553
554 $slug = sanitize_key($item['slug']);
555 $enabled = (bool) ($item['enabled'] ?? false);
556
557 $modules[$slug] = array_merge($modules[$slug] ?? [], [
558 'enabled' => $enabled,
559 'updated_at' => $timestamp,
560 ]);
561 }
562
563 update_option(self::OPTION_KEY, $modules);
564
565 // Trigger module activation/deactivation hooks for bulk updates
566 foreach ($items as $item) {
567 if (empty($item['slug'])) {
568 continue;
569 }
570
571 $slug = sanitize_key($item['slug']);
572 $enabled = (bool) ($item['enabled'] ?? false);
573
574 if ($enabled) {
575 do_action('yatra_module_active', $slug);
576 } else {
577 do_action('yatra_module_deactive', $slug);
578 }
579 }
580
581 return self::getModules();
582 }
583 }
584