'CRM_Core_BAO_ActionSchedule', 'schedule' => $dao, 'mapping' => $dao). * Ex: Array('class'=>'CRM_Mailing_BAO_MailingJob', 'mailing' => $dao). * * For lack of a better place, here's a list of known/intended context values: * * - controller: string, the class which is managing the mail-merge. * - smarty: bool, whether to enable smarty support. * - contactId: int, the main person/org discussed in the message. * - contact: array, the main person/org discussed in the message. * (Optional for performance tweaking; if omitted, will load * automatically from contactId.) * - actionSchedule: DAO, the rule which triggered the mailing * [for CRM_Core_BAO_ActionScheduler]. * - schema: array, a list of fields that will be provided for each row. * This is automatically populated with any general context * keys, but you may need to add extra keys for token-row data. * ex: ['contactId', 'activity_id']. (Note we are standardising on the latter). */ public $context; /** * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ protected $dispatcher; /** * @var array * Each message is an array with keys: * - string: Unprocessed message (eg "Hello, {display_name}."). * - format: Media type (eg "text/plain"). * - tokens: List of tokens which are actually used in this message. */ protected $messages; /** * DO NOT access field this directly. Use TokenRow. This is * marked as public only to benefit TokenRow. * * @var array * Array(int $pos => array $keyValues); */ public $rowContexts; /** * DO NOT access field this directly. Use TokenRow. This is * marked as public only to benefit TokenRow. * * @var array * Ex: $rowValues[$rowPos][$format][$entity][$field] = 'something'; * Ex: $rowValues[3]['text/plain']['contact']['display_name'] = 'something'; */ public $rowValues; /** * A list of available tokens * @var array * Array(string $dottedName => array('entity'=>string, 'field'=>string, 'label'=>string)). */ protected $tokens = NULL; /** * A list of available tokens formatted for display * @var array * Array('{' . $dottedName . '}' => 'labelString') */ protected $listTokens = NULL; protected $next = 0; /** * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher * @param array $context */ public function __construct($dispatcher, $context) { $context['schema'] = isset($context['schema']) ? array_unique(array_merge($context['schema'], array_keys($context))) : array_keys($context); $this->dispatcher = $dispatcher; $this->context = $context; } /** * Register a string for which we'll need to merge in tokens. * * @param string $name * Ex: 'subject', 'body_html'. * @param string $value * Ex: '

Hello {contact.name}

'. * @param string $format * Ex: 'text/html'. * @return TokenProcessor */ public function addMessage($name, $value, $format) { $this->messages[$name] = [ 'string' => $value, 'format' => $format, 'tokens' => \CRM_Utils_Token::getTokens($value), ]; return $this; } /** * Add a row of data. * * @return TokenRow */ public function addRow() { $key = $this->next++; $this->rowContexts[$key] = []; $this->rowValues[$key] = [ 'text/plain' => [], 'text/html' => [], ]; return new TokenRow($this, $key); } /** * @param array $params * Array with keys: * - entity: string, e.g. "profile". * - field: string, e.g. "viewUrl". * - label: string, e.g. "Default Profile URL (View Mode)". * @return TokenProcessor */ public function addToken($params) { $key = $params['entity'] . '.' . $params['field']; $this->tokens[$key] = $params; return $this; } /** * @param string $name * @return array * Keys: * - string: Unprocessed message (eg "Hello, {display_name}."). * - format: Media type (eg "text/plain"). */ public function getMessage($name) { return $this->messages[$name]; } /** * Get a list of all tokens used in registered messages. * * @return array */ public function getMessageTokens() { $tokens = []; foreach ($this->messages as $message) { $tokens = \CRM_Utils_Array::crmArrayMerge($tokens, $message['tokens']); } foreach (array_keys($tokens) as $e) { $tokens[$e] = array_unique($tokens[$e]); sort($tokens[$e]); } return $tokens; } public function getRow($key) { return new TokenRow($this, $key); } /** * @return \Traversable */ public function getRows() { return new TokenRowIterator($this, new \ArrayIterator($this->rowContexts)); } /** * Get a list of all unique values for a given context field, * whether defined at the processor or row level. * * @param string $field * Ex: 'contactId'. * @param $subfield * @return array * Ex: [12, 34, 56]. */ public function getContextValues($field, $subfield = NULL) { $values = []; if (isset($this->context[$field])) { if ($subfield) { if (isset($this->context[$field]->$subfield)) { $values[] = $this->context[$field]->$subfield; } } else { $values[] = $this->context[$field]; } } foreach ($this->getRows() as $row) { if (isset($row->context[$field])) { if ($subfield) { if (isset($row->context[$field]->$subfield)) { $values[] = $row->context[$field]->$subfield; } } else { $values[] = $row->context[$field]; } } } $values = array_unique($values); return $values; } /** * Get the list of available tokens. * * @return array * Ex: $tokens['event'] = array('location', 'start_date', 'end_date'). */ public function getTokens() { if ($this->tokens === NULL) { $this->tokens = []; $event = new TokenRegisterEvent($this, ['entity' => 'undefined']); $this->dispatcher->dispatch(Events::TOKEN_REGISTER, $event); } return $this->tokens; } /** * Get the list of available tokens, formatted for display * * @return array * Ex: $tokens[ '{token.name}' ] = "Token label" */ public function listTokens() { if ($this->listTokens === NULL) { $this->listTokens = []; foreach ($this->getTokens() as $token => $values) { $this->listTokens['{' . $token . '}'] = $values['label']; } } return $this->listTokens; } /** * Compute and store token values. */ public function evaluate() { $event = new TokenValueEvent($this); $this->dispatcher->dispatch(Events::TOKEN_EVALUATE, $event); return $this; } /** * Render a message. * * @param string $name * The name previously registered with addMessage(). * @param TokenRow|int $row * The object or ID for the row previously registered with addRow(). * @return string * Fully rendered message, with tokens merged. */ public function render($name, $row) { if (!is_object($row)) { $row = $this->getRow($row); } $message = $this->getMessage($name); $row->fill($message['format']); $useSmarty = !empty($row->context['smarty']); /** *@FIXME preg_callback. */ $tokens = $this->rowValues[$row->tokenRow][$message['format']]; $flatTokens = []; \CRM_Utils_Array::flatten($tokens, $flatTokens, '', '.'); $filteredTokens = []; foreach ($flatTokens as $k => $v) { $filteredTokens['{' . $k . '}'] = ($useSmarty ? \CRM_Utils_Token::tokenEscapeSmarty($v) : $v); } $event = new TokenRenderEvent($this); $event->message = $message; $event->context = $row->context; $event->row = $row; $event->string = strtr($message['string'], $filteredTokens); $this->dispatcher->dispatch(Events::TOKEN_RENDER, $event); return $event->string; } } class TokenRowIterator extends \IteratorIterator { protected $tokenProcessor; /** * @param TokenProcessor $tokenProcessor * @param \Traversable $iterator */ public function __construct(TokenProcessor $tokenProcessor, Traversable $iterator) { // TODO: Change the autogenerated stub parent::__construct($iterator); $this->tokenProcessor = $tokenProcessor; } public function current() { return new TokenRow($this->tokenProcessor, parent::key()); } }