From 7c6a641614fa1b65d8278796a7cd6663250622b3 Mon Sep 17 00:00:00 2001 From: colemanw Date: Mon, 2 Oct 2023 16:04:03 -0400 Subject: [PATCH] Managed - Backfill default values when reverting APIv4 entity --- CRM/Core/ManagedEntities.php | 42 ++++++++++++++++--- Civi/Api4/Generic/Traits/ManagedEntity.php | 3 +- .../api/v4/Entity/ManagedEntityTest.php | 41 +++++++++++------- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/CRM/Core/ManagedEntities.php b/CRM/Core/ManagedEntities.php index c6bc824419..daacdca7a5 100644 --- a/CRM/Core/ManagedEntities.php +++ b/CRM/Core/ManagedEntities.php @@ -1,6 +1,7 @@ value properties of CRM_Core_DAO_Managed used to match an existing record + * @param string $entityType + * @param $entityId + * @return bool */ - public function revert(array $params) { + public function revert(string $entityType, $entityId): bool { $mgd = new \CRM_Core_DAO_Managed(); - $mgd->copyValues($params); + $mgd->entity_type = $entityType; + $mgd->entity_id = $entityId; $mgd->find(TRUE); $declarations = $this->getDeclarations([$mgd->module]); $declarations = CRM_Utils_Array::findAll($declarations, [ @@ -130,12 +133,39 @@ class CRM_Core_ManagedEntities { 'entity' => $mgd->entity_type, ]); if ($mgd->id && isset($declarations[0])) { - $this->updateExistingEntity(['update' => 'always'] + $declarations[0] + $mgd->toArray()); + $item = ['update' => 'always'] + $declarations[0] + $mgd->toArray(); + $this->backfillDefaults($item); + $this->updateExistingEntity($item); return TRUE; } return FALSE; } + /** + * Backfill default values to restore record to a pristine state + * + * @param array $item Managed APIv4 record + */ + private function backfillDefaults(array &$item): void { + if ($item['params']['version'] != 4) { + return; + } + // Fetch default values for fields that are writeable + $condition = [['type', '=', 'Field'], ['readonly', 'IS EMPTY'], ['default_value', '!=', 'now']]; + // Exclude "weight" as that auto-adjusts + if (in_array('SortableEntity', CoreUtil::getInfoItem($item['entity_type'], 'type'), TRUE)) { + $weightCol = CoreUtil::getInfoItem($item['entity_type'], 'order_by'); + $condition[] = ['name', '!=', $weightCol]; + } + $getFields = civicrm_api4($item['entity_type'], 'getFields', [ + 'checkPermissions' => FALSE, + 'action' => 'create', + 'where' => $condition, + ]); + $defaultValues = $getFields->indexBy('name')->column('default_value'); + $item['params']['values'] += $defaultValues; + } + /** * Take appropriate action on every managed entity. * @@ -332,7 +362,7 @@ class CRM_Core_ManagedEntities { case 'unused': if (CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) { - $getRefCount = \Civi\Api4\Utils\CoreUtil::getRefCount($item['entity_type'], $item['entity_id']); + $getRefCount = CoreUtil::getRefCount($item['entity_type'], $item['entity_id']); } else { $getRefCount = civicrm_api3($item['entity_type'], 'getrefcount', [ diff --git a/Civi/Api4/Generic/Traits/ManagedEntity.php b/Civi/Api4/Generic/Traits/ManagedEntity.php index f014bc76a6..1e0ebe4b2e 100644 --- a/Civi/Api4/Generic/Traits/ManagedEntity.php +++ b/Civi/Api4/Generic/Traits/ManagedEntity.php @@ -27,8 +27,7 @@ trait ManagedEntity { */ public static function revert($checkPermissions = TRUE) { return (new BasicBatchAction(static::getEntityName(), __FUNCTION__, function($item, BasicBatchAction $action) { - $params = ['entity_type' => $action->getEntityName(), 'entity_id' => $item['id']]; - if (\CRM_Core_ManagedEntities::singleton()->revert($params)) { + if (\CRM_Core_ManagedEntities::singleton()->revert($action->getEntityName(), $item['id'])) { return $item; } else { diff --git a/tests/phpunit/api/v4/Entity/ManagedEntityTest.php b/tests/phpunit/api/v4/Entity/ManagedEntityTest.php index 7c739a4e2d..85e6b26241 100644 --- a/tests/phpunit/api/v4/Entity/ManagedEntityTest.php +++ b/tests/phpunit/api/v4/Entity/ManagedEntityTest.php @@ -85,6 +85,17 @@ class ManagedEntityTest extends TestCase implements HeadlessInterface, Transacti * @throws \CRM_Core_Exception */ public function testRevertSavedSearch(): void { + $originalState = [ + 'name' => 'TestManagedSavedSearch', + 'label' => 'Test Saved Search', + 'description' => 'Original state', + 'api_entity' => 'Contact', + 'api_params' => [ + 'version' => 4, + 'select' => ['id'], + 'orderBy' => ['id', 'ASC'], + ], + ]; $this->_managedEntities[] = [ // Setting module to 'civicrm' works for the test but not sure we should actually support that // as it's probably better to package stuff in a core extension instead of core itself. @@ -95,17 +106,7 @@ class ManagedEntityTest extends TestCase implements HeadlessInterface, Transacti 'update' => 'never', 'params' => [ 'version' => 4, - 'values' => [ - 'name' => 'TestManagedSavedSearch', - 'label' => 'Test Saved Search', - 'description' => 'Original state', - 'api_entity' => 'Contact', - 'api_params' => [ - 'version' => 4, - 'select' => ['id'], - 'orderBy' => ['id', 'ASC'], - ], - ], + 'values' => $originalState, ], ]; @@ -113,20 +114,24 @@ class ManagedEntityTest extends TestCase implements HeadlessInterface, Transacti $search = SavedSearch::get(FALSE) ->addWhere('name', '=', 'TestManagedSavedSearch') - ->addSelect('description', 'local_modified_date') + ->addSelect('*', 'local_modified_date') ->execute()->single(); - $this->assertEquals('Original state', $search['description']); + foreach ($originalState as $fieldName => $originalValue) { + $this->assertEquals($originalValue, $search[$fieldName]); + } + $this->assertNull($search['expires_date']); $this->assertNull($search['local_modified_date']); SavedSearch::update(FALSE) ->addValue('id', $search['id']) ->addValue('description', 'Altered state') + ->addValue('expires_date', 'now + 1 year') ->execute(); $time = $this->getCurrentTimestamp(); $search = SavedSearch::get(FALSE) ->addWhere('name', '=', 'TestManagedSavedSearch') - ->addSelect('description', 'has_base', 'base_module', 'local_modified_date') + ->addSelect('*', 'has_base', 'base_module', 'local_modified_date') ->execute()->single(); $this->assertEquals('Altered state', $search['description']); // Check calculated fields @@ -135,6 +140,7 @@ class ManagedEntityTest extends TestCase implements HeadlessInterface, Transacti // local_modified_date should reflect the update just made $this->assertGreaterThanOrEqual($time, $search['local_modified_date']); $this->assertLessThanOrEqual($this->getCurrentTimestamp(), $search['local_modified_date']); + $this->assertGreaterThan($time, $search['expires_date']); SavedSearch::revert(FALSE) ->addWhere('name', '=', 'TestManagedSavedSearch') @@ -143,10 +149,13 @@ class ManagedEntityTest extends TestCase implements HeadlessInterface, Transacti // Entity should be revered to original state $result = SavedSearch::get(FALSE) ->addWhere('name', '=', 'TestManagedSavedSearch') - ->addSelect('description', 'has_base', 'base_module', 'local_modified_date') + ->addSelect('*', 'has_base', 'base_module', 'local_modified_date') ->execute(); $search = $result->single(); - $this->assertEquals('Original state', $search['description']); + foreach ($originalState as $fieldName => $originalValue) { + $this->assertEquals($originalValue, $search[$fieldName]); + } + $this->assertNull($search['expires_date']); // Check calculated fields $this->assertTrue($search['has_base']); $this->assertEquals('civicrm', $search['base_module']); -- 2.25.1