diff --git a/Civi/Token/TokenCompatSubscriber.php b/Civi/Token/TokenCompatSubscriber.php index 3133df6eb3e4da10b1ada4f1136dd1d33f6f4139..b1d9408c6af4dd6e65157611a34d8be7e8d5960a 100644 --- a/Civi/Token/TokenCompatSubscriber.php +++ b/Civi/Token/TokenCompatSubscriber.php @@ -298,17 +298,15 @@ class TokenCompatSubscriber implements EventSubscriberInterface { * Load token data. * * @param \Civi\Token\Event\TokenValueEvent $e + * * @throws TokenException + * @throws \CRM_Core_Exception */ public function onEvaluate(TokenValueEvent $e) { - // For reasons unknown, replaceHookTokens used to require a pre-computed list of - // hook *categories* (aka entities aka namespaces). We cache - // this in the TokenProcessor's context but can likely remove it now. - - $e->getTokenProcessor()->context['hookTokenCategories'] = \CRM_Utils_Token::getTokenCategories(); - $messageTokens = $e->getTokenProcessor()->getMessageTokens()['contact'] ?? []; - + if (empty($messageTokens)) { + return; + } foreach ($e->getRows() as $row) { if (empty($row->context['contactId'])) { continue; @@ -317,15 +315,24 @@ class TokenCompatSubscriber implements EventSubscriberInterface { unset($swapLocale); $swapLocale = empty($row->context['locale']) ? NULL : \CRM_Utils_AutoClean::swapLocale($row->context['locale']); - /** @var int $contactId */ - $contactId = $row->context['contactId']; if (empty($row->context['contact'])) { - $contact = $this->getContact($contactId, $messageTokens); + $row->context['contact'] = $this->getContact($row->context['contactId'], $messageTokens); } - else { - $contact = $row->context['contact']; + foreach ($messageTokens as $token) { + if ($token === 'checksum') { + $cs = \CRM_Contact_BAO_Contact_Utils::generateChecksum($row->context['contactId'], + NULL, + NULL, + $row->context['hash'] ?? NULL + ); + $row->format('text/html') + ->tokens('contact', $token, "cs={$cs}"); + } + else { + $row->format('text/html') + ->tokens('contact', $token, $row->context['contact'][$token] ?? ''); + } } - $row->context('contact', $contact); } } @@ -333,13 +340,22 @@ class TokenCompatSubscriber implements EventSubscriberInterface { * Apply the various CRM_Utils_Token helpers. * * @param \Civi\Token\Event\TokenRenderEvent $e + * + * @throws \CRM_Core_Exception */ - public function onRender(TokenRenderEvent $e) { + public function onRender(TokenRenderEvent $e): void { $isHtml = ($e->message['format'] === 'text/html'); $useSmarty = !empty($e->context['smarty']); if (!empty($e->context['contact'])) { - \CRM_Utils_Token::replaceGreetingTokens($e->string, $e->context['contact'], $e->context['contact']['contact_id'] ?? $e->context['contactId'], NULL, $useSmarty); + // @todo - remove this - it simply removes the last unresolved tokens before + // they break smarty. + // historically it was only called when context['contact'] so that is + // retained but it only works because it's almost always true. + $remainingTokens = array_keys(\CRM_Utils_Token::getTokens($e->string)); + if (!empty($remainingTokens)) { + $e->string = \CRM_Utils_Token::replaceHookTokens($e->string, $e->context['contact'], $remainingTokens); + } } if ($useSmarty) { @@ -374,8 +390,12 @@ class TokenCompatSubscriber implements EventSubscriberInterface { $mappedFields = [ 'email_greeting' => 'email_greeting_display', 'postal_greeting' => 'postal_greeting_display', - 'addressee' => 'address_display', + 'addressee' => 'addressee_display', ]; + if (!empty($returnProperties['checksum'])) { + $returnProperties['hash'] = 1; + } + foreach ($mappedFields as $tokenName => $realName) { if (in_array($tokenName, $requiredFields, TRUE)) { $returnProperties[$realName] = 1; diff --git a/Civi/Token/TokenProcessor.php b/Civi/Token/TokenProcessor.php index 5411a74fecc80a27a15da702904691b985c18fb1..3bad2d891878c5bf08c94870906c4d27f8f7d2ba 100644 --- a/Civi/Token/TokenProcessor.php +++ b/Civi/Token/TokenProcessor.php @@ -383,7 +383,7 @@ class TokenProcessor { // 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:]+)'; + $tokRegex = '([\w]+)\.([\w:\.]+)'; $filterRegex = '(\w+)'; $event->string = preg_replace_callback(";\{$tokRegex(?:\|$filterRegex)?\};", $getToken, $message['string']); $this->dispatcher->dispatch('civi.token.render', $event); diff --git a/tests/phpunit/CRM/Core/FormTest.php b/tests/phpunit/CRM/Core/FormTest.php index d6f3fb2e85975a6ebd9a7d3595f77f7f48a6451b..0041d19ac39be487b4104f2b757fdba25fe3913f 100644 --- a/tests/phpunit/CRM/Core/FormTest.php +++ b/tests/phpunit/CRM/Core/FormTest.php @@ -62,7 +62,7 @@ class CRM_Core_FormTest extends CiviUnitTestCase { ]; } - public function testNewPriceField() { + public function testNewPriceField(): void { $this->createLoggedInUser(); $priceSetId = $this->callAPISuccess('PriceSet', 'create', [