[REF] [export] remove chunk of non-functional code
[civicrm-core.git] / Civi / Token / TokenProcessor.php
CommitLineData
8adcd073
TO
1<?php
2namespace Civi\Token;
3
4use Civi\Token\Event\TokenRegisterEvent;
5use Civi\Token\Event\TokenRenderEvent;
6use Civi\Token\Event\TokenValueEvent;
8adcd073
TO
7use Traversable;
8
9class TokenProcessor {
10
11 /**
12 * @var array
13 * Description of the context in which the tokens are being processed.
14 * Ex: Array('class'=>'CRM_Core_BAO_ActionSchedule', 'schedule' => $dao, 'mapping' => $dao).
15 * Ex: Array('class'=>'CRM_Mailing_BAO_MailingJob', 'mailing' => $dao).
16 *
17 * For lack of a better place, here's a list of known/intended context values:
18 *
19 * - controller: string, the class which is managing the mail-merge.
20 * - smarty: bool, whether to enable smarty support.
21 * - contactId: int, the main person/org discussed in the message.
22 * - contact: array, the main person/org discussed in the message.
23 * (Optional for performance tweaking; if omitted, will load
24 * automatically from contactId.)
25 * - actionSchedule: DAO, the rule which triggered the mailing
26 * [for CRM_Core_BAO_ActionScheduler].
37f37651
AS
27 * - schema: array, a list of fields that will be provided for each row.
28 * This is automatically populated with any general context
29 * keys, but you may need to add extra keys for token-row data.
30 * ex: ['contactId', 'activityId'].
8adcd073
TO
31 */
32 public $context;
33
34 /**
34f3bbd9 35 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
8adcd073
TO
36 */
37 protected $dispatcher;
38
39 /**
40 * @var array
41 * Each message is an array with keys:
42 * - string: Unprocessed message (eg "Hello, {display_name}.").
43 * - format: Media type (eg "text/plain").
44 * - tokens: List of tokens which are actually used in this message.
45 */
46 protected $messages;
47
48 /**
49 * DO NOT access field this directly. Use TokenRow. This is
50 * marked as public only to benefit TokenRow.
51 *
52 * @var array
53 * Array(int $pos => array $keyValues);
54 */
55 public $rowContexts;
56
57 /**
58 * DO NOT access field this directly. Use TokenRow. This is
59 * marked as public only to benefit TokenRow.
60 *
61 * @var array
62 * Ex: $rowValues[$rowPos][$format][$entity][$field] = 'something';
63 * Ex: $rowValues[3]['text/plain']['contact']['display_name'] = 'something';
64 */
65 public $rowValues;
66
67 /**
68 * A list of available tokens
69 * @var array
70 * Array(string $dottedName => array('entity'=>string, 'field'=>string, 'label'=>string)).
71 */
72 protected $tokens = NULL;
73
1c4a04c9
AS
74 /**
75 * A list of available tokens formatted for display
76 * @var array
77 * Array('{' . $dottedName . '}' => 'labelString')
78 */
79 protected $listTokens = NULL;
80
8adcd073
TO
81 protected $next = 0;
82
83 /**
34f3bbd9 84 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
8adcd073
TO
85 * @param array $context
86 */
87 public function __construct($dispatcher, $context) {
37f37651
AS
88 $context['schema'] = isset($context['schema'])
89 ? array_unique(array_merge($context['schema'], array_keys($context)))
90 : array_keys($context);
8adcd073
TO
91 $this->dispatcher = $dispatcher;
92 $this->context = $context;
93 }
94
95 /**
96 * Register a string for which we'll need to merge in tokens.
97 *
98 * @param string $name
99 * Ex: 'subject', 'body_html'.
100 * @param string $value
101 * Ex: '<p>Hello {contact.name}</p>'.
102 * @param string $format
103 * Ex: 'text/html'.
4b350175 104 * @return TokenProcessor
8adcd073
TO
105 */
106 public function addMessage($name, $value, $format) {
c64f69d9 107 $this->messages[$name] = [
8adcd073
TO
108 'string' => $value,
109 'format' => $format,
110 'tokens' => \CRM_Utils_Token::getTokens($value),
c64f69d9 111 ];
8adcd073
TO
112 return $this;
113 }
114
115 /**
116 * Add a row of data.
117 *
118 * @return TokenRow
119 */
120 public function addRow() {
121 $key = $this->next++;
c64f69d9
CW
122 $this->rowContexts[$key] = [];
123 $this->rowValues[$key] = [
124 'text/plain' => [],
125 'text/html' => [],
126 ];
8adcd073
TO
127
128 return new TokenRow($this, $key);
129 }
130
131 /**
132 * @param array $params
133 * Array with keys:
134 * - entity: string, e.g. "profile".
135 * - field: string, e.g. "viewUrl".
136 * - label: string, e.g. "Default Profile URL (View Mode)".
4b350175 137 * @return TokenProcessor
8adcd073
TO
138 */
139 public function addToken($params) {
140 $key = $params['entity'] . '.' . $params['field'];
141 $this->tokens[$key] = $params;
142 return $this;
143 }
144
145 /**
146 * @param string $name
147 * @return array
148 * Keys:
149 * - string: Unprocessed message (eg "Hello, {display_name}.").
150 * - format: Media type (eg "text/plain").
151 */
152 public function getMessage($name) {
153 return $this->messages[$name];
154 }
155
156 /**
157 * Get a list of all tokens used in registered messages.
158 *
159 * @return array
160 */
161 public function getMessageTokens() {
c64f69d9 162 $tokens = [];
8adcd073
TO
163 foreach ($this->messages as $message) {
164 $tokens = \CRM_Utils_Array::crmArrayMerge($tokens, $message['tokens']);
165 }
166 foreach (array_keys($tokens) as $e) {
167 $tokens[$e] = array_unique($tokens[$e]);
168 sort($tokens[$e]);
169 }
170 return $tokens;
171 }
172
173 public function getRow($key) {
174 return new TokenRow($this, $key);
175 }
176
177 /**
178 * @return \Traversable<TokenRow>
179 */
180 public function getRows() {
181 return new TokenRowIterator($this, new \ArrayIterator($this->rowContexts));
182 }
183
5d798402
AS
184 /**
185 * Get a list of all unique values for a given context field,
186 * whether defined at the processor or row level.
187 *
188 * @param string $field
189 * Ex: 'contactId'.
34f3bbd9 190 * @param $subfield
5d798402
AS
191 * @return array
192 * Ex: [12, 34, 56].
193 */
8580ab67 194 public function getContextValues($field, $subfield = NULL) {
5d798402
AS
195 $values = [];
196 if (isset($this->context[$field])) {
8580ab67
AS
197 if ($subfield) {
198 if (isset($this->context[$field]->$subfield)) {
199 $values[] = $this->context[$field]->$subfield;
200 }
201 }
202 else {
203 $values[] = $this->context[$field];
204 }
5d798402
AS
205 }
206 foreach ($this->getRows() as $row) {
207 if (isset($row->context[$field])) {
8580ab67
AS
208 if ($subfield) {
209 if (isset($row->context[$field]->$subfield)) {
210 $values[] = $row->context[$field]->$subfield;
211 }
212 }
213 else {
214 $values[] = $row->context[$field];
215 }
5d798402
AS
216 }
217 }
218 $values = array_unique($values);
219 return $values;
220 }
221
8adcd073
TO
222 /**
223 * Get the list of available tokens.
224 *
225 * @return array
226 * Ex: $tokens['event'] = array('location', 'start_date', 'end_date').
227 */
228 public function getTokens() {
229 if ($this->tokens === NULL) {
c64f69d9
CW
230 $this->tokens = [];
231 $event = new TokenRegisterEvent($this, ['entity' => 'undefined']);
8adcd073
TO
232 $this->dispatcher->dispatch(Events::TOKEN_REGISTER, $event);
233 }
234 return $this->tokens;
235 }
236
1c4a04c9
AS
237 /**
238 * Get the list of available tokens, formatted for display
239 *
240 * @return array
241 * Ex: $tokens[ '{token.name}' ] = "Token label"
242 */
243 public function listTokens() {
244 if ($this->listTokens === NULL) {
c64f69d9 245 $this->listTokens = [];
1c4a04c9
AS
246 foreach ($this->getTokens() as $token => $values) {
247 $this->listTokens['{' . $token . '}'] = $values['label'];
248 }
249 }
250 return $this->listTokens;
251 }
252
8adcd073
TO
253 /**
254 * Compute and store token values.
255 */
256 public function evaluate() {
257 $event = new TokenValueEvent($this);
258 $this->dispatcher->dispatch(Events::TOKEN_EVALUATE, $event);
259 return $this;
260 }
261
262 /**
263 * Render a message.
264 *
265 * @param string $name
266 * The name previously registered with addMessage().
267 * @param TokenRow|int $row
268 * The object or ID for the row previously registered with addRow().
269 * @return string
270 * Fully rendered message, with tokens merged.
271 */
272 public function render($name, $row) {
273 if (!is_object($row)) {
274 $row = $this->getRow($row);
275 }
276
277 $message = $this->getMessage($name);
278 $row->fill($message['format']);
279 $useSmarty = !empty($row->context['smarty']);
280
34f3bbd9
SL
281 /**
282 *@FIXME preg_callback.
283 */
8adcd073 284 $tokens = $this->rowValues[$row->tokenRow][$message['format']];
c64f69d9 285 $flatTokens = [];
8adcd073 286 \CRM_Utils_Array::flatten($tokens, $flatTokens, '', '.');
c64f69d9 287 $filteredTokens = [];
8adcd073
TO
288 foreach ($flatTokens as $k => $v) {
289 $filteredTokens['{' . $k . '}'] = ($useSmarty ? \CRM_Utils_Token::tokenEscapeSmarty($v) : $v);
290 }
291
292 $event = new TokenRenderEvent($this);
293 $event->message = $message;
294 $event->context = $row->context;
295 $event->row = $row;
296 $event->string = strtr($message['string'], $filteredTokens);
297 $this->dispatcher->dispatch(Events::TOKEN_RENDER, $event);
298 return $event->string;
299 }
300
301}
302
303class TokenRowIterator extends \IteratorIterator {
304
305 protected $tokenProcessor;
306
307 /**
308 * @param TokenProcessor $tokenProcessor
34f3bbd9 309 * @param \Traversable $iterator
8adcd073
TO
310 */
311 public function __construct(TokenProcessor $tokenProcessor, Traversable $iterator) {
34f3bbd9
SL
312 // TODO: Change the autogenerated stub
313 parent::__construct($iterator);
8adcd073
TO
314 $this->tokenProcessor = $tokenProcessor;
315 }
316
317 public function current() {
318 return new TokenRow($this->tokenProcessor, parent::key());
319 }
320
321}