From 283139dc0dab8556bd644357ef7b9cd8fff34f7e Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 8 Mar 2023 21:39:20 -0500 Subject: [PATCH] APIv4 - Support `match` in replaceAction This allows the replaceAction to support non-id matching, the same way as the saveAction does. Fixes dev/core#4168 --- Civi/Api4/Generic/AbstractSaveAction.php | 34 --------------- Civi/Api4/Generic/BasicReplaceAction.php | 11 ++++- Civi/Api4/Generic/Traits/MatchParamTrait.php | 36 ++++++++++++++++ .../crmSearchAdmin.component.js | 1 + tests/phpunit/api/v4/Action/ReplaceTest.php | 43 +++++++++++++++++++ 5 files changed, 89 insertions(+), 36 deletions(-) diff --git a/Civi/Api4/Generic/AbstractSaveAction.php b/Civi/Api4/Generic/AbstractSaveAction.php index e6e6959ff4..8cd462f1b4 100644 --- a/Civi/Api4/Generic/AbstractSaveAction.php +++ b/Civi/Api4/Generic/AbstractSaveAction.php @@ -118,40 +118,6 @@ abstract class AbstractSaveAction extends AbstractAction { } } - /** - * Find existing record based on $this->match param - * - * @param $record - */ - protected function matchExisting(&$record) { - $primaryKey = CoreUtil::getIdFieldName($this->getEntityName()); - if (empty($record[$primaryKey]) && !empty($this->match)) { - $where = []; - foreach ($record as $key => $val) { - if (in_array($key, $this->match, TRUE)) { - if ($val === '' || is_null($val)) { - // If we want to match empty string we have to match on NULL/'' - $where[] = [$key, 'IS EMPTY']; - } - else { - $where[] = [$key, '=', $val]; - } - } - } - if (count($where) === count($this->match)) { - $existing = civicrm_api4($this->getEntityName(), 'get', [ - 'select' => [$primaryKey], - 'where' => $where, - 'checkPermissions' => $this->checkPermissions, - 'limit' => 2, - ]); - if ($existing->count() === 1) { - $record[$primaryKey] = $existing->first()[$primaryKey]; - } - } - } - } - /** * @return string * @deprecated diff --git a/Civi/Api4/Generic/BasicReplaceAction.php b/Civi/Api4/Generic/BasicReplaceAction.php index 7291de47ff..9b83eb8f32 100644 --- a/Civi/Api4/Generic/BasicReplaceAction.php +++ b/Civi/Api4/Generic/BasicReplaceAction.php @@ -30,6 +30,7 @@ namespace Civi\Api4\Generic; * @method bool getReload() */ class BasicReplaceAction extends AbstractBatchAction { + use Traits\MatchParamTrait; /** * Array of $ENTITY records. @@ -86,6 +87,13 @@ class BasicReplaceAction extends AbstractBatchAction { } } + // Merge in defaults and perform non-id matching if match field(s) are specified + foreach ($this->records as &$record) { + $record += $this->defaults; + $this->formatWriteValues($record); + $this->matchExisting($record); + } + $idField = $this->getSelect()[0]; $toDelete = array_diff_key(array_column($items, NULL, $idField), array_flip(array_filter(\CRM_Utils_Array::collect($idField, $this->records)))); @@ -93,8 +101,7 @@ class BasicReplaceAction extends AbstractBatchAction { $saveAction ->setCheckPermissions($this->getCheckPermissions()) ->setReload($this->reload) - ->setRecords($this->records) - ->setDefaults($this->defaults); + ->setRecords($this->records); $result->exchangeArray((array) $saveAction->execute()); if ($toDelete) { diff --git a/Civi/Api4/Generic/Traits/MatchParamTrait.php b/Civi/Api4/Generic/Traits/MatchParamTrait.php index 6d8ed6c786..c16e30ef0f 100644 --- a/Civi/Api4/Generic/Traits/MatchParamTrait.php +++ b/Civi/Api4/Generic/Traits/MatchParamTrait.php @@ -12,6 +12,8 @@ namespace Civi\Api4\Generic\Traits; +use Civi\Api4\Utils\CoreUtil; + /** * @method $this setMatch(array $match) Specify fields to match for update. * @method bool getMatch() @@ -32,6 +34,40 @@ trait MatchParamTrait { */ protected $match = []; + /** + * Find existing record based on $this->match param + * + * @param $record + */ + protected function matchExisting(&$record) { + $primaryKey = CoreUtil::getIdFieldName($this->getEntityName()); + if (empty($record[$primaryKey]) && !empty($this->match)) { + $where = []; + foreach ($record as $key => $val) { + if (in_array($key, $this->match, TRUE)) { + if ($val === '' || is_null($val)) { + // If we want to match empty string we have to match on NULL/'' + $where[] = [$key, 'IS EMPTY']; + } + else { + $where[] = [$key, '=', $val]; + } + } + } + if (count($where) === count($this->match)) { + $existing = civicrm_api4($this->getEntityName(), 'get', [ + 'select' => [$primaryKey], + 'where' => $where, + 'checkPermissions' => $this->checkPermissions, + 'limit' => 2, + ]); + if ($existing->count() === 1) { + $record[$primaryKey] = $existing->first()[$primaryKey]; + } + } + } + } + /** * Options callback for $this->match * @return array diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js index 32fde96e7e..750f597eab 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js @@ -114,6 +114,7 @@ if (params.tag_id && params.tag_id.length) { chain.tag_id = ['EntityTag', 'replace', { where: [['entity_id', '=', '$id'], ['entity_table', '=', 'civicrm_saved_search']], + match: ['entity_id', 'entity_table', 'tag_id'], records: _.transform(params.tag_id, function(records, id) {records.push({tag_id: id});}) }]; } else if (params.id) { diff --git a/tests/phpunit/api/v4/Action/ReplaceTest.php b/tests/phpunit/api/v4/Action/ReplaceTest.php index 11784c43ef..dabee7a190 100644 --- a/tests/phpunit/api/v4/Action/ReplaceTest.php +++ b/tests/phpunit/api/v4/Action/ReplaceTest.php @@ -26,6 +26,7 @@ use Civi\Api4\Email; use api\v4\Traits\TableDropperTrait; use api\v4\Api4TestBase; use Civi\Api4\Contact; +use Civi\Api4\EntityTag; use Civi\Test\TransactionalInterface; /** @@ -184,4 +185,46 @@ class ReplaceTest extends Api4TestBase implements TransactionalInterface { $this->assertEquals('changed two', $newRecords[$cid1Records[0]['id']]['Custom2']); } + public function testReplaceEntityTag(): void { + $t1 = uniqid(); + $t2 = uniqid(); + $t3 = uniqid(); + $this->saveTestRecords('Tag', [ + 'records' => [['name' => $t1], ['name' => $t2], ['name' => $t3]], + 'defaults' => ['used_for' => ['civicrm_contact']], + ]); + + $cid = $this->createTestRecord('Contact')['id']; + + EntityTag::replace(FALSE) + ->addWhere('entity_id', '=', $cid) + ->setRecords([['tag_id:name' => $t1], ['tag_id:name' => $t2]]) + ->addDefault('entity_table', 'civicrm_contact') + ->setMatch(['entity_table', 'entity_id', 'tag_id']) + ->execute(); + + $result = EntityTag::get(FALSE) + ->addWhere('entity_table', '=', 'civicrm_contact') + ->addWhere('entity_id', '=', $cid) + ->addSelect('tag_id:name') + ->execute()->column('tag_id:name'); + + $this->assertEquals([$t1, $t2], $result); + + EntityTag::replace(FALSE) + ->addWhere('entity_id', '=', $cid) + ->setRecords([['tag_id:name' => $t1], ['tag_id:name' => $t3]]) + ->addDefault('entity_table', 'civicrm_contact') + ->setMatch(['entity_table', 'entity_id', 'tag_id']) + ->execute(); + + $result = EntityTag::get(FALSE) + ->addWhere('entity_table', '=', 'civicrm_contact') + ->addWhere('entity_id', '=', $cid) + ->addSelect('tag_id:name') + ->execute()->column('tag_id:name'); + + $this->assertEquals([$t1, $t3], $result); + } + } -- 2.25.1