PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.9
Yatra – Travel Booking & Tour Operator Software v3.0.2.9
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 / Services / SettingsService.php

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

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