alternative way to handle custom field token
[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;
68 $this->format('text/plain'); // Set a default.
69 $this->context = new TokenRowContext($tokenProcessor, $key);
70 }
71
72 /**
73 * @param string $format
4b350175 74 * @return TokenRow
8adcd073
TO
75 */
76 public function format($format) {
77 $this->format = $format;
78 $this->tokens = &$this->tokenProcessor->rowValues[$this->tokenRow][$format];
79 return $this;
80 }
81
82 /**
83 * Update the value of a context element.
84 *
85 * @param string|array $a
86 * @param mixed $b
4b350175 87 * @return TokenRow
8adcd073
TO
88 */
89 public function context($a = NULL, $b = NULL) {
90 if (is_array($a)) {
91 \CRM_Utils_Array::extend($this->tokenProcessor->rowContexts[$this->tokenRow], $a);
92 }
93 elseif (is_array($b)) {
94 \CRM_Utils_Array::extend($this->tokenProcessor->rowContexts[$this->tokenRow][$a], $b);
95 }
96 else {
97 $this->tokenProcessor->rowContexts[$this->tokenRow][$a] = $b;
98 }
99 return $this;
100 }
101
102 /**
103 * Update the value of a token.
104 *
105 * @param string|array $a
106 * @param string|array $b
107 * @param mixed $c
4b350175 108 * @return TokenRow
8adcd073
TO
109 */
110 public function tokens($a = NULL, $b = NULL, $c = NULL) {
111 if (is_array($a)) {
112 \CRM_Utils_Array::extend($this->tokens, $a);
113 }
114 elseif (is_array($b)) {
115 \CRM_Utils_Array::extend($this->tokens[$a], $b);
116 }
117 elseif (is_array($c)) {
118 \CRM_Utils_Array::extend($this->tokens[$a][$b], $c);
119 }
120 elseif ($c === NULL) {
121 $this->tokens[$a] = $b;
122 }
123 else {
124 $this->tokens[$a][$b] = $c;
125 }
126 return $this;
127 }
128
4e9b6a62 129 /**
130 * Update the value of a custom field token.
131 *
132 * @param string $entity
133 * @param string $fieldName
134 * @param string $entityID
135 * @return TokenRow
136 */
137 public function customToken($entity, $fieldName, $entityID) {
138 $fieldValue = civicrm_api3($entity, 'getvalue', array(
139 'return' => $fieldName,
140 'id' => $entityID,
141 ));
142 return $this->tokens($entity, $fieldName, $fieldValue);
143 }
144
2045389a
TO
145 /**
146 * Update the value of a token. Apply formatting based on DB schema.
147 *
148 * @param string $tokenEntity
149 * @param string $tokenField
150 * @param string $baoName
151 * @param array $baoField
152 * @param mixed $fieldValue
ee299f14
TO
153 * @return TokenRow
154 * @throws \CRM_Core_Exception
2045389a
TO
155 */
156 public function dbToken($tokenEntity, $tokenField, $baoName, $baoField, $fieldValue) {
157 if ($fieldValue === NULL || $fieldValue === '') {
158 return $this->tokens($tokenEntity, $tokenField, '');
159 }
160
161 $fields = $baoName::fields();
162 if (!empty($fields[$baoField]['pseudoconstant'])) {
163 $options = $baoName::buildOptions($baoField, 'get');
164 return $this->format('text/plain')->tokens($tokenEntity, $tokenField, $options[$fieldValue]);
165 }
166
167 switch ($fields[$baoField]['type']) {
168 case \CRM_Utils_Type::T_DATE + \CRM_Utils_Type::T_TIME:
169 return $this->format('text/plain')->tokens($tokenEntity, $tokenField, \CRM_Utils_Date::customFormat($fieldValue));
170
171 case \CRM_Utils_Type::T_MONEY:
172 // Is this something you should ever use? Seems like you need more context
173 // to know which currency to use.
174 return $this->format('text/plain')->tokens($tokenEntity, $tokenField, \CRM_Utils_Money::format($fieldValue));
175
176 case \CRM_Utils_Type::T_STRING:
177 case \CRM_Utils_Type::T_BOOLEAN:
178 case \CRM_Utils_Type::T_INT:
179 case \CRM_Utils_Type::T_TEXT:
180 return $this->format('text/plain')->tokens($tokenEntity, $tokenField, $fieldValue);
181
182 }
183
184 throw new \CRM_Core_Exception("Cannot format token for field '$baoField' in '$baoName'");
185 }
186
8adcd073
TO
187 /**
188 * Auto-convert between different formats
54957108 189 *
190 * @param string $format
191 *
4b350175 192 * @return TokenRow
8adcd073
TO
193 */
194 public function fill($format = NULL) {
195 if ($format === NULL) {
196 $format = $this->format;
197 }
198
199 if (!isset($this->tokenProcessor->rowValues[$this->tokenRow]['text/html'])) {
200 $this->tokenProcessor->rowValues[$this->tokenRow]['text/html'] = array();
201 }
202 if (!isset($this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'])) {
203 $this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'] = array();
204 }
205
206 $htmlTokens = &$this->tokenProcessor->rowValues[$this->tokenRow]['text/html'];
207 $textTokens = &$this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'];
208
209 switch ($format) {
210 case 'text/html':
211 // Plain => HTML.
212 foreach ($textTokens as $entity => $values) {
213 foreach ($values as $field => $value) {
214 if (!isset($htmlTokens[$entity][$field])) {
ee2eb45c 215 // CRM-18420 - Activity Details Field are enclosed within <p>,
216 // hence if $body_text is empty, htmlentities will lead to
217 // conversion of these tags resulting in raw HTML.
218 if ($entity == 'activity' && $field == 'details') {
219 $htmlTokens[$entity][$field] = $value;
220 }
221 else {
222 $htmlTokens[$entity][$field] = htmlentities($value);
223 }
8adcd073
TO
224 }
225 }
226 }
227 break;
228
229 case 'text/plain':
230 // HTML => Plain.
231 foreach ($htmlTokens as $entity => $values) {
232 foreach ($values as $field => $value) {
233 if (!isset($textTokens[$entity][$field])) {
234 $textTokens[$entity][$field] = html_entity_decode(strip_tags($value));
235 }
236 }
237 }
238 break;
239
240 default:
241 throw new \RuntimeException("Invalid format");
242 }
243
244 return $this;
245 }
246
247 /**
248 * Render a message.
249 *
250 * @param string $name
251 * The name previously registered with TokenProcessor::addMessage.
252 * @return string
253 * Fully rendered message, with tokens merged.
254 */
255 public function render($name) {
256 return $this->tokenProcessor->render($name, $this);
257 }
258
259}
260
261/**
262 * Class TokenRowContext
263 * @package Civi\Token
264 *
265 * Combine the row-context and general-context into a single array-like facade.
266 */
267class TokenRowContext implements \ArrayAccess, \IteratorAggregate, \Countable {
268
269 /**
270 * @var TokenProcessor
271 */
272 protected $tokenProcessor;
273
274 protected $tokenRow;
275
276 /**
e8e8f3ad 277 * Class constructor.
278 *
279 * @param array $tokenProcessor
280 * @param array $tokenRow
8adcd073
TO
281 */
282 public function __construct($tokenProcessor, $tokenRow) {
283 $this->tokenProcessor = $tokenProcessor;
284 $this->tokenRow = $tokenRow;
285 }
286
e8e8f3ad 287 /**
288 * Does offset exist.
289 *
290 * @param mixed $offset
291 *
292 * @return bool
293 */
8adcd073
TO
294 public function offsetExists($offset) {
295 return
296 isset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset])
297 || isset($this->tokenProcessor->context[$offset]);
298 }
299
e8e8f3ad 300 /**
301 * Get offset.
302 *
303 * @param string $offset
304 *
305 * @return string
306 */
8adcd073
TO
307 public function &offsetGet($offset) {
308 if (isset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset])) {
309 return $this->tokenProcessor->rowContexts[$this->tokenRow][$offset];
310 }
311 if (isset($this->tokenProcessor->context[$offset])) {
312 return $this->tokenProcessor->context[$offset];
313 }
314 $val = NULL;
315 return $val;
316 }
317
e8e8f3ad 318 /**
319 * Set offset.
320 *
321 * @param string $offset
322 * @param mixed $value
323 */
8adcd073
TO
324 public function offsetSet($offset, $value) {
325 $this->tokenProcessor->rowContexts[$this->tokenRow][$offset] = $value;
326 }
327
e8e8f3ad 328 /**
329 * Unset offset.
330 *
331 * @param mixed $offset
332 */
8adcd073
TO
333 public function offsetUnset($offset) {
334 unset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset]);
335 }
336
e8e8f3ad 337 /**
338 * Get iterator.
339 *
340 * @return \ArrayIterator
341 */
8adcd073
TO
342 public function getIterator() {
343 return new \ArrayIterator($this->createMergedArray());
344 }
345
e8e8f3ad 346 /**
347 * Count.
348 *
349 * @return int
350 */
8adcd073
TO
351 public function count() {
352 return count($this->createMergedArray());
353 }
354
e8e8f3ad 355 /**
356 * Create merged array.
357 *
358 * @return array
359 */
8adcd073
TO
360 protected function createMergedArray() {
361 return array_merge(
362 $this->tokenProcessor->rowContexts[$this->tokenRow],
363 $this->tokenProcessor->context
364 );
365 }
366
367}