Merge pull request #15503 from magnolia61/Generalize_Thank_You_in_Message_Templates
[civicrm-core.git] / Civi / Token / TokenRow.php
CommitLineData
8adcd073
TO
1<?php
2namespace Civi\Token;
8adcd073
TO
3
4/**
5 * Class TokenRow
6 * @package Civi\Token
7 *
8 * A TokenRow is a helper providing simplified access to the
9 * TokenProcessor.
10 *
11 * A TokenRow combines two elements:
12 * - context: This is backend data provided by the controller.
13 * - tokens: This is frontend data that can be mail-merged.
14 *
15 * The context and tokens can be accessed using either methods
16 * or attributes. The methods are appropriate for updates
17 * (and generally accept a mix of arrays), and the attributes
18 * are appropriate for reads.
19 *
20 * To update the context or the tokens, use the methods.
21 * Note that the methods are fairly flexible about accepting
22 * single values or arrays. If given an array, the values
23 * will be merged recursively.
24 *
25 * @code
26 * $row
27 * ->context('contact_id', 123)
28 * ->context(array('contact_id' => 123))
29 * ->tokens('profile', array('viewUrl' => 'http://example.com'))
30 * ->tokens('profile', 'viewUrl, 'http://example.com');
31 *
32 * echo $row->context['contact_id'];
33 * echo $row->tokens['profile']['viewUrl'];
34 *
35 * $row->tokens('profile', array(
36 * 'viewUrl' => 'http://example.com/view/' . urlencode($row->context['contact_id'];
37 * ));
38 * @endcode
39 */
40class TokenRow {
41
42 /**
43 * @var TokenProcessor
44 */
45 public $tokenProcessor;
46
47 public $tokenRow;
48
49 public $format;
50
51 /**
09c2328a 52 * @var array|\ArrayAccess
8adcd073
TO
53 * List of token values.
54 * Ex: array('contact' => array('display_name' => 'Alice')).
55 */
56 public $tokens;
57
58 /**
09c2328a 59 * @var array|\ArrayAccess
8adcd073
TO
60 * List of context values.
61 * Ex: array('controller' => 'CRM_Foo_Bar').
62 */
63 public $context;
64
65 public function __construct(TokenProcessor $tokenProcessor, $key) {
66 $this->tokenProcessor = $tokenProcessor;
67 $this->tokenRow = $key;
34f3bbd9
SL
68 // Set a default.
69 $this->format('text/plain');
8adcd073
TO
70 $this->context = new TokenRowContext($tokenProcessor, $key);
71 }
72
73 /**
74 * @param string $format
4b350175 75 * @return TokenRow
8adcd073
TO
76 */
77 public function format($format) {
78 $this->format = $format;
79 $this->tokens = &$this->tokenProcessor->rowValues[$this->tokenRow][$format];
80 return $this;
81 }
82
83 /**
84 * Update the value of a context element.
85 *
86 * @param string|array $a
87 * @param mixed $b
4b350175 88 * @return TokenRow
8adcd073
TO
89 */
90 public function context($a = NULL, $b = NULL) {
91 if (is_array($a)) {
92 \CRM_Utils_Array::extend($this->tokenProcessor->rowContexts[$this->tokenRow], $a);
93 }
94 elseif (is_array($b)) {
95 \CRM_Utils_Array::extend($this->tokenProcessor->rowContexts[$this->tokenRow][$a], $b);
96 }
97 else {
98 $this->tokenProcessor->rowContexts[$this->tokenRow][$a] = $b;
99 }
100 return $this;
101 }
102
103 /**
104 * Update the value of a token.
105 *
106 * @param string|array $a
107 * @param string|array $b
108 * @param mixed $c
4b350175 109 * @return TokenRow
8adcd073
TO
110 */
111 public function tokens($a = NULL, $b = NULL, $c = NULL) {
112 if (is_array($a)) {
113 \CRM_Utils_Array::extend($this->tokens, $a);
114 }
115 elseif (is_array($b)) {
116 \CRM_Utils_Array::extend($this->tokens[$a], $b);
117 }
118 elseif (is_array($c)) {
119 \CRM_Utils_Array::extend($this->tokens[$a][$b], $c);
120 }
121 elseif ($c === NULL) {
122 $this->tokens[$a] = $b;
123 }
124 else {
125 $this->tokens[$a][$b] = $c;
126 }
127 return $this;
128 }
129
4e9b6a62 130 /**
131 * Update the value of a custom field token.
132 *
133 * @param string $entity
8640061b 134 * @param int $customFieldID
135 * @param int $entityID
4e9b6a62 136 * @return TokenRow
137 */
8640061b 138 public function customToken($entity, $customFieldID, $entityID) {
139 $customFieldName = "custom_" . $customFieldID;
e5e0c47b 140 $record = civicrm_api3($entity, "getSingle", [
8640061b 141 'return' => $customFieldName,
34f3bbd9 142 'id' => $entityID,
e5e0c47b
AS
143 ]);
144 $fieldValue = \CRM_Utils_Array::value($customFieldName, $record, '');
8640061b 145
146 // format the raw custom field value into proper display value
b5654a0d 147 if (isset($fieldValue)) {
8640061b 148 $fieldValue = \CRM_Core_BAO_CustomField::displayValue($fieldValue, $customFieldID);
149 }
150
151 return $this->tokens($entity, $customFieldName, $fieldValue);
4e9b6a62 152 }
153
2045389a
TO
154 /**
155 * Update the value of a token. Apply formatting based on DB schema.
156 *
157 * @param string $tokenEntity
158 * @param string $tokenField
159 * @param string $baoName
160 * @param array $baoField
161 * @param mixed $fieldValue
ee299f14
TO
162 * @return TokenRow
163 * @throws \CRM_Core_Exception
2045389a
TO
164 */
165 public function dbToken($tokenEntity, $tokenField, $baoName, $baoField, $fieldValue) {
166 if ($fieldValue === NULL || $fieldValue === '') {
167 return $this->tokens($tokenEntity, $tokenField, '');
168 }
169
170 $fields = $baoName::fields();
171 if (!empty($fields[$baoField]['pseudoconstant'])) {
172 $options = $baoName::buildOptions($baoField, 'get');
173 return $this->format('text/plain')->tokens($tokenEntity, $tokenField, $options[$fieldValue]);
174 }
175
176 switch ($fields[$baoField]['type']) {
177 case \CRM_Utils_Type::T_DATE + \CRM_Utils_Type::T_TIME:
178 return $this->format('text/plain')->tokens($tokenEntity, $tokenField, \CRM_Utils_Date::customFormat($fieldValue));
179
180 case \CRM_Utils_Type::T_MONEY:
181 // Is this something you should ever use? Seems like you need more context
182 // to know which currency to use.
183 return $this->format('text/plain')->tokens($tokenEntity, $tokenField, \CRM_Utils_Money::format($fieldValue));
184
185 case \CRM_Utils_Type::T_STRING:
186 case \CRM_Utils_Type::T_BOOLEAN:
187 case \CRM_Utils_Type::T_INT:
188 case \CRM_Utils_Type::T_TEXT:
189 return $this->format('text/plain')->tokens($tokenEntity, $tokenField, $fieldValue);
190
191 }
192
193 throw new \CRM_Core_Exception("Cannot format token for field '$baoField' in '$baoName'");
194 }
195
8adcd073
TO
196 /**
197 * Auto-convert between different formats
54957108 198 *
199 * @param string $format
200 *
4b350175 201 * @return TokenRow
8adcd073
TO
202 */
203 public function fill($format = NULL) {
204 if ($format === NULL) {
205 $format = $this->format;
206 }
207
208 if (!isset($this->tokenProcessor->rowValues[$this->tokenRow]['text/html'])) {
c64f69d9 209 $this->tokenProcessor->rowValues[$this->tokenRow]['text/html'] = [];
8adcd073
TO
210 }
211 if (!isset($this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'])) {
c64f69d9 212 $this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'] = [];
8adcd073
TO
213 }
214
215 $htmlTokens = &$this->tokenProcessor->rowValues[$this->tokenRow]['text/html'];
216 $textTokens = &$this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'];
217
218 switch ($format) {
219 case 'text/html':
220 // Plain => HTML.
221 foreach ($textTokens as $entity => $values) {
c64f69d9 222 $entityFields = civicrm_api3($entity, "getFields", ['api_action' => 'get']);
8adcd073
TO
223 foreach ($values as $field => $value) {
224 if (!isset($htmlTokens[$entity][$field])) {
ee2eb45c 225 // CRM-18420 - Activity Details Field are enclosed within <p>,
226 // hence if $body_text is empty, htmlentities will lead to
227 // conversion of these tags resulting in raw HTML.
228 if ($entity == 'activity' && $field == 'details') {
229 $htmlTokens[$entity][$field] = $value;
230 }
65ddce7b
AS
231 elseif (\CRM_Utils_Array::value('data_type', \CRM_Utils_Array::value($field, $entityFields['values'])) == 'Memo') {
232 // Memo fields aka custom fields of type Note are html.
233 $htmlTokens[$entity][$field] = CRM_Utils_String::purifyHTML($value);
234 }
ee2eb45c 235 else {
236 $htmlTokens[$entity][$field] = htmlentities($value);
237 }
8adcd073
TO
238 }
239 }
240 }
241 break;
242
243 case 'text/plain':
244 // HTML => Plain.
245 foreach ($htmlTokens as $entity => $values) {
246 foreach ($values as $field => $value) {
247 if (!isset($textTokens[$entity][$field])) {
248 $textTokens[$entity][$field] = html_entity_decode(strip_tags($value));
249 }
250 }
251 }
252 break;
253
254 default:
255 throw new \RuntimeException("Invalid format");
256 }
257
258 return $this;
259 }
260
261 /**
262 * Render a message.
263 *
264 * @param string $name
265 * The name previously registered with TokenProcessor::addMessage.
266 * @return string
267 * Fully rendered message, with tokens merged.
268 */
269 public function render($name) {
270 return $this->tokenProcessor->render($name, $this);
271 }
272
273}
274
275/**
276 * Class TokenRowContext
277 * @package Civi\Token
278 *
279 * Combine the row-context and general-context into a single array-like facade.
280 */
281class TokenRowContext implements \ArrayAccess, \IteratorAggregate, \Countable {
282
283 /**
284 * @var TokenProcessor
285 */
286 protected $tokenProcessor;
287
288 protected $tokenRow;
289
290 /**
e8e8f3ad 291 * Class constructor.
292 *
293 * @param array $tokenProcessor
294 * @param array $tokenRow
8adcd073
TO
295 */
296 public function __construct($tokenProcessor, $tokenRow) {
297 $this->tokenProcessor = $tokenProcessor;
298 $this->tokenRow = $tokenRow;
299 }
300
e8e8f3ad 301 /**
302 * Does offset exist.
303 *
304 * @param mixed $offset
305 *
306 * @return bool
307 */
8adcd073 308 public function offsetExists($offset) {
34f3bbd9 309 return isset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset])
8adcd073
TO
310 || isset($this->tokenProcessor->context[$offset]);
311 }
312
e8e8f3ad 313 /**
314 * Get offset.
315 *
316 * @param string $offset
317 *
318 * @return string
319 */
8adcd073
TO
320 public function &offsetGet($offset) {
321 if (isset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset])) {
322 return $this->tokenProcessor->rowContexts[$this->tokenRow][$offset];
323 }
324 if (isset($this->tokenProcessor->context[$offset])) {
325 return $this->tokenProcessor->context[$offset];
326 }
327 $val = NULL;
328 return $val;
329 }
330
e8e8f3ad 331 /**
332 * Set offset.
333 *
334 * @param string $offset
335 * @param mixed $value
336 */
8adcd073
TO
337 public function offsetSet($offset, $value) {
338 $this->tokenProcessor->rowContexts[$this->tokenRow][$offset] = $value;
339 }
340
e8e8f3ad 341 /**
342 * Unset offset.
343 *
344 * @param mixed $offset
345 */
8adcd073
TO
346 public function offsetUnset($offset) {
347 unset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset]);
348 }
349
e8e8f3ad 350 /**
351 * Get iterator.
352 *
353 * @return \ArrayIterator
354 */
8adcd073
TO
355 public function getIterator() {
356 return new \ArrayIterator($this->createMergedArray());
357 }
358
e8e8f3ad 359 /**
360 * Count.
361 *
362 * @return int
363 */
8adcd073
TO
364 public function count() {
365 return count($this->createMergedArray());
366 }
367
e8e8f3ad 368 /**
369 * Create merged array.
370 *
371 * @return array
372 */
8adcd073
TO
373 protected function createMergedArray() {
374 return array_merge(
375 $this->tokenProcessor->rowContexts[$this->tokenRow],
376 $this->tokenProcessor->context
377 );
378 }
379
380}