| 1 |
<?php |
| 2 |
|
| 3 |
declare(strict_types=1); |
| 4 |
|
| 5 |
namespace Yatra\Services; |
| 6 |
|
| 7 |
/** |
| 8 |
* Reusable HTML shells for transactional / automation emails (table-based for clients like Outlook). |
| 9 |
* Inner fragments may contain {{merge_tags}} (defaults) or pre-escaped HTML (PHP fallbacks). |
| 10 |
*/ |
| 11 |
final class EmailTemplateLayout |
| 12 |
{ |
| 13 |
private const CUSTOMER_HEADER_BG = '#0d9488'; |
| 14 |
|
| 15 |
private const ADMIN_HEADER_BG = '#1e293b'; |
| 16 |
|
| 17 |
/** |
| 18 |
* Customer-facing email: soft outer background, colored header band, white card body. |
| 19 |
* |
| 20 |
* @param string $footerLine Site line or merge tag (e.g. {{site_name}}) |
| 21 |
*/ |
| 22 |
public static function customer( |
| 23 |
string $emoji, |
| 24 |
string $heading, |
| 25 |
string $innerHtml, |
| 26 |
string $footerLine = '{{site_name}}', |
| 27 |
string $preheader = '' |
| 28 |
): string { |
| 29 |
$headingEsc = esc_html($heading); |
| 30 |
$emojiHtml = $emoji !== '' ? '<p style="margin:0 0 10px;font-size:32px;line-height:1;">' . esc_html($emoji) . '</p>' : ''; |
| 31 |
$pre = $preheader !== '' |
| 32 |
? '<div style="display:none;font-size:1px;color:#f4f7fb;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;">' . esc_html($preheader) . '</div>' |
| 33 |
: ''; |
| 34 |
|
| 35 |
return '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>' . $headingEsc . '</title></head>' |
| 36 |
. '<body style="margin:0;padding:0;background-color:#e8eef5;">' . $pre |
| 37 |
. '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color:#e8eef5;">' |
| 38 |
. '<tr><td align="center" style="padding:28px 16px;">' |
| 39 |
. '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" style="max-width:600px;width:100%;background:#ffffff;border-radius:16px;overflow:hidden;box-shadow:0 12px 40px rgba(15,23,42,0.1);">' |
| 40 |
. '<tr><td style="background:' . self::CUSTOMER_HEADER_BG . ';padding:28px 32px;text-align:center;">' |
| 41 |
. $emojiHtml |
| 42 |
. '<h1 style="margin:0;font-family:Georgia,\'Times New Roman\',serif;font-size:24px;font-weight:600;color:#f0fdfa;letter-spacing:-0.02em;">' . $headingEsc . '</h1>' |
| 43 |
. '</td></tr>' |
| 44 |
. '<tr><td style="padding:32px 32px 24px;font-family:\'Segoe UI\',Roboto,\'Helvetica Neue\',Arial,sans-serif;font-size:16px;line-height:1.65;color:#334155;">' |
| 45 |
. $innerHtml |
| 46 |
. '</td></tr>' |
| 47 |
. '<tr><td style="padding:0 32px 32px;font-family:\'Segoe UI\',Roboto,\'Helvetica Neue\',Arial,sans-serif;font-size:13px;line-height:1.5;color:#94a3b8;text-align:center;border-top:1px solid #f1f5f9;">' |
| 48 |
. '<p style="margin:20px 0 0;">' . $footerLine . '</p>' |
| 49 |
. '</td></tr>' |
| 50 |
. '</table>' |
| 51 |
. '<p style="margin:16px 0 0;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:11px;color:#94a3b8;text-align:center;">' |
| 52 |
. esc_html__('You received this email because of a booking or enquiry on our site.', 'yatra') |
| 53 |
. '</p>' |
| 54 |
. '</td></tr></table></body></html>'; |
| 55 |
} |
| 56 |
|
| 57 |
/** |
| 58 |
* Admin / internal email: compact, high-contrast header. |
| 59 |
*/ |
| 60 |
public static function admin( |
| 61 |
string $emoji, |
| 62 |
string $heading, |
| 63 |
string $innerHtml, |
| 64 |
?string $footerLine = null |
| 65 |
): string { |
| 66 |
$headingEsc = esc_html($heading); |
| 67 |
$footer = $footerLine ?? esc_html__('Yatra · Admin notification', 'yatra'); |
| 68 |
|
| 69 |
return '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>' . $headingEsc . '</title></head>' |
| 70 |
. '<body style="margin:0;padding:0;background-color:#e2e8f0;">' |
| 71 |
. '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color:#e2e8f0;">' |
| 72 |
. '<tr><td align="center" style="padding:24px 12px;">' |
| 73 |
. '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" style="max-width:600px;width:100%;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #cbd5e1;">' |
| 74 |
. '<tr><td style="background:' . self::ADMIN_HEADER_BG . ';padding:20px 24px;">' |
| 75 |
. '<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>' |
| 76 |
. '<td style="font-size:26px;line-height:1;width:40px;">' . esc_html($emoji) . '</td>' |
| 77 |
. '<td style="font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:18px;font-weight:700;color:#f8fafc;">' . $headingEsc . '</td>' |
| 78 |
. '</tr></table>' |
| 79 |
. '</td></tr>' |
| 80 |
. '<tr><td style="padding:24px;font-family:\'Segoe UI\',Roboto,\'Helvetica Neue\',Arial,sans-serif;font-size:15px;line-height:1.6;color:#1e293b;">' |
| 81 |
. $innerHtml |
| 82 |
. '</td></tr>' |
| 83 |
. '<tr><td style="padding:16px 24px 20px;font-family:\'Segoe UI\',Roboto,Arial,sans-serif;font-size:12px;color:#64748b;background:#f8fafc;border-top:1px solid #e2e8f0;">' |
| 84 |
. $footer |
| 85 |
. '</td></tr>' |
| 86 |
. '</table></td></tr></table></body></html>'; |
| 87 |
} |
| 88 |
|
| 89 |
/** |
| 90 |
* Light card block for key–value rows (inner fragment for customer emails). |
| 91 |
* |
| 92 |
* @param array<int, array{label: string, value: string}> $rows label/value may contain merge tags |
| 93 |
*/ |
| 94 |
public static function detailCard(array $rows): string |
| 95 |
{ |
| 96 |
if ($rows === []) { |
| 97 |
return ''; |
| 98 |
} |
| 99 |
$out = '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;margin:24px 0;">'; |
| 100 |
$filtered = array_values(array_filter($rows, static function (array $row): bool { |
| 101 |
return trim((string) ($row['value'] ?? '')) !== ''; |
| 102 |
})); |
| 103 |
$last = count($filtered) - 1; |
| 104 |
foreach ($filtered as $i => $row) { |
| 105 |
$label = $row['label'] ?? ''; |
| 106 |
$value = $row['value'] ?? ''; |
| 107 |
$borderBottom = $i < $last ? 'border-bottom:1px solid #e2e8f0;' : ''; |
| 108 |
$out .= '<tr><td style="padding:14px 20px;' . $borderBottom . '">' |
| 109 |
. '<table role="presentation" width="100%" cellpadding="0" cellspacing="0">' |
| 110 |
. '<tr>' |
| 111 |
. '<td style="font-size:13px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:0.04em;width:38%;vertical-align:top;">' . $label . '</td>' |
| 112 |
. '<td style="font-size:15px;color:#0f172a;font-weight:600;vertical-align:top;">' . $value . '</td>' |
| 113 |
. '</tr></table></td></tr>'; |
| 114 |
} |
| 115 |
$out .= '</table>'; |
| 116 |
|
| 117 |
return $out; |
| 118 |
} |
| 119 |
|
| 120 |
/** |
| 121 |
* Primary CTA button (table for Outlook). |
| 122 |
*/ |
| 123 |
public static function button(string $href, string $label): string |
| 124 |
{ |
| 125 |
$hrefEsc = esc_url($href); |
| 126 |
$labelEsc = esc_html($label); |
| 127 |
|
| 128 |
return '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px 0;"><tr>' |
| 129 |
. '<td style="border-radius:10px;background:#0d9488;">' |
| 130 |
. '<a href="' . $hrefEsc . '" 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;">' |
| 131 |
. $labelEsc . '</a></td></tr></table>'; |
| 132 |
} |
| 133 |
} |
| 134 |
|