From 54a08b175618bb726e16da0a17cb435847b3a039 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 25 Mar 2020 23:48:49 -0400 Subject: [PATCH] Improve APIv4 selectUtils to handle join paths in fieldnames. --- Civi/Api4/Utils/SelectUtil.php | 23 +++++++++++++++---- tests/phpunit/api/v4/Utils/SelectUtilTest.php | 14 ++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Civi/Api4/Utils/SelectUtil.php b/Civi/Api4/Utils/SelectUtil.php index 38b085ceefaa..00863f051c8e 100644 --- a/Civi/Api4/Utils/SelectUtil.php +++ b/Civi/Api4/Utils/SelectUtil.php @@ -43,17 +43,32 @@ public static function isFieldSelected($field, $selects) { } /** + * 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))); })); } diff --git a/tests/phpunit/api/v4/Utils/SelectUtilTest.php b/tests/phpunit/api/v4/Utils/SelectUtilTest.php index 662d343cbc36..691ce6f57b63 100644 --- a/tests/phpunit/api/v4/Utils/SelectUtilTest.php +++ b/tests/phpunit/api/v4/Utils/SelectUtilTest.php @@ -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 @@ public function getSelectExamples() { ['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 @@ public function testIsFieldSelected($field, $selects, $expected) { 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 @@ public function getMatchingExamples() { [['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*'], ]; }