$useSmarty = !empty($row->context['smarty']);
$tokens = $this->rowValues[$row->tokenRow][$message['format']];
- $getToken = function($m) use ($tokens, $useSmarty) {
+ $getToken = function($m) use ($tokens, $useSmarty, $row) {
[$full, $entity, $field] = $m;
if (isset($tokens[$entity][$field])) {
$v = $tokens[$entity][$field];
+ if (isset($m[3])) {
+ $v = $this->filterTokenValue($v, $m[3], $row);
+ }
if ($useSmarty) {
$v = \CRM_Utils_Token::tokenEscapeSmarty($v);
}
$event->message = $message;
$event->context = $row->context;
$event->row = $row;
- // Regex examples: '{foo.bar}'
- // Regex counter-examples: '{foobar}', '{foo bar}', '{$foo.bar}', '{$foo.bar|whiz}'
- $event->string = preg_replace_callback(';\{(\w+)\.(\w+)\};', $getToken, $message['string']);
+ // Regex examples: '{foo.bar}', '{foo.bar|whiz}'
+ // Regex counter-examples: '{foobar}', '{foo bar}', '{$foo.bar}', '{$foo.bar|whiz}', '{foo.bar|whiz{bang}}'
+ // Key observations: Civi tokens MUST have a `.` and MUST NOT have a `$`. Civi filters MUST NOT have `{}`s or `$`s.
+ $tokRegex = '([\w]+)\.([\w:]+)';
+ $filterRegex = '(\w+)';
+ $event->string = preg_replace_callback(";\{$tokRegex(?:\|$filterRegex)?\};", $getToken, $message['string']);
$this->dispatcher->dispatch('civi.token.render', $event);
return $event->string;
}
+ /**
+ * Given a token value, run it through any filters.
+ *
+ * @param mixed $value
+ * Raw token value (e.g. from `$row->tokens['foo']['bar']`).
+ * @param string $filter
+ * @param TokenRow $row
+ * The current target/row.
+ * @return string
+ * @throws \CRM_Core_Exception
+ */
+ private function filterTokenValue($value, $filter, TokenRow $row) {
+ // KISS demonstration. This should change... e.g. provide a filter-registry or reuse Smarty's registry...
+ switch ($filter) {
+ case NULL:
+ return $value;
+
+ case 'upper':
+ return mb_strtoupper($value);
+
+ case 'lower':
+ return mb_strtolower($value);
+
+ default:
+ throw new \CRM_Core_Exception("Invalid token filter: $filter");
+ }
+ }
+
}
class TokenRowIterator extends \IteratorIterator {
$this->assertEquals(1, $this->counts['onEvalTokens']);
}
+ public function testFilter() {
+ $exampleTokens['foo_bar']['whiz_bang'] = 'Some Text';
+ $exampleMessages = [
+ 'This is {foo_bar.whiz_bang}.' => 'This is Some Text.',
+ 'This is {foo_bar.whiz_bang|lower}...' => 'This is some text...',
+ 'This is {foo_bar.whiz_bang|upper}!' => 'This is SOME TEXT!',
+ ];
+ $expectExampleCount = /* {#msgs} x {smarty:on,off} */ 6;
+ $actualExampleCount = 0;
+
+ foreach ($exampleMessages as $inputMessage => $expectOutput) {
+ foreach ([TRUE, FALSE] as $useSmarty) {
+ $p = new TokenProcessor($this->dispatcher, [
+ 'controller' => __CLASS__,
+ 'smarty' => $useSmarty,
+ ]);
+ $p->addMessage('example', $inputMessage, 'text/plain');
+ $p->addRow()
+ ->format('text/plain')->tokens($exampleTokens);
+ foreach ($p->evaluate()->getRows() as $key => $row) {
+ $this->assertEquals($expectOutput, $row->render('example'));
+ $actualExampleCount++;
+ }
+ }
+ }
+
+ $this->assertEquals($expectExampleCount, $actualExampleCount);
+ }
+
public function onListTokens(TokenRegisterEvent $e) {
$this->counts[__FUNCTION__]++;
$e->register('custom', [