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