APIv4 - Add getRefCount utility & include afforms in SavedSearch refCounts.
authorColeman Watts <coleman@civicrm.org>
Wed, 10 Nov 2021 13:47:36 +0000 (08:47 -0500)
committerColeman Watts <coleman@civicrm.org>
Wed, 10 Nov 2021 13:52:58 +0000 (08:52 -0500)
CRM/Core/DAO.php
Civi/Api4/Utils/CoreUtil.php
ext/afform/core/afform.php
ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php

index 9f6470a8d7f774a541c1d5be0fdc9cf1bbb058fc..9c4efe41ab17b874ec4d45fc883021e3bbdd0a54 100644 (file)
@@ -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
index f57b1aa8a54948d511ec929780370d473292e600..d6d6320ca41a009e9c8bce7f22669d4924aff137 100644 (file)
@@ -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();
+  }
+
 }
index 2ecf7c854dc33b98ee908b12508392ddfd486ff0..72a152feb399c79f68e8b53b470346dd363dd219 100644 (file)
@@ -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
index feaee1539f1a3b28e7577ac9c513d5d9abe582fa..131e223347874df80ab08782965d1adaf4f5452a 100644 (file)
@@ -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', '<div><crm-search-display-table search-name="TestSearchToDelete" display-name="TestDisplayToDelete"></crm-search-display-table></div>')
       ->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', '<div><crm-search-display-table search-name="TestSearchToDelete"></crm-search-display-table></div>')
+      ->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());
   }
 
 }