diff --git a/CRM/Core/DAO.php b/CRM/Core/DAO.php index 727150de8c31..b0d7feb28427 100644 --- a/CRM/Core/DAO.php +++ b/CRM/Core/DAO.php @@ -2523,8 +2523,7 @@ public static function createReferenceColumns($className) { /** * Find all records which refer to this entity. * - * @return array - * Array of objects referencing this + * @return CRM_Core_DAO[] */ public function findReferences() { $links = self::getReferencesToTable(static::getTableName()); diff --git a/Civi/Api4/Generic/ExportAction.php b/Civi/Api4/Generic/ExportAction.php new file mode 100644 index 000000000000..210a8e0a72ee --- /dev/null +++ b/Civi/Api4/Generic/ExportAction.php @@ -0,0 +1,135 @@ +exportRecord($this->getEntityName(), $this->id, $result); + } + + /** + * @param string $entityType + * @param int $entityId + * @param \Civi\Api4\Generic\Result $result + */ + private function exportRecord(string $entityType, int $entityId, Result $result) { + if (isset($this->exportedEntities[$entityType][$entityId])) { + throw new \API_Exception("Circular reference detected: attempted to export $entityType id $entityId multiple times."); + } + $this->exportedEntities[$entityType][$entityId] = TRUE; + $select = []; + foreach ($this->getFieldsForExport($entityType) as $field) { + if (!empty($field['fk_entity'])) { + if (array_key_exists('name', $this->getFieldsForExport($field['fk_entity']))) { + $select[] = $field['name'] . '.name'; + } + } + else { + $select[] = $field['name']; + } + } + $record = civicrm_api4($entityType, 'get', [ + 'checkPermissions' => $this->checkPermissions, + 'select' => $select, + 'where' => [['id', '=', $entityId]], + ])->first(); + if (!$record) { + return; + } + // The get api always returns ID but it should not be included in an export + unset($record['id']); + $name = $record['name'] ?? count($this->exportedEntities[$entityType]); + $result[] = [ + 'name' => $entityType . '_' . $name, + 'entity' => $entityType, + 'cleanup' => $this->cleanup, + 'update' => $this->update, + 'params' => [ + 'version' => 4, + 'values' => $record, + ], + ]; + // Export entities that reference this one + $daoName = CoreUtil::getInfoItem($entityType, 'dao'); + /** @var \CRM_Core_DAO $dao */ + $dao = new $daoName(); + $dao->id = $entityId; + foreach ($dao->findReferences() as $reference) { + $refEntity = $reference::fields()['id']['entity'] ?? ''; + $refApiType = CoreUtil::getInfoItem($refEntity, 'type') ?? []; + // Reference must be a ManagedEntity, and not the same type as the current entity + if ($refEntity !== $entityType && in_array('ManagedEntity', $refApiType, TRUE)) { + $this->exportRecord($refEntity, $reference->id, $result); + } + } + } + + /** + * @param $entityType + * @return array + */ + private function getFieldsForExport($entityType): array { + return (array) civicrm_api4($entityType, 'getFields', [ + 'action' => 'create', + 'where' => [['type', 'IN', ['Field', 'Custom']]], + 'checkPermissions' => $this->checkPermissions, + ])->indexBy('name'); + } + +} diff --git a/Civi/Api4/Generic/Traits/ManagedEntity.php b/Civi/Api4/Generic/Traits/ManagedEntity.php index da2e25899c4e..df7fc0edf6be 100644 --- a/Civi/Api4/Generic/Traits/ManagedEntity.php +++ b/Civi/Api4/Generic/Traits/ManagedEntity.php @@ -12,6 +12,7 @@ namespace Civi\Api4\Generic\Traits; use Civi\Api4\Generic\BasicBatchAction; +use Civi\Api4\Generic\ExportAction; /** * A managed entity includes extra fields and methods to revert from an overridden local to base state. @@ -36,4 +37,13 @@ public static function revert($checkPermissions = TRUE) { }))->setCheckPermissions($checkPermissions); } + /** + * @param bool $checkPermissions + * @return \Civi\Api4\Generic\ExportAction + */ + public static function export($checkPermissions = TRUE) { + return (new ExportAction(static::getEntityName(), __FUNCTION__)) + ->setCheckPermissions($checkPermissions); + } + } diff --git a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchExportTest.php b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchExportTest.php new file mode 100644 index 000000000000..b28c143d0462 --- /dev/null +++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchExportTest.php @@ -0,0 +1,101 @@ +installMe(__DIR__) + ->apply(); + } + + /** + * Test running a searchDisplay within an afform. + */ + public function testExportSearch() { + $search = SavedSearch::create(FALSE) + ->setValues([ + 'name' => 'TestSearchToExport', + 'label' => 'TestSearchToExport', + 'api_entity' => 'Contact', + 'api_params' => [ + 'version' => 4, + 'select' => ['id'], + ], + ]) + ->execute()->first(); + + SearchDisplay::create(FALSE) + ->setValues([ + 'name' => 'TestDisplayToExport', + 'label' => 'TestDisplayToExport', + 'saved_search_id.name' => 'TestSearchToExport', + 'type' => 'table', + 'settings' => [ + 'columns' => [ + [ + 'key' => 'id', + 'label' => 'Contact ID', + 'dataType' => 'Integer', + 'type' => 'field', + ], + ], + ], + 'acl_bypass' => FALSE, + ]) + ->execute(); + + $export = SavedSearch::export(FALSE) + ->setId($search['id']) + ->execute() + ->indexBy('name'); + + $this->assertCount(2, $export); + // The savedSearch should be first before its reference entities + $this->assertEquals('SavedSearch', $export->first()['entity']); + // Ensure api version is set to 4 + $this->assertEquals(4, $export['SavedSearch_TestSearchToExport']['params']['version']); + // Ensure FK is set correctly + $this->assertEquals('TestSearchToExport', $export['SearchDisplay_TestDisplayToExport']['params']['values']['saved_search_id.name']); + + // Add a second display + SearchDisplay::create(FALSE) + ->setValues([ + 'name' => 'SecondDisplayToExport', + 'label' => 'TestDisplayToExport', + 'saved_search_id.name' => 'TestSearchToExport', + 'type' => 'table', + 'settings' => [ + 'columns' => [ + [ + 'key' => 'id', + 'label' => 'Contact ID', + 'dataType' => 'Integer', + 'type' => 'field', + ], + ], + ], + 'acl_bypass' => FALSE, + ]) + ->execute(); + + $export = SavedSearch::export(FALSE) + ->setId($search['id']) + ->execute() + ->indexBy('name'); + + $this->assertCount(3, $export); + $this->assertEquals('TestSearchToExport', $export['SearchDisplay_SecondDisplayToExport']['params']['values']['saved_search_id.name']); + } + +}