diff --git a/CRM/Contact/Tokens.php b/CRM/Contact/Tokens.php index bed30b86f737bc14d81e64789940ba5fea4b14a5..01c1e358a30c5b1774f4227e28b09aa224f462b4 100644 --- a/CRM/Contact/Tokens.php +++ b/CRM/Contact/Tokens.php @@ -415,7 +415,7 @@ class CRM_Contact_Tokens extends CRM_Core_EntityTokens { // Manually add in the abbreviated state province as that maps to // what has traditionally been delivered. $tokensMetadata['address_primary.state_province_id:abbr'] = $tokensMetadata['address_primary.state_province_id:label']; - $tokensMetadata['address_primary.state_province_id:abbr']['name'] = 'state_province_id:abbr'; + $tokensMetadata['address_primary.state_province_id:abbr']['name'] = 'address_primary.state_province_id:abbr'; $tokensMetadata['address_primary.state_province_id:abbr']['audience'] = 'user'; // Hide the label for now because we are not sure if there are paths // where legacy token resolution is in play where this could not be resolved. @@ -654,7 +654,7 @@ class CRM_Contact_Tokens extends CRM_Core_EntityTokens { ], 'address_primary.country_id.region_id:name' => [ 'title' => ts('World Region'), - 'name' => 'country_id.region_id.name', + 'name' => 'address_primary.country_id.region_id:name', 'type' => 'mapped', 'api_v3' => 'world_region', 'options' => NULL, diff --git a/CRM/Core/DAO/AllCoreTables.data.php b/CRM/Core/DAO/AllCoreTables.data.php index 456041dd3f3b279dc0d385f1aee7fd4b6f40e6f5..e043d1c0cbdff5e836b7382bf91c8d7a0f9f6178 100644 --- a/CRM/Core/DAO/AllCoreTables.data.php +++ b/CRM/Core/DAO/AllCoreTables.data.php @@ -53,7 +53,7 @@ return [ 'table' => 'civicrm_translation', ], 'CRM_Core_DAO_Worldregion' => [ - 'name' => 'Worldregion', + 'name' => 'WorldRegion', 'class' => 'CRM_Core_DAO_Worldregion', 'table' => 'civicrm_worldregion', ], diff --git a/CRM/Core/DAO/Worldregion.php b/CRM/Core/DAO/Worldregion.php index 3131b0e9408f3fd29f63ba376ce2424dc36b65c1..375beb1d00f917f16279b65893fa5848926fdcce 100644 --- a/CRM/Core/DAO/Worldregion.php +++ b/CRM/Core/DAO/Worldregion.php @@ -6,11 +6,11 @@ * * Generated from xml/schema/CRM/Core/Worldregion.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:9f82f7db4b9ea76b609244a81635d1f1) + * (GenCodeChecksum:2604f3d61e573a996ee6f5d7bf4d04dc) */ /** - * Database access object for the Worldregion entity. + * Database access object for the WorldRegion entity. */ class CRM_Core_DAO_Worldregion extends CRM_Core_DAO { const EXT = 'civicrm'; @@ -82,7 +82,7 @@ class CRM_Core_DAO_Worldregion extends CRM_Core_DAO { 'required' => TRUE, 'where' => 'civicrm_worldregion.id', 'table_name' => 'civicrm_worldregion', - 'entity' => 'Worldregion', + 'entity' => 'WorldRegion', 'bao' => 'CRM_Core_DAO_Worldregion', 'localizable' => 0, 'html' => [ @@ -101,7 +101,7 @@ class CRM_Core_DAO_Worldregion extends CRM_Core_DAO { 'where' => 'civicrm_worldregion.name', 'export' => TRUE, 'table_name' => 'civicrm_worldregion', - 'entity' => 'Worldregion', + 'entity' => 'WorldRegion', 'bao' => 'CRM_Core_DAO_Worldregion', 'localizable' => 0, 'add' => '1.8', diff --git a/CRM/Core/EntityTokens.php b/CRM/Core/EntityTokens.php index 9672f08c1407003dd05564242698f893a31d3d36..9d33cf13599b1ee82740aa4f88f573e14d23586f 100644 --- a/CRM/Core/EntityTokens.php +++ b/CRM/Core/EntityTokens.php @@ -290,10 +290,19 @@ class CRM_Core_EntityTokens extends AbstractTokenSubscriber { protected function getPseudoValue(string $realField, string $pseudoKey, $fieldValue): string { $bao = CRM_Core_DAO_AllCoreTables::getFullName($this->getMetadataForField($realField)['entity']); if ($pseudoKey === 'name') { + // There is a theoretical possibility fieldValue could be an array but + // specifically for preferred communication type - but real world usage + // hitting this is unlikely & the unexpectation is unclear so commenting, + // rather than adding handling. $fieldValue = (string) CRM_Core_PseudoConstant::getName($bao, $realField, $fieldValue); } if ($pseudoKey === 'label') { - $fieldValue = (string) CRM_Core_PseudoConstant::getLabel($bao, $realField, $fieldValue); + $newValue = []; + // Preferred communication method is an array that would resolve to (e.g) 'Phone, Email' + foreach ((array) $fieldValue as $individualValue) { + $newValue[] = CRM_Core_PseudoConstant::getLabel($bao, $realField, $individualValue); + } + $fieldValue = implode(', ', $newValue); } if ($pseudoKey === 'abbr' && $realField === 'state_province_id') { // hack alert - currently only supported for state. diff --git a/Civi/Api4/WorldRegion.php b/Civi/Api4/WorldRegion.php new file mode 100644 index 0000000000000000000000000000000000000000..a62c1c94abd73602af7141b8d5a07ecd46cfa453 --- /dev/null +++ b/Civi/Api4/WorldRegion.php @@ -0,0 +1,24 @@ +<?php +/* + +--------------------------------------------------------------------+ + | Copyright CiviCRM LLC. All rights reserved. | + | | + | This work is published under the GNU AGPLv3 license with some | + | permitted exceptions and without any warranty. For full license | + | and copyright information, see https://civicrm.org/licensing | + +--------------------------------------------------------------------+ + */ +namespace Civi\Api4; + +/** + * WordRegion entity. + * + * This entity stores world regions. + * + * @searchable none + * @since 5.59 + * @package Civi\Api4 + */ +class WorldRegion extends Generic\DAOEntity { + +} diff --git a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php index 095dd65b1b62a70e1c4b57e368cc4e52fcd69e63..0377bbd046b11701d3f36092d93fe009c9305e94 100644 --- a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php +++ b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php @@ -425,8 +425,8 @@ London, 90210 */ public function testContactTokens(): void { // Freeze the time at the start of the test, so checksums don't suffer from second rollovers. - putenv('TIME_FUNC=frozen'); - CRM_Utils_Time::setTime(date('Y-m-d H:i:s')); + $restoreTime = $this->useFrozenTime(); + $this->hookClass->setHook('civicrm_tokenValues', [$this, 'hookTokenValues']); $this->hookClass->setHook('civicrm_tokens', [$this, 'hookTokens']); @@ -483,10 +483,63 @@ emo $this->assertEquals($expected_parts[0], $returned_parts[0]); $this->assertApproxEquals($expected_parts[1], $returned_parts[1], 2); $this->assertEquals($expected_parts[2], $returned_parts[2]); + } + + /** + * Test tokens resolve when parsed one at a time. + * + * Assuming that `testContactTokens()` has asserted tokens work en masse, we have another + * question -- do the tokens work the same when evaluated en-masse and individually? + */ + public function testTokensIndividually(): void { + // Freeze the time at the start of the test, so checksums don't suffer from second rollovers. + // This variable releases the time on destruct so needs to be assigned for the + // duration of the test. + /** @noinspection PhpUnusedLocalVariableInspection */ + $restoreTime = $this->useFrozenTime(); + + $this->hookClass->setHook('civicrm_tokenValues', [$this, 'hookTokenValues']); + $this->hookClass->setHook('civicrm_tokens', [$this, 'hookTokens']); + + $this->createCustomGroupWithFieldsOfAllTypes([]); + $tokenData = $this->getOldContactTokens(); + $this->setupContactFromTokeData($tokenData); + + $context = ['contactId' => $tokenData['contact_id']]; + $render = static function (string $templateText) use ($context, $tokenData) { + try { + return CRM_Core_TokenSmarty::render(['text' => $templateText], $context)['text']; + } + catch (\Throwable $t) { + return 'EXCEPTION:' . $t->getMessage(); + } + }; - // reset time - putenv('TIME_FUNC'); - CRM_Utils_Time::resetTime(); + // Build $tokenLines, a list of expressions like 'contact.display_name:{contact.display_name}' + $tokenLines = []; + $tokenNames = array_keys($this->getAdvertisedTokens()); + foreach ($tokenNames as $tokenName) { + $tokenLines[] = trim($tokenName, '{}') . ':' . $tokenName; + } + foreach (array_keys($tokenData) as $key) { + $tokenLines[] .= "contact.$key:{contact.$key}"; + } + $tokenLines = array_unique($tokenLines); + sort($tokenLines); + + // Evaluate all these token lines + $oneByOne = array_map($render, $tokenLines); + $allAtOnce = $render(implode("\n", $tokenLines)); + $this->assertEquals($allAtOnce, implode("\n", $oneByOne)); + + $emptyLines = preg_grep('/:$/', $oneByOne); + $this->assertEquals([ + 'contact.address_primary.county_id:label:', + 'contact.contact_is_deleted:', + 'contact.county:', + 'contact.custom_6:', + 'contact.do_not_phone:', + ], array_values($emptyLines), 'Most tokens should have data.'); } /** @@ -721,8 +774,7 @@ emo * Note it will render additional custom fields if they exist. * * @return array - * - * @throws \CRM_Core_Exception + * @noinspection PhpDocMissingThrowsInspection */ public function getOldContactTokens(): array { return [ @@ -868,6 +920,21 @@ primary_phone:123-456 ', $messageHtml); } + /** + * Temporarily freeze time, as perceived through `CRM_Utils_Time`. + * + * @return \CRM_Utils_AutoClean + */ + protected function useFrozenTime(): CRM_Utils_AutoClean { + $oldTimeFunc = getenv('TIME_FUNC'); + putenv('TIME_FUNC=frozen'); + CRM_Utils_Time::setTime(date('Y-m-d H:i:s')); + return CRM_Utils_AutoClean::with(function () use ($oldTimeFunc) { + putenv($oldTimeFunc === NULL ? 'TIME_FUNC' : "TIME_FUNC=$oldTimeFunc"); + CRM_Utils_Time::resetTime(); + }); + } + /** * @param array $tokenData * diff --git a/xml/schema/Core/Worldregion.xml b/xml/schema/Core/Worldregion.xml index 9e2b2a9b29550837a38aa60a71ac471aa28b3fd1..f24a8cc02672f7af3fe2e210c9268a28fa0bc6ff 100644 --- a/xml/schema/Core/Worldregion.xml +++ b/xml/schema/Core/Worldregion.xml @@ -4,6 +4,7 @@ <base>CRM/Core</base> <class>Worldregion</class> <name>civicrm_worldregion</name> + <entity>WorldRegion</entity> <add>1.8</add> <field> <name>id</name>