SearchKit - Allow smarty in field rewrite
authorColeman Watts <coleman@civicrm.org>
Fri, 31 Dec 2021 01:39:48 +0000 (20:39 -0500)
committerColeman Watts <coleman@civicrm.org>
Fri, 21 Jan 2022 01:42:45 +0000 (20:42 -0500)
ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php
ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php

index f6957086d0790aef50d9d150153a6f75c2ab2b1f..d65071d156676a274124a945bd0733c6732e7324 100644 (file)
@@ -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') {
index ee09339fc1a2d607e178ed23167f136ed7f78761..2712da75be9f3c12e42026b21b8891b41bf35537 100644 (file)
@@ -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.
    */