Commit | Line | Data |
---|---|---|
17b6f179 EM |
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 | use Civi\Token\AbstractTokenSubscriber; | |
14 | use Civi\Token\TokenRow; | |
45e8391a EM |
15 | use Civi\ActionSchedule\Event\MailingQueryEvent; |
16 | use Civi\Token\TokenProcessor; | |
17b6f179 EM |
17 | |
18 | /** | |
19 | * Class CRM_Core_EntityTokens | |
20 | * | |
21 | * Parent class for generic entity token functionality. | |
22 | * | |
23 | * WARNING - this class is highly likely to be temporary and | |
24 | * to be consolidated with the TokenTrait and / or the | |
25 | * AbstractTokenSubscriber in future. It is being used to clarify | |
26 | * functionality but should NOT be used from outside of core tested code. | |
27 | */ | |
28 | class CRM_Core_EntityTokens extends AbstractTokenSubscriber { | |
29 | ||
30 | /** | |
17b6f179 | 31 | * @inheritDoc |
45e8391a | 32 | * @throws \CRM_Core_Exception |
17b6f179 EM |
33 | */ |
34 | public function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL) { | |
45e8391a EM |
35 | $fieldValue = $this->getFieldValue($row, $field); |
36 | ||
37 | if ($this->isPseudoField($field)) { | |
38 | $split = explode(':', $field); | |
39 | return $row->tokens($entity, $field, $this->getPseudoValue($split[0], $split[1], $this->getFieldValue($row, $split[0]))); | |
40 | } | |
41 | if ($this->isMoneyField($field)) { | |
42 | return $row->format('text/plain')->tokens($entity, $field, | |
43 | \CRM_Utils_Money::format($fieldValue, $this->getFieldValue($row, 'currency'))); | |
44 | } | |
45 | if ($this->isDateField($field)) { | |
46 | return $row->format('text/plain')->tokens($entity, $field, \CRM_Utils_Date::customFormat($fieldValue)); | |
47 | } | |
48 | if ($this->isCustomField($field)) { | |
49 | $row->customToken($entity, \CRM_Core_BAO_CustomField::getKeyID($field), $this->getFieldValue($row, 'id')); | |
50 | } | |
51 | else { | |
52 | $row->format('text/plain')->tokens($entity, $field, (string) $fieldValue); | |
53 | } | |
17b6f179 EM |
54 | } |
55 | ||
45e8391a EM |
56 | /** |
57 | * Metadata about the entity fields. | |
58 | * | |
59 | * @var array | |
60 | */ | |
61 | protected $fieldMetadata = []; | |
62 | ||
29f2b53e EM |
63 | /** |
64 | * Get the entity name for api v4 calls. | |
65 | * | |
66 | * @return string | |
67 | */ | |
68 | protected function getApiEntityName(): string { | |
69 | return ''; | |
70 | } | |
71 | ||
72 | /** | |
73 | * Get the entity alias to use within queries. | |
74 | * | |
75 | * The default has a double underscore which should prevent any | |
76 | * ambiguity with an existing table name. | |
77 | * | |
78 | * @return string | |
79 | */ | |
80 | protected function getEntityAlias(): string { | |
81 | return $this->getApiEntityName() . '__'; | |
82 | } | |
83 | ||
3fcf0983 EM |
84 | /** |
85 | * Get the name of the table this token class can extend. | |
86 | * | |
87 | * The default is based on the entity but some token classes, | |
88 | * specifically the event class, latch on to other tables - ie | |
89 | * the participant table. | |
90 | */ | |
91 | public function getExtendableTableName(): string { | |
92 | return CRM_Core_DAO_AllCoreTables::getTableForEntityName($this->getApiEntityName()); | |
93 | } | |
94 | ||
29f2b53e EM |
95 | /** |
96 | * Get the relevant bao name. | |
97 | */ | |
98 | public function getBAOName(): string { | |
99 | return CRM_Core_DAO_AllCoreTables::getFullName($this->getApiEntityName()); | |
100 | } | |
101 | ||
3fcf0983 EM |
102 | /** |
103 | * Get an array of fields to be requested. | |
104 | * | |
105 | * @return string[] | |
106 | */ | |
107 | public function getReturnFields(): array { | |
108 | return array_keys($this->getBasicTokens()); | |
109 | } | |
110 | ||
4a526c1b EM |
111 | /** |
112 | * Get all the tokens supported by this processor. | |
113 | * | |
114 | * @return array|string[] | |
115 | */ | |
116 | public function getAllTokens(): array { | |
117 | return array_merge($this->getBasicTokens(), $this->getPseudoTokens(), CRM_Utils_Token::getCustomFieldTokens('Contribution')); | |
118 | } | |
119 | ||
17b6f179 EM |
120 | /** |
121 | * Is the given field a date field. | |
122 | * | |
123 | * @param string $fieldName | |
124 | * | |
125 | * @return bool | |
126 | */ | |
127 | public function isDateField(string $fieldName): bool { | |
29f2b53e EM |
128 | return $this->getFieldMetadata()[$fieldName]['data_type'] === 'Timestamp'; |
129 | } | |
130 | ||
131 | /** | |
132 | * Is the given field a pseudo field. | |
133 | * | |
134 | * @param string $fieldName | |
135 | * | |
136 | * @return bool | |
137 | */ | |
138 | public function isPseudoField(string $fieldName): bool { | |
139 | return strpos($fieldName, ':') !== FALSE; | |
140 | } | |
141 | ||
142 | /** | |
143 | * Is the given field a custom field. | |
144 | * | |
145 | * @param string $fieldName | |
146 | * | |
147 | * @return bool | |
148 | */ | |
149 | public function isCustomField(string $fieldName) : bool { | |
150 | return (bool) \CRM_Core_BAO_CustomField::getKeyID($fieldName); | |
17b6f179 EM |
151 | } |
152 | ||
153 | /** | |
154 | * Is the given field a date field. | |
155 | * | |
156 | * @param string $fieldName | |
157 | * | |
158 | * @return bool | |
159 | */ | |
160 | public function isMoneyField(string $fieldName): bool { | |
29f2b53e | 161 | return $this->getFieldMetadata()[$fieldName]['data_type'] === 'Money'; |
17b6f179 EM |
162 | } |
163 | ||
164 | /** | |
165 | * Get the metadata for the available fields. | |
166 | * | |
167 | * @return array | |
168 | */ | |
169 | protected function getFieldMetadata(): array { | |
170 | if (empty($this->fieldMetadata)) { | |
29f2b53e EM |
171 | try { |
172 | // Tests fail without checkPermissions = FALSE | |
173 | $this->fieldMetadata = (array) civicrm_api4($this->getApiEntityName(), 'getfields', ['checkPermissions' => FALSE], 'name'); | |
174 | } | |
175 | catch (API_Exception $e) { | |
176 | $this->fieldMetadata = []; | |
17b6f179 EM |
177 | } |
178 | } | |
179 | return $this->fieldMetadata; | |
180 | } | |
181 | ||
29f2b53e EM |
182 | /** |
183 | * Get pseudoTokens - it tokens that reflect the name or label of a pseudoconstant. | |
184 | * | |
185 | * @internal - this function will likely be made protected soon. | |
186 | * | |
187 | * @return array | |
188 | */ | |
189 | public function getPseudoTokens(): array { | |
190 | $return = []; | |
191 | foreach (array_keys($this->getBasicTokens()) as $fieldName) { | |
192 | if ($this->isAddPseudoTokens($fieldName)) { | |
193 | $return[$fieldName . ':label'] = $this->fieldMetadata[$fieldName]['input_attrs']['label']; | |
194 | $return[$fieldName . ':name'] = ts('Machine name') . ': ' . $this->fieldMetadata[$fieldName]['input_attrs']['label']; | |
195 | } | |
196 | } | |
197 | return $return; | |
198 | } | |
199 | ||
200 | /** | |
201 | * Is this a field we should add pseudo-tokens to? | |
202 | * | |
203 | * Pseudo-tokens allow access to name and label fields - e.g | |
204 | * | |
205 | * {contribution.contribution_status_id:name} might resolve to 'Completed' | |
206 | * | |
207 | * @param string $fieldName | |
208 | */ | |
209 | public function isAddPseudoTokens($fieldName): bool { | |
210 | if ($fieldName === 'currency') { | |
211 | // 'currency' is manually added to the skip list as an anomaly. | |
212 | // name & label aren't that suitable for 'currency' (symbol, which | |
213 | // possibly maps to 'abbr' would be) and we can't gather that | |
214 | // from the metadata as yet. | |
215 | return FALSE; | |
216 | } | |
bd6b5299 EM |
217 | if ($this->getFieldMetadata()[$fieldName]['type'] === 'Custom') { |
218 | // If we remove this early return then we get that extra nuanced goodness | |
219 | // and support for the more portable v4 style field names | |
220 | // on custom fields - where labels or names can be returned. | |
221 | // At present the gap is that the metadata for the label is not accessed | |
222 | // and tests failed on the enotice and we don't have a clear plan about | |
223 | // v4 style custom tokens - but medium term this IF will probably go. | |
224 | return FALSE; | |
225 | } | |
29f2b53e EM |
226 | return (bool) $this->getFieldMetadata()[$fieldName]['options']; |
227 | } | |
228 | ||
229 | /** | |
230 | * Get the value for the relevant pseudo field. | |
231 | * | |
232 | * @param string $realField e.g contribution_status_id | |
233 | * @param string $pseudoKey e.g name | |
234 | * @param int|string $fieldValue e.g 1 | |
235 | * | |
236 | * @return string | |
237 | * Eg. 'Completed' in the example above. | |
238 | * | |
239 | * @internal function will likely be protected soon. | |
240 | */ | |
241 | public function getPseudoValue(string $realField, string $pseudoKey, $fieldValue): string { | |
242 | if ($pseudoKey === 'name') { | |
243 | $fieldValue = (string) CRM_Core_PseudoConstant::getName($this->getBAOName(), $realField, $fieldValue); | |
244 | } | |
245 | if ($pseudoKey === 'label') { | |
246 | $fieldValue = (string) CRM_Core_PseudoConstant::getLabel($this->getBAOName(), $realField, $fieldValue); | |
247 | } | |
248 | return (string) $fieldValue; | |
249 | } | |
250 | ||
9c9c61af EM |
251 | /** |
252 | * @param \Civi\Token\TokenRow $row | |
253 | * @param string $field | |
254 | * @return string|int | |
255 | */ | |
256 | protected function getFieldValue(TokenRow $row, string $field) { | |
257 | $actionSearchResult = $row->context['actionSearchResult']; | |
258 | $aliasedField = $this->getEntityAlias() . $field; | |
259 | return $actionSearchResult->{$aliasedField} ?? NULL; | |
260 | } | |
261 | ||
45e8391a EM |
262 | /** |
263 | * Class constructor. | |
264 | */ | |
265 | public function __construct() { | |
266 | $tokens = $this->getAllTokens(); | |
267 | parent::__construct($this->getEntityName(), $tokens); | |
268 | } | |
269 | ||
270 | /** | |
271 | * Check if the token processor is active. | |
272 | * | |
273 | * @param \Civi\Token\TokenProcessor $processor | |
274 | * | |
275 | * @return bool | |
276 | */ | |
277 | public function checkActive(TokenProcessor $processor) { | |
278 | return !empty($processor->context['actionMapping']) | |
279 | && $processor->context['actionMapping']->getEntity() === $this->getExtendableTableName(); | |
280 | } | |
281 | ||
282 | /** | |
283 | * Alter action schedule query. | |
284 | * | |
285 | * @param \Civi\ActionSchedule\Event\MailingQueryEvent $e | |
286 | */ | |
287 | public function alterActionScheduleQuery(MailingQueryEvent $e): void { | |
288 | if ($e->mapping->getEntity() !== $this->getExtendableTableName()) { | |
289 | return; | |
290 | } | |
291 | foreach ($this->getReturnFields() as $token) { | |
292 | $e->query->select('e.' . $token . ' AS ' . $this->getEntityAlias() . $token); | |
293 | } | |
294 | } | |
295 | ||
bd6b5299 EM |
296 | /** |
297 | * Get tokens supporting the syntax we are migrating to. | |
298 | * | |
299 | * In general these are tokens that were not previously supported | |
300 | * so we can add them in the preferred way or that we have | |
301 | * undertaken some, as yet to be written, db update. | |
302 | * | |
303 | * See https://lab.civicrm.org/dev/core/-/issues/2650 | |
304 | * | |
305 | * @return string[] | |
306 | * @throws \API_Exception | |
307 | */ | |
308 | public function getBasicTokens(): array { | |
309 | $return = []; | |
310 | foreach ($this->getExposedFields() as $fieldName) { | |
311 | $return[$fieldName] = $this->getFieldMetadata()[$fieldName]['title']; | |
312 | } | |
313 | return $return; | |
314 | } | |
315 | ||
316 | /** | |
317 | * Get entity fields that should be exposed as tokens. | |
318 | * | |
319 | * @return string[] | |
320 | * | |
321 | */ | |
322 | public function getExposedFields(): array { | |
323 | $return = []; | |
324 | foreach ($this->getFieldMetadata() as $field) { | |
325 | if (!in_array($field['name'], $this->getSkippedFields(), TRUE)) { | |
326 | $return[] = $field['name']; | |
327 | } | |
328 | } | |
329 | return $return; | |
330 | } | |
331 | ||
332 | /** | |
333 | * Get entity fields that should not be exposed as tokens. | |
334 | * | |
335 | * @return string[] | |
336 | */ | |
337 | public function getSkippedFields(): array { | |
338 | $fields = ['contact_id']; | |
339 | if (!CRM_Campaign_BAO_Campaign::isCampaignEnable()) { | |
340 | $fields[] = 'campaign_id'; | |
341 | } | |
342 | return $fields; | |
343 | } | |
344 | ||
345 | /** | |
346 | * @return string | |
347 | */ | |
348 | protected function getEntityName(): string { | |
349 | return CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($this->getApiEntityName()); | |
350 | } | |
351 | ||
17b6f179 | 352 | } |