From 9e0cd4fd9f8e75cc1bdcf39998985296880f10da Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Mon, 13 Sep 2021 13:45:38 +1200 Subject: [PATCH] dev/core#2814 Fix tokenCompatSubscriber to conditionally evaluate legacy hook tokens --- Civi/Token/TokenCompatSubscriber.php | 69 +++++++++++++++---- .../CRM/Core/BAO/MessageTemplateTest.php | 9 ++- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/Civi/Token/TokenCompatSubscriber.php b/Civi/Token/TokenCompatSubscriber.php index 417450ef93..79eac1f259 100644 --- a/Civi/Token/TokenCompatSubscriber.php +++ b/Civi/Token/TokenCompatSubscriber.php @@ -30,6 +30,7 @@ class TokenCompatSubscriber implements EventSubscriberInterface { return [ 'civi.token.eval' => [ ['setupSmartyAliases', 1000], + ['evaluateLegacyHookTokens', 500], ['onEvaluate'], ], 'civi.token.render' => 'onRender', @@ -242,6 +243,57 @@ class TokenCompatSubscriber implements EventSubscriberInterface { $proc->addMessage('TokenCompatSubscriber.aliases', $fakeMessage, 'text/plain'); } + /** + * Load token data from legacy hooks. + * + * While our goal is for people to move towards implementing + * toke processors the old-style hooks can extend contact + * token data. + * + * When that is happening we need to load the full contact record + * to send to the hooks (not great for performance but the + * fix is to move away from implementing legacy style hooks). + * + * Consistent with prior behaviour we only load the contact it it + * is already loaded. In that scenario we also load any extra fields + * that might be wanted for the contact tokens. + * + * @param \Civi\Token\Event\TokenValueEvent $e + * @throws TokenException + */ + public function evaluateLegacyHookTokens(TokenValueEvent $e): void { + $messageTokens = $e->getTokenProcessor()->getMessageTokens(); + $hookTokens = array_intersect(\CRM_Utils_Token::getTokenCategories(), array_keys($messageTokens)); + if (empty($hookTokens)) { + return; + } + foreach ($e->getRows() as $row) { + if (empty($row->context['contactId'])) { + continue; + } + unset($swapLocale); + $swapLocale = empty($row->context['locale']) ? NULL : \CRM_Utils_AutoClean::swapLocale($row->context['locale']); + if (empty($row->context['contact'])) { + // If we don't have the contact already load it now, getting full + // details for hooks and anything the contact token resolution might + // want later. + $row->context['contact'] = $this->getContact($row->context['contactId'], $messageTokens['contact'] ?? [], TRUE); + } + $contactArray = [$row->context['contactId'] => $row->context['contact']]; + \CRM_Utils_Hook::tokenValues($contactArray, + [$row->context['contactId']], + empty($row->context['mailingJobId']) ? NULL : $row->context['mailingJobId'], + $messageTokens, + $row->context['controller'] + ); + foreach ($hookTokens as $hookToken) { + foreach ($messageTokens[$hookToken] as $tokenName) { + $row->format('text/plain')->tokens($hookToken, $tokenName, $contactArray[$row->context['contactId']][$tokenName] ?? ''); + } + } + } + } + /** * Load token data. * @@ -255,7 +307,7 @@ class TokenCompatSubscriber implements EventSubscriberInterface { $e->getTokenProcessor()->context['hookTokenCategories'] = \CRM_Utils_Token::getTokenCategories(); - $messageTokens = $e->getTokenProcessor()->getMessageTokens(); + $messageTokens = $e->getTokenProcessor()->getMessageTokens()['contact'] ?? []; foreach ($e->getRows() as $row) { if (empty($row->context['contactId'])) { @@ -268,24 +320,11 @@ class TokenCompatSubscriber implements EventSubscriberInterface { /** @var int $contactId */ $contactId = $row->context['contactId']; if (empty($row->context['contact'])) { - $contact = $this->getContact($contactId, $messageTokens['contact'] ?? [], TRUE); + $contact = $this->getContact($contactId, $messageTokens); } else { $contact = $row->context['contact']; } - - $contactArray = [$contactId => $contact]; - \CRM_Utils_Hook::tokenValues($contactArray, - [$contactId], - empty($row->context['mailingJobId']) ? NULL : $row->context['mailingJobId'], - $messageTokens, - $row->context['controller'] - ); - - // merge the custom tokens in the $contact array - if (!empty($contactArray[$contactId])) { - $contact = array_merge($contact, $contactArray[$contactId]); - } $row->context('contact', $contact); } } diff --git a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php index b288a7ca98..0f25e72d6b 100644 --- a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php +++ b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php @@ -339,6 +339,7 @@ London, 90210 // work in combination. $tokenString = '{$pre_assigned_smarty}{$passed_smarty} {domain.name} +{important_stuff.favourite_emoticon} '; foreach (array_keys($tokenData) as $key) { $tokenString .= "{$key}:{contact.{$key}}\n"; @@ -356,6 +357,7 @@ London, 90210 ]); $expected = 'weewhoa Default Domain Name +emo '; $expected .= $this->getExpectedContactOutput($address['id'], $tokenData, $messageContent['html']); $this->assertEquals($expected, $messageContent['html']); @@ -455,7 +457,12 @@ Default Domain Name ]; } - public function getAdvertisedTokens() { + /** + * Get the tokens we expect to see advertised. + * + * @return string[] + */ + public function getAdvertisedTokens(): array { return [ '{contact.contact_type}' => 'Contact Type', '{contact.do_not_email}' => 'Do Not Email', -- 2.25.1