Improve APIv4 selectUtils to handle join paths in fieldnames.
authorColeman Watts <coleman@civicrm.org>
Thu, 26 Mar 2020 03:48:49 +0000 (23:48 -0400)
committerColeman Watts <coleman@civicrm.org>
Thu, 26 Mar 2020 03:56:22 +0000 (23:56 -0400)
Civi/Api4/Utils/SelectUtil.php
tests/phpunit/api/v4/Utils/SelectUtilTest.php

index 38b085ceefaa9f61ee403dc3a5bdd9d521c5721d..00863f051c8e98a433c661748b986a6e510b14d9 100644 (file)
@@ -43,17 +43,32 @@ class SelectUtil {
   }
 
   /**
+   * Filters a list of fieldnames by matching a pattern which may contain * wildcards.
+   *
+   * For fieldnames joined with a dot (e.g. email.contact_id), wildcards are only allowed after the last dot.
+   *
    * @param string $pattern
    * @param array $fieldNames
    * @return array
    */
   public static function getMatchingFields($pattern, $fieldNames) {
+    // If the pattern is "select all" then we return all base fields (excluding those with a dot)
     if ($pattern === '*') {
-      return $fieldNames;
+      return array_values(array_filter($fieldNames, function($field) {
+        return strpos($field, '.') === FALSE;
+      }));
     }
-    $pattern = '/^' . str_replace('\*', '.*', preg_quote($pattern, '/')) . '$/';
-    return array_values(array_filter($fieldNames, function($field) use ($pattern) {
-      return preg_match($pattern, $field);
+    $dot = strrpos($pattern, '.');
+    $prefix = $dot === FALSE ? '' : substr($pattern, 0, $dot + 1);
+    $search = $dot === FALSE ? $pattern : substr($pattern, $dot + 1);
+    $search = '/^' . str_replace('\*', '.*', preg_quote($search, '/')) . '$/';
+    return array_values(array_filter($fieldNames, function($field) use ($search, $prefix) {
+      // Exclude fields that don't have the same join prefix
+      if (($prefix !== '' && strpos($field, $prefix) !== 0) || substr_count($prefix, '.') !== substr_count($field, '.')) {
+        return FALSE;
+      }
+      // Now strip the prefix and compare field name to the pattern
+      return preg_match($search, substr($field, strlen($prefix)));
     }));
   }
 
index 662d343cbc36832886ef4b2da75dd0177dd5a5c6..691ce6f57b6337f6220f53d496606e5ef3fc5c95 100644 (file)
@@ -42,6 +42,12 @@ class SelectUtilTest extends UnitTestCase {
     'reset_date',
     'signature_text',
     'signature_html',
+    'contact.id',
+    'contact.display_name',
+    'contact.sort_name',
+    'contact.phone.id',
+    'contact.phone.phone',
+    'contact.phone.phone_type_id',
   ];
 
   public function getSelectExamples() {
@@ -52,6 +58,8 @@ class SelectUtilTest extends UnitTestCase {
       ['one', ['o*', 'two'], TRUE],
       ['one', ['*o', 'two'], FALSE],
       ['zoo', ['one', 'two'], FALSE],
+      ['one.id', ['one.id', 'two'], TRUE],
+      ['one.id', ['one.*', 'two'], TRUE],
     ];
   }
 
@@ -67,7 +75,7 @@ class SelectUtilTest extends UnitTestCase {
 
   public function getMatchingExamples() {
     return [
-      [$this->emailFieldNames, '*'],
+      [array_slice($this->emailFieldNames, 0, 12), '*'],
       [[], 'nothing'],
       [['email'], 'email'],
       [['contact_id', 'location_type_id'], '*_id'],
@@ -75,6 +83,10 @@ class SelectUtilTest extends UnitTestCase {
       [['contact_id'], 'con*_id'],
       [['is_primary', 'is_billing', 'is_bulkmail'], 'is_*'],
       [['is_billing', 'is_bulkmail'], 'is_*l*'],
+      [['contact.id', 'contact.display_name', 'contact.sort_name'], 'contact.*'],
+      [['contact.display_name', 'contact.sort_name'], 'contact.*_name'],
+      [['contact.phone.id', 'contact.phone.phone', 'contact.phone.phone_type_id'], 'contact.phone.*'],
+      [['contact.phone.phone', 'contact.phone.phone_type_id'], 'contact.phone.phone*'],
     ];
   }