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