diff --git a/Civi/Token/TokenCompatSubscriber.php b/Civi/Token/TokenCompatSubscriber.php index e14539489773..33e394f3cdfc 100644 --- a/Civi/Token/TokenCompatSubscriber.php +++ b/Civi/Token/TokenCompatSubscriber.php @@ -60,6 +60,12 @@ public function onRender(TokenRenderEvent $e): void { return ''; }); + // This removes the pattern used in greetings of having bits of text that + // depend on the tokens around them - ie '{first_name}{ }{last_name} + // has an extra construct '{ }' which will resolve as a space if the + // tokens on either side are resolved to 'something' + $e->string = preg_replace('/\\\\|\{(\s*)?\}/', ' ', $e->string); + if ($useSmarty) { $smartyVars = []; foreach ($e->context['smartyTokenAlias'] ?? [] as $smartyName => $tokenName) { diff --git a/tests/phpunit/CRM/Utils/TokenConsistencyTest.php b/tests/phpunit/CRM/Utils/TokenConsistencyTest.php index 679cd0b44807..7e7f00c1f14b 100644 --- a/tests/phpunit/CRM/Utils/TokenConsistencyTest.php +++ b/tests/phpunit/CRM/Utils/TokenConsistencyTest.php @@ -234,6 +234,45 @@ public function getUnadvertisedTokens(): array { ]; } + /** + * Test tokens in 2 ways to ensure consistent handling. + * + * 1) as part of the greeting processing + * 2) via the token processor. + * + */ + public function testOddTokens(): void { + + $variants = [ + [ + 'string' => '{contact.individual_prefix}{ }{contact.first_name}{ }{contact.middle_name}{ }{contact.last_name}{ }{contact.individual_suffix}', + 'expected' => 'Mr. Anthony Anderson II', + ], + [ + 'string' => '{contact.prefix_id:label}{ }{contact.first_name}{ }{contact.middle_name}{ }{contact.last_name}{ }{contact.suffix_id:label}', + 'expected' => 'Mr. Anthony Anderson II', + ], + ]; + $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [ + 'smarty' => FALSE, + 'schema' => ['contactId'], + ]); + $contactID = $this->individualCreate(['middle_name' => '']); + $tokenProcessor->addRow(['contactId' => $contactID]); + $tokenProcessor->evaluate(); + foreach ($variants as $index => $variant) { + $tokenProcessor->addMessage($index, $variant['string'], 'text/plain'); + } + $tokenProcessor->evaluate(); + $result = $tokenProcessor->getRow(0); + foreach ($variants as $index => $variant) { + $greetingString = $variant['string']; + CRM_Utils_Token::replaceGreetingTokens($greetingString, $this->callAPISuccessGetSingle('Contact', ['id' => $contactID]), $contactID); + $this->assertEquals($variant['expected'], $greetingString); + $this->assertEquals($variant['expected'], $result->render($index)); + } + } + /** * Get the contribution recur tokens keyed by the token. *