From b304136115ffa2670ff74daf483e28ac884c78ac Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 21 Sep 2021 03:22:03 -0700 Subject: [PATCH] TokenProcessor - render() and addMessage() should use same regex to find tokens --- Civi/Token/TokenProcessor.php | 48 ++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/Civi/Token/TokenProcessor.php b/Civi/Token/TokenProcessor.php index 2023a0dc17..d23f8a581e 100644 --- a/Civi/Token/TokenProcessor.php +++ b/Civi/Token/TokenProcessor.php @@ -138,10 +138,14 @@ class TokenProcessor { * @return TokenProcessor */ public function addMessage($name, $value, $format) { + $tokens = []; + $this->visitTokens($value ?: '', function (?string $fullToken, ?string $entity, ?string $field, ?array $modifier) use (&$tokens) { + $tokens[$entity][] = $field; + }); $this->messages[$name] = [ 'string' => $value, 'format' => $format, - 'tokens' => \CRM_Utils_Token::getTokens($value), + 'tokens' => $tokens, ]; return $this; } @@ -361,38 +365,37 @@ class TokenProcessor { $useSmarty = !empty($row->context['smarty']); $tokens = $this->rowValues[$row->tokenRow][$message['format']]; - $getToken = function($m) use ($tokens, $useSmarty, $row) { - [$full, $entity, $field] = $m; + $getToken = function(?string $fullToken, ?string $entity, ?string $field, ?array $modifier) use ($tokens, $useSmarty, $row) { if (isset($tokens[$entity][$field])) { $v = $tokens[$entity][$field]; - if ($v instanceof \DateTime) { - if (!isset($m[3])) { - $m[3] = 'crmDate'; - } - } - if (isset($m[3])) { - $v = $this->filterTokenValue($v, $m[3], $row); - } + $v = $this->filterTokenValue($v, $modifier, $row); if ($useSmarty) { $v = \CRM_Utils_Token::tokenEscapeSmarty($v); } return $v; } - return $full; + return $fullToken; }; $event = new TokenRenderEvent($this); $event->message = $message; $event->context = $row->context; $event->row = $row; - // Regex examples: '{foo.bar}', '{foo.bar|whiz}' + $event->string = $this->visitTokens($message['string'] ?? '', $getToken); + $this->dispatcher->dispatch('civi.token.render', $event); + return $event->string; + } + + private function visitTokens(string $expression, callable $callback): string { + // Regex examples: '{foo.bar}', '{foo.bar|whiz}', '{foo.bar|whiz:bang}' // 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+:?\w+)'; - $event->string = preg_replace_callback(";\{$tokRegex(?:\|$filterRegex)?\};", $getToken, $message['string']); - $this->dispatcher->dispatch('civi.token.render', $event); - return $event->string; + $filterRegex = '(\w+:?\w*)'; + return preg_replace_callback(";\{$tokRegex(?:\|$filterRegex)?\};", function($m) use ($callback) { + $filterParts = isset($m[3]) ? explode(':', $m[3]) : NULL; + return $callback($m[0] ?? NULL, $m[1] ?? NULL, $m[2] ?? NULL, $filterParts); + }, $expression); } /** @@ -400,15 +403,20 @@ class TokenProcessor { * * @param mixed $value * Raw token value (e.g. from `$row->tokens['foo']['bar']`). - * @param string $filter + * @param array|null $filter * @param TokenRow $row * The current target/row. * @return string * @throws \CRM_Core_Exception */ - private function filterTokenValue($value, $filter, TokenRow $row) { + private function filterTokenValue($value, ?array $filter, TokenRow $row) { // KISS demonstration. This should change... e.g. provide a filter-registry or reuse Smarty's registry... - switch ($filter) { + + if ($value instanceof \DateTime && $filter === NULL) { + $filter = ['crmDate']; + } + + switch ($filter[0]) { case NULL: return $value; -- 2.25.1