Add upgrade-safe DAO::getSupportedFields method
authorColeman Watts <coleman@civicrm.org>
Wed, 1 Jul 2020 17:25:31 +0000 (13:25 -0400)
committerColeman Watts <coleman@civicrm.org>
Fri, 3 Jul 2020 15:19:29 +0000 (11:19 -0400)
Switches api v3 and v4 to use that method so they are upgrade-safe by default.

CRM/Core/DAO.php
Civi/Api4/Service/Spec/SpecGatherer.php
api/v3/utils.php
sql/test_data_second_domain.mysql
tests/phpunit/CRM/Core/DAOTest.php

index 42d07846aaf95bad6293b897e59fc4171818569c..11a990b52db855d5bbd5659d5e0feb5b2f916658 100644 (file)
@@ -530,6 +530,43 @@ class CRM_Core_DAO extends DB_DataObject {
     return $result;
   }
 
+  /**
+   * Returns all usable fields, indexed by name.
+   *
+   * This function differs from fields() in that it indexes by name rather than unique_name.
+   *
+   * It excludes fields not added yet by pending upgrades.
+   * This avoids problems with trying to SELECT a field that exists in code but has not yet been added to the db.
+   *
+   * @param bool $checkPermissions
+   *   Filter by field permissions.
+   * @return array
+   */
+  public static function getSupportedFields($checkPermissions = FALSE) {
+    $fields = array_column((array) static::fields(), NULL, 'name');
+
+    // Exclude fields yet not added by pending upgrades
+    $dbVer = \CRM_Core_BAO_Domain::version();
+    if ($fields && version_compare($dbVer, \CRM_Utils_System::version()) < 0) {
+      $fields = array_filter($fields, function($field) use ($dbVer) {
+        $add = $field['add'] ?? '1.0.0';
+        if (substr_count($add, '.') < 2) {
+          $add .= '.alpha1';
+        }
+        return version_compare($dbVer, $add, '>=');
+      });
+    }
+
+    // Exclude fields the user does not have permission for
+    if ($checkPermissions) {
+      $fields = array_filter($fields, function($field) {
+        return empty($field['permission']) || CRM_Core_Permission::check($field['permission']);
+      });
+    }
+
+    return $fields;
+  }
+
   /**
    * Get/set an associative array of table columns
    *
index 00fa09b5032e1b1a74fd5022229b053804385ebf..6116c4c69d0594dcc5244228273223a1883a3cb7 100644 (file)
@@ -166,7 +166,7 @@ class SpecGatherer {
   private function getDAOFields($entityName) {
     $bao = CoreUtil::getBAOFromApiName($entityName);
 
-    return $bao::fields();
+    return $bao::getSupportedFields();
   }
 
 }
index 3aa58b6c8f6f0bbf4f038d0f3e9f327eceeb51c7..50268a400c1dba09291521f5b25adef03a2aa0ea 100644 (file)
@@ -294,7 +294,7 @@ function _civicrm_api3_load_DAO($entity) {
  *   return the DAO name to manipulate this function
  *   eg. "civicrm_api3_contact_create" or "Contact" will return "CRM_Contact_BAO_Contact"
  *
- * @return mixed|string
+ * @return CRM_Core_DAO|string
  */
 function _civicrm_api3_get_DAO($name) {
   if (strpos($name, 'civicrm_api3') !== FALSE) {
@@ -1878,15 +1878,19 @@ function _civicrm_api_get_fields($entity, $unique = FALSE, &$params = []) {
   if (empty($dao)) {
     return [];
   }
-  $d = new $dao();
-  $fields = $d->fields();
+  $fields = $dao::fields();
+  $supportedFields = $dao::getSupportedFields();
 
-  foreach ($fields as $name => &$field) {
+  foreach ($fields as $name => $field) {
     // Denote as core field
-    $field['is_core_field'] = TRUE;
+    $fields[$name]['is_core_field'] = TRUE;
     // Set html attributes for text fields
     if (isset($field['html'])) {
-      $field['html'] += (array) $d::makeAttribute($field);
+      $fields[$name]['html'] += (array) $dao::makeAttribute($field);
+    }
+    // Delete field if not supported by current db schema (prevents errors when there are pending db updates)
+    if (!isset($supportedFields[$field['name']])) {
+      unset($fields[$name]);
     }
   }
 
index 10ec4ac1ee9c418aae8bba0204f168476490d88e..a0cf296334f90e1d85d38fa5d227d30c0dcb562a 100644 (file)
@@ -963,4 +963,4 @@ INSERT INTO civicrm_navigation
 VALUES
     ( @domainID, CONCAT('civicrm/report/instance/', @instanceID,'&reset=1'), 'Mailing Detail Report', 'Mailing Detail Report', 'administer CiviMail', 'OR', @reportlastID, '1', NULL, @instanceID+2 );
 UPDATE civicrm_report_instance SET navigation_id = LAST_INSERT_ID() WHERE id = @instanceID;
-UPDATE civicrm_domain SET version = '5.27.alpha1';
+UPDATE civicrm_domain SET version = '5.28.alpha1';
index 22183ac8ce485d1e8ab52a67c5c804f7d992a84f..d1eb22eaa5383a8553a8fef5f6038ffc30d75961 100644 (file)
@@ -516,4 +516,30 @@ class CRM_Core_DAOTest extends CiviUnitTestCase {
     $this->fail('String not altered');
   }
 
+  public function testSupportedFields() {
+    // Hack a different db version which will trigger getSupportedFields to filter out newer fields
+    \CRM_Core_DAO::$_dbColumnValueCache['CRM_Core_DAO_Domain']['id'][1]['version'] = '5.26.0';
+
+    $customGroupFields = CRM_Core_DAO_CustomGroup::getSupportedFields();
+    // 'icon' was added in 5.28
+    $this->assertArrayNotHasKey('icon', $customGroupFields);
+
+    // Remove domain version override:
+    \CRM_Core_DAO::$_dbColumnValueCache = NULL;
+
+    $activityFields = CRM_Activity_DAO_Activity::getSupportedFields();
+    // Fields should be indexed by name not unique_name (which is "activity_id")
+    $this->assertEquals('id', $activityFields['id']['name']);
+
+    $customGroupFields = CRM_Core_DAO_CustomGroup::getSupportedFields();
+    $this->assertArrayHasKey('icon', $customGroupFields);
+
+    \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'view all contacts'];
+    $contactFields = CRM_Contact_DAO_Contact::getSupportedFields();
+    $this->assertArrayHasKey('api_key', $contactFields);
+
+    $permissionedContactFields = CRM_Contact_DAO_Contact::getSupportedFields(TRUE);
+    $this->assertArrayNotHasKey('api_key', $permissionedContactFields);
+  }
+
 }