Commit | Line | Data |
---|---|---|
1348d066 | 1 | <?php |
ebb4e06b EM |
2 | |
3 | use Civi\Test\EventCheck; | |
4 | use Civi\Test\HookInterface; | |
5 | ||
6 | return new class() extends EventCheck implements HookInterface { | |
1348d066 TO |
7 | |
8 | private $paramSpecs = [ | |
9 | ||
10 | // ## Envelope: Common | |
11 | ||
12 | 'toName' => ['type' => 'string|NULL'], | |
13 | 'toEmail' => ['type' => 'string|NULL'], | |
14 | 'cc' => ['type' => 'string|NULL'], | |
15 | 'bcc' => ['type' => 'string|NULL'], | |
16 | 'headers' => ['type' => 'array'], | |
17 | 'attachments' => ['type' => 'array|NULL'], | |
18 | 'isTest' => ['type' => 'bool|int'], | |
19 | ||
20 | // ## Envelope: singleEmail/messageTemplate | |
21 | ||
22 | 'from' => ['type' => 'string|NULL', 'for' => ['messageTemplate', 'singleEmail']], | |
23 | 'replyTo' => ['type' => 'string|NULL', 'for' => ['messageTemplate', 'singleEmail']], | |
24 | 'returnPath' => ['type' => 'string|NULL', 'for' => ['messageTemplate', 'singleEmail']], | |
25 | 'isEmailPdf' => ['type' => 'bool', 'for' => 'messageTemplate'], | |
26 | 'PDFFilename' => ['type' => 'string|NULL', 'for' => 'messageTemplate'], | |
27 | 'autoSubmitted' => ['type' => 'bool', 'for' => 'messageTemplate'], | |
28 | 'Message-ID' => ['type' => 'string', 'for' => ['messageTemplate', 'singleEmail']], | |
29 | 'messageId' => ['type' => 'string', 'for' => ['messageTemplate', 'singleEmail']], | |
30 | ||
31 | // ## Envelope: CiviMail/Flexmailer | |
32 | ||
33 | 'Reply-To' => ['type' => 'string|NULL', 'for' => ['civimail', 'flexmailer']], | |
34 | 'Return-Path' => ['type' => 'string|NULL', 'for' => ['civimail', 'flexmailer']], | |
35 | 'From' => ['type' => 'string|NULL', 'for' => ['civimail', 'flexmailer']], | |
36 | 'Subject' => ['type' => 'string|NULL', 'for' => ['civimail', 'flexmailer']], | |
37 | 'List-Unsubscribe' => ['type' => 'string|NULL', 'for' => ['civimail', 'flexmailer']], | |
38 | 'X-CiviMail-Bounce' => ['type' => 'string|NULL', 'for' => ['civimail', 'flexmailer']], | |
39 | 'Precedence' => ['type' => 'string|NULL', 'for' => ['civimail', 'flexmailer'], 'regex' => '/(bulk|first-class|list)/'], | |
40 | 'job_id' => ['type' => 'int|NULL', 'for' => ['civimail', 'flexmailer']], | |
41 | ||
42 | // ## Content | |
43 | ||
44 | 'subject' => ['for' => ['messageTemplate', 'singleEmail'], 'type' => 'string'], | |
45 | 'text' => ['type' => 'string|NULL'], | |
46 | 'html' => ['type' => 'string|NULL'], | |
47 | ||
48 | // ## Model: messageTemplate | |
49 | ||
50 | 'tokenContext' => ['type' => 'array', 'for' => 'messageTemplate'], | |
51 | 'tplParams' => ['type' => 'array', 'for' => 'messageTemplate'], | |
52 | 'contactId' => ['type' => 'int|NULL', 'for' => 'messageTemplate' /* deprecated in favor of tokenContext[contactId] */], | |
b7ade3e0 TO |
53 | 'workflow' => [ |
54 | 'regex' => '/^([a-zA-Z_]+)$/', | |
55 | 'type' => 'string', | |
56 | 'for' => 'messageTemplate', | |
57 | ], | |
1348d066 TO |
58 | 'valueName' => [ |
59 | 'regex' => '/^([a-zA-Z_]+)$/', | |
60 | 'type' => 'string', | |
61 | 'for' => 'messageTemplate', | |
62 | ], | |
63 | 'groupName' => [ | |
64 | // This field is generally deprecated. Historically, this was tied to various option-groups (`msg_*`), | |
65 | // but it also seems to have been used with a few long-form English names. | |
66 | 'regex' => '/^(msg_[a-zA-Z_]+|Scheduled Reminder Sender|Activity Email Sender|Report Email Sender|Mailing Event Welcome|CRM_Core_Config_MailerTest)$/', | |
67 | 'type' => 'string', | |
68 | 'for' => ['messageTemplate', 'singleEmail'], | |
69 | ], | |
70 | ||
71 | // The model is not passed into this hook because it would create ambiguity when you alter properties. | |
72 | // If you want to expose it via hook, add another hook. | |
73 | 'model' => ['for' => 'messageTemplate', 'type' => 'NULL'], | |
74 | 'modelProps' => ['for' => 'messageTemplate', 'type' => 'NULL'], | |
75 | ||
76 | // ## Model: Adhoc/incomplete/needs attention | |
77 | ||
78 | 'contributionId' => ['type' => 'int', 'for' => 'messageTemplate'], | |
79 | 'petitionId' => ['type' => 'int', 'for' => 'messageTemplate'], | |
80 | 'petitionTitle' => ['type' => 'string', 'for' => 'messageTemplate'], | |
81 | 'table' => ['type' => 'string', 'for' => 'messageTemplate', 'regex' => '/civicrm_msg_template/'], | |
82 | 'entity' => ['type' => 'string|NULL', 'for' => 'singleEmail'], | |
83 | 'entity_id' => ['type' => 'int|NULL', 'for' => 'singleEmail'], | |
84 | ||
85 | // ## View: messageTemplate | |
86 | ||
87 | 'messageTemplateID' => ['type' => 'int|NULL', 'for' => 'messageTemplate'], | |
88 | 'messageTemplate' => ['type' => 'array|NULL', 'for' => 'messageTemplate'], | |
89 | 'disableSmarty' => ['type' => 'bool|int', 'for' => 'messageTemplate'], | |
90 | ]; | |
91 | ||
ebb4e06b | 92 | public function isSupported($test): bool { |
1348d066 TO |
93 | // MailTest does intentionally breaky things to provoke+ensure decent error-handling. |
94 | //So we will not enforce generic rules on it. | |
95 | return !($test instanceof CRM_Utils_MailTest); | |
96 | } | |
97 | ||
98 | /** | |
99 | * Ensure that the hook data is always well-formed. | |
100 | * | |
101 | * @see \CRM_Utils_Hook::alterMailParams() | |
102 | */ | |
ebb4e06b EM |
103 | public function hook_civicrm_alterMailParams(&$params, $context = NULL): void { |
104 | $msg = 'Non-conforming hook_civicrm_alterMailParams(..., $context)'; | |
1348d066 TO |
105 | $dump = print_r($params, 1); |
106 | ||
107 | $this->assertRegExp('/^(messageTemplate|civimail|singleEmail|flexmailer)$/', | |
108 | $context, "$msg: Unrecognized context ($context)\n$dump"); | |
109 | ||
110 | $contexts = [$context]; | |
111 | if ($context === 'singleEmail' && array_key_exists('tokenContext', $params)) { | |
112 | // Don't look now, but `sendTemplate()` fires this hook twice for the message! Once with $context=messageTemplate; again with $context=singleEmail. | |
113 | $contexts[] = 'messageTemplate'; | |
114 | } | |
115 | ||
116 | $paramSpecs = array_filter($this->paramSpecs, function ($f) use ($contexts) { | |
117 | return !isset($f['for']) || array_intersect((array) $f['for'], $contexts); | |
118 | }); | |
119 | ||
120 | $unknownKeys = array_diff(array_keys($params), array_keys($paramSpecs)); | |
121 | if ($unknownKeys !== []) { | |
122 | echo ''; | |
123 | } | |
124 | $this->assertEquals([], $unknownKeys, "$msg: Unrecognized keys: " . implode(', ', $unknownKeys) . "\n$dump"); | |
125 | ||
126 | foreach ($params as $key => $value) { | |
3d1f6d1c TO |
127 | if (isset($paramSpecs[$key]['type'])) { |
128 | $this->assertType($paramSpecs[$key]['type'], $value, "$msg: Bad data-type found in param ($key)\n$dump"); | |
129 | } | |
130 | if (isset($paramSpecs[$key]['regex']) && $value !== NULL) { | |
1348d066 TO |
131 | $this->assertRegExp($paramSpecs[$key]['regex'], $value, "Parameter [$key => $value] should match regex ({$paramSpecs[$key]['regex']})"); |
132 | } | |
133 | } | |
134 | ||
135 | if ($context === 'messageTemplate') { | |
ebb4e06b | 136 | $this->assertNotEmpty($params['workflow'], "$msg: Message templates must always specify a symbolic name of the step/task\n$dump"); |
b7ade3e0 TO |
137 | if (isset($params['valueName'])) { |
138 | // This doesn't require that valueName be supplied - but if it is supplied, it must match the workflow name. | |
139 | $this->assertEquals($params['workflow'], $params['valueName'], "$msg: If given, workflow and valueName must match\n$dump"); | |
140 | } | |
1348d066 TO |
141 | $this->assertEquals($params['contactId'] ?? NULL, $params['tokenContext']['contactId'] ?? NULL, "$msg: contactId moved to tokenContext, but legacy value should be equivalent\n$dump"); |
142 | ||
143 | // This assertion is surprising -- yet true. We should perhaps check if it was true in past releases... | |
144 | $this->assertTrue(empty($params['text']) && empty($params['html']) && empty($params['subject']), "$msg: Content is not given if context==messageTemplate\n$dump"); | |
145 | } | |
146 | ||
147 | if ($context !== 'messageTemplate') { | |
148 | $this->assertTrue(!empty($params['text']) || !empty($params['html']) || !empty($params['subject']), "$msg: Must provide at least one of: text, html, subject\n$dump"); | |
149 | } | |
150 | ||
151 | if (isset($params['groupName']) && $params['groupName'] === 'Scheduled Reminder Sender') { | |
ebb4e06b EM |
152 | $this->assertNotEmpty($params['entity'], "$msg: Scheduled reminders should have entity\n$dump"); |
153 | $this->assertNotEmpty($params['entity_id'], "$msg: Scheduled reminders should have entity_id\n$dump"); | |
1348d066 TO |
154 | } |
155 | } | |
156 | ||
157 | }; |