| @@ -44,8 +44,10 @@ | ||
| 44 | 44 | public const EVENT_BOOKING_CANCELLED = 'booking.cancelled'; |
| 45 | 45 | public const EVENT_BOOKING_COMPLETED = 'booking.completed'; |
| 46 | 46 | public const EVENT_BOOKING_EXPIRED = 'booking.expired'; |
| 47 | 47 | public const EVENT_PAYMENT_RECEIVED = 'payment.received'; |
| 48 | + /** A payment landed but a balance is still outstanding (deposit / instalment). */ | |
| 49 | + public const EVENT_PAYMENT_PARTIAL_RECEIVED = 'payment.partial_received'; | |
| 48 | 50 | public const EVENT_PAYMENT_REMINDER = 'payment.reminder'; |
| 49 | 51 | public const EVENT_REMINDER_TRIP = 'reminder.trip'; |
| 50 | 52 | public const EVENT_ENQUIRY_CREATED = 'enquiry.created'; |
| 51 | 53 | public const EVENT_ENQUIRY_RESPONDED = 'enquiry.responded'; |
| @@ -51,8 +53,14 @@ | ||
| 51 | 53 | public const EVENT_ENQUIRY_RESPONDED = 'enquiry.responded'; |
| 52 | 54 | public const EVENT_REVIEW_REQUEST = 'marketing.review_request'; |
| 53 | 55 | public const EVENT_CONSENT_REQUESTED = 'consent.requested'; |
| 54 | 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'; | |
| 55 | 63 | public const EVENT_SCHEDULED_PAYMENT_REMINDER = 'scheduled.payment.reminder'; |
| 56 | 64 | public const EVENT_SCHEDULED_PAYMENT_SUCCEEDED = 'scheduled.payment.succeeded'; |
| 57 | 65 | public const EVENT_SCHEDULED_PAYMENT_FAILED = 'scheduled.payment.failed'; |
| 58 | 66 | public const EVENT_BOOKING_ABANDONED_RECOVERY = 'booking.abandoned_recovery'; |
| @@ -61,8 +69,15 @@ | ||
| 61 | 69 | * Every event that resolves a `variablesFromBooking()`-derived |
| 62 | 70 | * booking context. Booking-context tags inherit this list so |
| 63 | 71 | * the per-event whitelist stays in sync as events evolve. |
| 64 | 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 | + | |
| 65 | 80 | private const BOOKING_CONTEXT_EVENTS = [ |
| 66 | 81 | self::EVENT_BOOKING_CREATED, |
| 67 | 82 | self::EVENT_BOOKING_CONFIRMED, |
| 68 | 83 | self::EVENT_BOOKING_CANCELLED, |
| @@ -68,8 +83,9 @@ | ||
| 68 | 83 | self::EVENT_BOOKING_CANCELLED, |
| 69 | 84 | self::EVENT_BOOKING_COMPLETED, |
| 70 | 85 | self::EVENT_BOOKING_EXPIRED, |
| 71 | 86 | self::EVENT_PAYMENT_RECEIVED, |
| 87 | + self::EVENT_PAYMENT_PARTIAL_RECEIVED, | |
| 72 | 88 | self::EVENT_PAYMENT_REMINDER, |
| 73 | 89 | self::EVENT_REMINDER_TRIP, |
| 74 | 90 | self::EVENT_REVIEW_REQUEST, |
| 75 | 91 | self::EVENT_SCHEDULED_PAYMENT_REMINDER, |
| @@ -198,12 +214,10 @@ | ||
| 198 | 214 | 'sample' => 'John Doe', |
| 199 | 215 | 'events' => array_merge( |
| 200 | 216 | $bookingContextEvents, |
| 201 | 217 | $enquiryContextEvents, |
| 202 | - [ | |
| 203 | - self::EVENT_ACCOUNT_EMAIL_VERIFICATION, | |
| 204 | - self::EVENT_BOOKING_ABANDONED_RECOVERY, | |
| 205 | - ] | |
| 218 | + self::ACCOUNT_CONTEXT_EVENTS, | |
| 219 | + [self::EVENT_BOOKING_ABANDONED_RECOVERY] | |
| 206 | 220 | ), |
| 207 | 221 | ], |
| 208 | 222 | 'customer_first_name' => [ |
| 209 | 223 | 'key' => 'customer_first_name', |
| @@ -212,9 +226,9 @@ | ||
| 212 | 226 | 'category' => self::CATEGORY_CUSTOMER, |
| 213 | 227 | 'sample' => 'John', |
| 214 | 228 | 'events' => array_merge( |
| 215 | 229 | $bookingContextEvents, |
| 216 | - [self::EVENT_ACCOUNT_EMAIL_VERIFICATION] | |
| 230 | + self::ACCOUNT_CONTEXT_EVENTS | |
| 217 | 231 | ), |
| 218 | 232 | ], |
| 219 | 233 | 'customer_last_name' => [ |
| 220 | 234 | 'key' => 'customer_last_name', |
| @@ -232,12 +246,10 @@ | ||
| 232 | 246 | 'sample' => '[email protected]', |
| 233 | 247 | 'events' => array_merge( |
| 234 | 248 | $bookingContextEvents, |
| 235 | 249 | $enquiryContextEvents, |
| 236 | - [ | |
| 237 | - self::EVENT_ACCOUNT_EMAIL_VERIFICATION, | |
| 238 | - self::EVENT_BOOKING_ABANDONED_RECOVERY, | |
| 239 | - ] | |
| 250 | + self::ACCOUNT_CONTEXT_EVENTS, | |
| 251 | + [self::EVENT_BOOKING_ABANDONED_RECOVERY] | |
| 240 | 252 | ), |
| 241 | 253 | ], |
| 242 | 254 | 'customer_phone' => [ |
| 243 | 255 | 'key' => 'customer_phone', |
| @@ -491,9 +503,9 @@ | ||
| 491 | 503 | 'label' => 'Payment Amount (formatted)', |
| 492 | 504 | 'description' => 'Amount of the specific payment with currency.', |
| 493 | 505 | 'category' => self::CATEGORY_PAYMENT, |
| 494 | 506 | 'sample' => '$500.00', |
| 495 | - 'events' => [self::EVENT_PAYMENT_RECEIVED, self::EVENT_PAYMENT_REMINDER], | |
| 507 | + 'events' => [self::EVENT_PAYMENT_RECEIVED, self::EVENT_PAYMENT_PARTIAL_RECEIVED, self::EVENT_PAYMENT_REMINDER], | |
| 496 | 508 | ], |
| 497 | 509 | 'payment_method' => [ |
| 498 | 510 | 'key' => 'payment_method', |
| 499 | 511 | 'label' => 'Payment Method', |
| @@ -499,9 +511,9 @@ | ||
| 499 | 511 | 'label' => 'Payment Method', |
| 500 | 512 | 'description' => 'Instrument label (e.g. Card, Bank Transfer).', |
| 501 | 513 | 'category' => self::CATEGORY_PAYMENT, |
| 502 | 514 | 'sample' => 'Credit Card', |
| 503 | - 'events' => [self::EVENT_PAYMENT_RECEIVED, self::EVENT_PAYMENT_REMINDER], | |
| 515 | + 'events' => [self::EVENT_PAYMENT_RECEIVED, self::EVENT_PAYMENT_PARTIAL_RECEIVED, self::EVENT_PAYMENT_REMINDER], | |
| 504 | 516 | ], |
| 505 | 517 | 'transaction_id' => [ |
| 506 | 518 | 'key' => 'transaction_id', |
| 507 | 519 | 'label' => 'Transaction ID', |
| @@ -756,20 +768,29 @@ | ||
| 756 | 768 | // --------------------------------------------------------- |
| 757 | 769 | 'verification_link' => [ |
| 758 | 770 | 'key' => 'verification_link', |
| 759 | 771 | 'label' => 'Verification Link', |
| 760 | - '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.', | |
| 761 | 773 | 'category' => self::CATEGORY_ACCOUNT, |
| 762 | 774 | 'sample' => $verificationSampleLink, |
| 763 | - '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], | |
| 764 | 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 | + ], | |
| 765 | 786 | 'intro_paragraph' => [ |
| 766 | 787 | 'key' => 'intro_paragraph', |
| 767 | 788 | 'label' => 'Intro Paragraph', |
| 768 | - 'description' => 'Opening sentence (registration / resend variant).', | |
| 789 | + 'description' => 'Opening sentence, set by the sender for each account email (verification, change request, changed notice).', | |
| 769 | 790 | 'category' => self::CATEGORY_ACCOUNT, |
| 770 | 791 | 'sample' => 'Thank you for registering. Click the button in this email to verify your address.', |
| 771 | - 'events' => [self::EVENT_ACCOUNT_EMAIL_VERIFICATION], | |
| 792 | + 'events' => self::ACCOUNT_CONTEXT_EVENTS, | |
| 772 | 793 | ], |
| 773 | 794 | 'footer_note' => [ |
| 774 | 795 | 'key' => 'footer_note', |
| 775 | 796 | 'label' => 'Footer Note', |
| @@ -775,9 +796,9 @@ | ||
| 775 | 796 | 'label' => 'Footer Note', |
| 776 | 797 | 'description' => 'Disclaimer for unintended recipients.', |
| 777 | 798 | 'category' => self::CATEGORY_ACCOUNT, |
| 778 | 799 | 'sample' => 'If you did not create an account, you can ignore this email.', |
| 779 | - 'events' => [self::EVENT_ACCOUNT_EMAIL_VERIFICATION], | |
| 800 | + 'events' => self::ACCOUNT_CONTEXT_EVENTS, | |
| 780 | 801 | ], |
| 781 | 802 | 'expiry_notice_html' => [ |
| 782 | 803 | 'key' => 'expiry_notice_html', |
| 783 | 804 | 'label' => 'Expiry Notice (HTML)', |
| @@ -872,10 +893,19 @@ | ||
| 872 | 893 | $section = $config[$sectionKey] ?? null; |
| 873 | 894 | if (!is_array($section) || (isset($section['enabled']) && !$section['enabled'])) { |
| 874 | 895 | continue; |
| 875 | 896 | } |
| 876 | - foreach (($section['fields'] ?? []) as $field) { | |
| 877 | - 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'])) { | |
| 878 | 908 | continue; |
| 879 | 909 | } |
| 880 | 910 | // Text blocks are display-only content, not inputs — they hold no |
| 881 | 911 | // booking value, so they must not become email merge tags. |