From 7616c89d87646c30d13f5d38e9f972b867598b7e Mon Sep 17 00:00:00 2001 From: Coleman Watts <coleman@civicrm.org> Date: Thu, 30 Dec 2021 20:39:48 -0500 Subject: [PATCH] SearchKit - Allow smarty in field rewrite --- .../SearchDisplay/AbstractRunAction.php | 25 +++++- .../api/v4/SearchDisplay/SearchRunTest.php | 87 ++++++++++++++++++- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php index f6957086d0..d65071d156 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php @@ -203,8 +203,8 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { } /** - * @param $column - * @param $data + * @param array $column + * @param array $data * @return array{val: mixed, links: array, edit: array, label: string, title: string, image: array, cssClass: string} */ private function formatColumn($column, $data) { @@ -217,7 +217,7 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { $out['val'] = $this->replaceTokens($column['empty_value'], $data, 'view'); } elseif ($column['rewrite']) { - $out['val'] = $this->replaceTokens($column['rewrite'], $data, 'view'); + $out['val'] = $this->rewrite($column, $data); } else { $out['val'] = $this->formatViewValue($column['key'], $rawValue); @@ -275,6 +275,23 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { return $out; } + /** + * Rewrite field value, subtituting tokens and evaluating smarty tags + * + * @param array $column + * @param array $data + * @return string + */ + private function rewrite(array $column, array $data): string { + $output = $this->replaceTokens($column['rewrite'], $data, 'view'); + // Cheap strpos to skip Smarty processing if not needed + if (strpos($output, '{') !== FALSE) { + $smarty = \CRM_Core_Smarty::singleton(); + $output = $smarty->fetchWith("string:$output", []); + } + return $output; + } + /** * Evaluates conditional style rules * @@ -545,7 +562,7 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { * @return string */ private function replaceTokens($tokenExpr, $data, $format, $index = 0) { - if ($tokenExpr) { + if (strpos($tokenExpr, '[') !== FALSE) { foreach ($this->getTokens($tokenExpr) as $token) { $val = $data[$token] ?? NULL; if (isset($val) && $format === 'view') { diff --git a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php index ee09339fc1..2712da75be 100644 --- a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php +++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php @@ -208,9 +208,9 @@ class SearchRunTest extends \PHPUnit\Framework\TestCase implements HeadlessInter $this->assertNotEmpty($result->first()['data']['sort_name']); // These items are not part of the search, but will be added via links - $this->assertArrayNotHasKey('contact_type', $result->first()); - $this->assertArrayNotHasKey('source', $result->first()); - $this->assertArrayNotHasKey('last_name', $result->first()); + $this->assertArrayNotHasKey('contact_type', $result->first()['data']); + $this->assertArrayNotHasKey('source', $result->first()['data']); + $this->assertArrayNotHasKey('last_name', $result->first()['data']); // Add links $params['display']['settings']['columns'][] = [ @@ -227,6 +227,87 @@ class SearchRunTest extends \PHPUnit\Framework\TestCase implements HeadlessInter $this->assertEquals($lastName, $result->first()['data']['last_name']); } + /** + * Test smarty rewrite syntax. + */ + public function testRunWithSmartyRewrite() { + $lastName = uniqid(__FUNCTION__); + $sampleData = [ + ['first_name' => 'One', 'last_name' => $lastName, 'nick_name' => 'Uno'], + ['first_name' => 'Two', 'last_name' => $lastName], + ]; + $contacts = Contact::save(FALSE)->setRecords($sampleData)->execute(); + Email::create(FALSE) + ->addValue('contact_id', $contacts[0]['id']) + ->addValue('email', 'testmail@unit.test') + ->execute(); + + $params = [ + 'checkPermissions' => FALSE, + 'return' => 'page:1', + 'savedSearch' => [ + 'api_entity' => 'Contact', + 'api_params' => [ + 'version' => 4, + 'select' => ['id', 'first_name', 'last_name', 'nick_name', 'Contact_Email_contact_id_01.email', 'Contact_Email_contact_id_01.location_type_id:label'], + 'where' => [['last_name', '=', $lastName]], + 'join' => [ + [ + "Email AS Contact_Email_contact_id_01", + "LEFT", + ["id", "=", "Contact_Email_contact_id_01.contact_id"], + ["Contact_Email_contact_id_01.is_primary", "=", TRUE], + ], + ], + ], + ], + 'display' => [ + 'type' => 'table', + 'label' => '', + 'settings' => [ + 'limit' => 20, + 'pager' => TRUE, + 'columns' => [ + [ + 'key' => 'id', + 'label' => 'Contact ID', + 'type' => 'field', + ], + [ + 'key' => 'first_name', + 'label' => 'Name', + 'type' => 'field', + 'rewrite' => '{if "[nick_name]"}[nick_name]{else}[first_name]{/if} [last_name]', + ], + [ + 'key' => 'Contact_Email_contact_id_01.email', + 'label' => 'Email', + 'type' => 'field', + 'rewrite' => '{if "[Contact_Email_contact_id_01.email]"}[Contact_Email_contact_id_01.email] ([Contact_Email_contact_id_01.location_type_id:label]){/if}', + ], + ], + 'sort' => [ + ['id', 'ASC'], + ], + ], + ], + ]; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertEquals("Uno $lastName", $result[0]['columns'][1]['val']); + $this->assertEquals("Two $lastName", $result[1]['columns'][1]['val']); + $this->assertEquals("testmail@unit.test (Home)", $result[0]['columns'][2]['val']); + $this->assertEquals("", $result[1]['columns'][2]['val']); + + // Try running it with illegal tags like {crmApi} + $params['display']['columns'][1]['rewrite'] = '{crmApi entity="Email" action="get" va="notAllowed"}'; + try { + civicrm_api4('SearchDisplay', 'run', $params); + $this->fail(); + } + catch (\Exception $e) { + } + } + /** * Test running a searchDisplay as a restricted user. */ -- 2.25.1