From: Coleman Watts Date: Wed, 10 Nov 2021 13:47:36 +0000 (-0500) Subject: APIv4 - Add getRefCount utility & include afforms in SavedSearch refCounts. X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=04309075f0d3aec4cd85f5a2e26262d1510ecc37;p=civicrm-core.git APIv4 - Add getRefCount utility & include afforms in SavedSearch refCounts. --- diff --git a/CRM/Core/DAO.php b/CRM/Core/DAO.php index 9f6470a8d7..9c4efe41ab 100644 --- a/CRM/Core/DAO.php +++ b/CRM/Core/DAO.php @@ -2545,7 +2545,7 @@ SELECT contact_id } /** - * @return array + * @return array{name: string, type: string, count: int, table: string|null, key: string|null}[] * each item has keys: * - name: string * - type: string diff --git a/Civi/Api4/Utils/CoreUtil.php b/Civi/Api4/Utils/CoreUtil.php index f57b1aa8a5..d6d6320ca4 100644 --- a/Civi/Api4/Utils/CoreUtil.php +++ b/Civi/Api4/Utils/CoreUtil.php @@ -12,6 +12,7 @@ namespace Civi\Api4\Utils; +use Civi\API\Exception\NotImplementedException; use Civi\API\Request; use Civi\Api4\Entity; use Civi\Api4\Event\CreateApi4RequestEvent; @@ -252,4 +253,24 @@ class CoreUtil { return $schemaMap; } + /** + * Fetches database references + those returned by hook + * + * @see \CRM_Utils_Hook::referenceCounts() + * @param string $entityName + * @param int $entityId + * @return array{name: string, type: string, count: int, table: string|null, key: string|null}[] + * @throws NotImplementedException + */ + public static function getRefCount(string $entityName, $entityId) { + $daoName = self::getInfoItem($entityName, 'dao'); + if (!$daoName) { + throw new NotImplementedException("Cannot getRefCount for $entityName - dao not found."); + } + /** @var \CRM_Core_DAO $dao */ + $dao = new $daoName(); + $dao->id = $entityId; + return $dao->getReferenceCounts(); + } + } diff --git a/ext/afform/core/afform.php b/ext/afform/core/afform.php index 2ecf7c854d..72a152feb3 100644 --- a/ext/afform/core/afform.php +++ b/ext/afform/core/afform.php @@ -535,6 +535,72 @@ function afform_civicrm_pre($op, $entity, $id, &$params) { ->addWhere('search_displays', 'CONTAINS', $display['saved_search_id.name'] . ".{$display['name']}") ->execute(); } + // When deleting a savedSearch, delete any Afforms which use the default display + if ($entity === 'SearchDisplay' && $op === 'delete') { + $search = \Civi\Api4\SavedSearch::get(FALSE) + ->addSelect('name') + ->addWhere('id', '=', $id) + ->execute()->first(); + \Civi\Api4\Afform::revert(FALSE) + ->addWhere('search_displays', 'CONTAINS', $search['name']) + ->execute(); + } +} + +/** + * Implements hook_civicrm_referenceCounts(). + */ +function afform_civicrm_referenceCounts($dao, &$counts) { + // Count afforms which contain a search display + if (is_a($dao, 'CRM_Search_DAO_SearchDisplay') && $dao->id) { + if (empty($dao->saved_search_id) || empty($dao->name)) { + $dao->find(TRUE); + } + $search = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_SavedSearch', $dao->saved_search_id); + $afforms = \Civi\Api4\Afform::get(FALSE) + ->selectRowCount() + ->addWhere('search_displays', 'CONTAINS', "$search.$dao->name") + ->execute(); + if ($afforms->count()) { + $counts[] = [ + 'name' => 'Afform', + 'type' => 'Afform', + 'count' => $afforms->count(), + ]; + } + } + // Count afforms which contain any displays from a SavedSearch (including the default display) + elseif (is_a($dao, 'CRM_Contact_DAO_SavedSearch') && $dao->id) { + if (empty($dao->name)) { + $dao->find(TRUE); + } + $clauses = [ + ['search_displays', 'CONTAINS', $dao->name], + ]; + try { + $displays = civicrm_api4('SearchDisplay', 'get', [ + 'where' => [['saved_search_id', '=', $dao->id]], + 'select' => 'name', + ], ['name']); + foreach ($displays as $displayName) { + $clauses[] = ['search_displays', 'CONTAINS', $dao->name . '.' . $displayName]; + } + } + catch (Exception $e) { + // In case SearchKit is not installed, the api call would fail + } + $afforms = \Civi\Api4\Afform::get(FALSE) + ->selectRowCount() + ->addClause('OR', $clauses) + ->execute(); + if ($afforms->count()) { + $counts[] = [ + 'name' => 'Afform', + 'type' => 'Afform', + 'count' => $afforms->count(), + ]; + } + } } // Wordpress only: Register callback for rendering shortcodes diff --git a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php index feaee1539f..131e223347 100644 --- a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php +++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php @@ -7,6 +7,7 @@ use Civi\Api4\Contact; use Civi\Api4\Email; use Civi\Api4\SavedSearch; use Civi\Api4\SearchDisplay; +use Civi\Api4\Utils\CoreUtil; use Civi\Test\HeadlessInterface; use Civi\Test\TransactionalInterface; @@ -23,6 +24,11 @@ class SearchAfformTest extends \PHPUnit\Framework\TestCase implements HeadlessIn ->apply(); } + public function tearDown() { + Afform::revert(FALSE)->addWhere('has_local', '=', TRUE)->execute(); + parent::tearDown(); + } + /** * Test running a searchDisplay within an afform. */ @@ -153,7 +159,7 @@ class SearchAfformTest extends \PHPUnit\Framework\TestCase implements HeadlessIn $this->assertCount(1, $result); } - public function testDeleteSearchWillDeleteAfform() { + public function testSearchReferencesToAfform() { $search = SavedSearch::create(FALSE) ->setValues([ 'name' => 'TestSearchToDelete', @@ -186,6 +192,14 @@ class SearchAfformTest extends \PHPUnit\Framework\TestCase implements HeadlessIn ]) ->execute()->first(); + // The search should have one reference (its display) + $refs = CoreUtil::getRefCount('SavedSearch', $search['id']); + $this->assertCount(1, $refs); + + // The display should have zero references + $refs = CoreUtil::getRefCount('SearchDisplay', $display['id']); + $this->assertCount(0, $refs); + Afform::create(FALSE) ->addValue('name', 'TestAfformToDelete') ->addValue('title', 'TestAfformToDelete') @@ -193,13 +207,44 @@ class SearchAfformTest extends \PHPUnit\Framework\TestCase implements HeadlessIn ->addValue('layout', '
') ->execute(); - $this->assertCount(1, Afform::get(FALSE)->addWhere('name', '=', 'TestAfformToDelete')->execute()); + $this->assertCount(1, Afform::get(FALSE)->addWhere('search_displays', 'CONTAINS', 'TestSearchToDelete.TestDisplayToDelete')->execute()); + + // The search should now have two references (its display + Afform) + $refs = CoreUtil::getRefCount('SavedSearch', $search['id']); + $this->assertCount(2, $refs); + + // The display should now have one reference (the Afform) + $refs = CoreUtil::getRefCount('SearchDisplay', $display['id']); + $this->assertCount(1, $refs); + $this->assertEquals('Afform', $refs[0]['type']); + + // Create an afform that uses the search default display + Afform::create(FALSE) + ->addValue('name', 'TestAfformToDelete2') + ->addValue('title', 'TestAfformToDelete2') + ->setLayoutFormat('html') + ->addValue('layout', '
') + ->execute(); + + $this->assertCount(1, Afform::get(FALSE)->addWhere('search_displays', 'CONTAINS', 'TestSearchToDelete')->execute()); + $this->assertCount(2, Afform::get(FALSE)->addWhere('name', 'CONTAINS', 'TestAfformToDelete')->execute()); + + // The search should now have three references (its display + 2 Afforms) + $refs = CoreUtil::getRefCount('SavedSearch', $search['id']); + $this->assertCount(2, $refs); + $this->assertEquals(2, array_column($refs, 'count', 'type')['Afform']); + + // The display should still have one reference (the Afform) + $refs = CoreUtil::getRefCount('SearchDisplay', $display['id']); + $this->assertCount(1, $refs); + $this->assertEquals('Afform', $refs[0]['type']); SavedSearch::delete(FALSE) ->addWhere('name', '=', 'TestSearchToDelete') ->execute(); - $this->assertCount(0, Afform::get(FALSE)->addWhere('name', '=', 'TestAfformToDelete')->execute()); + $this->assertCount(0, Afform::get(FALSE)->addWhere('search_displays', 'CONTAINS', 'TestSearchToDelete.TestDisplayToDelete')->execute()); + $this->assertCount(0, Afform::get(FALSE)->addWhere('name', 'CONTAINS', 'TestAfformToDelete')->execute()); } }