4 use Civi\Token\Event\TokenRegisterEvent
;
5 use Civi\Token\Event\TokenRenderEvent
;
6 use Civi\Token\Event\TokenValueEvent
;
7 use Symfony\Component\EventDispatcher\EventDispatcherInterface
;
10 class TokenProcessor
{
14 * Description of the context in which the tokens are being processed.
15 * Ex: Array('class'=>'CRM_Core_BAO_ActionSchedule', 'schedule' => $dao, 'mapping' => $dao).
16 * Ex: Array('class'=>'CRM_Mailing_BAO_MailingJob', 'mailing' => $dao).
18 * For lack of a better place, here's a list of known/intended context values:
20 * - controller: string, the class which is managing the mail-merge.
21 * - smarty: bool, whether to enable smarty support.
22 * - contactId: int, the main person/org discussed in the message.
23 * - contact: array, the main person/org discussed in the message.
24 * (Optional for performance tweaking; if omitted, will load
25 * automatically from contactId.)
26 * - actionSchedule: DAO, the rule which triggered the mailing
27 * [for CRM_Core_BAO_ActionScheduler].
28 * - schema: array, a list of fields that will be provided for each row.
29 * This is automatically populated with any general context
30 * keys, but you may need to add extra keys for token-row data.
31 * ex: ['contactId', 'activityId'].
36 * @var EventDispatcherInterface
38 protected $dispatcher;
42 * Each message is an array with keys:
43 * - string: Unprocessed message (eg "Hello, {display_name}.").
44 * - format: Media type (eg "text/plain").
45 * - tokens: List of tokens which are actually used in this message.
50 * DO NOT access field this directly. Use TokenRow. This is
51 * marked as public only to benefit TokenRow.
54 * Array(int $pos => array $keyValues);
59 * DO NOT access field this directly. Use TokenRow. This is
60 * marked as public only to benefit TokenRow.
63 * Ex: $rowValues[$rowPos][$format][$entity][$field] = 'something';
64 * Ex: $rowValues[3]['text/plain']['contact']['display_name'] = 'something';
69 * A list of available tokens
71 * Array(string $dottedName => array('entity'=>string, 'field'=>string, 'label'=>string)).
73 protected $tokens = NULL;
76 * A list of available tokens formatted for display
78 * Array('{' . $dottedName . '}' => 'labelString')
80 protected $listTokens = NULL;
85 * @param EventDispatcherInterface $dispatcher
86 * @param array $context
88 public function __construct($dispatcher, $context) {
89 $context['schema'] = isset($context['schema'])
90 ?
array_unique(array_merge($context['schema'], array_keys($context)))
91 : array_keys($context);
92 $this->dispatcher
= $dispatcher;
93 $this->context
= $context;
97 * Register a string for which we'll need to merge in tokens.
100 * Ex: 'subject', 'body_html'.
101 * @param string $value
102 * Ex: '<p>Hello {contact.name}</p>'.
103 * @param string $format
105 * @return TokenProcessor
107 public function addMessage($name, $value, $format) {
108 $this->messages
[$name] = array(
111 'tokens' => \CRM_Utils_Token
::getTokens($value),
121 public function addRow() {
122 $key = $this->next++
;
123 $this->rowContexts
[$key] = array();
124 $this->rowValues
[$key] = array(
125 'text/plain' => array(),
126 'text/html' => array(),
129 return new TokenRow($this, $key);
133 * @param array $params
135 * - entity: string, e.g. "profile".
136 * - field: string, e.g. "viewUrl".
137 * - label: string, e.g. "Default Profile URL (View Mode)".
138 * @return TokenProcessor
140 public function addToken($params) {
141 $key = $params['entity'] . '.' . $params['field'];
142 $this->tokens
[$key] = $params;
147 * @param string $name
150 * - string: Unprocessed message (eg "Hello, {display_name}.").
151 * - format: Media type (eg "text/plain").
153 public function getMessage($name) {
154 return $this->messages
[$name];
158 * Get a list of all tokens used in registered messages.
162 public function getMessageTokens() {
164 foreach ($this->messages
as $message) {
165 $tokens = \CRM_Utils_Array
::crmArrayMerge($tokens, $message['tokens']);
167 foreach (array_keys($tokens) as $e) {
168 $tokens[$e] = array_unique($tokens[$e]);
174 public function getRow($key) {
175 return new TokenRow($this, $key);
179 * @return \Traversable<TokenRow>
181 public function getRows() {
182 return new TokenRowIterator($this, new \
ArrayIterator($this->rowContexts
));
186 * Get a list of all unique values for a given context field,
187 * whether defined at the processor or row level.
189 * @param string $field
194 public function getContextValues($field, $subfield = NULL) {
196 if (isset($this->context
[$field])) {
198 if (isset($this->context
[$field]->$subfield)) {
199 $values[] = $this->context
[$field]->$subfield;
203 $values[] = $this->context
[$field];
206 foreach ($this->getRows() as $row) {
207 if (isset($row->context
[$field])) {
209 if (isset($row->context
[$field]->$subfield)) {
210 $values[] = $row->context
[$field]->$subfield;
214 $values[] = $row->context
[$field];
218 $values = array_unique($values);
223 * Get the list of available tokens.
226 * Ex: $tokens['event'] = array('location', 'start_date', 'end_date').
228 public function getTokens() {
229 if ($this->tokens
=== NULL) {
230 $this->tokens
= array();
231 $event = new TokenRegisterEvent($this, array('entity' => 'undefined'));
232 $this->dispatcher
->dispatch(Events
::TOKEN_REGISTER
, $event);
234 return $this->tokens
;
238 * Get the list of available tokens, formatted for display
241 * Ex: $tokens[ '{token.name}' ] = "Token label"
243 public function listTokens() {
244 if ($this->listTokens
=== NULL) {
245 $this->listTokens
= array();
246 foreach ($this->getTokens() as $token => $values) {
247 $this->listTokens
['{' . $token . '}'] = $values['label'];
250 return $this->listTokens
;
254 * Compute and store token values.
256 public function evaluate() {
257 $event = new TokenValueEvent($this);
258 $this->dispatcher
->dispatch(Events
::TOKEN_EVALUATE
, $event);
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().
270 * Fully rendered message, with tokens merged.
272 public function render($name, $row) {
273 if (!is_object($row)) {
274 $row = $this->getRow($row);
277 $message = $this->getMessage($name);
278 $row->fill($message['format']);
279 $useSmarty = !empty($row->context
['smarty']);
281 // FIXME preg_callback.
282 $tokens = $this->rowValues
[$row->tokenRow
][$message['format']];
283 $flatTokens = array();
284 \CRM_Utils_Array
::flatten($tokens, $flatTokens, '', '.');
285 $filteredTokens = array();
286 foreach ($flatTokens as $k => $v) {
287 $filteredTokens['{' . $k . '}'] = ($useSmarty ? \CRM_Utils_Token
::tokenEscapeSmarty($v) : $v);
290 $event = new TokenRenderEvent($this);
291 $event->message
= $message;
292 $event->context
= $row->context
;
294 $event->string = strtr($message['string'], $filteredTokens);
295 $this->dispatcher
->dispatch(Events
::TOKEN_RENDER
, $event);
296 return $event->string;
301 class TokenRowIterator
extends \IteratorIterator
{
303 protected $tokenProcessor;
306 * @param TokenProcessor $tokenProcessor
307 * @param Traversable $iterator
309 public function __construct(TokenProcessor
$tokenProcessor, Traversable
$iterator) {
310 parent
::__construct($iterator); // TODO: Change the autogenerated stub
311 $this->tokenProcessor
= $tokenProcessor;
314 public function current() {
315 return new TokenRow($this->tokenProcessor
, parent
::key());