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 +276 -4 3.0.8 → 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(),
@@ -43,8 +45,14 @@
43 45 // save accepts them (SettingsController whitelists by default_settings)
44 46 // and the admin template editor pre-fills them.
45 47 'email_tpl_guest_verification_subject' => '✉️ [{{site_name}}] Verify your email to complete your booking',
46 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(),
47 55 'email_tpl_booking_completed_subject' => '🌟 [{{site_name}}] Thanks for traveling · {{booking_reference}}',
48 56 'email_tpl_booking_completed_body' => self::htmlTripCompleted(),
49 57 'email_tpl_booking_expired_customer_subject' => '⏱️ [{{site_name}}] Booking expired · {{booking_reference}}',
50 58 'email_tpl_booking_expired_customer_body' => self::htmlBookingExpiredCustomer(),
@@ -72,8 +80,26 @@
72 80 'email_tpl_abandoned_booking_recovery_second_body' => self::htmlAbandonedBookingRecoverySecond(),
73 81 'email_tpl_abandoned_booking_recovery_final_subject' => '⚠️ [{{site_name}}] Final reminder: complete your booking',
74 82 'email_tpl_abandoned_booking_recovery_final_body' => self::htmlAbandonedBookingRecoveryFinal(),
75 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;
76 102 }
77 103
78 104 /**
79 105 * Full subject + body for Pro system templates (DB). Null = no bundled default for that key.
@@ -106,8 +132,12 @@
106 132 'booking_cancelled' => [
107 133 'subject' => '📋 Booking cancelled · {{booking_reference}}',
108 134 'body' => self::htmlCancellationCustomer(),
109 135 ],
136 + 'admin_booking_cancelled' => [
137 + 'subject' => '📋 Booking cancelled · {{booking_reference}} (#{{booking_id}})',
138 + 'body' => self::htmlAdminBookingCancelled(),
139 + ],
110 140 'booking_completed' => [
111 141 'subject' => '🌟 Thanks for traveling with us · {{booking_reference}}',
112 142 'body' => self::htmlTripCompleted(),
113 143 ],
@@ -114,8 +144,16 @@
114 144 'payment_received' => [
115 145 'subject' => '✅ Payment received · {{booking_reference}}',
116 146 'body' => self::htmlPaymentCustomer(),
117 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 + ],
118 156 'payment_reminder' => [
119 157 'subject' => '💳 Payment reminder · {{booking_reference}}',
120 158 'body' => self::htmlPaymentReminder(),
121 159 ],
@@ -146,8 +184,24 @@
146 184 'customer_email_verification' => [
147 185 'subject' => '✉️ [{{site_name}}] Verify your email address',
148 186 'body' => self::htmlCustomerEmailVerification(),
149 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 + ],
150 204 'booking_expired_customer' => [
151 205 'subject' => '⏱️ [{{site_name}}] Booking expired · {{booking_reference}}',
152 206 'body' => self::htmlBookingExpiredCustomer(),
153 207 ],
@@ -292,10 +346,61 @@
292 346 );
293 347 }
294 348
295 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 + *
296 356 * @param array<string, string> $v
297 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 + */
298 403 public static function fallbackTransactionalCancellation(array $v): string
299 404 {
300 405 $name = esc_html($v['customer_first_name'] ?: $v['customer_name'] ?: __('Customer', 'yatra'));
301 406 $site = esc_html($v['site_name'] ?? get_bloginfo('name'));
@@ -496,8 +601,90 @@
496 601 );
497 602 }
498 603
499 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 + /**
500 687 * Fallback HTML for admin new-booking notice (empty saved template).
501 688 *
502 689 * @param array<string, string> $v
503 690 */
@@ -779,8 +966,35 @@
779 966 'Payment received'
780 967 );
781 968 }
782 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 +
783 997 private static function htmlCancellationCustomer(): string
784 998 {
785 999 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>'
786 1000 . '<p style="margin:0 0 20px;color:#475569;">'
@@ -912,8 +1126,55 @@
912 1126 __('Account verification', 'yatra')
913 1127 );
914 1128 }
915 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 +
916 1177 private static function htmlPaymentReminder(): string
917 1178 {
918 1179 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>'
919 1180 . '<p style="margin:0 0 20px;color:#475569;">'
@@ -1082,20 +1343,31 @@
1082 1343 }
1083 1344
1084 1345 private static function htmlScheduledPaymentReminder(): string
1085 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 +
1086 1357 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>'
1087 1358 . '<p style="margin:0 0 20px;color:#475569;">'
1088 - . esc_html__('This is a reminder that a scheduled payment is coming up for your booking.', 'yatra')
1359 + . esc_html__('This is a reminder that the remaining balance for your booking is coming up.', 'yatra')
1089 1360 . '</p>'
1090 1361 . EmailTemplateLayout::detailCard([
1091 1362 ['label' => 'Reference', 'value' => '{{booking_reference}}'],
1092 1363 ['label' => 'Amount', 'value' => '{{scheduled_amount_formatted}}'],
1093 - ['label' => 'Scheduled date', 'value' => '{{scheduled_date_formatted}}'],
1364 + ['label' => 'Due date', 'value' => '{{scheduled_date_formatted}}'],
1094 1365 ['label' => 'Type', 'value' => '{{payment_type_label}}'],
1095 1366 ])
1367 + . $cta
1096 1368 . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">'
1097 - . esc_html__('If you need to update your payment method, contact us before the charge date.', 'yatra')
1369 + . esc_html__('If you have any questions about this payment, please contact us before the due date.', 'yatra')
1098 1370 . '</p>';
1099 1371
1100 1372 return EmailTemplateLayout::customer(
1101 1373 '💳',