PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
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
yatra / app / Services / TransactionalEmailTemplateService.php

TransactionalEmailTemplateService.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.6, at app/Services/TransactionalEmailTemplateService.php

369 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Services;
6
7 /**
8 * Central transactional emails (booking, payment, cancellation, reminder).
9 * Content is editable under Email → Templates (settings-backed) or overridden by Pro Email Automation DB templates via filter.
10 */
11 class TransactionalEmailTemplateService
12 {
13 public const TYPE_BOOKING_CONFIRMATION = 'booking_confirmation';
14
15 public const TYPE_PAYMENT_CONFIRMATION = 'payment_confirmation';
16
17 public const TYPE_BOOKING_CANCELLATION = 'booking_cancellation';
18
19 public const TYPE_BOOKING_REMINDER = 'booking_reminder';
20
21 public const TYPE_ADMIN_NEW_BOOKING = 'admin_new_booking';
22
23 public const TYPE_ADMIN_PAYMENT_RECEIVED = 'admin_payment_received';
24
25 public const TYPE_ADMIN_BOOKING_CANCELLED = 'admin_booking_cancelled_notice';
26
27 /**
28 * Map catalog / settings UI keys to internal render types.
29 */
30 public static function coreTemplateKeyToType(string $templateKey): ?string
31 {
32 $map = [
33 'booking_confirmation' => self::TYPE_BOOKING_CONFIRMATION,
34 'payment_received' => self::TYPE_PAYMENT_CONFIRMATION,
35 'booking_cancelled' => self::TYPE_BOOKING_CANCELLATION,
36 'trip_reminder' => self::TYPE_BOOKING_REMINDER,
37 'admin_new_booking' => self::TYPE_ADMIN_NEW_BOOKING,
38 'admin_payment_received' => self::TYPE_ADMIN_PAYMENT_RECEIVED,
39 'admin_booking_cancelled' => self::TYPE_ADMIN_BOOKING_CANCELLED,
40 ];
41
42 return $map[$templateKey] ?? null;
43 }
44
45 /**
46 * Sample merge-tag values for admin preview (core templates).
47 *
48 * @return array<string, string>
49 */
50 public static function sampleVariablesForCoreTemplateKey(string $templateKey): array
51 {
52 return EmailTemplateSampleData::forTemplateKey($templateKey);
53 }
54
55 /**
56 * @param array<string, string|int|float> $variables
57 * @return array<string, string>
58 */
59 public static function mergeTemplateVariables(array $variables): array
60 {
61 return self::mergeDefaultVariables($variables);
62 }
63
64 /**
65 * Replace {{word}} placeholders (used by previews and extensions).
66 *
67 * @param array<string, string|int|float> $variables
68 */
69 public static function parseMergeTags(string $template, array $variables): string
70 {
71 $variables = self::mergeDefaultVariables($variables);
72
73 return self::parseTemplate($template, $variables);
74 }
75
76 /**
77 * Render using explicit subject/body templates (e.g. unsaved editor content). Empty strings use built-in defaults.
78 *
79 * @param array<string, string|int|float> $variables
80 * @return array{subject: string, body: string}
81 */
82 public static function renderWithStringTemplates(string $type, string $subjectTpl, string $bodyTpl, array $variables): array
83 {
84 $variables = self::mergeDefaultVariables($variables);
85 $map = self::typeToSettingsKeys();
86 if (!isset($map[$type])) {
87 return ['subject' => '', 'body' => ''];
88 }
89
90 if ($subjectTpl === '') {
91 $subject = self::defaultSubject($type, $variables);
92 } else {
93 $subject = self::parseTemplate($subjectTpl, $variables);
94 }
95
96 if ($bodyTpl === '') {
97 $body = self::defaultBody($type, $variables);
98 } else {
99 $body = self::parseTemplate($bodyTpl, $variables);
100 }
101
102 return [
103 'subject' => $subject,
104 'body' => $body,
105 ];
106 }
107
108 /**
109 * @return array<string, string>
110 */
111 private static function typeToSettingsKeys(): array
112 {
113 return [
114 self::TYPE_BOOKING_CONFIRMATION => [
115 'flag' => 'email_template_booking',
116 'subject' => 'email_tpl_booking_subject',
117 'body' => 'email_tpl_booking_body',
118 ],
119 self::TYPE_PAYMENT_CONFIRMATION => [
120 'flag' => 'email_template_confirmation',
121 'subject' => 'email_tpl_payment_subject',
122 'body' => 'email_tpl_payment_body',
123 ],
124 self::TYPE_BOOKING_CANCELLATION => [
125 'flag' => 'email_template_cancellation',
126 'subject' => 'email_tpl_cancellation_subject',
127 'body' => 'email_tpl_cancellation_body',
128 ],
129 self::TYPE_BOOKING_REMINDER => [
130 'flag' => 'email_template_reminder',
131 'subject' => 'email_tpl_reminder_subject',
132 'body' => 'email_tpl_reminder_body',
133 ],
134 self::TYPE_ADMIN_NEW_BOOKING => [
135 'flag' => 'email_template_admin_new_booking',
136 'subject' => 'email_tpl_admin_booking_subject',
137 'body' => 'email_tpl_admin_booking_body',
138 ],
139 self::TYPE_ADMIN_PAYMENT_RECEIVED => [
140 'flag' => 'email_template_admin_payment',
141 'subject' => 'email_tpl_admin_payment_subject',
142 'body' => 'email_tpl_admin_payment_body',
143 ],
144 self::TYPE_ADMIN_BOOKING_CANCELLED => [
145 'flag' => 'email_template_admin_cancellation',
146 'subject' => 'email_tpl_admin_cancellation_subject',
147 'body' => 'email_tpl_admin_cancellation_body',
148 ],
149 ];
150 }
151
152 /**
153 * Send if the type is enabled in settings. Pro may handle via {@see 'yatra_send_transactional_email'}.
154 *
155 * Optional string `transactional_context` (e.g. `booking_created`, `status_confirmed`) is passed through
156 * to the filter so Pro can choose a different template row for the same TYPE_BOOKING_CONFIRMATION.
157 *
158 * @param array<string, string|int|float> $variables Merge tags: {{key}}
159 */
160 public static function sendIfEnabled(string $type, string $to, array $variables = []): bool
161 {
162 $to = sanitize_email($to);
163 if ($to === '' || !is_email($to)) {
164 return false;
165 }
166
167 $map = self::typeToSettingsKeys();
168 if (!isset($map[$type])) {
169 return false;
170 }
171
172 $flag = $map[$type]['flag'];
173 $proOwnsType = (bool) apply_filters('yatra_pro_email_automation_owns_transactional_type', false, $type);
174
175 if (!$proOwnsType && !SettingsService::isEnabled($flag)) {
176 return false;
177 }
178
179 $variables = self::mergeDefaultVariables($variables);
180
181 /**
182 * Allow Yatra Pro (or extensions) to send instead of core templates.
183 * Return null to use core; true/false if handled.
184 *
185 * @param null|bool $handled
186 */
187 $handled = apply_filters('yatra_send_transactional_email', null, $type, $to, $variables);
188 if ($handled !== null) {
189 return (bool) $handled;
190 }
191
192 if (!SettingsService::isEnabled($flag)) {
193 return false;
194 }
195
196 $rendered = self::render($type, $variables);
197
198 return EmailService::send(
199 $to,
200 $rendered['subject'],
201 $rendered['body'],
202 ['Content-Type: text/html; charset=UTF-8']
203 );
204 }
205
206 /**
207 * @param array<string, string|int|float> $variables
208 * @return array{subject: string, body: string}
209 */
210 public static function render(string $type, array $variables): array
211 {
212 $map = self::typeToSettingsKeys();
213 if (!isset($map[$type])) {
214 return ['subject' => '', 'body' => ''];
215 }
216
217 $subjectKey = $map[$type]['subject'];
218 $bodyKey = $map[$type]['body'];
219
220 $subjectTpl = SettingsService::getString($subjectKey, '');
221 $bodyTpl = SettingsService::getString($bodyKey, '');
222
223 return self::renderWithStringTemplates($type, $subjectTpl, $bodyTpl, $variables);
224 }
225
226 /**
227 * @param array<string, string|int|float> $variables
228 * @return array<string, string>
229 */
230 private static function mergeDefaultVariables(array $variables): array
231 {
232 $defaults = [
233 'site_name' => get_bloginfo('name'),
234 'site_url' => home_url('/'),
235 'admin_email' => SettingsService::getString('admin_email', get_option('admin_email')),
236 ];
237
238 $out = [];
239 foreach (array_merge($defaults, $variables) as $k => $v) {
240 $out[(string) $k] = is_scalar($v) ? (string) $v : '';
241 }
242
243 return $out;
244 }
245
246 /**
247 * @param array<string, string> $variables
248 */
249 private static function parseTemplate(string $template, array $variables): string
250 {
251 return (string) preg_replace_callback(
252 '/\{\{(\w+)\}\}/',
253 static function (array $m) use ($variables): string {
254 $key = $m[1];
255
256 return $variables[$key] ?? $m[0];
257 },
258 $template
259 );
260 }
261
262 /**
263 * @param array<string, string> $v
264 */
265 private static function defaultSubject(string $type, array $v): string
266 {
267 $site = $v['site_name'] ?? get_bloginfo('name');
268 $ref = $v['booking_reference'] ?? $v['booking_id'] ?? '';
269
270 switch ($type) {
271 case self::TYPE_BOOKING_CONFIRMATION:
272 return sprintf(__('✈️ [%s] Booking update · %s', 'yatra'), $site, $ref);
273
274 case self::TYPE_PAYMENT_CONFIRMATION:
275 return sprintf(__('�
276 [%s] Payment received · %s', 'yatra'), $site, $ref);
277
278 case self::TYPE_BOOKING_CANCELLATION:
279 return sprintf(__('📋 [%s] Booking cancelled · %s', 'yatra'), $site, $ref);
280
281 case self::TYPE_BOOKING_REMINDER:
282 return sprintf(__('🗓️ [%s] Your trip is coming up · %s', 'yatra'), $site, $ref);
283
284 case self::TYPE_ADMIN_NEW_BOOKING:
285 return sprintf(__('🔔 [%s] New booking · %s (#%s)', 'yatra'), $site, $ref, $v['booking_id'] ?? '');
286
287 case self::TYPE_ADMIN_PAYMENT_RECEIVED:
288 return sprintf(__('�
289 [%s] Payment received · %s (#%s)', 'yatra'), $site, $ref, $v['booking_id'] ?? '');
290
291 case self::TYPE_ADMIN_BOOKING_CANCELLED:
292 return sprintf(__('📋 [%s] Booking cancelled · %s (#%s)', 'yatra'), $site, $ref, $v['booking_id'] ?? '');
293
294 default:
295 return sprintf(__('✉️ [%s] Notification', 'yatra'), $site);
296 }
297 }
298
299 /**
300 * @param array<string, string> $v
301 */
302 private static function defaultBody(string $type, array $v): string
303 {
304 switch ($type) {
305 case self::TYPE_BOOKING_CONFIRMATION:
306 return EmailTemplateDefaults::fallbackTransactionalBooking($v);
307
308 case self::TYPE_PAYMENT_CONFIRMATION:
309 return EmailTemplateDefaults::fallbackTransactionalPayment($v);
310
311 case self::TYPE_BOOKING_CANCELLATION:
312 return EmailTemplateDefaults::fallbackTransactionalCancellation($v);
313
314 case self::TYPE_BOOKING_REMINDER:
315 return EmailTemplateDefaults::fallbackTransactionalReminder($v);
316
317 case self::TYPE_ADMIN_NEW_BOOKING:
318 return EmailTemplateDefaults::fallbackAdminNewBooking($v);
319
320 case self::TYPE_ADMIN_PAYMENT_RECEIVED:
321 return EmailTemplateDefaults::fallbackAdminPaymentReceived($v);
322
323 case self::TYPE_ADMIN_BOOKING_CANCELLED:
324 return EmailTemplateDefaults::fallbackAdminBookingCancelled($v);
325
326 default:
327 return EmailTemplateLayout::customer(
328 '✉️',
329 __('Notification', 'yatra'),
330 '<p style="margin:0;color:#475569;">' . esc_html__('This is an automated message from your travel site.', 'yatra') . '</p>',
331 esc_html($v['site_name'] ?? get_bloginfo('name'))
332 );
333 }
334 }
335
336 /**
337 * Build variables from a booking row (admin / cron).
338 */
339 public static function variablesFromBooking(object $booking): array
340 {
341 $currency = $booking->currency ?? SettingsService::getCurrency();
342 $travelDate = !empty($booking->travel_date)
343 ? date_i18n(get_option('date_format'), strtotime((string) $booking->travel_date))
344 : '';
345
346 return [
347 'customer_name' => trim((string) (($booking->contact_first_name ?? '') . ' ' . ($booking->contact_last_name ?? ''))),
348 'customer_first_name' => (string) ($booking->contact_first_name ?? ''),
349 'customer_last_name' => (string) ($booking->contact_last_name ?? ''),
350 'customer_email' => (string) ($booking->contact_email ?? ''),
351 'customer_phone' => (string) ($booking->contact_phone ?? ''),
352 'booking_reference' => (string) ($booking->reference ?? ''),
353 'booking_id' => (string) (int) ($booking->id ?? 0),
354 'trip_name' => (string) ($booking->trip_title ?? ''),
355 'trip_url' => !empty($booking->trip_slug)
356 ? home_url('/' . SettingsService::getTripBase() . '/' . rawurlencode((string) $booking->trip_slug) . '/')
357 : home_url('/'),
358 'travel_date' => $travelDate,
359 'travelers_count' => (string) (int) ($booking->travelers_count ?? 0),
360 'total_amount_formatted' => yatra_format_price((float) ($booking->total_amount ?? 0)),
361 'amount_due_formatted' => yatra_format_price((float) ($booking->amount_due ?? 0)),
362 'currency' => $currency,
363 'booking_status' => (string) ($booking->status ?? ''),
364 'payment_status' => (string) ($booking->payment_status ?? ''),
365 'admin_url' => admin_url('admin.php?page=yatra'),
366 ];
367 }
368 }
369