| 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.yatra.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.yatra.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.yatra.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.yatra.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.yatra.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.yatra.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.yatra.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.yatra.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.yatra.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.yatra.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' => 'dynamic_pricing', |
| 193 |
'name' => __('Dynamic Pricing', 'yatra'), |
| 194 |
'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'), |
| 195 |
'category' => __('Sales', 'yatra'), |
| 196 |
'docs_url' => 'https://docs.yatra.com/modules/dynamic-pricing', |
| 197 |
'is_premium' => true, |
| 198 |
'purchase_url' => 'https://wpyatra.com/pricing?module=dynamic-pricing', |
| 199 |
'is_core' => false, |
| 200 |
'enabled' => false, |
| 201 |
'tags' => ['pricing', 'revenue', 'automation', 'discounts', 'sales'], |
| 202 |
'video_url' => self::DEFAULT_VIDEO_URL, |
| 203 |
'requires_pro' => true, |
| 204 |
'settings_page' => 'dynamic-pricing', |
| 205 |
], |
| 206 |
[ |
| 207 |
'slug' => 'abandoned_booking_recovery', |
| 208 |
'name' => __('Abandoned Booking Recovery', 'yatra'), |
| 209 |
'description' => __('Recover lost sales by automatically tracking abandoned bookings and sending targeted email sequences. Win back customers who left before completing their purchase.', 'yatra'), |
| 210 |
'category' => __('Marketing', 'yatra'), |
| 211 |
'docs_url' => 'https://docs.yatra.com/modules/abandoned-booking-recovery', |
| 212 |
'is_premium' => true, |
| 213 |
'purchase_url' => 'https://wpyatra.com/pricing?module=abandoned-booking-recovery', |
| 214 |
'is_core' => false, |
| 215 |
'enabled' => false, |
| 216 |
'tags' => ['recovery', 'email', 'automation', 'marketing', 'conversion'], |
| 217 |
'video_url' => self::DEFAULT_VIDEO_URL, |
| 218 |
'requires_pro' => true, |
| 219 |
'settings_page' => 'abandoned-recovery', |
| 220 |
], |
| 221 |
]; |
| 222 |
|
| 223 |
return apply_filters('yatra_default_modules', $modules); |
| 224 |
} |
| 225 |
|
| 226 |
/** |
| 227 |
* Get modules merged with stored option state |
| 228 |
*/ |
| 229 |
public static function getModules(): array |
| 230 |
{ |
| 231 |
// Get enable/disable status ONLY from database |
| 232 |
$stored_status = get_option(self::OPTION_KEY, []); |
| 233 |
$stored_status = is_array($stored_status) ? $stored_status : []; |
| 234 |
|
| 235 |
// Get module definitions ONLY from hardcoded array |
| 236 |
$modules = self::getDefaultModules(); |
| 237 |
|
| 238 |
$result = []; |
| 239 |
foreach ($modules as $module) { |
| 240 |
$slug = $module['slug']; |
| 241 |
|
| 242 |
// Get enable/disable status from database only |
| 243 |
$enabled = isset($stored_status[$slug]['enabled']) |
| 244 |
? (bool) $stored_status[$slug]['enabled'] |
| 245 |
: (bool) ($module['enabled'] ?? false); |
| 246 |
|
| 247 |
// Check if module is available (can be enabled) |
| 248 |
$is_available = true; |
| 249 |
if (!empty($module['requires_pro'])) { |
| 250 |
$pro_active = apply_filters('yatra_is_pro_active', false); |
| 251 |
if ($pro_active) { |
| 252 |
// Check if this module is available in Pro |
| 253 |
$available_modules = apply_filters('yatra_pro_available_modules', []); |
| 254 |
$is_available = in_array($slug, $available_modules, true); |
| 255 |
} else { |
| 256 |
$is_available = false; |
| 257 |
} |
| 258 |
} |
| 259 |
|
| 260 |
$result[] = array_merge($module, [ |
| 261 |
'enabled' => $enabled, |
| 262 |
'updated_at' => $stored_status[$slug]['updated_at'] ?? null, |
| 263 |
'is_available' => $is_available, |
| 264 |
]); |
| 265 |
} |
| 266 |
|
| 267 |
return $result; |
| 268 |
} |
| 269 |
|
| 270 |
/** |
| 271 |
* Check if module is enabled |
| 272 |
*/ |
| 273 |
public static function isModuleEnabled(string $slug): bool |
| 274 |
{ |
| 275 |
// Allow direct override for specific modules |
| 276 |
$override = apply_filters('yatra_module_enabled_status', null, $slug); |
| 277 |
if ($override !== null) { |
| 278 |
return (bool) $override; |
| 279 |
} |
| 280 |
|
| 281 |
$modules = self::getModules(); |
| 282 |
foreach ($modules as $module) { |
| 283 |
if ($module['slug'] === $slug) { |
| 284 |
// Check if module requires Pro and if Pro is active |
| 285 |
if (!empty($module['requires_pro'])) { |
| 286 |
$pro_active = apply_filters('yatra_is_pro_active', false); |
| 287 |
if (!$pro_active) { |
| 288 |
return false; |
| 289 |
} |
| 290 |
} |
| 291 |
|
| 292 |
return (bool) $module['enabled']; |
| 293 |
} |
| 294 |
} |
| 295 |
|
| 296 |
return false; |
| 297 |
} |
| 298 |
|
| 299 |
/** |
| 300 |
* Check if module is available (can be enabled) |
| 301 |
*/ |
| 302 |
public static function isModuleAvailable(string $slug): bool |
| 303 |
{ |
| 304 |
// Allow direct override for specific modules |
| 305 |
$override = apply_filters('yatra_module_is_available_' . $slug, null); |
| 306 |
if ($override !== null) { |
| 307 |
return (bool) $override; |
| 308 |
} |
| 309 |
|
| 310 |
$modules = self::getModules(); |
| 311 |
foreach ($modules as $module) { |
| 312 |
if ($module['slug'] === $slug) { |
| 313 |
// If module requires Pro, check if Pro is active |
| 314 |
if (!empty($module['requires_pro'])) { |
| 315 |
// Check if Pro is active |
| 316 |
$pro_active = apply_filters('yatra_is_pro_active', false); |
| 317 |
if (!$pro_active) { |
| 318 |
return false; |
| 319 |
} |
| 320 |
|
| 321 |
// Check if this module is available in Pro |
| 322 |
$available_modules = apply_filters('yatra_pro_available_modules', []); |
| 323 |
return in_array($slug, $available_modules, true); |
| 324 |
} |
| 325 |
|
| 326 |
return true; |
| 327 |
} |
| 328 |
} |
| 329 |
|
| 330 |
return false; |
| 331 |
} |
| 332 |
|
| 333 |
/** |
| 334 |
* Update module enabled state |
| 335 |
*/ |
| 336 |
public static function setModuleStatus(string $slug, bool $enabled): array |
| 337 |
{ |
| 338 |
$modules = get_option(self::OPTION_KEY, []); |
| 339 |
$modules = is_array($modules) ? $modules : []; |
| 340 |
|
| 341 |
$modules[$slug] = array_merge($modules[$slug] ?? [], [ |
| 342 |
'enabled' => $enabled, |
| 343 |
'updated_at' => current_time('mysql'), |
| 344 |
]); |
| 345 |
|
| 346 |
update_option(self::OPTION_KEY, $modules); |
| 347 |
|
| 348 |
// Trigger module activation/deactivation hook |
| 349 |
if ($enabled) { |
| 350 |
do_action('yatra_module_active', $slug); |
| 351 |
} else { |
| 352 |
do_action('yatra_module_deactive', $slug); |
| 353 |
} |
| 354 |
|
| 355 |
return self::getModules(); |
| 356 |
} |
| 357 |
|
| 358 |
/** |
| 359 |
* Update multiple modules at once |
| 360 |
* |
| 361 |
* @param array<array{slug:string, enabled:bool}> $items |
| 362 |
*/ |
| 363 |
public static function setMultipleStatuses(array $items): array |
| 364 |
{ |
| 365 |
if (empty($items)) { |
| 366 |
return self::getModules(); |
| 367 |
} |
| 368 |
|
| 369 |
$modules = get_option(self::OPTION_KEY, []); |
| 370 |
$modules = is_array($modules) ? $modules : []; |
| 371 |
$timestamp = current_time('mysql'); |
| 372 |
|
| 373 |
foreach ($items as $item) { |
| 374 |
if (empty($item['slug'])) { |
| 375 |
continue; |
| 376 |
} |
| 377 |
|
| 378 |
$slug = sanitize_key($item['slug']); |
| 379 |
$enabled = (bool) ($item['enabled'] ?? false); |
| 380 |
|
| 381 |
$modules[$slug] = array_merge($modules[$slug] ?? [], [ |
| 382 |
'enabled' => $enabled, |
| 383 |
'updated_at' => $timestamp, |
| 384 |
]); |
| 385 |
} |
| 386 |
|
| 387 |
update_option(self::OPTION_KEY, $modules); |
| 388 |
|
| 389 |
// Trigger module activation/deactivation hooks for bulk updates |
| 390 |
foreach ($items as $item) { |
| 391 |
if (empty($item['slug'])) { |
| 392 |
continue; |
| 393 |
} |
| 394 |
|
| 395 |
$slug = sanitize_key($item['slug']); |
| 396 |
$enabled = (bool) ($item['enabled'] ?? false); |
| 397 |
|
| 398 |
if ($enabled) { |
| 399 |
do_action('yatra_module_active', $slug); |
| 400 |
} else { |
| 401 |
do_action('yatra_module_deactive', $slug); |
| 402 |
} |
| 403 |
} |
| 404 |
|
| 405 |
return self::getModules(); |
| 406 |
} |
| 407 |
} |
| 408 |
|