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 +268 -4 3.0.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(),
@@ -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 ],
@@ -154,8 +192,16 @@
154 192 // send-time via {{intro_paragraph}} / {{footer_note}}.
155 193 'subject' => '✉️ [{{site_name}}] Verify your email to complete your booking',
156 194 'body' => self::htmlCustomerEmailVerification(),
157 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 + ],
158 204 'booking_expired_customer' => [
159 205 'subject' => '⏱️ [{{site_name}}] Booking expired · {{booking_reference}}',
160 206 'body' => self::htmlBookingExpiredCustomer(),
161 207 ],
@@ -300,10 +346,61 @@
300 346 );
301 347 }
302 348
303 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 + *
304 356 * @param array<string, string> $v
305 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 + */
306 403 public static function fallbackTransactionalCancellation(array $v): string
307 404 {
308 405 $name = esc_html($v['customer_first_name'] ?: $v['customer_name'] ?: __('Customer', 'yatra'));
309 406 $site = esc_html($v['site_name'] ?? get_bloginfo('name'));
@@ -504,8 +601,90 @@
504 601 );
505 602 }
506 603
507 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 + /**
508 687 * Fallback HTML for admin new-booking notice (empty saved template).
509 688 *
510 689 * @param array<string, string> $v
511 690 */
@@ -787,8 +966,35 @@
787 966 'Payment received'
788 967 );
789 968 }
790 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 +
791 997 private static function htmlCancellationCustomer(): string
792 998 {
793 999 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>'
794 1000 . '<p style="margin:0 0 20px;color:#475569;">'
@@ -920,8 +1126,55 @@
920 1126 __('Account verification', 'yatra')
921 1127 );
922 1128 }
923 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 +
924 1177 private static function htmlPaymentReminder(): string
925 1178 {
926 1179 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_name}},</p>'
927 1180 . '<p style="margin:0 0 20px;color:#475569;">'
@@ -1090,20 +1343,31 @@
1090 1343 }
1091 1344
1092 1345 private static function htmlScheduledPaymentReminder(): string
1093 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 +
1094 1357 $inner = '<p style="margin:0 0 16px;font-size:17px;color:#0f172a;">Hello {{customer_first_name}},</p>'
1095 1358 . '<p style="margin:0 0 20px;color:#475569;">'
1096 - . 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')
1097 1360 . '</p>'
1098 1361 . EmailTemplateLayout::detailCard([
1099 1362 ['label' => 'Reference', 'value' => '{{booking_reference}}'],
1100 1363 ['label' => 'Amount', 'value' => '{{scheduled_amount_formatted}}'],
1101 - ['label' => 'Scheduled date', 'value' => '{{scheduled_date_formatted}}'],
1364 + ['label' => 'Due date', 'value' => '{{scheduled_date_formatted}}'],
1102 1365 ['label' => 'Type', 'value' => '{{payment_type_label}}'],
1103 1366 ])
1367 + . $cta
1104 1368 . '<p style="margin:24px 0 0;font-size:14px;color:#64748b;">'
1105 - . 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')
1106 1370 . '</p>';
1107 1371
1108 1372 return EmailTemplateLayout::customer(
1109 1373 '💳',