| @@ -53,8 +53,14 @@ | ||
| 53 | 53 | public const EVENT_ENQUIRY_RESPONDED = 'enquiry.responded'; |
| 54 | 54 | public const EVENT_REVIEW_REQUEST = 'marketing.review_request'; |
| 55 | 55 | public const EVENT_CONSENT_REQUESTED = 'consent.requested'; |
| 56 | 56 | public const EVENT_ACCOUNT_EMAIL_VERIFICATION = 'account.email_verification'; |
| 57 | + // Account email change (CustomerService): the request goes to the NEW | |
| 58 | + // address with a confirmation link; the "changed" security notice goes to | |
| 59 | + // the OLD address. Pro seeds templates on these keys, so they must be | |
| 60 | + // registered here or the template editor rejects them ("Invalid event key"). | |
| 61 | + public const EVENT_ACCOUNT_EMAIL_CHANGE_REQUEST = 'account.email_change_request'; | |
| 62 | + public const EVENT_ACCOUNT_EMAIL_CHANGED = 'account.email_changed'; | |
| 57 | 63 | public const EVENT_SCHEDULED_PAYMENT_REMINDER = 'scheduled.payment.reminder'; |
| 58 | 64 | public const EVENT_SCHEDULED_PAYMENT_SUCCEEDED = 'scheduled.payment.succeeded'; |
| 59 | 65 | public const EVENT_SCHEDULED_PAYMENT_FAILED = 'scheduled.payment.failed'; |
| 60 | 66 | public const EVENT_BOOKING_ABANDONED_RECOVERY = 'booking.abandoned_recovery'; |
| @@ -63,8 +69,15 @@ | ||
| 63 | 69 | * Every event that resolves a `variablesFromBooking()`-derived |
| 64 | 70 | * booking context. Booking-context tags inherit this list so |
| 65 | 71 | * the per-event whitelist stays in sync as events evolve. |
| 66 | 72 | */ |
| 73 | + /** Customer-account emails: share the customer + intro/footer tags. */ | |
| 74 | + private const ACCOUNT_CONTEXT_EVENTS = [ | |
| 75 | + self::EVENT_ACCOUNT_EMAIL_VERIFICATION, | |
| 76 | + self::EVENT_ACCOUNT_EMAIL_CHANGE_REQUEST, | |
| 77 | + self::EVENT_ACCOUNT_EMAIL_CHANGED, | |
| 78 | + ]; | |
| 79 | + | |
| 67 | 80 | private const BOOKING_CONTEXT_EVENTS = [ |
| 68 | 81 | self::EVENT_BOOKING_CREATED, |
| 69 | 82 | self::EVENT_BOOKING_CONFIRMED, |
| 70 | 83 | self::EVENT_BOOKING_CANCELLED, |
| @@ -201,12 +214,10 @@ | ||
| 201 | 214 | 'sample' => 'John Doe', |
| 202 | 215 | 'events' => array_merge( |
| 203 | 216 | $bookingContextEvents, |
| 204 | 217 | $enquiryContextEvents, |
| 205 | - [ | |
| 206 | - self::EVENT_ACCOUNT_EMAIL_VERIFICATION, | |
| 207 | - self::EVENT_BOOKING_ABANDONED_RECOVERY, | |
| 208 | - ] | |
| 218 | + self::ACCOUNT_CONTEXT_EVENTS, | |
| 219 | + [self::EVENT_BOOKING_ABANDONED_RECOVERY] | |
| 209 | 220 | ), |
| 210 | 221 | ], |
| 211 | 222 | 'customer_first_name' => [ |
| 212 | 223 | 'key' => 'customer_first_name', |
| @@ -215,9 +226,9 @@ | ||
| 215 | 226 | 'category' => self::CATEGORY_CUSTOMER, |
| 216 | 227 | 'sample' => 'John', |
| 217 | 228 | 'events' => array_merge( |
| 218 | 229 | $bookingContextEvents, |
| 219 | - [self::EVENT_ACCOUNT_EMAIL_VERIFICATION] | |
| 230 | + self::ACCOUNT_CONTEXT_EVENTS | |
| 220 | 231 | ), |
| 221 | 232 | ], |
| 222 | 233 | 'customer_last_name' => [ |
| 223 | 234 | 'key' => 'customer_last_name', |
| @@ -235,12 +246,10 @@ | ||
| 235 | 246 | 'sample' => '[email protected]', |
| 236 | 247 | 'events' => array_merge( |
| 237 | 248 | $bookingContextEvents, |
| 238 | 249 | $enquiryContextEvents, |
| 239 | - [ | |
| 240 | - self::EVENT_ACCOUNT_EMAIL_VERIFICATION, | |
| 241 | - self::EVENT_BOOKING_ABANDONED_RECOVERY, | |
| 242 | - ] | |
| 250 | + self::ACCOUNT_CONTEXT_EVENTS, | |
| 251 | + [self::EVENT_BOOKING_ABANDONED_RECOVERY] | |
| 243 | 252 | ), |
| 244 | 253 | ], |
| 245 | 254 | 'customer_phone' => [ |
| 246 | 255 | 'key' => 'customer_phone', |
| @@ -759,20 +768,29 @@ | ||
| 759 | 768 | // --------------------------------------------------------- |
| 760 | 769 | 'verification_link' => [ |
| 761 | 770 | 'key' => 'verification_link', |
| 762 | 771 | 'label' => 'Verification Link', |
| 763 | - 'description' => 'Magic link the customer opens to verify their email.', | |
| 772 | + 'description' => 'Magic link the customer opens to verify their email, or to confirm a requested new address.', | |
| 764 | 773 | 'category' => self::CATEGORY_ACCOUNT, |
| 765 | 774 | 'sample' => $verificationSampleLink, |
| 766 | - 'events' => [self::EVENT_ACCOUNT_EMAIL_VERIFICATION], | |
| 775 | + // Not offered for the "changed" notice — that email carries no link. | |
| 776 | + 'events' => [self::EVENT_ACCOUNT_EMAIL_VERIFICATION, self::EVENT_ACCOUNT_EMAIL_CHANGE_REQUEST], | |
| 767 | 777 | ], |
| 778 | + 'new_email' => [ | |
| 779 | + 'key' => 'new_email', | |
| 780 | + 'label' => 'New Email Address', | |
| 781 | + 'description' => 'The address the customer asked to switch their account to.', | |
| 782 | + 'category' => self::CATEGORY_ACCOUNT, | |
| 783 | + 'sample' => '[email protected]', | |
| 784 | + 'events' => [self::EVENT_ACCOUNT_EMAIL_CHANGE_REQUEST, self::EVENT_ACCOUNT_EMAIL_CHANGED], | |
| 785 | + ], | |
| 768 | 786 | 'intro_paragraph' => [ |
| 769 | 787 | 'key' => 'intro_paragraph', |
| 770 | 788 | 'label' => 'Intro Paragraph', |
| 771 | - 'description' => 'Opening sentence (registration / resend variant).', | |
| 789 | + 'description' => 'Opening sentence, set by the sender for each account email (verification, change request, changed notice).', | |
| 772 | 790 | 'category' => self::CATEGORY_ACCOUNT, |
| 773 | 791 | 'sample' => 'Thank you for registering. Click the button in this email to verify your address.', |
| 774 | - 'events' => [self::EVENT_ACCOUNT_EMAIL_VERIFICATION], | |
| 792 | + 'events' => self::ACCOUNT_CONTEXT_EVENTS, | |
| 775 | 793 | ], |
| 776 | 794 | 'footer_note' => [ |
| 777 | 795 | 'key' => 'footer_note', |
| 778 | 796 | 'label' => 'Footer Note', |
| @@ -778,9 +796,9 @@ | ||
| 778 | 796 | 'label' => 'Footer Note', |
| 779 | 797 | 'description' => 'Disclaimer for unintended recipients.', |
| 780 | 798 | 'category' => self::CATEGORY_ACCOUNT, |
| 781 | 799 | 'sample' => 'If you did not create an account, you can ignore this email.', |
| 782 | - 'events' => [self::EVENT_ACCOUNT_EMAIL_VERIFICATION], | |
| 800 | + 'events' => self::ACCOUNT_CONTEXT_EVENTS, | |
| 783 | 801 | ], |
| 784 | 802 | 'expiry_notice_html' => [ |
| 785 | 803 | 'key' => 'expiry_notice_html', |
| 786 | 804 | 'label' => 'Expiry Notice (HTML)', |
| @@ -875,10 +893,19 @@ | ||
| 875 | 893 | $section = $config[$sectionKey] ?? null; |
| 876 | 894 | if (!is_array($section) || (isset($section['enabled']) && !$section['enabled'])) { |
| 877 | 895 | continue; |
| 878 | 896 | } |
| 879 | - foreach (($section['fields'] ?? []) as $field) { | |
| 880 | - if (empty($field['enabled']) || empty($field['id'])) { | |
| 897 | + // Fields the section can ask on ANY trip: the global list plus every | |
| 898 | + // per-trip condition's list (Pro) — a field that only a "Trekking" | |
| 899 | + // version of the form asks still needs its merge tag. | |
| 900 | + $fields = is_array($section['fields'] ?? null) ? $section['fields'] : []; | |
| 901 | + foreach ((array) ($section['conditions'] ?? []) as $condition) { | |
| 902 | + if (is_array($condition) && is_array($condition['fields'] ?? null)) { | |
| 903 | + $fields = array_merge($fields, $condition['fields']); | |
| 904 | + } | |
| 905 | + } | |
| 906 | + foreach ($fields as $field) { | |
| 907 | + if (!is_array($field) || empty($field['enabled']) || empty($field['id'])) { | |
| 881 | 908 | continue; |
| 882 | 909 | } |
| 883 | 910 | // Text blocks are display-only content, not inputs — they hold no |
| 884 | 911 | // booking value, so they must not become email merge tags. |