| @@ -47,15 +47,31 @@ | ||
| 47 | 47 | * @return void |
| 48 | 48 | */ |
| 49 | 49 | private static function setDefaultOptions(): void |
| 50 | 50 | { |
| 51 | + // Whether this is a brand-new install vs. a reactivation/upgrade. | |
| 52 | + // Captured before version/date are stamped below so it reflects the | |
| 53 | + // pre-activation state. | |
| 54 | + $isFreshInstall = self::isFreshInstallation(); | |
| 55 | + | |
| 56 | + // Seed a default only when the option is absent. add_option() is a | |
| 57 | + // no-op when the option already exists (existence is keyed on the | |
| 58 | + // option NAME, so values legitimately stored as false/0/'' are still | |
| 59 | + // preserved). This makes activation idempotent: a deactivate -> | |
| 60 | + // reactivate, or a plugin update, can never overwrite an operator's | |
| 61 | + // saved settings, while a fresh install (and any newly-introduced | |
| 62 | + // default on upgrade) is still seeded. | |
| 63 | + $seed = static function (string $name, $value): void { | |
| 64 | + add_option($name, $value); | |
| 65 | + }; | |
| 66 | + | |
| 51 | 67 | // Payment Gateway Settings - Only enable Pay Later by default |
| 52 | 68 | // These match SettingsService defaults exactly |
| 53 | - update_option('yatra_payment_gateways', ['pay_later']); | |
| 54 | - update_option('yatra_payment_methods', []); | |
| 55 | - update_option('yatra_payment_test_mode', true); | |
| 56 | - update_option('yatra_auto_confirm_pay_later', true); | |
| 57 | - update_option('yatra_partial_payment', false); | |
| 69 | + $seed('yatra_payment_gateways', ['pay_later']); | |
| 70 | + $seed('yatra_payment_methods', []); | |
| 71 | + $seed('yatra_payment_test_mode', true); | |
| 72 | + $seed('yatra_auto_confirm_pay_later', true); | |
| 73 | + $seed('yatra_partial_payment', false); | |
| 58 | 74 | // Set gateway configs with proper structure - only enable pay_later by default |
| 59 | 75 | $gateway_configs = [ |
| 60 | 76 | 'pay_later' => [ |
| 61 | 77 | 'enabled' => true, |
| @@ -106,103 +122,108 @@ | ||
| 106 | 122 | 'api_key' => '', |
| 107 | 123 | 'api_secret' => '', |
| 108 | 124 | ] |
| 109 | 125 | ]; |
| 110 | - update_option('yatra_gateway_configs', $gateway_configs); | |
| 111 | - update_option('yatra_gateway_order', []); | |
| 126 | + $seed('yatra_gateway_configs', $gateway_configs); | |
| 127 | + $seed('yatra_gateway_order', []); | |
| 112 | 128 | |
| 113 | 129 | // Currency Settings - Match SettingsService defaults |
| 114 | - update_option('yatra_currency', 'USD'); | |
| 115 | - update_option('yatra_currency_position', 'before'); | |
| 116 | - update_option('yatra_thousand_separator', ','); | |
| 117 | - update_option('yatra_decimal_separator', '.'); | |
| 118 | - update_option('yatra_decimal_places', 2); | |
| 130 | + $seed('yatra_currency', 'USD'); | |
| 131 | + $seed('yatra_currency_position', 'before'); | |
| 132 | + $seed('yatra_thousand_separator', ','); | |
| 133 | + $seed('yatra_decimal_separator', '.'); | |
| 134 | + $seed('yatra_decimal_places', 2); | |
| 119 | 135 | |
| 120 | 136 | // Flexible Payment Settings - Match SettingsService defaults |
| 121 | - update_option('yatra_enable_deposit', false); | |
| 122 | - update_option('yatra_deposit_type', 'percentage'); | |
| 123 | - update_option('yatra_deposit_amount', 20); | |
| 124 | - update_option('yatra_deposit_required', false); | |
| 125 | - update_option('yatra_deposit_percentage', 20); | |
| 126 | - update_option('yatra_partial_payment_percentage', 30); | |
| 137 | + $seed('yatra_enable_deposit', false); | |
| 138 | + $seed('yatra_deposit_type', 'percentage'); | |
| 139 | + $seed('yatra_deposit_amount', 20); | |
| 140 | + $seed('yatra_deposit_required', false); | |
| 141 | + $seed('yatra_deposit_percentage', 20); | |
| 142 | + $seed('yatra_partial_payment_percentage', 30); | |
| 127 | 143 | |
| 128 | - update_option('yatra_allow_save_payment_methods', false); | |
| 144 | + $seed('yatra_allow_save_payment_methods', false); | |
| 129 | 145 | |
| 130 | 146 | // Trip Settings - Match SettingsService defaults |
| 131 | - update_option('yatra_trip_base', 'trip'); | |
| 132 | - update_option('yatra_trips_per_page', 12); | |
| 133 | - update_option('yatra_enable_wishlist', false); | |
| 134 | - update_option('yatra_enable_comparison', false); | |
| 135 | - update_option('yatra_show_sold_out', true); | |
| 147 | + $seed('yatra_trip_base', 'trip'); | |
| 148 | + $seed('yatra_trips_per_page', 12); | |
| 149 | + $seed('yatra_enable_wishlist', false); | |
| 150 | + $seed('yatra_enable_comparison', false); | |
| 151 | + $seed('yatra_show_sold_out', true); | |
| 136 | 152 | |
| 137 | 153 | // Customer Settings - Match SettingsService defaults |
| 138 | - update_option('yatra_enable_customer_accounts', true); | |
| 139 | - update_option('yatra_enable_customer_registration', true); | |
| 154 | + $seed('yatra_enable_customer_accounts', true); | |
| 155 | + $seed('yatra_enable_customer_registration', true); | |
| 140 | 156 | |
| 141 | 157 | // Booking Settings - Match SettingsService defaults |
| 142 | - update_option('yatra_booking_base', 'book'); | |
| 143 | - update_option('yatra_use_booking_page', false); | |
| 144 | - update_option('yatra_booking_page_id', 0); | |
| 145 | - update_option('yatra_enable_guest_booking', true); | |
| 146 | - update_option('yatra_booking_confirmation', true); | |
| 147 | - update_option('yatra_auto_confirm_bookings', false); | |
| 148 | - update_option('yatra_require_login', false); | |
| 149 | - update_option('yatra_allow_guest_checkout', true); | |
| 158 | + $seed('yatra_booking_base', 'book'); | |
| 159 | + $seed('yatra_use_booking_page', false); | |
| 160 | + $seed('yatra_booking_page_id', 0); | |
| 161 | + $seed('yatra_enable_guest_booking', true); | |
| 162 | + $seed('yatra_booking_confirmation', true); | |
| 163 | + $seed('yatra_auto_confirm_bookings', false); | |
| 164 | + $seed('yatra_require_login', false); | |
| 165 | + $seed('yatra_allow_guest_checkout', true); | |
| 150 | 166 | // cancellation_policy / cancellation_days / refund_policy |
| 151 | 167 | // intentionally not seeded — these are removed settings (see |
| 152 | 168 | // SettingsController::$default_settings comment). Existing |
| 153 | 169 | // sites that already have orphan values stored will keep |
| 154 | 170 | // them in wp_options; new sites won't acquire them. |
| 155 | - update_option('yatra_booking_expiry_hours', 24); | |
| 156 | - update_option('yatra_booking_reminder_days', 3); | |
| 157 | - update_option('yatra_allow_waitlist', true); | |
| 171 | + $seed('yatra_booking_expiry_hours', 24); | |
| 172 | + $seed('yatra_booking_reminder_days', 3); | |
| 173 | + $seed('yatra_allow_waitlist', true); | |
| 158 | 174 | |
| 159 | 175 | // Email identity: canonical keys (REST / EmailService) + legacy keys for older code paths |
| 160 | 176 | $wpAdminEmail = (string) get_option('admin_email', ''); |
| 161 | 177 | $blogName = (string) get_bloginfo('name'); |
| 162 | - update_option('yatra_from_email', $wpAdminEmail); | |
| 163 | - update_option('yatra_from_name', $blogName); | |
| 164 | - update_option('yatra_admin_email', $wpAdminEmail); | |
| 165 | - update_option('yatra_email_from_name', $blogName); | |
| 166 | - update_option('yatra_email_from_address', $wpAdminEmail); | |
| 167 | - update_option('yatra_enable_admin_notifications', true); | |
| 168 | - update_option('yatra_enable_customer_notifications', true); | |
| 178 | + $seed('yatra_from_email', $wpAdminEmail); | |
| 179 | + $seed('yatra_from_name', $blogName); | |
| 180 | + $seed('yatra_admin_email', $wpAdminEmail); | |
| 181 | + $seed('yatra_email_from_name', $blogName); | |
| 182 | + $seed('yatra_email_from_address', $wpAdminEmail); | |
| 183 | + $seed('yatra_enable_admin_notifications', true); | |
| 184 | + $seed('yatra_enable_customer_notifications', true); | |
| 169 | 185 | |
| 170 | 186 | // Default transactional template HTML + subjects (Email → Templates / settings API) |
| 171 | 187 | foreach (EmailTemplateDefaults::settingsOptionDefaults() as $optionKey => $value) { |
| 172 | - update_option('yatra_' . $optionKey, $value); | |
| 188 | + $seed('yatra_' . $optionKey, $value); | |
| 173 | 189 | } |
| 174 | - update_option('yatra_email_template_booking', true); | |
| 175 | - update_option('yatra_email_template_confirmation', true); | |
| 176 | - update_option('yatra_email_template_cancellation', true); | |
| 177 | - update_option('yatra_email_template_reminder', true); | |
| 178 | - update_option('yatra_email_template_admin_new_booking', true); | |
| 179 | - update_option('yatra_email_template_admin_payment', true); | |
| 180 | - update_option('yatra_email_template_admin_cancellation', true); | |
| 181 | - update_option('yatra_email_template_trip_consent', true); | |
| 182 | - update_option('yatra_email_template_customer_verification', true); | |
| 183 | - update_option('yatra_email_template_guest_verification', true); | |
| 184 | - update_option('yatra_email_template_booking_completed', true); | |
| 185 | - update_option('yatra_email_template_booking_expired_customer', true); | |
| 186 | - update_option('yatra_email_template_admin_booking_expired', true); | |
| 187 | - update_option('yatra_email_template_scheduled_payment_reminder', true); | |
| 188 | - update_option('yatra_email_template_scheduled_payment_succeeded', true); | |
| 189 | - update_option('yatra_email_template_scheduled_payment_failed', true); | |
| 190 | - update_option('yatra_email_template_admin_scheduled_payment_failed', true); | |
| 191 | - update_option('yatra_email_template_enquiry_received', true); | |
| 192 | - update_option('yatra_email_template_enquiry_admin', true); | |
| 193 | - update_option('yatra_email_template_enquiry_response', true); | |
| 194 | - update_option('yatra_email_template_review_request', true); | |
| 195 | - update_option('yatra_email_template_abandoned_booking_recovery_first', true); | |
| 196 | - update_option('yatra_email_template_abandoned_booking_recovery_second', true); | |
| 197 | - update_option('yatra_email_template_abandoned_booking_recovery_final', true); | |
| 190 | + $seed('yatra_email_template_booking', true); | |
| 191 | + $seed('yatra_email_template_confirmation', true); | |
| 192 | + $seed('yatra_email_template_cancellation', true); | |
| 193 | + $seed('yatra_email_template_reminder', true); | |
| 194 | + $seed('yatra_email_template_admin_new_booking', true); | |
| 195 | + $seed('yatra_email_template_admin_payment', true); | |
| 196 | + $seed('yatra_email_template_admin_cancellation', true); | |
| 197 | + $seed('yatra_email_template_trip_consent', true); | |
| 198 | + $seed('yatra_email_template_customer_verification', true); | |
| 199 | + $seed('yatra_email_template_guest_verification', true); | |
| 200 | + $seed('yatra_email_template_booking_completed', true); | |
| 201 | + $seed('yatra_email_template_booking_expired_customer', true); | |
| 202 | + $seed('yatra_email_template_admin_booking_expired', true); | |
| 203 | + $seed('yatra_email_template_scheduled_payment_reminder', true); | |
| 204 | + $seed('yatra_email_template_scheduled_payment_succeeded', true); | |
| 205 | + $seed('yatra_email_template_scheduled_payment_failed', true); | |
| 206 | + $seed('yatra_email_template_admin_scheduled_payment_failed', true); | |
| 207 | + $seed('yatra_email_template_enquiry_received', true); | |
| 208 | + $seed('yatra_email_template_enquiry_admin', true); | |
| 209 | + $seed('yatra_email_template_enquiry_response', true); | |
| 210 | + $seed('yatra_email_template_review_request', true); | |
| 211 | + $seed('yatra_email_template_abandoned_booking_recovery_first', true); | |
| 212 | + $seed('yatra_email_template_abandoned_booking_recovery_second', true); | |
| 213 | + $seed('yatra_email_template_abandoned_booking_recovery_final', true); | |
| 198 | 214 | |
| 199 | - // Clear any existing Stripe/PayPal settings that might exist | |
| 200 | - delete_option('yatra_stripe_settings'); | |
| 201 | - delete_option('yatra_paypal_settings'); | |
| 215 | + // Clear pre-existing legacy Stripe/PayPal settings, but only on a | |
| 216 | + // brand-new install. On a reactivation these may hold the operator's | |
| 217 | + // configured gateway data, so deleting them would be destructive. | |
| 218 | + if ($isFreshInstall) { | |
| 219 | + delete_option('yatra_stripe_settings'); | |
| 220 | + delete_option('yatra_paypal_settings'); | |
| 221 | + } | |
| 202 | 222 | |
| 203 | - // Set installation tracking (not in SettingsService but needed for tracking) | |
| 204 | - update_option('yatra_installation_date', current_time('mysql')); | |
| 223 | + // Installation tracking: stamp the date once (seed); keep the | |
| 224 | + // version current so upgrade routines can detect version changes. | |
| 225 | + $seed('yatra_installation_date', current_time('mysql')); | |
| 205 | 226 | update_option('yatra_version', defined('YATRA_VERSION') ? YATRA_VERSION : '3.0.3'); |
| 206 | 227 | |
| 207 | 228 | |
| 208 | 229 | } |
| @@ -492,8 +513,74 @@ | ||
| 492 | 513 | . "WHERE `status` = '' AND `payment_status` = 'pending'" |
| 493 | 514 | ); |
| 494 | 515 | |
| 495 | 516 | update_option('yatra_booking_status_pending_verification_v1', '1', false); |
| 517 | + } | |
| 518 | + | |
| 519 | + /** | |
| 520 | + * Add the `duration_hours` column to the trips table for hour-based | |
| 521 | + * (single-day) tours. Purely additive and nullable — existing trips get | |
| 522 | + * NULL and behave exactly as before (day-based via `duration_days`). Only | |
| 523 | + * tours that later set a positive `duration_hours` change behaviour. | |
| 524 | + * | |
| 525 | + * Idempotent: guarded by a one-shot option AND an INFORMATION_SCHEMA check, | |
| 526 | + * so it runs its ALTER at most once and is a no-op when the column already | |
| 527 | + * exists (fresh installs get it from TripsTable::getSchema()). | |
| 528 | + */ | |
| 529 | + public static function maybeAddTripDurationHoursColumn(): void | |
| 530 | + { | |
| 531 | + if (get_option('yatra_trip_duration_hours_v1')) { | |
| 532 | + return; | |
| 533 | + } | |
| 534 | + | |
| 535 | + if (!class_exists('Yatra\\Database\\Tables\\TripsTable')) { | |
| 536 | + return; | |
| 537 | + } | |
| 538 | + | |
| 539 | + $table = \Yatra\Database\Tables\TripsTable::getTableName(); | |
| 540 | + if (!self::databaseTableExists($table)) { | |
| 541 | + return; | |
| 542 | + } | |
| 543 | + | |
| 544 | + global $wpdb; | |
| 545 | + | |
| 546 | + $columnExists = $wpdb->get_var( | |
| 547 | + $wpdb->prepare( | |
| 548 | + "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS | |
| 549 | + WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = %s", | |
| 550 | + DB_NAME, | |
| 551 | + $table, | |
| 552 | + 'duration_hours' | |
| 553 | + ) | |
| 554 | + ); | |
| 555 | + | |
| 556 | + if (!$columnExists) { | |
| 557 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name escaped, static column definition. | |
| 558 | + $wpdb->query( | |
| 559 | + 'ALTER TABLE `' . esc_sql($table) . '` ' | |
| 560 | + . "ADD COLUMN `duration_hours` smallint(5) UNSIGNED DEFAULT NULL " | |
| 561 | + . "COMMENT 'Duration in hours for hour-based single-day tours, NULL means day-based' " | |
| 562 | + . 'AFTER `duration_nights`' | |
| 563 | + ); | |
| 564 | + | |
| 565 | + // Re-check so we only mark this done when the column really exists. | |
| 566 | + // If the ALTER failed (e.g. a restrictive host), leave the one-shot | |
| 567 | + // flag unset so it retries on the next admin load rather than | |
| 568 | + // disabling the feature permanently. | |
| 569 | + $columnExists = $wpdb->get_var( | |
| 570 | + $wpdb->prepare( | |
| 571 | + "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS | |
| 572 | + WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = %s", | |
| 573 | + DB_NAME, | |
| 574 | + $table, | |
| 575 | + 'duration_hours' | |
| 576 | + ) | |
| 577 | + ); | |
| 578 | + } | |
| 579 | + | |
| 580 | + if ($columnExists) { | |
| 581 | + update_option('yatra_trip_duration_hours_v1', '1', false); | |
| 582 | + } | |
| 496 | 583 | } |
| 497 | 584 | |
| 498 | 585 | /** |
| 499 | 586 | * Fill canonical + legacy email identity options when empty (upgrades, partial installs, or empty strings in DB). |