use Civi\Token\Event\TokenRegisterEvent;
use Civi\Token\Event\TokenRenderEvent;
use Civi\Token\Event\TokenValueEvent;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Traversable;
+/**
+ * The TokenProcessor is a template/token-engine. It is heavily influenced by
+ * traditional expectations of CiviMail, but it's adapted to an object-oriented,
+ * extensible design.
+ *
+ * BACKGROUND
+ *
+ * The CiviMail heritage gives the following expectations:
+ *
+ * - Messages are often composed of multiple parts (e.g. HTML-part, text-part, and subject-part).
+ * - Messages are often composed in batches for multiple recipients.
+ * - Tokens are denoted as `{foo.bar}`.
+ * - Data should be loaded in an optimized fashion - fetch only the needed
+ * columns, and fetch them with one query (per-table).
+ *
+ * The question of "optimized" data-loading is a key differentiator/complication.
+ * This requires some kind of communication/integration between the template-parser and data-loader.
+ *
+ * USAGE
+ *
+ * There are generally two perspectives on using TokenProcessor:
+ *
+ * 1. Composing messages: You need to specify the template contents (eg `addMessage(...)`)
+ * and the recipients' key data (eg `addRow(['contact_id' => 123])`).
+ * 2. Defining tokens/entities/data-loaders: You need to listen for TokenProcessor
+ * events; if any of your tokens/entities are used, then load the batch of data.
+ *
+ * Each use-case is presented with examples in the Developer Guide:
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/framework/token/
+ */
class TokenProcessor {
/**
public $context;
/**
- * @var EventDispatcherInterface
+ * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $dispatcher;
protected $next = 0;
/**
- * @param EventDispatcherInterface $dispatcher
+ * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* @param array $context
*/
public function __construct($dispatcher, $context) {
* @return TokenProcessor
*/
public function addMessage($name, $value, $format) {
- $this->messages[$name] = array(
+ $this->messages[$name] = [
'string' => $value,
'format' => $format,
'tokens' => \CRM_Utils_Token::getTokens($value),
- );
+ ];
return $this;
}
/**
* Add a row of data.
*
+ * @param array|NULL $context
+ * Optionally, initialize the context for this row.
+ * Ex: ['contact_id' => 123].
* @return TokenRow
*/
- public function addRow() {
+ public function addRow($context = NULL) {
$key = $this->next++;
- $this->rowContexts[$key] = array();
- $this->rowValues[$key] = array(
- 'text/plain' => array(),
- 'text/html' => array(),
- );
+ $this->rowContexts[$key] = [];
+ $this->rowValues[$key] = [
+ 'text/plain' => [],
+ 'text/html' => [],
+ ];
+
+ $row = new TokenRow($this, $key);
+ if ($context !== NULL) {
+ $row->context($context);
+ }
+ return $row;
+ }
- return new TokenRow($this, $key);
+ /**
+ * Add several rows.
+ *
+ * @param array $contexts
+ * List of rows to add.
+ * Ex: [['contact_id'=>123], ['contact_id'=>456]]
+ * @return TokenRow[]
+ * List of row objects
+ */
+ public function addRows($contexts) {
+ $rows = [];
+ foreach ($contexts as $context) {
+ $row = $this->addRow($context);
+ $rows[$row->tokenRow] = $row;
+ }
+ return $rows;
}
/**
* Get a list of all tokens used in registered messages.
*
* @return array
+ * The list of activated tokens, indexed by object/entity.
+ * Array(string $entityName => string[] $fieldNames)
+ *
+ * Ex: If a message says 'Hello {contact.first_name} {contact.last_name}!',
+ * then $result['contact'] would be ['first_name', 'last_name'].
*/
public function getMessageTokens() {
- $tokens = array();
+ $tokens = [];
foreach ($this->messages as $message) {
$tokens = \CRM_Utils_Array::crmArrayMerge($tokens, $message['tokens']);
}
return $tokens;
}
+ /**
+ * Get a specific row (i.e. target or recipient).
+ *
+ * Ex: echo $p->getRow(2)->context['contact_id'];
+ * Ex: $p->getRow(3)->token('profile', 'viewUrl', 'http://example.com/profile?cid=3');
+ *
+ * @param int $key
+ * The row ID
+ * @return \Civi\Token\TokenRow
+ * The row is presented with a fluent, OOP facade.
+ * @see TokenRow
+ */
public function getRow($key) {
return new TokenRow($this, $key);
}
/**
+ * Get the list of rows (i.e. targets/recipients to generate).
+ *
+ * @see TokenRow
* @return \Traversable<TokenRow>
+ * Each row is presented with a fluent, OOP facade.
*/
public function getRows() {
return new TokenRowIterator($this, new \ArrayIterator($this->rowContexts));
*
* @param string $field
* Ex: 'contactId'.
+ * @param string|NULL $subfield
* @return array
* Ex: [12, 34, 56].
*/
* Get the list of available tokens.
*
* @return array
- * Ex: $tokens['event'] = array('location', 'start_date', 'end_date').
+ * Ex: $tokens['event'] = ['location', 'start_date', 'end_date'].
*/
public function getTokens() {
if ($this->tokens === NULL) {
- $this->tokens = array();
- $event = new TokenRegisterEvent($this, array('entity' => 'undefined'));
- $this->dispatcher->dispatch(Events::TOKEN_REGISTER, $event);
+ $this->tokens = [];
+ $event = new TokenRegisterEvent($this, ['entity' => 'undefined']);
+ $this->dispatcher->dispatch('civi.token.list', $event);
}
return $this->tokens;
}
* Get the list of available tokens, formatted for display
*
* @return array
- * Ex: $tokens[ '{token.name}' ] = "Token label"
+ * Ex: $tokens['{token.name}'] = "Token label"
*/
public function listTokens() {
if ($this->listTokens === NULL) {
- $this->listTokens = array();
+ $this->listTokens = [];
foreach ($this->getTokens() as $token => $values) {
$this->listTokens['{' . $token . '}'] = $values['label'];
}
*/
public function evaluate() {
$event = new TokenValueEvent($this);
- $this->dispatcher->dispatch(Events::TOKEN_EVALUATE, $event);
+ $this->dispatcher->dispatch('civi.token.eval', $event);
return $this;
}
$row->fill($message['format']);
$useSmarty = !empty($row->context['smarty']);
- // FIXME preg_callback.
+ /**
+ *@FIXME preg_callback.
+ */
$tokens = $this->rowValues[$row->tokenRow][$message['format']];
- $flatTokens = array();
+ $flatTokens = [];
\CRM_Utils_Array::flatten($tokens, $flatTokens, '', '.');
- $filteredTokens = array();
+ $filteredTokens = [];
foreach ($flatTokens as $k => $v) {
$filteredTokens['{' . $k . '}'] = ($useSmarty ? \CRM_Utils_Token::tokenEscapeSmarty($v) : $v);
}
$event->context = $row->context;
$event->row = $row;
$event->string = strtr($message['string'], $filteredTokens);
- $this->dispatcher->dispatch(Events::TOKEN_RENDER, $event);
+ $this->dispatcher->dispatch('civi.token.render', $event);
return $event->string;
}
/**
* @param TokenProcessor $tokenProcessor
- * @param Traversable $iterator
+ * @param \Traversable $iterator
*/
public function __construct(TokenProcessor $tokenProcessor, Traversable $iterator) {
- parent::__construct($iterator); // TODO: Change the autogenerated stub
+ // TODO: Change the autogenerated stub
+ parent::__construct($iterator);
$this->tokenProcessor = $tokenProcessor;
}