PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
3.0.16 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 All 84 releases
yatra / app / Services / SettingsService.php

SettingsService.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.6, at app/Services/SettingsService.php

708 lines 24.7 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\Services;
6
7 /**
8 * Centralized Settings Service
9 *
10 * Provides a single point of access for all plugin settings.
11 * Caches settings to avoid multiple database queries.
12 *
13 * @package Yatra
14 */
15 class SettingsService
16 {
17 /**
18 * Cached settings
19 */
20 private static ?array $settings = null;
21
22 /**
23 * Settings option prefix in database
24 * Each setting is stored as yatra_{key}
25 */
26 private const OPTION_PREFIX = 'yatra_';
27
28 /**
29 * Default settings
30 */
31 private static array $defaults = [
32 // General
33 'company_name' => '',
34 'company_email' => '',
35 'company_phone' => '',
36 'company_address' => '',
37 'date_format' => 'Y-m-d',
38 'time_format' => 'H:i',
39
40 // Booking
41 'booking_base' => 'book',
42 'use_booking_page' => false,
43 'booking_page_id' => 0,
44 'enable_guest_booking' => true,
45 'booking_confirmation' => true,
46 'auto_confirm_bookings' => false,
47 'require_login' => false,
48 'allow_guest_checkout' => true,
49 'cancellation_policy' => 'full_refund',
50 'cancellation_days' => 7,
51 'refund_policy' => '',
52 'booking_expiry_hours' => 24,
53 'booking_reminder_days' => 3,
54 'allow_waitlist' => true,
55 'waitlist_auto_confirm' => false,
56
57 // Payment
58 'currency' => 'USD',
59 'payment_test_mode' => true,
60 'currency_position' => 'before',
61 'thousand_separator' => ',',
62 'decimal_separator' => '.',
63 'decimal_places' => 2,
64 // Flexible payments (deposit/partial) - Pro feature
65 // These defaults are overridden by Pro's FlexiblePaymentsModule when active
66 'enable_deposit' => false,
67 'deposit_type' => 'percentage',
68 'deposit_amount' => 20,
69 'deposit_required' => false,
70 'deposit_percentage' => 20,
71 'partial_payment' => false,
72 'partial_payment_percentage' => 30,
73 'auto_confirm_pay_later' => true,
74 'payment_gateways' => ['pay_later'],
75 'payment_methods' => [],
76 'gateway_configs' => [],
77 'gateway_order' => [],
78
79 // Scheduled/Recurring Payments - Pro feature
80 // These defaults are overridden by Pro's FlexiblePaymentsModule when active
81 'enable_scheduled_payments' => false,
82 'scheduled_payment_type' => 'single',
83 'scheduled_payment_days' => 15,
84 'scheduled_payment_installments' => 1,
85 'scheduled_payment_interval' => 30,
86 'scheduled_payment_reminder_days' => 3,
87 'allow_save_payment_methods' => false,
88
89 // Email
90 'email_from_name' => '',
91 'email_from_address' => '',
92 'admin_email' => '',
93 'enable_admin_notifications' => true,
94 'enable_customer_notifications' => true,
95
96 // Trip
97 'trip_base' => 'trip',
98 'trips_per_page' => 12,
99 'enable_wishlist' => false,
100 'enable_comparison' => false,
101 'show_sold_out' => true,
102
103 // Customer
104 'enable_customer_accounts' => true,
105 'enable_customer_registration' => true,
106 'customer_account_page' => 0,
107
108 // Review
109 'enable_reviews' => true,
110 'require_booking_to_review' => false,
111 'auto_approve_reviews' => false,
112 'enable_review_moderation' => true,
113 'minimum_rating' => 1,
114 'review_reminder_days' => 7,
115
116 // Tax
117 'enable_tax' => false,
118 'tax_rate' => 0,
119 'tax_inclusive' => false,
120 'tax_label' => 'Tax',
121 'multiple_taxes_enabled' => false,
122 'multiple_taxes' => [],
123 'multiple_taxes_by_country' => [],
124
125 // Currency
126 'enabled_currencies' => ['USD'],
127 'default_currency' => 'USD',
128
129 // Notification
130 'enable_push_notifications' => false,
131 'enable_sms_notifications' => false,
132
133 // Permalink
134 'destination_base' => 'destination',
135 'activity_base' => 'activity',
136 'trip_category_base' => 'trip-category',
137
138 // Advanced
139 'enable_debug_mode' => false,
140 'delete_data_on_uninstall' => false,
141
142 // Booking Form Builder
143 'booking_form_config' => [],
144 ];
145
146 /**
147 * Get default booking form configuration
148 *
149 * @return array
150 */
151 public static function getDefaultBookingFormConfig(): array
152 {
153 return [
154 'contact_form' => [
155 'title' => 'Lead Traveler / Contact Information',
156 'description' => 'Primary contact person for this booking',
157 'fields' => [
158 ['id' => 'first_name', 'type' => 'text', 'label' => 'First Name', 'placeholder' => 'Enter first name', 'required' => true, 'enabled' => true, 'order' => 1, 'width' => 'half', 'locked' => true],
159 ['id' => 'last_name', 'type' => 'text', 'label' => 'Last Name', 'placeholder' => 'Enter last name', 'required' => true, 'enabled' => true, 'order' => 2, 'width' => 'half', 'locked' => true],
160 ['id' => 'email', 'type' => 'email', 'label' => 'Email Address', 'placeholder' => '[email protected]', 'required' => true, 'enabled' => true, 'order' => 3, 'width' => 'half', 'locked' => true],
161 ['id' => 'phone', 'type' => 'tel', 'label' => 'Phone Number', 'placeholder' => '+1 234 567 8900', 'required' => true, 'enabled' => true, 'order' => 4, 'width' => 'half', 'locked' => true],
162 ['id' => 'country', 'type' => 'country', 'label' => 'Country', 'placeholder' => 'Select Country', 'required' => true, 'enabled' => true, 'order' => 5, 'width' => 'half', 'locked' => true],
163 ['id' => 'nationality', 'type' => 'country', 'label' => 'Nationality', 'placeholder' => 'Select Nationality', 'required' => false, 'enabled' => true, 'order' => 6, 'width' => 'half'],
164 ['id' => 'address', 'type' => 'text', 'label' => 'Address', 'placeholder' => 'Street address (optional)', 'required' => false, 'enabled' => true, 'order' => 7, 'width' => 'full'],
165 ],
166 ],
167 'emergency_contact_form' => [
168 'title' => 'Emergency Contact',
169 'description' => 'Person to contact in case of emergency',
170 'enabled' => true,
171 'fields' => [
172 ['id' => 'name', 'type' => 'text', 'label' => 'Contact Name', 'placeholder' => 'Full name', 'required' => true, 'enabled' => true, 'order' => 1, 'width' => 'half'],
173 ['id' => 'phone', 'type' => 'tel', 'label' => 'Contact Phone', 'placeholder' => '+1 234 567 8900', 'required' => true, 'enabled' => true, 'order' => 2, 'width' => 'half'],
174 ['id' => 'relationship', 'type' => 'select', 'label' => 'Relationship', 'placeholder' => 'Select Relationship', 'required' => false, 'enabled' => true, 'order' => 3, 'width' => 'full', 'options' => [
175 ['value' => 'spouse', 'label' => 'Spouse/Partner'],
176 ['value' => 'parent', 'label' => 'Parent'],
177 ['value' => 'sibling', 'label' => 'Sibling'],
178 ['value' => 'child', 'label' => 'Child'],
179 ['value' => 'friend', 'label' => 'Friend'],
180 ['value' => 'other', 'label' => 'Other'],
181 ]],
182 ],
183 ],
184 'traveler_form' => [
185 'title' => 'Traveler Information',
186 'description' => 'Please provide details for each traveler',
187 'fields' => [
188 ['id' => 'first_name', 'type' => 'text', 'label' => 'First Name', 'placeholder' => 'Legal first name', 'required' => true, 'enabled' => true, 'order' => 1, 'width' => 'half'],
189 ['id' => 'last_name', 'type' => 'text', 'label' => 'Last Name', 'placeholder' => 'Legal last name', 'required' => true, 'enabled' => true, 'order' => 2, 'width' => 'half'],
190 ['id' => 'date_of_birth', 'type' => 'date', 'label' => 'Date of Birth', 'placeholder' => '', 'required' => true, 'enabled' => true, 'order' => 3, 'width' => 'half'],
191 ['id' => 'gender', 'type' => 'select', 'label' => 'Gender', 'placeholder' => 'Select Gender', 'required' => true, 'enabled' => true, 'order' => 4, 'width' => 'half', 'options' => [
192 ['value' => 'male', 'label' => 'Male'],
193 ['value' => 'female', 'label' => 'Female'],
194 ['value' => 'other', 'label' => 'Other'],
195 ]],
196 ['id' => 'nationality', 'type' => 'country', 'label' => 'Nationality', 'placeholder' => 'Select Nationality', 'required' => true, 'enabled' => true, 'order' => 5, 'width' => 'full'],
197 ['id' => 'dietary', 'type' => 'select', 'label' => 'Dietary Requirements', 'placeholder' => 'Select', 'required' => false, 'enabled' => true, 'order' => 6, 'width' => 'half', 'section' => 'dietary_medical', 'options' => [
198 ['value' => 'none', 'label' => 'No special requirements'],
199 ['value' => 'vegetarian', 'label' => 'Vegetarian'],
200 ['value' => 'vegan', 'label' => 'Vegan'],
201 ['value' => 'halal', 'label' => 'Halal'],
202 ['value' => 'kosher', 'label' => 'Kosher'],
203 ['value' => 'gluten_free', 'label' => 'Gluten Free'],
204 ['value' => 'lactose_free', 'label' => 'Lactose Free'],
205 ['value' => 'other', 'label' => 'Other (specify in notes)'],
206 ]],
207 ['id' => 'medical', 'type' => 'text', 'label' => 'Medical Conditions / Allergies', 'placeholder' => 'Any allergies or conditions we should know', 'required' => false, 'enabled' => true, 'order' => 7, 'width' => 'half', 'section' => 'dietary_medical'],
208 ],
209 ],
210 ];
211 }
212
213 /**
214 * Get booking form configuration (merged with defaults)
215 *
216 * @return array
217 */
218 public static function getBookingFormConfig(): array
219 {
220 $saved_config = self::get('booking_form_config', []);
221 $default_config = self::getDefaultBookingFormConfig();
222
223 // If no saved config, return defaults (Pro may filter)
224 if (empty($saved_config)) {
225 return apply_filters('yatra_booking_form_config', $default_config);
226 }
227
228 // Build a map of locked field IDs from defaults
229 $locked_fields = [];
230 foreach ($default_config as $form_type => $form_config) {
231 if (!empty($form_config['fields'])) {
232 foreach ($form_config['fields'] as $field) {
233 if (!empty($field['locked'])) {
234 $locked_fields[$form_type][$field['id']] = true;
235 }
236 }
237 }
238 }
239
240 // Merge saved with defaults
241 $merged = array_replace_recursive($default_config, $saved_config);
242
243 // Ensure locked status is preserved from defaults (locked cannot be overridden)
244 foreach ($merged as $form_type => &$form_config) {
245 if (!empty($form_config['fields']) && is_array($form_config['fields'])) {
246 foreach ($form_config['fields'] as &$field) {
247 // If this field ID is in the locked list, force locked=true and required=true
248 if (isset($locked_fields[$form_type][$field['id']])) {
249 $field['locked'] = true;
250 $field['required'] = true;
251 }
252 }
253 }
254 }
255
256 return apply_filters('yatra_booking_form_config', $merged);
257 }
258
259 private static function isEmailIdentityKey(string $key): bool
260 {
261 return $key === 'admin_email' || $key === 'from_email' || $key === 'from_name';
262 }
263
264 private static function isEmptyScalar($value): bool
265 {
266 return $value === null || $value === false || $value === ''
267 || (is_string($value) && trim($value) === '');
268 }
269
270 /**
271 * When Yatra delivery options are empty, use WordPress site admin email / blog name (same as installer defaults).
272 *
273 * @param mixed $value
274 * @return mixed
275 */
276 private static function applyEmailIdentityFallback(string $key, $value)
277 {
278 if (!self::isEmailIdentityKey($key) || !self::isEmptyScalar($value)) {
279 return $value;
280 }
281 if ($key === 'from_name') {
282 $wp = (string) get_bloginfo('name');
283
284 return $wp !== '' ? $wp : $value;
285 }
286 $wp = (string) get_option('admin_email', '');
287
288 return $wp !== '' ? $wp : $value;
289 }
290
291 /**
292 * Get all settings
293 *
294 * @return array All settings with defaults applied
295 */
296 public static function all(): array
297 {
298 if (self::$settings === null) {
299 self::load();
300 }
301
302 return self::$settings;
303 }
304
305 /**
306 * Get setting value with fallback to default
307 *
308 * @param string $key Setting key
309 * @param mixed $default Default value if setting not found
310 * @return mixed Setting value or default
311 */
312 public static function get(string $key, $default = null)
313 {
314 if (self::$settings === null) {
315 self::load();
316 }
317
318 // Support dot notation for nested access (future use)
319 if (strpos($key, '.') !== false) {
320 $keys = explode('.', $key);
321 $value = self::$settings;
322 foreach ($keys as $k) {
323 if (!isset($value[$k])) {
324 return $default ?? (self::$defaults[$key] ?? null);
325 }
326 $value = $value[$k];
327 }
328 return $value;
329 }
330
331 // If setting exists in cache, return it
332 if (isset(self::$settings[$key])) {
333 return self::applyEmailIdentityFallback($key, self::$settings[$key]);
334 }
335
336 // Try to fetch from database directly for settings not in defaults
337 $option_name = self::OPTION_PREFIX . $key;
338 $value = get_option($option_name, null);
339
340 // Installer / migrations used yatra_email_from_*; REST + EmailService use yatra_from_*.
341 if (($value === null || $value === false || $value === '') && $key === 'from_email') {
342 $legacy = get_option(self::OPTION_PREFIX . 'email_from_address', '');
343 if (is_string($legacy) && $legacy !== '') {
344 $value = $legacy;
345 }
346 }
347 if (($value === null || $value === false || $value === '') && $key === 'from_name') {
348 $legacy = get_option(self::OPTION_PREFIX . 'email_from_name', '');
349 if (is_string($legacy) && $legacy !== '') {
350 $value = $legacy;
351 }
352 }
353
354 if ($value !== null) {
355 // Handle serialized arrays
356 if (is_string($value) && is_serialized($value)) {
357 $value = maybe_unserialize($value);
358 }
359 // Cache the value
360 self::$settings[$key] = $value;
361
362 return self::applyEmailIdentityFallback($key, $value);
363 }
364
365 $fallback = $default ?? (self::$defaults[$key] ?? null);
366
367 return self::applyEmailIdentityFallback($key, $fallback);
368 }
369
370 /**
371 * Check if a boolean setting is enabled
372 *
373 * @param string $key Setting key
374 * @return bool
375 */
376 public static function isEnabled(string $key): bool
377 {
378 // Flexible payment settings require Pro module
379 if (self::isFlexiblePaymentSetting($key)) {
380 $value = apply_filters('yatra_flexible_payment_setting', false, $key);
381 return filter_var($value, FILTER_VALIDATE_BOOLEAN);
382 }
383
384 $value = self::get($key, false);
385 return filter_var($value, FILTER_VALIDATE_BOOLEAN);
386 }
387
388 /**
389 * Get a setting as integer
390 *
391 * @param string $key Setting key
392 * @param int $default Default value
393 * @return int
394 */
395 public static function getInt(string $key, int $default = 0): int
396 {
397 // Flexible payment settings require Pro module
398 if (self::isFlexiblePaymentSetting($key)) {
399 return (int) apply_filters('yatra_flexible_payment_setting', $default, $key);
400 }
401
402 return (int) self::get($key, $default);
403 }
404
405 /**
406 * Get a setting as float
407 *
408 * @param string $key Setting key
409 * @param float $default Default value
410 * @return float
411 */
412 public static function getFloat(string $key, float $default = 0.0): float
413 {
414 return (float) self::get($key, $default);
415 }
416
417 /**
418 * Get a setting as string
419 *
420 * @param string $key Setting key
421 * @param string $default Default value
422 * @return string
423 */
424 public static function getString(string $key, string $default = ''): string
425 {
426 return (string) self::get($key, $default);
427 }
428
429 /**
430 * Load settings from database
431 * Settings are stored as individual options with yatra_ prefix
432 */
433 private static function load(): void
434 {
435 self::$settings = [];
436
437 // Load each setting from individual options
438 foreach (self::$defaults as $key => $default_value) {
439 $option_name = self::OPTION_PREFIX . $key;
440 $value = get_option($option_name, $default_value);
441
442 // Handle serialized arrays
443 if (is_string($value) && is_serialized($value)) {
444 $value = maybe_unserialize($value);
445 }
446
447 self::$settings[$key] = $value;
448 }
449
450 self::mergeAdminReviewOptionAliases();
451 }
452
453 /**
454 * REST/Settings UI uses yatra_require_booking, yatra_review_moderation, yatra_min_rating;
455 * internal helpers use require_booking_to_review, enable_review_moderation, minimum_rating.
456 */
457 private static function mergeAdminReviewOptionAliases(): void
458 {
459 $map = [
460 'require_booking' => 'require_booking_to_review',
461 'review_moderation' => 'enable_review_moderation',
462 'min_rating' => 'minimum_rating',
463 ];
464 foreach ($map as $adminKey => $internalKey) {
465 $v = get_option(self::OPTION_PREFIX . $adminKey, null);
466 if ($v !== null) {
467 self::$settings[$internalKey] = $v;
468 }
469 }
470 }
471
472 /**
473 * Reload settings (clear cache)
474 */
475 public static function reload(): void
476 {
477 self::$settings = null;
478 self::load();
479 }
480
481 /**
482 * Get default settings
483 *
484 * @return array
485 */
486 public static function getDefaults(): array
487 {
488 return self::$defaults;
489 }
490
491 // =========================================
492 // Convenience Methods for Common Settings
493 // =========================================
494
495 /**
496 * Check if reviews are enabled
497 */
498 public static function reviewsEnabled(): bool
499 {
500 return self::isEnabled('enable_reviews');
501 }
502
503 /**
504 * Check if booking is required for reviews
505 */
506 public static function requireBookingForReview(): bool
507 {
508 return self::isEnabled('require_booking_to_review');
509 }
510
511 /**
512 * Check if reviews auto-approve
513 */
514 public static function autoApproveReviews(): bool
515 {
516 return self::isEnabled('auto_approve_reviews');
517 }
518
519 /**
520 * Check if review moderation is enabled
521 */
522 public static function reviewModerationEnabled(): bool
523 {
524 return self::isEnabled('enable_review_moderation');
525 }
526
527 /**
528 * Get minimum rating allowed
529 */
530 public static function getMinimumRating(): int
531 {
532 return self::getInt('minimum_rating', 1);
533 }
534
535 /**
536 * Get currency settings
537 * Checks both 'currency' and 'default_currency' keys for compatibility
538 * (Admin UI Currency Settings saves as 'default_currency')
539 */
540 public static function getCurrency(): string
541 {
542 // Priority: 'currency' key first (Payment Settings), then 'default_currency' (Currency Settings)
543 $currency = self::getString('currency', '');
544 if (!empty($currency) && $currency !== 'USD') {
545 return $currency;
546 }
547
548 // Check default_currency (from Currency Settings section)
549 $defaultCurrency = self::getString('default_currency', '');
550 if (!empty($defaultCurrency)) {
551 return $defaultCurrency;
552 }
553
554 // Return whatever currency is set, even if USD
555 return !empty($currency) ? $currency : 'USD';
556 }
557
558 /**
559 * Get currency position (before/after)
560 */
561 public static function getCurrencyPosition(): string
562 {
563 return self::getString('currency_position', 'before');
564 }
565
566 /**
567 * Get trip base slug
568 */
569 public static function getTripBase(): string
570 {
571 $base = self::getString('trip_base', 'trip');
572 return preg_replace('/[^a-z0-9_-]/i', '', $base) ?: 'trip';
573 }
574
575 /**
576 * Get booking base slug
577 */
578 public static function getBookingBase(): string
579 {
580 $base = self::getString('booking_base', 'booking');
581 return preg_replace('/[^a-z0-9_-]/i', '', $base) ?: 'booking';
582 }
583
584 /**
585 * URL slug for the customer account area (Settings → Customer → account path).
586 * Derives from yatra_customer_account_page first so routing matches the configured path
587 * even when yatra_account_base was never saved or is out of sync.
588 */
589 public static function getAccountBase(): string
590 {
591 $customerPath = get_option('yatra_customer_account_page', '');
592 if (is_string($customerPath) && $customerPath !== '' && $customerPath !== '0') {
593 $slug = self::slugFromAccountPathString($customerPath);
594 if ($slug !== '') {
595 return $slug;
596 }
597 }
598
599 $base = self::getString('account_base', '');
600 $base = preg_replace('/[^a-z0-9_-]/i', '', $base) ?: '';
601
602 return $base !== '' ? $base : 'account';
603 }
604
605 private static function slugFromAccountPathString(string $path): string
606 {
607 $path = trim(str_replace('\\', '/', $path), '/');
608 $parts = array_values(array_filter(explode('/', $path), static fn ($p) => $p !== ''));
609 $segment = $parts !== [] ? end($parts) : 'account';
610 $slug = sanitize_title($segment);
611
612 return $slug !== '' ? $slug : 'account';
613 }
614
615 /**
616 * Check if using custom booking page
617 */
618 public static function useCustomBookingPage(): bool
619 {
620 return self::isEnabled('use_booking_page') && self::getInt('booking_page_id') > 0;
621 }
622
623 /**
624 * Get booking page ID
625 */
626 public static function getBookingPageId(): int
627 {
628 return self::getInt('booking_page_id', 0);
629 }
630
631 /**
632 * Check if guest booking is allowed
633 */
634 public static function guestBookingEnabled(): bool
635 {
636 return self::isEnabled('enable_guest_booking');
637 }
638
639 /**
640 * Wishlist (saved trips) is a Yatra Pro feature and must be enabled in settings.
641 */
642 public static function wishlistEnabled(): bool
643 {
644 if (!apply_filters('yatra_is_pro_active', false)) {
645 return false;
646 }
647
648 return self::isEnabled('enable_wishlist');
649 }
650
651 /**
652 * Trips per page on front-end listings (aligned with WordPress Reading "posts per page").
653 */
654 public static function getTripsPerPage(): int
655 {
656 if (function_exists('yatra_get_posts_per_page')) {
657 return yatra_get_posts_per_page();
658 }
659
660 return max(1, absint((int) get_option('posts_per_page', 10)));
661 }
662
663 /**
664 * Check if a setting key is a flexible payment setting (Pro feature)
665 *
666 * @param string $key Setting key
667 * @return bool
668 */
669 private static function isFlexiblePaymentSetting(string $key): bool
670 {
671 $flexiblePaymentSettings = [
672 'deposit_required',
673 'deposit_percentage',
674 'partial_payment',
675 'partial_payment_percentage',
676 'enable_deposit',
677 'enable_scheduled_payments',
678 'scheduled_payment_type',
679 'scheduled_payment_days',
680 'scheduled_payment_installments',
681 'scheduled_payment_interval',
682 'scheduled_payment_reminder_days',
683 'allow_save_payment_methods',
684 ];
685
686 return in_array($key, $flexiblePaymentSettings, true);
687 }
688
689 /**
690 * Check if flexible payments module is available (Pro active + module enabled)
691 *
692 * @return bool
693 */
694 public static function isFlexiblePaymentsAvailable(): bool
695 {
696 return apply_filters('yatra_flexible_payments_enabled', false);
697 }
698
699 /**
700 * Global payment test/sandbox toggle (Settings → Payment).
701 */
702 public static function isPaymentTestMode(): bool
703 {
704 return self::isEnabled('payment_test_mode');
705 }
706 }
707
708