PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | app/Services/EmailTemplateDefaults.php +821 -17 3.0.2.9 → 3.0.16 View file →
@@ -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).