Merge pull request #24192 from demeritcowboy/php81-frontend4
[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\Core\ClassScanner;
17 use Civi\WorkflowMessage\Exception\WorkflowMessageException;
18
19 /**
20 * A WorkflowMessage describes the inputs to an automated email messages.
21 *
22 * These classes may be instantiated by either class-name or workflow-name.
23 *
24 * Ex: $msgWf = new \CRM_Foo_WorkflowMessage_MyAlert(['tplParams' => [...tplValues...]]);
25 * Ex: $msgWf = new \CRM_Foo_WorkflowMessage_MyAlert(['modelProps' => [...classProperties...]]);
26 * Ex: $msgWf = WorkflowMessage::create('my_alert_name', ['tplParams' => [...tplValues...]]);
27 * Ex: $msgWf = WorkflowMessage::create('my_alert_name', ['modelProps' => [...classProperties...]]);
28 *
29 * Instantiating by class-name will provide better hinting and inspection.
30 * However, some workflows may not have specific classes at the time of writing.
31 * Instantiating by workflow-name will work regardless of whether there is a specific class.
32 */
33 class WorkflowMessage {
34
35 /**
36 * Create a new instance of the workflow-message context.
37 *
38 * @param string $wfName
39 * Name of the workflow.
40 * Ex: 'case_activity'
41 * @param array $imports
42 * List of data to use when populating the message.
43 *
44 * The parameters may be given in a mix of formats. This mix reflects two modes of operation:
45 *
46 * - (Informal/Generic) Traditionally, workflow-messages did not have formal parameters. Instead,
47 * they relied on a mix of un(der)documented/unverifiable inputs -- supplied as a mix of Smarty
48 * assignments, token-data, and sendTemplate() params.
49 * - (Formal) More recently, workflow-messages could be defined with a PHP class that lists the
50 * inputs explicitly.
51 *
52 * You may supply inputs using these keys:
53 *
54 * - `tplParams` (array): Smarty data. These values go to `$smarty->assign()`.
55 * - `tokenContext` (array): Token-processing data. These values go to `$tokenProcessor->context`.
56 * - `envelope` (array): Email delivery data. These values go to `sendTemplate(...)`
57 * - `modelProps` (array): Formal parameters defined by a class.
58 *
59 * Informal workflow-messages ONLY support 'tplParams', 'tokenContext', and/or 'envelope'.
60 * Formal workflow-messages accept any format.
61 *
62 * @return \Civi\WorkflowMessage\WorkflowMessageInterface
63 * If there is a workflow-message class, then it will return an instance of that class.
64 * Otherwise, it will return an instance of `GenericWorkflowMessage`.
65 * @throws \Civi\WorkflowMessage\Exception\WorkflowMessageException
66 */
67 public static function create(string $wfName, array $imports = []) {
68 $classMap = static::getWorkflowNameClassMap();
69 $class = $classMap[$wfName] ?? 'Civi\WorkflowMessage\GenericWorkflowMessage';
70 $imports['envelope']['workflow'] = $wfName;
71 $model = new $class();
72 static::importAll($model, $imports);
73 return $model;
74 }
75
76 /**
77 * Import a batch of params, updating the $model.
78 *
79 * @param \Civi\WorkflowMessage\WorkflowMessageInterface $model
80 * @param array $params
81 * List of parameters, per MessageTemplate::sendTemplate().
82 * Ex: Initialize using adhoc data:
83 * ['tplParams' => [...], 'tokenContext' => [...]]
84 * Ex: Initialize using properties of the class-model
85 * ['modelProps' => [...]]
86 * @return \Civi\WorkflowMessage\WorkflowMessageInterface
87 * The updated model.
88 * @throws \Civi\WorkflowMessage\Exception\WorkflowMessageException
89 */
90 public static function importAll(WorkflowMessageInterface $model, array $params) {
91 // The $params format is defined to match the traditional format of CRM_Core_BAO_MessageTemplate::sendTemplate().
92 // At the top level, it is an "envelope", but it also has keys for other sections.
93 if (isset($params['model'])) {
94 if ($params['model'] !== $model) {
95 throw new WorkflowMessageException(sprintf("%s: Cannot apply mismatched model", get_class($model)));
96 }
97 unset($params['model']);
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 foreach (ClassScanner::get(['interface' => WorkflowMessageInterface::class]) as $wfClass) {
151 $wfName = ($wfClass === GenericWorkflowMessage::class) ? 'generic' : $wfClass::WORKFLOW;
152 $map[$wfName] = $wfClass;
153 }
154 $cache->set($cacheKey, $map);
155 }
156 return $map;
157 }
158
159 /**
160 * Get general description of available workflow-messages.
161 *
162 * @return array
163 * Array(string $workflowName => string $className).
164 * Ex: ["case_activity" => ["name" => "case_activity", "group" => "msg_workflow_case"]
165 * @internal
166 */
167 public static function getWorkflowSpecs() {
168 $compute = function() {
169 $keys = ['name', 'group', 'class', 'description', 'comment', 'support'];
170 $list = [];
171 foreach (self::getWorkflowNameClassMap() as $name => $class) {
172 $specs = [
173 'name' => $name,
174 'group' => \CRM_Utils_Constant::value($class . '::GROUP'),
175 'class' => $class,
176 ];
177 $list[$name] = \CRM_Utils_Array::subset(
178 array_merge(ReflectionUtils::getCodeDocs(new \ReflectionClass($class)), $specs),
179 $keys);
180 }
181 return $list;
182 };
183
184 $cache = \Civi::cache('long');
185 $cacheKey = 'WorkflowMessage-' . __FUNCTION__;
186 $list = $cache->get($cacheKey);
187 if ($list === NULL) {
188 $cache->set($cacheKey, $list = $compute());
189 }
190 return $list;
191 }
192
193 }