Commit | Line | Data |
---|---|---|
92f656cb TO |
1 | <?php |
2 | ||
3 | /* | |
4 | +--------------------------------------------------------------------+ | |
5 | | Copyright CiviCRM LLC. All rights reserved. | | |
6 | | | | |
7 | | This work is published under the GNU AGPLv3 license with some | | |
8 | | permitted exceptions and without any warranty. For full license | | |
9 | | and copyright information, see https://civicrm.org/licensing | | |
10 | +--------------------------------------------------------------------+ | |
11 | */ | |
12 | ||
13 | namespace Civi\WorkflowMessage; | |
14 | ||
ebd92daf | 15 | use Civi\Api4\Utils\ReflectionUtils; |
92f656cb TO |
16 | use Civi\WorkflowMessage\Exception\WorkflowMessageException; |
17 | ||
18 | /** | |
19 | * A WorkflowMessage describes the inputs to an automated email messages. | |
20 | * | |
21 | * These classes may be instantiated by either class-name or workflow-name. | |
22 | * | |
23 | * Ex: $msgWf = new \CRM_Foo_WorkflowMessage_MyAlert(['tplParams' => [...tplValues...]]); | |
24 | * Ex: $msgWf = new \CRM_Foo_WorkflowMessage_MyAlert(['modelProps' => [...classProperties...]]); | |
25 | * Ex: $msgWf = WorkflowMessage::create('my_alert_name', ['tplParams' => [...tplValues...]]); | |
26 | * Ex: $msgWf = WorkflowMessage::create('my_alert_name', ['modelProps' => [...classProperties...]]); | |
27 | * | |
28 | * Instantiating by class-name will provide better hinting and inspection. | |
29 | * However, some workflows may not have specific classes at the time of writing. | |
30 | * Instantiating by workflow-name will work regardless of whether there is a specific class. | |
31 | */ | |
32 | class WorkflowMessage { | |
33 | ||
34 | /** | |
35 | * Create a new instance of the workflow-message context. | |
36 | * | |
37 | * @param string $wfName | |
38 | * Name of the workflow. | |
39 | * Ex: 'case_activity' | |
40 | * @param array $imports | |
41 | * List of data to use when populating the message. | |
42 | * | |
43 | * The parameters may be given in a mix of formats. This mix reflects two modes of operation: | |
44 | * | |
45 | * - (Informal/Generic) Traditionally, workflow-messages did not have formal parameters. Instead, | |
46 | * they relied on a mix of un(der)documented/unverifiable inputs -- supplied as a mix of Smarty | |
47 | * assignments, token-data, and sendTemplate() params. | |
48 | * - (Formal) More recently, workflow-messages could be defined with a PHP class that lists the | |
49 | * inputs explicitly. | |
50 | * | |
51 | * You may supply inputs using these keys: | |
52 | * | |
53 | * - `tplParams` (array): Smarty data. These values go to `$smarty->assign()`. | |
54 | * - `tokenContext` (array): Token-processing data. These values go to `$tokenProcessor->context`. | |
55 | * - `envelope` (array): Email delivery data. These values go to `sendTemplate(...)` | |
56 | * - `modelProps` (array): Formal parameters defined by a class. | |
57 | * | |
58 | * Informal workflow-messages ONLY support 'tplParams', 'tokenContext', and/or 'envelope'. | |
59 | * Formal workflow-messages accept any format. | |
60 | * | |
61 | * @return \Civi\WorkflowMessage\WorkflowMessageInterface | |
62 | * If there is a workflow-message class, then it will return an instance of that class. | |
63 | * Otherwise, it will return an instance of `GenericWorkflowMessage`. | |
64 | * @throws \Civi\WorkflowMessage\Exception\WorkflowMessageException | |
65 | */ | |
66 | public static function create(string $wfName, array $imports = []) { | |
67 | $classMap = static::getWorkflowNameClassMap(); | |
68 | $class = $classMap[$wfName] ?? 'Civi\WorkflowMessage\GenericWorkflowMessage'; | |
69 | $imports['envelope']['valueName'] = $wfName; | |
70 | $model = new $class(); | |
71 | static::importAll($model, $imports); | |
72 | return $model; | |
73 | } | |
74 | ||
75 | /** | |
76 | * Import a batch of params, updating the $model. | |
77 | * | |
78 | * @param \Civi\WorkflowMessage\WorkflowMessageInterface $model | |
79 | * @param array $params | |
80 | * List of parameters, per MessageTemplate::sendTemplate(). | |
81 | * Ex: Initialize using adhoc data: | |
82 | * ['tplParams' => [...], 'tokenContext' => [...]] | |
83 | * Ex: Initialize using properties of the class-model | |
84 | * ['modelProps' => [...]] | |
85 | * @return \Civi\WorkflowMessage\WorkflowMessageInterface | |
86 | * The updated model. | |
87 | * @throws \Civi\WorkflowMessage\Exception\WorkflowMessageException | |
88 | */ | |
89 | public static function importAll(WorkflowMessageInterface $model, array $params) { | |
90 | // The $params format is defined to match the traditional format of CRM_Core_BAO_MessageTemplate::sendTemplate(). | |
91 | // At the top level, it is an "envelope", but it also has keys for other sections. | |
92 | if (isset($params['model'])) { | |
93 | if ($params['model'] !== $model) { | |
94 | throw new WorkflowMessageException(sprintf("%s: Cannot apply mismatched model", get_class($model))); | |
95 | } | |
96 | unset($params['model']); | |
97 | } | |
98 | ||
99 | \CRM_Utils_Array::pathMove($params, ['contactId'], ['tokenContext', 'contactId']); | |
100 | ||
101 | // Core#644 - handle Email ID passed as "From". | |
102 | if (isset($params['from'])) { | |
103 | $params['from'] = \CRM_Utils_Mail::formatFromAddress($params['from']); | |
104 | } | |
105 | ||
106 | if (isset($params['tplParams'])) { | |
107 | $model->import('tplParams', $params['tplParams']); | |
108 | unset($params['tplParams']); | |
109 | } | |
110 | if (isset($params['tokenContext'])) { | |
111 | $model->import('tokenContext', $params['tokenContext']); | |
112 | unset($params['tokenContext']); | |
113 | } | |
114 | if (isset($params['modelProps'])) { | |
115 | $model->import('modelProps', $params['modelProps']); | |
116 | unset($params['modelProps']); | |
117 | } | |
118 | if (isset($params['envelope'])) { | |
119 | $model->import('envelope', $params['envelope']); | |
120 | unset($params['envelope']); | |
121 | } | |
122 | $model->import('envelope', $params); | |
123 | return $model; | |
124 | } | |
125 | ||
126 | /** | |
127 | * @param \Civi\WorkflowMessage\WorkflowMessageInterface $model | |
128 | * @return array | |
129 | * List of parameters, per MessageTemplate::sendTemplate(). | |
130 | * Ex: ['tplParams' => [...], 'tokenContext' => [...]] | |
131 | */ | |
132 | public static function exportAll(WorkflowMessageInterface $model): array { | |
133 | // The format is defined to match the traditional format of CRM_Core_BAO_MessageTemplate::sendTemplate(). | |
134 | // At the top level, it is an "envelope", but it also has keys for other sections. | |
135 | $values = $model->export('envelope'); | |
136 | $values['tplParams'] = $model->export('tplParams'); | |
137 | $values['tokenContext'] = $model->export('tokenContext'); | |
138 | if (isset($values['tokenContext']['contactId'])) { | |
139 | $values['contactId'] = $values['tokenContext']['contactId']; | |
140 | } | |
141 | return $values; | |
142 | } | |
143 | ||
144 | /** | |
145 | * @return array | |
146 | * Array(string $workflowName => string $className). | |
147 | * Ex: ["case_activity" => "CRM_Case_WorkflowMessage_CaseActivity"] | |
148 | * @internal | |
149 | */ | |
150 | public static function getWorkflowNameClassMap() { | |
151 | $cache = \Civi::cache('long'); | |
152 | $cacheKey = 'WorkflowMessage-' . __FUNCTION__; | |
153 | $map = $cache->get($cacheKey); | |
154 | if ($map === NULL) { | |
155 | $map = []; | |
156 | $map['generic'] = GenericWorkflowMessage::class; | |
157 | $baseDirs = explode(PATH_SEPARATOR, get_include_path()); | |
158 | foreach ($baseDirs as $baseDir) { | |
159 | $baseDir = \CRM_Utils_File::addTrailingSlash($baseDir); | |
160 | $glob = (array) glob($baseDir . 'CRM/*/WorkflowMessage/*.php'); | |
161 | $glob = preg_grep('/\.ex\.php$/', $glob, PREG_GREP_INVERT); | |
162 | foreach ($glob as $file) { | |
163 | $class = strtr(preg_replace('/\.php$/', '', \CRM_Utils_File::relativize($file, $baseDir)), ['/' => '_', '\\' => '_']); | |
164 | if (class_exists($class) && (new \ReflectionClass($class))->implementsInterface(WorkflowMessageInterface::class)) { | |
165 | $map[$class::WORKFLOW] = $class; | |
166 | } | |
167 | } | |
168 | } | |
169 | $cache->set($cacheKey, $map); | |
170 | } | |
171 | return $map; | |
172 | } | |
173 | ||
ebd92daf TO |
174 | /** |
175 | * Get general description of available workflow-messages. | |
176 | * | |
177 | * @return array | |
178 | * Array(string $workflowName => string $className). | |
179 | * Ex: ["case_activity" => ["name" => "case_activity", "group" => "msg_workflow_case"] | |
180 | * @internal | |
181 | */ | |
182 | public static function getWorkflowSpecs() { | |
183 | $compute = function() { | |
8cbb82e5 | 184 | $keys = ['name', 'group', 'class', 'description', 'comment', 'support']; |
ebd92daf TO |
185 | $list = []; |
186 | foreach (self::getWorkflowNameClassMap() as $name => $class) { | |
187 | $specs = [ | |
188 | 'name' => $name, | |
189 | 'group' => \CRM_Utils_Constant::value($class . '::GROUP'), | |
190 | 'class' => $class, | |
191 | ]; | |
192 | $list[$name] = \CRM_Utils_Array::subset( | |
193 | array_merge(ReflectionUtils::getCodeDocs(new \ReflectionClass($class)), $specs), | |
194 | $keys); | |
195 | } | |
196 | return $list; | |
197 | }; | |
198 | ||
199 | $cache = \Civi::cache('long'); | |
200 | $cacheKey = 'WorkflowMessage-' . __FUNCTION__; | |
201 | $list = $cache->get($cacheKey); | |
202 | if ($list === NULL) { | |
203 | $cache->set($cacheKey, $list = $compute()); | |
204 | } | |
205 | return $list; | |
206 | } | |
207 | ||
92f656cb | 208 | } |