| @@ -16,13 +16,15 @@ | ||
| 16 | 16 | * @return array<string, string> |
| 17 | 17 | */ |
| 18 | 18 | public static function settingsOptionDefaults(): array |
| 19 | 19 | { |
| 20 | - return [ | |
| 20 | + $defaults = [ | |
| 21 | 21 | 'email_tpl_booking_subject' => '✈️ [{{site_name}}] Booking update · {{booking_reference}}', |
| 22 | 22 | 'email_tpl_booking_body' => self::htmlBookingCustomer(), |
| 23 | 23 | 'email_tpl_payment_subject' => '✅ [{{site_name}}] Payment received · {{booking_reference}}', |
| 24 | 24 | 'email_tpl_payment_body' => self::htmlPaymentCustomer(), |
| 25 | + 'email_tpl_partial_payment_subject' => '💳 [{{site_name}}] Part payment received · {{booking_reference}}', | |
| 26 | + 'email_tpl_partial_payment_body' => self::htmlPartialPaymentCustomer(), | |
| 25 | 27 | 'email_tpl_cancellation_subject' => '📋 [{{site_name}}] Booking cancelled · {{booking_reference}}', |
| 26 | 28 | 'email_tpl_cancellation_body' => self::htmlCancellationCustomer(), |
| 27 | 29 | 'email_tpl_reminder_subject' => '🗓️ [{{site_name}}] Your trip is coming up · {{booking_reference}}', |
| 28 | 30 | 'email_tpl_reminder_body' => self::htmlReminderCustomer(), |
| @@ -31,9 +33,73 @@ | ||
| 31 | 33 | 'email_tpl_admin_payment_subject' => '✅ [{{site_name}}] Payment received · {{booking_reference}} (#{{booking_id}})', |
| 32 | 34 | 'email_tpl_admin_payment_body' => self::htmlAdminPaymentReceived(), |
| 33 | 35 | 'email_tpl_admin_cancellation_subject' => '📋 [{{site_name}}] Booking cancelled · {{booking_reference}} (#{{booking_id}})', |
| 34 | 36 | 'email_tpl_admin_cancellation_body' => self::htmlAdminBookingCancelled(), |
| 37 | + 'email_tpl_trip_consent_subject' => '📝 [{{site_name}}] Action required · {{form_name}}', | |
| 38 | + 'email_tpl_trip_consent_body' => self::htmlTripConsentRequest(), | |
| 39 | + 'email_tpl_customer_verification_subject' => '✉️ [{{site_name}}] Verify your email address', | |
| 40 | + 'email_tpl_customer_verification_body' => self::htmlCustomerEmailVerification(), | |
| 41 | + // Guest-checkout verification (distinct from account registration above). | |
| 42 | + // Subject mirrors the render's hardcoded fallback; body reuses the | |
| 43 | + // customer-verification HTML so the email is identical until an operator | |
| 44 | + // customizes it. Registering these keys here is required so the settings | |
| 45 | + // save accepts them (SettingsController whitelists by default_settings) | |
| 46 | + // and the admin template editor pre-fills them. | |
| 47 | + 'email_tpl_guest_verification_subject' => '✉️ [{{site_name}}] Verify your email to complete your booking', | |
| 48 | + 'email_tpl_guest_verification_body' => self::htmlCustomerEmailVerification(), | |
| 49 | + // Account email change: confirmation link to the NEW address, and a | |
| 50 | + // security notice to the OLD address once the change is confirmed. | |
| 51 | + 'email_tpl_account_email_change_subject' => '✉️ [{{site_name}}] Confirm your new email address', | |
| 52 | + 'email_tpl_account_email_change_body' => self::htmlAccountEmailChangeRequest(), | |
| 53 | + 'email_tpl_account_email_changed_subject' => '🔔 [{{site_name}}] Your email address was changed', | |
| 54 | + 'email_tpl_account_email_changed_body' => self::htmlAccountEmailChanged(), | |
| 55 | + 'email_tpl_booking_completed_subject' => '🌟 [{{site_name}}] Thanks for traveling · {{booking_reference}}', | |
| 56 | + 'email_tpl_booking_completed_body' => self::htmlTripCompleted(), | |
| 57 | + 'email_tpl_booking_expired_customer_subject' => '⏱️ [{{site_name}}] Booking expired · {{booking_reference}}', | |
| 58 | + 'email_tpl_booking_expired_customer_body' => self::htmlBookingExpiredCustomer(), | |
| 59 | + 'email_tpl_admin_booking_expired_subject' => '⏱️ [{{site_name}}] Booking expired · {{booking_reference}} (#{{booking_id}})', | |
| 60 | + 'email_tpl_admin_booking_expired_body' => self::htmlAdminBookingExpired(), | |
| 61 | + 'email_tpl_scheduled_payment_reminder_subject' => '💳 [{{site_name}}] Upcoming payment · {{booking_reference}}', | |
| 62 | + 'email_tpl_scheduled_payment_reminder_body' => self::htmlScheduledPaymentReminder(), | |
| 63 | + 'email_tpl_scheduled_payment_succeeded_subject' => '✅ [{{site_name}}] Scheduled payment received · {{booking_reference}}', | |
| 64 | + 'email_tpl_scheduled_payment_succeeded_body' => self::htmlScheduledPaymentSucceeded(), | |
| 65 | + 'email_tpl_scheduled_payment_failed_subject' => '⚠️ [{{site_name}}] Payment issue · {{booking_reference}}', | |
| 66 | + 'email_tpl_scheduled_payment_failed_body' => self::htmlScheduledPaymentFailed(), | |
| 67 | + 'email_tpl_admin_scheduled_payment_failed_subject' => '⚠️ [{{site_name}}] Scheduled payment failed · {{booking_reference}}', | |
| 68 | + 'email_tpl_admin_scheduled_payment_failed_body' => self::htmlAdminScheduledPaymentFailed(), | |
| 69 | + 'email_tpl_enquiry_admin_subject' => '💬 [{{site_name}}] New enquiry · {{customer_name}}', | |
| 70 | + 'email_tpl_enquiry_admin_body' => self::htmlEnquiryAdmin(), | |
| 71 | + 'email_tpl_enquiry_received_subject' => '✉️ [{{site_name}}] We received your message', | |
| 72 | + 'email_tpl_enquiry_received_body' => self::htmlEnquiryCustomer(), | |
| 73 | + 'email_tpl_enquiry_response_subject' => '💬 [{{site_name}}] Re: your enquiry', | |
| 74 | + 'email_tpl_enquiry_response_body' => self::htmlEnquiryResponse(), | |
| 75 | + 'email_tpl_review_request_subject' => '⭐ [{{site_name}}] How was {{trip_name}}?', | |
| 76 | + 'email_tpl_review_request_body' => self::htmlReviewRequest(), | |
| 77 | + 'email_tpl_abandoned_booking_recovery_first_subject' => '🛒 [{{site_name}}] Complete your booking', | |
| 78 | + 'email_tpl_abandoned_booking_recovery_first_body' => self::htmlAbandonedBookingRecoveryFirst(), | |
| 79 | + 'email_tpl_abandoned_booking_recovery_second_subject' => '⏳ [{{site_name}}] Still interested? Your booking is waiting', | |
| 80 | + 'email_tpl_abandoned_booking_recovery_second_body' => self::htmlAbandonedBookingRecoverySecond(), | |
| 81 | + 'email_tpl_abandoned_booking_recovery_final_subject' => '⚠️ [{{site_name}}] Final reminder: complete your booking', | |
| 82 | + 'email_tpl_abandoned_booking_recovery_final_body' => self::htmlAbandonedBookingRecoveryFinal(), | |
| 35 | 83 | ]; |
| 84 | + | |
| 85 | + // Every template also gets an opt-in Bcc / Cc option, derived from its | |
| 86 | + // own subject key so the two lists can never drift apart (and so a | |
| 87 | + // template type added later picks them up for free). Empty by default: | |
| 88 | + // no address configured means no copy is sent and nothing changes for | |
| 89 | + // existing sites. Registered here because this array doubles as the | |
| 90 | + // whitelist of settings the REST API will persist. | |
| 91 | + foreach (array_keys($defaults) as $key) { | |
| 92 | + if (substr($key, -8) !== '_subject') { | |
| 93 | + continue; | |
| 94 | + } | |
| 95 | + | |
| 96 | + $base = substr($key, 0, -8); | |
| 97 | + $defaults[$base . '_bcc'] = ''; | |
| 98 | + $defaults[$base . '_cc'] = ''; | |
| 99 | + } | |
| 100 | + | |
| 101 | + return $defaults; | |
| 36 | 102 | } |
| 37 | 103 | |
| 38 | 104 | /** |
| 39 | 105 | * Full subject + body for Pro system templates (DB). Null = no bundled default for that key. |
| @@ -66,8 +132,12 @@ | ||
| 66 | 132 | 'booking_cancelled' => [ |
| 67 | 133 | 'subject' => '📋 Booking cancelled · {{booking_reference}}', |
| 68 | 134 | 'body' => self::htmlCancellationCustomer(), |
| 69 | 135 | ], |
| 136 | + 'admin_booking_cancelled' => [ | |
| 137 | + 'subject' => '📋 Booking cancelled · {{booking_reference}} (#{{booking_id}})', | |
| 138 | + 'body' => self::htmlAdminBookingCancelled(), | |
| 139 | + ], | |
| 70 | 140 | 'booking_completed' => [ |
| 71 | 141 | 'subject' => '🌟 Thanks for traveling with us · {{booking_reference}}', |
| 72 | 142 | 'body' => self::htmlTripCompleted(), |
| 73 | 143 | ], |
| @@ -74,8 +144,16 @@ | ||
| 74 | 144 | 'payment_received' => [ |
| 75 | 145 | 'subject' => '✅ Payment received · {{booking_reference}}', |
| 76 | 146 | 'body' => self::htmlPaymentCustomer(), |
| 77 | 147 | ], |
| 148 | + 'admin_payment_received' => [ | |
| 149 | + 'subject' => '✅ Payment received · {{booking_reference}} (#{{booking_id}})', | |
| 150 | + 'body' => self::htmlAdminPaymentReceived(), | |
| 151 | + ], | |
| 152 | + 'partial_payment_received' => [ | |
| 153 | + 'subject' => '💳 Part payment received · {{booking_reference}}', | |
| 154 | + 'body' => self::htmlPartialPaymentCustomer(), | |
| 155 | + ], | |
| 78 | 156 | 'payment_reminder' => [ |
| 79 | 157 | 'subject' => '💳 Payment reminder · {{booking_reference}}', |
| 80 | 158 | 'body' => self::htmlPaymentReminder(), |
| 81 | 159 | ], |
| @@ -98,8 +176,68 @@ | ||
| 98 | 176 | 'review_request' => [ |
| 99 | 177 | 'subject' => '⭐ How was {{trip_name}}?', |
| 100 | 178 | 'body' => self::htmlReviewRequest(), |
| 101 | 179 | ], |
| 180 | + 'trip_consent_request' => [ | |
| 181 | + 'subject' => '📝 [{{site_name}}] Action required · {{form_name}}', | |
| 182 | + 'body' => self::htmlTripConsentRequest(), | |
| 183 | + ], | |
| 184 | + 'customer_email_verification' => [ | |
| 185 | + 'subject' => '✉️ [{{site_name}}] Verify your email address', | |
| 186 | + 'body' => self::htmlCustomerEmailVerification(), | |
| 187 | + ], | |
| 188 | + 'guest_email_verification' => [ | |
| 189 | + // Guest-checkout verification reuses the customer-verification | |
| 190 | + // body — the flow is identical (click a magic link to prove | |
| 191 | + // ownership) and the booking-specific copy is injected at | |
| 192 | + // send-time via {{intro_paragraph}} / {{footer_note}}. | |
| 193 | + 'subject' => '✉️ [{{site_name}}] Verify your email to complete your booking', | |
| 194 | + 'body' => self::htmlCustomerEmailVerification(), | |
| 195 | + ], | |
| 196 | + 'account_email_change_request' => [ | |
| 197 | + 'subject' => '✉️ [{{site_name}}] Confirm your new email address', | |
| 198 | + 'body' => self::htmlAccountEmailChangeRequest(), | |
| 199 | + ], | |
| 200 | + 'account_email_changed' => [ | |
| 201 | + 'subject' => '🔔 [{{site_name}}] Your email address was changed', | |
| 202 | + 'body' => self::htmlAccountEmailChanged(), | |
| 203 | + ], | |
| 204 | + 'booking_expired_customer' => [ | |
| 205 | + 'subject' => '⏱️ [{{site_name}}] Booking expired · {{booking_reference}}', | |
| 206 | + 'body' => self::htmlBookingExpiredCustomer(), | |
| 207 | + ], | |
| 208 | + 'admin_booking_expired' => [ | |
| 209 | + 'subject' => '⏱️ [{{site_name}}] Booking expired · {{booking_reference}} (#{{booking_id}})', | |
| 210 | + 'body' => self::htmlAdminBookingExpired(), | |
| 211 | + ], | |
| 212 | + 'scheduled_payment_reminder' => [ | |
| 213 | + 'subject' => '💳 [{{site_name}}] Upcoming payment · {{booking_reference}}', | |
| 214 | + 'body' => self::htmlScheduledPaymentReminder(), | |
| 215 | + ], | |
| 216 | + 'scheduled_payment_succeeded' => [ | |
| 217 | + 'subject' => '✅ [{{site_name}}] Scheduled payment received · {{booking_reference}}', | |
| 218 | + 'body' => self::htmlScheduledPaymentSucceeded(), | |
| 219 | + ], | |
| 220 | + 'scheduled_payment_failed' => [ | |
| 221 | + 'subject' => '⚠️ [{{site_name}}] Payment issue · {{booking_reference}}', | |
| 222 | + 'body' => self::htmlScheduledPaymentFailed(), | |
| 223 | + ], | |
| 224 | + 'admin_scheduled_payment_failed' => [ | |
| 225 | + 'subject' => '⚠️ [{{site_name}}] Scheduled payment failed · {{booking_reference}}', | |
| 226 | + 'body' => self::htmlAdminScheduledPaymentFailed(), | |
| 227 | + ], | |
| 228 | + 'abandoned_booking_recovery_first' => [ | |
| 229 | + 'subject' => '🛒 [{{site_name}}] Complete your booking', | |
| 230 | + 'body' => self::htmlAbandonedBookingRecoveryFirst(), | |
| 231 | + ], | |
| 232 | + 'abandoned_booking_recovery_second' => [ | |
| 233 | + 'subject' => '⏳ [{{site_name}}] Still interested? Your booking is waiting', | |
| 234 | + 'body' => self::htmlAbandonedBookingRecoverySecond(), | |
| 235 | + ], | |
| 236 | + 'abandoned_booking_recovery_final' => [ | |
| 237 | + 'subject' => '⚠️ [{{site_name}}] Final reminder: complete your booking', | |
| 238 | + 'body' => self::htmlAbandonedBookingRecoveryFinal(), | |
| 239 | + ], | |
| 102 | 240 | ]; |
| 103 | 241 | |
| 104 | 242 | return $map[$templateKey] ?? null; |
| 105 | 243 | } |
| @@ -149,8 +287,9 @@ | ||
| 149 | 287 | })); |
| 150 | 288 | |
| 151 | 289 | $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">' |
| 152 | 290 | . sprintf( |
| 291 | + /* translators: %s: customer name */ | |
| 153 | 292 | esc_html__('Hello %s,', 'yatra'), |
| 154 | 293 | $name |
| 155 | 294 | ) |
| 156 | 295 | . '</p>' |
| @@ -186,9 +325,13 @@ | ||
| 186 | 325 | return $row['value'] !== ''; |
| 187 | 326 | })); |
| 188 | 327 | |
| 189 | 328 | $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">' |
| 190 | - . sprintf(esc_html__('Dear %s,', 'yatra'), $name) | |
| 329 | + . sprintf( | |
| 330 | + /* translators: %s: customer name */ | |
| 331 | + esc_html__('Dear %s,', 'yatra'), | |
| 332 | + $name | |
| 333 | + ) | |
| 191 | 334 | . '</p>' |
| 192 | 335 | . '<p style="margin:0 0 8px;color:#475569;">' |
| 193 | 336 | . esc_html__('We’ve received your payment — thank you! Here’s a quick summary.', 'yatra') |
| 194 | 337 | . '</p>' |
| @@ -203,10 +346,61 @@ | ||
| 203 | 346 | ); |
| 204 | 347 | } |
| 205 | 348 | |
| 206 | 349 | /** |
| 350 | + * Part-payment received — a balance is still outstanding. | |
| 351 | + * | |
| 352 | + * Wording differs from the full-payment email on purpose: it confirms what | |
| 353 | + * was received AND states what is still owed, which is the whole reason this | |
| 354 | + * template is separate. | |
| 355 | + * | |
| 207 | 356 | * @param array<string, string> $v |
| 208 | 357 | */ |
| 358 | + public static function fallbackTransactionalPartialPayment(array $v): string | |
| 359 | + { | |
| 360 | + // Null-coalescing rather than `?:` so this renders cleanly even when a | |
| 361 | + // caller passes a sparse variable set — this fallback runs on sites that | |
| 362 | + // upgraded before the template's body option was seeded. | |
| 363 | + $name = esc_html( | |
| 364 | + ($v['customer_first_name'] ?? '') ?: ($v['customer_name'] ?? '') ?: __('Customer', 'yatra') | |
| 365 | + ); | |
| 366 | + $site = esc_html($v['site_name'] ?? get_bloginfo('name')); | |
| 367 | + $rows = array_values(array_filter([ | |
| 368 | + ['label' => __('Booking reference', 'yatra'), 'value' => esc_html($v['booking_reference'] ?? '')], | |
| 369 | + ['label' => __('Amount received', 'yatra'), 'value' => esc_html($v['payment_amount_formatted'] ?? '')], | |
| 370 | + ['label' => __('Paid so far', 'yatra'), 'value' => esc_html($v['amount_paid_formatted'] ?? '')], | |
| 371 | + ['label' => __('Balance remaining', 'yatra'), 'value' => esc_html($v['amount_due_formatted'] ?? '')], | |
| 372 | + ['label' => __('Booking total', 'yatra'), 'value' => esc_html($v['total_amount_formatted'] ?? '')], | |
| 373 | + ['label' => __('Method', 'yatra'), 'value' => esc_html($v['payment_method'] ?? '')], | |
| 374 | + ['label' => __('Transaction ID', 'yatra'), 'value' => esc_html($v['transaction_id'] ?? '')], | |
| 375 | + ], static function (array $row): bool { | |
| 376 | + return $row['value'] !== ''; | |
| 377 | + })); | |
| 378 | + | |
| 379 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">' | |
| 380 | + . sprintf( | |
| 381 | + /* translators: %s: customer name */ | |
| 382 | + esc_html__('Dear %s,', 'yatra'), | |
| 383 | + $name | |
| 384 | + ) | |
| 385 | + . '</p>' | |
| 386 | + . '<p style="margin:0 0 8px;color:#475569;">' | |
| 387 | + . esc_html__('Thank you — we’ve received your part payment. Your booking is not yet paid in full, so the remaining balance is shown below.', 'yatra') | |
| 388 | + . '</p>' | |
| 389 | + . EmailTemplateLayout::detailCard($rows); | |
| 390 | + | |
| 391 | + return EmailTemplateLayout::customer( | |
| 392 | + '💳', | |
| 393 | + __('Part payment received', 'yatra'), | |
| 394 | + $inner, | |
| 395 | + $site, | |
| 396 | + __('Payment update', 'yatra') | |
| 397 | + ); | |
| 398 | + } | |
| 399 | + | |
| 400 | + /** | |
| 401 | + * @param array<string, string> $v | |
| 402 | + */ | |
| 209 | 403 | public static function fallbackTransactionalCancellation(array $v): string |
| 210 | 404 | { |
| 211 | 405 | $name = esc_html($v['customer_first_name'] ?: $v['customer_name'] ?: __('Customer', 'yatra')); |
| 212 | 406 | $site = esc_html($v['site_name'] ?? get_bloginfo('name')); |
| @@ -218,9 +412,13 @@ | ||
| 218 | 412 | return $row['value'] !== ''; |
| 219 | 413 | })); |
| 220 | 414 | |
| 221 | 415 | $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">' |
| 222 | - . sprintf(esc_html__('Hello %s,', 'yatra'), $name) | |
| 416 | + . sprintf( | |
| 417 | + /* translators: %s: customer name */ | |
| 418 | + esc_html__('Hello %s,', 'yatra'), | |
| 419 | + $name | |
| 420 | + ) | |
| 223 | 421 | . '</p>' |
| 224 | 422 | . '<p style="margin:0 0 20px;color:#475569;">' |
| 225 | 423 | . esc_html__('Your booking has been cancelled as requested. If you have questions, just reply to this email.', 'yatra') |
| 226 | 424 | . '</p>' |
| @@ -260,9 +458,13 @@ | ||
| 260 | 458 | $rows = array_merge($rows, $balanceRow); |
| 261 | 459 | } |
| 262 | 460 | |
| 263 | 461 | $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">' |
| 264 | - . sprintf(esc_html__('Dear %s,', 'yatra'), $name) | |
| 462 | + . sprintf( | |
| 463 | + /* translators: %s: customer name */ | |
| 464 | + esc_html__('Dear %s,', 'yatra'), | |
| 465 | + $name | |
| 466 | + ) | |
| 265 | 467 | . '</p>' |
| 266 | 468 | . '<p style="margin:0 0 20px;color:#475569;">' |
| 267 | 469 | . ($days !== '' |
| 268 | 470 | ? sprintf( |
| @@ -284,8 +486,205 @@ | ||
| 284 | 486 | ); |
| 285 | 487 | } |
| 286 | 488 | |
| 287 | 489 | /** |
| 490 | + * @param array<string, string> $v | |
| 491 | + */ | |
| 492 | + public static function fallbackTransactionalTripConsent(array $v): string | |
| 493 | + { | |
| 494 | + $recipient = esc_html($v['recipient_name'] ?: __('Traveler', 'yatra')); | |
| 495 | + $site = esc_html($v['site_name'] ?? get_bloginfo('name')); | |
| 496 | + $formNameRaw = (string) ($v['form_name'] ?? ''); | |
| 497 | + $formNameEsc = esc_html($formNameRaw); | |
| 498 | + $link = esc_url((string) ($v['consent_link'] ?? home_url('/'))); | |
| 499 | + $rows = array_values(array_filter([ | |
| 500 | + ['label' => __('Form', 'yatra'), 'value' => $formNameEsc], | |
| 501 | + ['label' => __('Trip', 'yatra'), 'value' => esc_html($v['trip_name'] ?? '')], | |
| 502 | + ['label' => __('Travel date', 'yatra'), 'value' => esc_html($v['travel_date'] ?? '')], | |
| 503 | + ['label' => __('Booking reference', 'yatra'), 'value' => esc_html($v['booking_reference'] ?? '')], | |
| 504 | + ], static function (array $row): bool { | |
| 505 | + return $row['value'] !== ''; | |
| 506 | + })); | |
| 507 | + | |
| 508 | + $notice = !empty($v['expiry_notice_html']) | |
| 509 | + ? '<div style="margin:16px 0 0;padding:12px 16px;background:#fff7ed;border:1px solid #fed7aa;border-radius:10px;font-size:14px;color:#9a3412;">' | |
| 510 | + . wp_kses_post((string) $v['expiry_notice_html']) | |
| 511 | + . '</div>' | |
| 512 | + : ''; | |
| 513 | + | |
| 514 | + $testBlock = !empty($v['consent_test_notice_html']) | |
| 515 | + ? '<div style="margin:0 0 20px;padding:12px 16px;background:#f0fdfa;border:1px solid #99f6e4;border-radius:10px;font-size:14px;color:#115e59;">' | |
| 516 | + . wp_kses_post((string) $v['consent_test_notice_html']) | |
| 517 | + . '</div>' | |
| 518 | + : ''; | |
| 519 | + | |
| 520 | + $inner = $testBlock | |
| 521 | + . '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">' | |
| 522 | + . sprintf( | |
| 523 | + /* translators: %s: customer name */ | |
| 524 | + esc_html__('Hello %s,', 'yatra'), | |
| 525 | + $recipient | |
| 526 | + ) | |
| 527 | + . '</p>' | |
| 528 | + . '<p style="margin:0 0 20px;color:#475569;">' | |
| 529 | + . sprintf( | |
| 530 | + /* translators: %s: consent form title */ | |
| 531 | + esc_html__('Please complete and sign “%s” before your trip.', 'yatra'), | |
| 532 | + $formNameRaw !== '' ? $formNameEsc : esc_html__('your consent form', 'yatra') | |
| 533 | + ) | |
| 534 | + . '</p>' | |
| 535 | + . EmailTemplateLayout::detailCard($rows) | |
| 536 | + . $notice | |
| 537 | + . EmailTemplateLayout::button($link, __('Sign consent form', 'yatra')) | |
| 538 | + . '<p style="margin:20px 0 0;font-size:13px;color:#64748b;">' | |
| 539 | + . esc_html__('If the button does not work, copy this link into your browser:', 'yatra') | |
| 540 | + . '</p>' | |
| 541 | + . '<p style="margin:8px 0 0;font-size:13px;word-break:break-all;"><a href="' . $link . '" style="color:#0d9488;">' . $link . '</a></p>'; | |
| 542 | + | |
| 543 | + return EmailTemplateLayout::customer( | |
| 544 | + '📝', | |
| 545 | + __('Consent required', 'yatra'), | |
| 546 | + $inner, | |
| 547 | + $site, | |
| 548 | + __('Sign your trip consent form', 'yatra') | |
| 549 | + ); | |
| 550 | + } | |
| 551 | + | |
| 552 | + /** | |
| 553 | + * @param array<string, string> $v | |
| 554 | + */ | |
| 555 | + public static function fallbackTransactionalCustomerEmailVerification(array $v): string | |
| 556 | + { | |
| 557 | + $firstRaw = (string) ($v['customer_first_name'] ?? ''); | |
| 558 | + if ($firstRaw === '') { | |
| 559 | + $firstRaw = (string) ($v['customer_name'] ?? ''); | |
| 560 | + } | |
| 561 | + $first = esc_html($firstRaw !== '' ? $firstRaw : __('there', 'yatra')); | |
| 562 | + $site = esc_html($v['site_name'] ?? get_bloginfo('name')); | |
| 563 | + $link = esc_url((string) ($v['verification_link'] ?? home_url('/'))); | |
| 564 | + $intro = isset($v['intro_paragraph']) ? esc_html((string) $v['intro_paragraph']) : ''; | |
| 565 | + $footer = isset($v['footer_note']) ? esc_html((string) $v['footer_note']) : ''; | |
| 566 | + $expiryInner = !empty($v['expiry_notice_html']) | |
| 567 | + ? wp_kses_post((string) $v['expiry_notice_html']) | |
| 568 | + : esc_html( | |
| 569 | + sprintf( | |
| 570 | + /* translators: %d: hours until link expiry */ | |
| 571 | + __('This verification link expires in %d hours for your security.', 'yatra'), | |
| 572 | + 24 | |
| 573 | + ) | |
| 574 | + ); | |
| 575 | + $expiry = '<div style="margin:20px 0 0;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">' | |
| 576 | + . $expiryInner | |
| 577 | + . '</div>'; | |
| 578 | + | |
| 579 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">' | |
| 580 | + . sprintf( | |
| 581 | + /* translators: %s: customer name */ | |
| 582 | + esc_html__('Hello %s,', 'yatra'), | |
| 583 | + $first | |
| 584 | + ) | |
| 585 | + . '</p>' | |
| 586 | + . ($intro !== '' ? '<p style="margin:0 0 20px;color:#475569;">' . $intro . '</p>' : '') | |
| 587 | + . EmailTemplateLayout::button($link, __('Verify email address', 'yatra')) | |
| 588 | + . $expiry | |
| 589 | + . ($footer !== '' ? '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">' . $footer . '</p>' : '') | |
| 590 | + . '<p style="margin:20px 0 0;font-size:13px;color:#64748b;">' | |
| 591 | + . esc_html__('If the button does not work, copy this link into your browser:', 'yatra') | |
| 592 | + . '</p>' | |
| 593 | + . '<p style="margin:8px 0 0;font-size:13px;word-break:break-all;"><a href="' . $link . '" style="color:#0d9488;">' . $link . '</a></p>'; | |
| 594 | + | |
| 595 | + return EmailTemplateLayout::customer( | |
| 596 | + '✉️', | |
| 597 | + __('Verify your email', 'yatra'), | |
| 598 | + $inner, | |
| 599 | + $site, | |
| 600 | + __('Account verification', 'yatra') | |
| 601 | + ); | |
| 602 | + } | |
| 603 | + | |
| 604 | + /** | |
| 605 | + * Fallback HTML for the account email-change confirmation (empty saved template). | |
| 606 | + * | |
| 607 | + * @param array<string, string> $v | |
| 608 | + */ | |
| 609 | + public static function fallbackTransactionalAccountEmailChangeRequest(array $v): string | |
| 610 | + { | |
| 611 | + $firstRaw = (string) ($v['customer_first_name'] ?? ''); | |
| 612 | + if ($firstRaw === '') { | |
| 613 | + $firstRaw = (string) ($v['customer_name'] ?? ''); | |
| 614 | + } | |
| 615 | + $first = esc_html($firstRaw !== '' ? $firstRaw : __('there', 'yatra')); | |
| 616 | + $site = esc_html($v['site_name'] ?? get_bloginfo('name')); | |
| 617 | + $link = esc_url((string) ($v['verification_link'] ?? home_url('/'))); | |
| 618 | + $newEmail = esc_html((string) ($v['new_email'] ?? '')); | |
| 619 | + $intro = isset($v['intro_paragraph']) ? esc_html((string) $v['intro_paragraph']) : ''; | |
| 620 | + $footer = isset($v['footer_note']) ? esc_html((string) $v['footer_note']) : ''; | |
| 621 | + | |
| 622 | + $newBlock = $newEmail !== '' | |
| 623 | + ? '<div style="margin:0 0 20px;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">' | |
| 624 | + . esc_html__('New email address:', 'yatra') . ' <strong>' . $newEmail . '</strong></div>' | |
| 625 | + : ''; | |
| 626 | + | |
| 627 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">' | |
| 628 | + . sprintf(/* translators: %s: customer name */ esc_html__('Hello %s,', 'yatra'), $first) | |
| 629 | + . '</p>' | |
| 630 | + . ($intro !== '' ? '<p style="margin:0 0 20px;color:#475569;">' . $intro . '</p>' : '') | |
| 631 | + . $newBlock | |
| 632 | + . EmailTemplateLayout::button($link, __('Confirm email change', 'yatra')) | |
| 633 | + . ($footer !== '' ? '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">' . $footer . '</p>' : '') | |
| 634 | + . '<p style="margin:20px 0 0;font-size:13px;color:#64748b;">' | |
| 635 | + . esc_html__('If the button does not work, copy this link into your browser:', 'yatra') | |
| 636 | + . '</p>' | |
| 637 | + . '<p style="margin:8px 0 0;font-size:13px;word-break:break-all;"><a href="' . $link . '" style="color:#0d9488;">' . $link . '</a></p>'; | |
| 638 | + | |
| 639 | + return EmailTemplateLayout::customer( | |
| 640 | + '✉️', | |
| 641 | + __('Confirm your new email', 'yatra'), | |
| 642 | + $inner, | |
| 643 | + $site, | |
| 644 | + __('Account security', 'yatra') | |
| 645 | + ); | |
| 646 | + } | |
| 647 | + | |
| 648 | + /** | |
| 649 | + * Fallback HTML for the "email changed" security notice (empty saved template). | |
| 650 | + * | |
| 651 | + * @param array<string, string> $v | |
| 652 | + */ | |
| 653 | + public static function fallbackTransactionalAccountEmailChanged(array $v): string | |
| 654 | + { | |
| 655 | + $firstRaw = (string) ($v['customer_first_name'] ?? ''); | |
| 656 | + if ($firstRaw === '') { | |
| 657 | + $firstRaw = (string) ($v['customer_name'] ?? ''); | |
| 658 | + } | |
| 659 | + $first = esc_html($firstRaw !== '' ? $firstRaw : __('there', 'yatra')); | |
| 660 | + $site = esc_html($v['site_name'] ?? get_bloginfo('name')); | |
| 661 | + $newEmail = esc_html((string) ($v['new_email'] ?? '')); | |
| 662 | + $intro = isset($v['intro_paragraph']) ? esc_html((string) $v['intro_paragraph']) : ''; | |
| 663 | + $footer = isset($v['footer_note']) ? esc_html((string) $v['footer_note']) : ''; | |
| 664 | + | |
| 665 | + $newBlock = $newEmail !== '' | |
| 666 | + ? '<div style="margin:0 0 20px;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">' | |
| 667 | + . esc_html__('New email address:', 'yatra') . ' <strong>' . $newEmail . '</strong></div>' | |
| 668 | + : ''; | |
| 669 | + | |
| 670 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">' | |
| 671 | + . sprintf(/* translators: %s: customer name */ esc_html__('Hello %s,', 'yatra'), $first) | |
| 672 | + . '</p>' | |
| 673 | + . ($intro !== '' ? '<p style="margin:0 0 20px;color:#475569;">' . $intro . '</p>' : '') | |
| 674 | + . $newBlock | |
| 675 | + . ($footer !== '' ? '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">' . $footer . '</p>' : ''); | |
| 676 | + | |
| 677 | + return EmailTemplateLayout::customer( | |
| 678 | + '🔔', | |
| 679 | + __('Your email address was changed', 'yatra'), | |
| 680 | + $inner, | |
| 681 | + $site, | |
| 682 | + __('Account security', 'yatra') | |
| 683 | + ); | |
| 684 | + } | |
| 685 | + | |
| 686 | + /** | |
| 288 | 687 | * Fallback HTML for admin new-booking notice (empty saved template). |
| 289 | 688 | * |
| 290 | 689 | * @param array<string, string> $v |
| 291 | 690 | */ |
| @@ -377,9 +776,9 @@ | ||
| 377 | 776 | ['label' => __('Phone', 'yatra'), 'value' => esc_html($v['customer_phone'] ?? '')], |
| 378 | 777 | ['label' => __('Trip', 'yatra'), 'value' => esc_html($v['trip_name'] ?? '')], |
| 379 | 778 | ['label' => __('Travel date', 'yatra'), 'value' => esc_html($v['travel_date'] ?? '')], |
| 380 | 779 | ['label' => __('Travelers', 'yatra'), 'value' => esc_html($v['travelers_count'] ?? '')], |
| 381 | - ['label' => __('Total', 'yatra'), 'value' => trim(esc_html($v['total_amount_formatted'] ?? '') . ' ' . esc_html($v['currency'] ?? ''))], | |
| 780 | + ['label' => __('Total', 'yatra'), 'value' => esc_html($v['total_amount_formatted'] ?? '')], | |
| 382 | 781 | ['label' => __('Booking status', 'yatra'), 'value' => esc_html($v['booking_status'] ?? '')], |
| 383 | 782 | ['label' => __('Payment status', 'yatra'), 'value' => esc_html($v['payment_status'] ?? '')], |
| 384 | 783 | ], static function (array $row): bool { |
| 385 | 784 | return trim((string) ($row['value'] ?? '')) !== ''; |
| @@ -524,9 +923,9 @@ | ||
| 524 | 923 | ['label' => 'Phone', 'value' => '{{customer_phone}}'], |
| 525 | 924 | ['label' => 'Trip', 'value' => '{{trip_name}}'], |
| 526 | 925 | ['label' => 'Travel date', 'value' => '{{travel_date}}'], |
| 527 | 926 | ['label' => 'Travelers', 'value' => '{{travelers_count}}'], |
| 528 | - ['label' => 'Total', 'value' => '{{total_amount_formatted}} {{currency}}'], | |
| 927 | + ['label' => 'Total', 'value' => '{{total_amount_formatted}}'], | |
| 529 | 928 | ['label' => 'Booking status', 'value' => '{{booking_status}}'], |
| 530 | 929 | ['label' => 'Payment status', 'value' => '{{payment_status}}'], |
| 531 | 930 | ]) |
| 532 | 931 | . '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0 0;"><tr>' |
| @@ -567,8 +966,35 @@ | ||
| 567 | 966 | 'Payment received' |
| 568 | 967 | ); |
| 569 | 968 | } |
| 570 | 969 | |
| 970 | + /** | |
| 971 | + * Editor pre-fill for the part-payment template ({{merge tags}} intact). | |
| 972 | + */ | |
| 973 | + private static function htmlPartialPaymentCustomer(): string | |
| 974 | + { | |
| 975 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Dear {{customer_name}},</p>' | |
| 976 | + . '<p style="margin:0 0 20px;color:#475569;">' | |
| 977 | + . esc_html__('Thank you — we’ve received your part payment. The remaining balance for your booking is shown below.', 'yatra') | |
| 978 | + . '</p>' | |
| 979 | + . EmailTemplateLayout::detailCard([ | |
| 980 | + ['label' => 'Booking reference', 'value' => '{{booking_reference}}'], | |
| 981 | + ['label' => 'Amount received', 'value' => '{{payment_amount_formatted}}'], | |
| 982 | + ['label' => 'Paid so far', 'value' => '{{amount_paid_formatted}}'], | |
| 983 | + ['label' => 'Balance remaining', 'value' => '{{amount_due_formatted}}'], | |
| 984 | + ['label' => 'Booking total', 'value' => '{{total_amount_formatted}}'], | |
| 985 | + ['label' => 'Method', 'value' => '{{payment_method}}'], | |
| 986 | + ]); | |
| 987 | + | |
| 988 | + return EmailTemplateLayout::customer( | |
| 989 | + '💳', | |
| 990 | + 'Part payment received', | |
| 991 | + $inner, | |
| 992 | + '{{site_name}}', | |
| 993 | + 'Payment update' | |
| 994 | + ); | |
| 995 | + } | |
| 996 | + | |
| 571 | 997 | private static function htmlCancellationCustomer(): string |
| 572 | 998 | { |
| 573 | 999 | $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>' |
| 574 | 1000 | . '<p style="margin:0 0 20px;color:#475569;">' |
| @@ -636,8 +1062,119 @@ | ||
| 636 | 1062 | 'Trip reminder' |
| 637 | 1063 | ); |
| 638 | 1064 | } |
| 639 | 1065 | |
| 1066 | + private static function htmlTripConsentRequest(): string | |
| 1067 | + { | |
| 1068 | + $cta = '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0;"><tr>' | |
| 1069 | + . '<td style="border-radius:10px;background:#0d9488;">' | |
| 1070 | + . '<a href="{{consent_link}}" style="display:inline-block;padding:14px 28px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:10px;">' | |
| 1071 | + . esc_html__('Sign consent form', 'yatra') | |
| 1072 | + . '</a></td></tr></table>'; | |
| 1073 | + | |
| 1074 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{recipient_name}},</p>' | |
| 1075 | + . '<p style="margin:0 0 20px;color:#475569;">' | |
| 1076 | + . esc_html__('Please review and sign the consent form for your upcoming trip.', 'yatra') | |
| 1077 | + . '</p>' | |
| 1078 | + . '{{consent_test_notice_html}}' | |
| 1079 | + . EmailTemplateLayout::detailCard([ | |
| 1080 | + ['label' => 'Form', 'value' => '{{form_name}}'], | |
| 1081 | + ['label' => 'Trip', 'value' => '{{trip_name}}'], | |
| 1082 | + ['label' => 'Travel date', 'value' => '{{travel_date}}'], | |
| 1083 | + ['label' => 'Booking reference', 'value' => '{{booking_reference}}'], | |
| 1084 | + ]) | |
| 1085 | + . '{{expiry_notice_html}}' | |
| 1086 | + . $cta | |
| 1087 | + . '<p style="margin:20px 0 0;font-size:13px;color:#64748b;">' | |
| 1088 | + . esc_html__('If the button does not work, copy this link into your browser:', 'yatra') | |
| 1089 | + . '</p>' | |
| 1090 | + . '<p style="margin:8px 0 0;font-size:13px;word-break:break-all;"><a href="{{consent_link}}" style="color:#0d9488;">{{consent_link}}</a></p>'; | |
| 1091 | + | |
| 1092 | + return EmailTemplateLayout::customer( | |
| 1093 | + '📝', | |
| 1094 | + 'Consent required', | |
| 1095 | + $inner, | |
| 1096 | + '{{site_name}}', | |
| 1097 | + 'Trip consent' | |
| 1098 | + ); | |
| 1099 | + } | |
| 1100 | + | |
| 1101 | + private static function htmlCustomerEmailVerification(): string | |
| 1102 | + { | |
| 1103 | + $cta = '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0;"><tr>' | |
| 1104 | + . '<td style="border-radius:10px;background:#0d9488;">' | |
| 1105 | + . '<a href="{{verification_link}}" style="display:inline-block;padding:14px 28px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:10px;">' | |
| 1106 | + . esc_html__('Verify email address', 'yatra') | |
| 1107 | + . '</a></td></tr></table>'; | |
| 1108 | + | |
| 1109 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>' | |
| 1110 | + . '<p style="margin:0 0 20px;color:#475569;">{{intro_paragraph}}</p>' | |
| 1111 | + . $cta | |
| 1112 | + . '<div style="margin:20px 0 0;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">' | |
| 1113 | + . '{{expiry_notice_html}}' | |
| 1114 | + . '</div>' | |
| 1115 | + . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">{{footer_note}}</p>' | |
| 1116 | + . '<p style="margin:20px 0 0;font-size:13px;color:#64748b;">' | |
| 1117 | + . esc_html__('If the button does not work, copy this link into your browser:', 'yatra') | |
| 1118 | + . '</p>' | |
| 1119 | + . '<p style="margin:8px 0 0;font-size:13px;word-break:break-all;"><a href="{{verification_link}}" style="color:#0d9488;">{{verification_link}}</a></p>'; | |
| 1120 | + | |
| 1121 | + return EmailTemplateLayout::customer( | |
| 1122 | + '✉️', | |
| 1123 | + __('Verify your email', 'yatra'), | |
| 1124 | + $inner, | |
| 1125 | + '{{site_name}}', | |
| 1126 | + __('Account verification', 'yatra') | |
| 1127 | + ); | |
| 1128 | + } | |
| 1129 | + | |
| 1130 | + private static function htmlAccountEmailChangeRequest(): string | |
| 1131 | + { | |
| 1132 | + $cta = '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0;"><tr>' | |
| 1133 | + . '<td style="border-radius:10px;background:#0d9488;">' | |
| 1134 | + . '<a href="{{verification_link}}" style="display:inline-block;padding:14px 28px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:10px;">' | |
| 1135 | + . esc_html__('Confirm email change', 'yatra') | |
| 1136 | + . '</a></td></tr></table>'; | |
| 1137 | + | |
| 1138 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>' | |
| 1139 | + . '<p style="margin:0 0 20px;color:#475569;">{{intro_paragraph}}</p>' | |
| 1140 | + . '<div style="margin:0 0 20px;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">' | |
| 1141 | + . esc_html__('New email address:', 'yatra') . ' <strong>{{new_email}}</strong>' | |
| 1142 | + . '</div>' | |
| 1143 | + . $cta | |
| 1144 | + . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">{{footer_note}}</p>' | |
| 1145 | + . '<p style="margin:20px 0 0;font-size:13px;color:#64748b;">' | |
| 1146 | + . esc_html__('If the button does not work, copy this link into your browser:', 'yatra') | |
| 1147 | + . '</p>' | |
| 1148 | + . '<p style="margin:8px 0 0;font-size:13px;word-break:break-all;"><a href="{{verification_link}}" style="color:#0d9488;">{{verification_link}}</a></p>'; | |
| 1149 | + | |
| 1150 | + return EmailTemplateLayout::customer( | |
| 1151 | + '✉️', | |
| 1152 | + __('Confirm your new email', 'yatra'), | |
| 1153 | + $inner, | |
| 1154 | + '{{site_name}}', | |
| 1155 | + __('Account security', 'yatra') | |
| 1156 | + ); | |
| 1157 | + } | |
| 1158 | + | |
| 1159 | + private static function htmlAccountEmailChanged(): string | |
| 1160 | + { | |
| 1161 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>' | |
| 1162 | + . '<p style="margin:0 0 20px;color:#475569;">{{intro_paragraph}}</p>' | |
| 1163 | + . '<div style="margin:0 0 20px;padding:12px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#475569;">' | |
| 1164 | + . esc_html__('New email address:', 'yatra') . ' <strong>{{new_email}}</strong>' | |
| 1165 | + . '</div>' | |
| 1166 | + . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">{{footer_note}}</p>'; | |
| 1167 | + | |
| 1168 | + return EmailTemplateLayout::customer( | |
| 1169 | + '🔔', | |
| 1170 | + __('Your email address was changed', 'yatra'), | |
| 1171 | + $inner, | |
| 1172 | + '{{site_name}}', | |
| 1173 | + __('Account security', 'yatra') | |
| 1174 | + ); | |
| 1175 | + } | |
| 1176 | + | |
| 640 | 1177 | private static function htmlPaymentReminder(): string |
| 641 | 1178 | { |
| 642 | 1179 | $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>' |
| 643 | 1180 | . '<p style="margin:0 0 20px;color:#475569;">' |
| @@ -753,8 +1290,285 @@ | ||
| 753 | 1290 | 'Review request' |
| 754 | 1291 | ); |
| 755 | 1292 | } |
| 756 | 1293 | |
| 1294 | + private static function htmlBookingExpiredCustomer(): string | |
| 1295 | + { | |
| 1296 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>' | |
| 1297 | + . '<p style="margin:0 0 20px;color:#475569;">' | |
| 1298 | + . esc_html__('Your booking was cancelled automatically because payment was not received in time.', 'yatra') | |
| 1299 | + . '</p>' | |
| 1300 | + . EmailTemplateLayout::detailCard([ | |
| 1301 | + ['label' => 'Reference', 'value' => '{{booking_reference}}'], | |
| 1302 | + ['label' => 'Trip', 'value' => '{{trip_name}}'], | |
| 1303 | + ['label' => 'Travel date', 'value' => '{{travel_date}}'], | |
| 1304 | + ['label' => 'Policy', 'value' => '{{expiry_policy_note}}'], | |
| 1305 | + ]) | |
| 1306 | + . '<p style="margin:24px 0 0;"><a href="{{trip_url}}" style="color:#0d9488;font-weight:600;text-decoration:none;">' | |
| 1307 | + . esc_html__('Browse trips again →', 'yatra') | |
| 1308 | + . '</a></p>'; | |
| 1309 | + | |
| 1310 | + return EmailTemplateLayout::customer( | |
| 1311 | + '⏱️', | |
| 1312 | + __('Booking expired', 'yatra'), | |
| 1313 | + $inner, | |
| 1314 | + '{{site_name}}', | |
| 1315 | + __('Booking expired', 'yatra') | |
| 1316 | + ); | |
| 1317 | + } | |
| 1318 | + | |
| 1319 | + private static function htmlAdminBookingExpired(): string | |
| 1320 | + { | |
| 1321 | + $inner = '<p style="margin:0 0 20px;color:#475569;">' | |
| 1322 | + . esc_html__('A booking was cancelled automatically due to non-payment (expiry policy).', 'yatra') | |
| 1323 | + . '</p>' | |
| 1324 | + . EmailTemplateLayout::detailCard([ | |
| 1325 | + ['label' => 'Reference', 'value' => '{{booking_reference}}'], | |
| 1326 | + ['label' => 'Booking ID', 'value' => '{{booking_id}}'], | |
| 1327 | + ['label' => 'Customer', 'value' => '{{customer_name}}'], | |
| 1328 | + ['label' => 'Email', 'value' => '{{customer_email}}'], | |
| 1329 | + ['label' => 'Trip', 'value' => '{{trip_name}}'], | |
| 1330 | + ]) | |
| 1331 | + . '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0 0;"><tr>' | |
| 1332 | + . '<td style="border-radius:10px;background:#2563eb;">' | |
| 1333 | + . '<a href="{{admin_url}}" style="display:inline-block;padding:14px 24px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:10px;">' | |
| 1334 | + . esc_html__('Open in Yatra admin', 'yatra') | |
| 1335 | + . '</a></td></tr></table>'; | |
| 1336 | + | |
| 1337 | + return EmailTemplateLayout::admin( | |
| 1338 | + '⏱️', | |
| 1339 | + __('Booking expired', 'yatra'), | |
| 1340 | + $inner, | |
| 1341 | + '{{site_name}} · ' . esc_html__('Admin', 'yatra') | |
| 1342 | + ); | |
| 1343 | + } | |
| 1344 | + | |
| 1345 | + private static function htmlScheduledPaymentReminder(): string | |
| 1346 | + { | |
| 1347 | + // "Pay your balance now" CTA. The href is the {{balance_payment_url}} | |
| 1348 | + // merge tag (a secure, guest-accessible balance link supplied by Pro | |
| 1349 | + // Scheduled Payments); it is built manually rather than via | |
| 1350 | + // EmailTemplateLayout::button() because that esc_url()s the href, which | |
| 1351 | + // would strip the merge-tag braces. Pro passes an already-escaped URL. | |
| 1352 | + $cta = '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0;"><tr>' | |
| 1353 | + . '<td style="border-radius:10px;background:#0d9488;">' | |
| 1354 | + . '<a href="{{balance_payment_url}}" style="display:inline-block;padding:14px 28px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:10px;">' | |
| 1355 | + . esc_html__('Pay your balance now', 'yatra') . '</a></td></tr></table>'; | |
| 1356 | + | |
| 1357 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>' | |
| 1358 | + . '<p style="margin:0 0 20px;color:#475569;">' | |
| 1359 | + . esc_html__('This is a reminder that the remaining balance for your booking is coming up.', 'yatra') | |
| 1360 | + . '</p>' | |
| 1361 | + . EmailTemplateLayout::detailCard([ | |
| 1362 | + ['label' => 'Reference', 'value' => '{{booking_reference}}'], | |
| 1363 | + ['label' => 'Amount', 'value' => '{{scheduled_amount_formatted}}'], | |
| 1364 | + ['label' => 'Due date', 'value' => '{{scheduled_date_formatted}}'], | |
| 1365 | + ['label' => 'Type', 'value' => '{{payment_type_label}}'], | |
| 1366 | + ]) | |
| 1367 | + . $cta | |
| 1368 | + . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">' | |
| 1369 | + . esc_html__('If you have any questions about this payment, please contact us before the due date.', 'yatra') | |
| 1370 | + . '</p>'; | |
| 1371 | + | |
| 1372 | + return EmailTemplateLayout::customer( | |
| 1373 | + '💳', | |
| 1374 | + __('Upcoming payment', 'yatra'), | |
| 1375 | + $inner, | |
| 1376 | + '{{site_name}}', | |
| 1377 | + __('Scheduled payment reminder', 'yatra') | |
| 1378 | + ); | |
| 1379 | + } | |
| 1380 | + | |
| 1381 | + private static function htmlScheduledPaymentSucceeded(): string | |
| 1382 | + { | |
| 1383 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>' | |
| 1384 | + . '<p style="margin:0 0 20px;color:#475569;">' | |
| 1385 | + . esc_html__('Your scheduled payment was processed successfully.', 'yatra') | |
| 1386 | + . '</p>' | |
| 1387 | + . EmailTemplateLayout::detailCard([ | |
| 1388 | + ['label' => 'Reference', 'value' => '{{booking_reference}}'], | |
| 1389 | + ['label' => 'Amount charged', 'value' => '{{scheduled_amount_formatted}}'], | |
| 1390 | + ['label' => 'Type', 'value' => '{{payment_type_label}}'], | |
| 1391 | + ['label' => 'Balance due now', 'value' => '{{balance_after_formatted}}'], | |
| 1392 | + ]); | |
| 1393 | + | |
| 1394 | + return EmailTemplateLayout::customer( | |
| 1395 | + '✅', | |
| 1396 | + __('Payment received', 'yatra'), | |
| 1397 | + $inner, | |
| 1398 | + '{{site_name}}', | |
| 1399 | + __('Scheduled payment', 'yatra') | |
| 1400 | + ); | |
| 1401 | + } | |
| 1402 | + | |
| 1403 | + private static function htmlScheduledPaymentFailed(): string | |
| 1404 | + { | |
| 1405 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>' | |
| 1406 | + . '<p style="margin:0 0 20px;color:#475569;">' | |
| 1407 | + . '{{failure_intro_html}}' | |
| 1408 | + . '</p>' | |
| 1409 | + . EmailTemplateLayout::detailCard([ | |
| 1410 | + ['label' => 'Reference', 'value' => '{{booking_reference}}'], | |
| 1411 | + ['label' => 'Amount', 'value' => '{{scheduled_amount_formatted}}'], | |
| 1412 | + ['label' => 'Details', 'value' => '{{failure_reason}}'], | |
| 1413 | + ]) | |
| 1414 | + . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">{{failure_followup_html}}</p>'; | |
| 1415 | + | |
| 1416 | + return EmailTemplateLayout::customer( | |
| 1417 | + '⚠️', | |
| 1418 | + __('Payment issue', 'yatra'), | |
| 1419 | + $inner, | |
| 1420 | + '{{site_name}}', | |
| 1421 | + __('Scheduled payment', 'yatra') | |
| 1422 | + ); | |
| 1423 | + } | |
| 1424 | + | |
| 1425 | + private static function htmlAdminScheduledPaymentFailed(): string | |
| 1426 | + { | |
| 1427 | + $inner = '<p style="margin:0 0 20px;color:#475569;">' | |
| 1428 | + . esc_html__('A scheduled payment permanently failed after retries.', 'yatra') | |
| 1429 | + . '</p>' | |
| 1430 | + . EmailTemplateLayout::detailCard([ | |
| 1431 | + ['label' => 'Reference', 'value' => '{{booking_reference}}'], | |
| 1432 | + ['label' => 'Customer', 'value' => '{{customer_name}}'], | |
| 1433 | + ['label' => 'Email', 'value' => '{{customer_email}}'], | |
| 1434 | + ['label' => 'Amount', 'value' => '{{scheduled_amount_formatted}}'], | |
| 1435 | + ['label' => 'Error', 'value' => '{{failure_reason}}'], | |
| 1436 | + ]) | |
| 1437 | + . '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0 0;"><tr>' | |
| 1438 | + . '<td style="border-radius:10px;background:#2563eb;">' | |
| 1439 | + . '<a href="{{admin_url}}" style="display:inline-block;padding:14px 24px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:10px;">' | |
| 1440 | + . esc_html__('Open in Yatra admin', 'yatra') | |
| 1441 | + . '</a></td></tr></table>'; | |
| 1442 | + | |
| 1443 | + return EmailTemplateLayout::admin( | |
| 1444 | + '⚠️', | |
| 1445 | + __('Scheduled payment failed', 'yatra'), | |
| 1446 | + $inner, | |
| 1447 | + '{{site_name}} · ' . esc_html__('Admin', 'yatra') | |
| 1448 | + ); | |
| 1449 | + } | |
| 1450 | + | |
| 1451 | + private static function htmlAbandonedBookingRecoveryBase(string $headline, string $subLabel): string | |
| 1452 | + { | |
| 1453 | + $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>' | |
| 1454 | + . '<p style="margin:0 0 20px;color:#475569;">{{recovery_intro_html}}</p>' | |
| 1455 | + . EmailTemplateLayout::detailCard([ | |
| 1456 | + ['label' => 'Trip', 'value' => '{{trip_name}}'], | |
| 1457 | + ['label' => 'Reminder', 'value' => '{{recovery_reminder_label}}'], | |
| 1458 | + ]) | |
| 1459 | + . '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0;"><tr>' | |
| 1460 | + . '<td style="border-radius:10px;background:#0d9488;">' | |
| 1461 | + . '<a href="{{recovery_link}}" style="display:inline-block;padding:14px 28px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:10px;">' | |
| 1462 | + . esc_html__('Continue booking', 'yatra') | |
| 1463 | + . '</a></td></tr></table>' | |
| 1464 | + . '<p style="margin:20px 0 0;font-size:13px;color:#64748b;">' | |
| 1465 | + . esc_html__('If you did not start this booking, you can ignore this email.', 'yatra') | |
| 1466 | + . '</p>'; | |
| 1467 | + | |
| 1468 | + return EmailTemplateLayout::customer( | |
| 1469 | + '🛒', | |
| 1470 | + $headline, | |
| 1471 | + $inner, | |
| 1472 | + '{{site_name}}', | |
| 1473 | + $subLabel | |
| 1474 | + ); | |
| 1475 | + } | |
| 1476 | + | |
| 1477 | + private static function htmlAbandonedBookingRecoveryFirst(): string | |
| 1478 | + { | |
| 1479 | + return self::htmlAbandonedBookingRecoveryBase( | |
| 1480 | + __('Complete your booking', 'yatra'), | |
| 1481 | + __('Abandoned checkout · First reminder', 'yatra') | |
| 1482 | + ); | |
| 1483 | + } | |
| 1484 | + | |
| 1485 | + private static function htmlAbandonedBookingRecoverySecond(): string | |
| 1486 | + { | |
| 1487 | + return self::htmlAbandonedBookingRecoveryBase( | |
| 1488 | + __('Still interested?', 'yatra'), | |
| 1489 | + __('Abandoned checkout · Second reminder', 'yatra') | |
| 1490 | + ); | |
| 1491 | + } | |
| 1492 | + | |
| 1493 | + private static function htmlAbandonedBookingRecoveryFinal(): string | |
| 1494 | + { | |
| 1495 | + return self::htmlAbandonedBookingRecoveryBase( | |
| 1496 | + __('Final reminder', 'yatra'), | |
| 1497 | + __('Abandoned checkout · Final reminder', 'yatra') | |
| 1498 | + ); | |
| 1499 | + } | |
| 1500 | + | |
| 1501 | + public static function fallbackTransactionalBookingCompleted(array $v): string | |
| 1502 | + { | |
| 1503 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlTripCompleted(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1504 | + } | |
| 1505 | + | |
| 1506 | + public static function fallbackTransactionalBookingExpiredCustomer(array $v): string | |
| 1507 | + { | |
| 1508 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlBookingExpiredCustomer(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1509 | + } | |
| 1510 | + | |
| 1511 | + public static function fallbackAdminBookingExpired(array $v): string | |
| 1512 | + { | |
| 1513 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlAdminBookingExpired(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1514 | + } | |
| 1515 | + | |
| 1516 | + public static function fallbackTransactionalScheduledPaymentReminder(array $v): string | |
| 1517 | + { | |
| 1518 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlScheduledPaymentReminder(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1519 | + } | |
| 1520 | + | |
| 1521 | + public static function fallbackTransactionalScheduledPaymentSucceeded(array $v): string | |
| 1522 | + { | |
| 1523 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlScheduledPaymentSucceeded(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1524 | + } | |
| 1525 | + | |
| 1526 | + public static function fallbackTransactionalScheduledPaymentFailed(array $v): string | |
| 1527 | + { | |
| 1528 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlScheduledPaymentFailed(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1529 | + } | |
| 1530 | + | |
| 1531 | + public static function fallbackAdminScheduledPaymentFailed(array $v): string | |
| 1532 | + { | |
| 1533 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlAdminScheduledPaymentFailed(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1534 | + } | |
| 1535 | + | |
| 1536 | + public static function fallbackTransactionalEnquiryAdmin(array $v): string | |
| 1537 | + { | |
| 1538 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlEnquiryAdmin(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1539 | + } | |
| 1540 | + | |
| 1541 | + public static function fallbackTransactionalEnquiryReceived(array $v): string | |
| 1542 | + { | |
| 1543 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlEnquiryCustomer(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1544 | + } | |
| 1545 | + | |
| 1546 | + public static function fallbackTransactionalEnquiryResponse(array $v): string | |
| 1547 | + { | |
| 1548 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlEnquiryResponse(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1549 | + } | |
| 1550 | + | |
| 1551 | + public static function fallbackTransactionalReviewRequest(array $v): string | |
| 1552 | + { | |
| 1553 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlReviewRequest(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1554 | + } | |
| 1555 | + | |
| 1556 | + public static function fallbackTransactionalAbandonedBookingRecoveryFirst(array $v): string | |
| 1557 | + { | |
| 1558 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlAbandonedBookingRecoveryFirst(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1559 | + } | |
| 1560 | + | |
| 1561 | + public static function fallbackTransactionalAbandonedBookingRecoverySecond(array $v): string | |
| 1562 | + { | |
| 1563 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlAbandonedBookingRecoverySecond(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1564 | + } | |
| 1565 | + | |
| 1566 | + public static function fallbackTransactionalAbandonedBookingRecoveryFinal(array $v): string | |
| 1567 | + { | |
| 1568 | + return TransactionalEmailTemplateService::parseMergeTags(self::htmlAbandonedBookingRecoveryFinal(), TransactionalEmailTemplateService::mergeTemplateVariables($v)); | |
| 1569 | + } | |
| 1570 | + | |
| 757 | 1571 | /** |
| 758 | 1572 | * Merge-tag variables for enquiry templates (core wp_mail path when Pro does not own the channel). |
| 759 | 1573 | * |
| 760 | 1574 | * @param object $enquiry Row from EnquiryRepository::findWithTrip() |
| @@ -760,19 +1574,9 @@ | ||
| 760 | 1574 | * @param object $enquiry Row from EnquiryRepository::findWithTrip() |
| 761 | 1575 | */ |
| 762 | 1576 | private static function enquiryVariablesForTemplates(object $enquiry, string $responsePlain): array |
| 763 | 1577 | { |
| 764 | - $trip = trim((string) ($enquiry->trip_title ?? '')); | |
| 765 | - | |
| 766 | - return [ | |
| 767 | - 'customer_name' => (string) ($enquiry->name ?? ''), | |
| 768 | - 'customer_email' => (string) ($enquiry->email ?? ''), | |
| 769 | - 'customer_phone' => (string) ($enquiry->phone ?? ''), | |
| 770 | - 'trip_name' => $trip !== '' ? $trip : __('General enquiry', 'yatra'), | |
| 771 | - 'message' => nl2br(esc_html((string) ($enquiry->message ?? ''))), | |
| 772 | - 'response' => $responsePlain !== '' ? nl2br(esc_html($responsePlain)) : '', | |
| 773 | - 'response_message' => $responsePlain !== '' ? nl2br(esc_html($responsePlain)) : '', | |
| 774 | - ]; | |
| 1578 | + return TransactionalEmailTemplateService::variablesFromEnquiry($enquiry, $responsePlain); | |
| 775 | 1579 | } |
| 776 | 1580 | |
| 777 | 1581 | /** |
| 778 | 1582 | * Full HTML body matching Pro/system enquiry_received (for free core sends). |