Merge pull request #22677 from eileenmcnaughton/aclmem
[civicrm-core.git] / Civi / WorkflowMessage / WorkflowMessage.php
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
15 use Civi\Api4\Utils\ReflectionUtils;
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']['workflow'] = $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 if (isset($params['tplParams'])) {
100 $model->import('tplParams', $params['tplParams']);
101 unset($params['tplParams']);
102 }
103 if (isset($params['tokenContext'])) {
104 $model->import('tokenContext', $params['tokenContext']);
105 unset($params['tokenContext']);
106 }
107 if (isset($params['modelProps'])) {
108 $model->import('modelProps', $params['modelProps']);
109 unset($params['modelProps']);
110 }
111 if (isset($params['envelope'])) {
112 $model->import('envelope', $params['envelope']);
113 unset($params['envelope']);
114 }
115 $model->import('envelope', $params);
116 return $model;
117 }
118
119 /**
120 * @param \Civi\WorkflowMessage\WorkflowMessageInterface $model
121 * @return array
122 * List of parameters, per MessageTemplate::sendTemplate().
123 * Ex: ['tplParams' => [...], 'tokenContext' => [...]]
124 */
125 public static function exportAll(WorkflowMessageInterface $model): array {
126 // The format is defined to match the traditional format of CRM_Core_BAO_MessageTemplate::sendTemplate().
127 // At the top level, it is an "envelope", but it also has keys for other sections.
128 $values = $model->export('envelope');
129 $values['tplParams'] = $model->export('tplParams');
130 $values['tokenContext'] = $model->export('tokenContext');
131 if (isset($values['tokenContext']['contactId'])) {
132 $values['contactId'] = $values['tokenContext']['contactId'];
133 }
134 return $values;
135 }
136
137 /**
138 * @return array
139 * Array(string $workflowName => string $className).
140 * Ex: ["case_activity" => "CRM_Case_WorkflowMessage_CaseActivity"]
141 * @internal
142 */
143 public static function getWorkflowNameClassMap() {
144 $cache = \Civi::cache('long');
145 $cacheKey = 'WorkflowMessage-' . __FUNCTION__;
146 $map = $cache->get($cacheKey);
147 if ($map === NULL) {
148 $map = [];
149 $map['generic'] = GenericWorkflowMessage::class;
150 $baseDirs = explode(PATH_SEPARATOR, get_include_path());
151 foreach ($baseDirs as $baseDir) {
152 $baseDir = \CRM_Utils_File::addTrailingSlash($baseDir);
153 $glob = (array) glob($baseDir . 'CRM/*/WorkflowMessage/*.php');
154 $glob = preg_grep('/\.ex\.php$/', $glob, PREG_GREP_INVERT);
155 foreach ($glob as $file) {
156 $class = strtr(preg_replace('/\.php$/', '', \CRM_Utils_File::relativize($file, $baseDir)), ['/' => '_', '\\' => '_']);
157 if (class_exists($class) && (new \ReflectionClass($class))->implementsInterface(WorkflowMessageInterface::class)) {
158 $map[$class::WORKFLOW] = $class;
159 }
160 }
161 }
162 $cache->set($cacheKey, $map);
163 }
164 return $map;
165 }
166
167 /**
168 * Get general description of available workflow-messages.
169 *
170 * @return array
171 * Array(string $workflowName => string $className).
172 * Ex: ["case_activity" => ["name" => "case_activity", "group" => "msg_workflow_case"]
173 * @internal
174 */
175 public static function getWorkflowSpecs() {
176 $compute = function() {
177 $keys = ['name', 'group', 'class', 'description', 'comment', 'support'];
178 $list = [];
179 foreach (self::getWorkflowNameClassMap() as $name => $class) {
180 $specs = [
181 'name' => $name,
182 'group' => \CRM_Utils_Constant::value($class . '::GROUP'),
183 'class' => $class,
184 ];
185 $list[$name] = \CRM_Utils_Array::subset(
186 array_merge(ReflectionUtils::getCodeDocs(new \ReflectionClass($class)), $specs),
187 $keys);
188 }
189 return $list;
190 };
191
192 $cache = \Civi::cache('long');
193 $cacheKey = 'WorkflowMessage-' . __FUNCTION__;
194 $list = $cache->get($cacheKey);
195 if ($list === NULL) {
196 $cache->set($cacheKey, $list = $compute());
197 }
198 return $list;
199 }
200
201 }