Find DAO by ID instead of just calling the constructor and setting the ID
[civicrm-core.git] / Civi / Api4 / Generic / ExportAction.php
index bb7e14a0c74394e58a28a56cb6279e625aeefc8d..b7e73717960ef36592aed1f02ee26d5f1b4ab2c5 100644 (file)
@@ -81,11 +81,13 @@ class ExportAction extends AbstractAction {
     foreach ($allFields as $field) {
       // Use implicit join syntax but only if the fk entity has a `name` field
       if (!empty($field['fk_entity']) && array_key_exists('name', $this->getFieldsForExport($field['fk_entity']))) {
+        $select[] = $field['name'];
         $select[] = $field['name'] . '.name';
         $pseudofields[$field['name'] . '.name'] = $field['name'];
       }
       // Use pseudoconstant syntax if appropriate
       elseif ($this->shouldUsePseudoconstant($entityType, $field)) {
+        $select[] = $field['name'];
         $select[] = $field['name'] . ':name';
         $pseudofields[$field['name'] . ':name'] = $field['name'];
       }
@@ -103,13 +105,6 @@ class ExportAction extends AbstractAction {
     }
     // The get api always returns ID, but it should not be included in an export
     unset($record['id']);
-    // Null fields should not use joins/pseudoconstants
-    foreach ($pseudofields as $alias => $fieldName) {
-      if (is_null($record[$alias])) {
-        unset($record[$alias]);
-        $record[$fieldName] = NULL;
-      }
-    }
     // Should references be limited to the current domain?
     $limitRefsByDomain = $entityType === 'OptionGroup' && \CRM_Core_OptionGroup::isDomainOptionGroup($record['name']) ? \CRM_Core_BAO_Domain::getDomain()->id : FALSE;
     foreach ($allFields as $fieldName => $field) {
@@ -129,6 +124,27 @@ class ExportAction extends AbstractAction {
       }
     }
     $name = ($parentName ?? '') . $entityType . '_' . ($record['name'] ?? count($this->exportedEntities[$entityType]));
+    // Ensure safe characters, max length
+    $name = \CRM_Utils_String::munge($name, '_', 127);
+    // Include option group with custom field
+    if ($entityType === 'CustomField') {
+      if (
+        !empty($record['option_group_id.name']) &&
+        // Sometimes fields share an option group; only export it once.
+        empty($this->exportedEntities['OptionGroup'][$record['option_group_id']])
+      ) {
+        $this->exportRecord('OptionGroup', $record['option_group_id'], $result);
+      }
+    }
+    // Don't use joins/pseudoconstants if null or if it has the same value as the original
+    foreach ($pseudofields as $alias => $fieldName) {
+      if (!isset($record[$alias]) || $record[$alias] == ($record[$fieldName] ?? NULL)) {
+        unset($record[$alias]);
+      }
+      else {
+        unset($record[$fieldName]);
+      }
+    }
     $result[] = [
       'name' => $name,
       'entity' => $entityType,
@@ -143,12 +159,15 @@ class ExportAction extends AbstractAction {
     $daoName = CoreUtil::getInfoItem($entityType, 'dao');
     if ($daoName) {
       /** @var \CRM_Core_DAO $dao */
-      $dao = new $daoName();
-      $dao->id = $entityId;
+      $dao = $daoName::findById($entityId);
       // Collect references into arrays keyed by entity type
       $references = [];
       foreach ($dao->findReferences() as $reference) {
         $refEntity = \CRM_Utils_Array::first($reference::fields())['entity'] ?? '';
+        // Custom fields don't really "belong" to option groups despite the reference
+        if ($refEntity === 'CustomField' && $entityType === 'OptionGroup') {
+          continue;
+        }
         // Limit references by domain
         if (property_exists($reference, 'domain_id')) {
           if (!isset($reference->domain_id)) {
@@ -200,6 +219,10 @@ class ExportAction extends AbstractAction {
       return FALSE;
     }
     $daoName = CoreUtil::getInfoItem($entityType, 'dao');
+    // Exception for Profile.field_name
+    if ($entityType === 'UFField' && $field['name'] === 'field_name') {
+      return TRUE;
+    }
     // Options generated by a callback function tend to be stable,
     // and the :name property may not be reliable. Use plain value.
     if ($daoName && !empty($daoName::getSupportedFields()[$field['name']]['pseudoconstant']['callback'])) {