From: Coleman Watts Date: Thu, 16 Jan 2020 16:58:22 +0000 (-0500) Subject: Api4 - format output consistently across get/create/update. X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=2929a8fb2a481f262f9793885d5d7a47ed4d8328;p=civicrm-core.git Api4 - format output consistently across get/create/update. Previously output from a DAO-based Get would be run through an unserializer/type-converter. This applies that same conversion to DAO-based create/update ops. --- diff --git a/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php b/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php index 6d7cd949f0..00b2cc9439 100644 --- a/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php +++ b/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php @@ -25,6 +25,7 @@ use Civi\Api4\Event\Events; use Civi\Api4\Event\PostSelectQueryEvent; use Civi\Api4\Query\Api4SelectQuery; use Civi\Api4\Utils\ArrayInsertionUtil; +use Civi\Api4\Utils\FormattingUtil; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -32,8 +33,6 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; */ class PostSelectQuerySubscriber implements EventSubscriberInterface { - private $contactFieldsToRemove = []; - /** * @inheritdoc */ @@ -62,7 +61,7 @@ class PostSelectQuerySubscriber implements EventSubscriberInterface { return $results; } - $this->formatFieldValues($results, $query->getApiFieldSpec(), $query->getEntity()); + FormattingUtil::formatOutputValues($results, $query->getApiFieldSpec(), $query->getEntity()); // Group the selects to avoid queries for each field $groupedSelects = $this->getNtoManyJoinSelects($query); @@ -99,60 +98,8 @@ class PostSelectQuerySubscriber implements EventSubscriberInterface { $fields[array_pop($name)] = $field->toArray(); } if ($fields) { - $this->formatFieldValues($joinResults, $fields, $join->getEntity()); - } - } - - /** - * Unserialize values and convert to correct type - * - * @param array $results - * @param array $fields - * @param string $entity - */ - protected function formatFieldValues(&$results, $fields, $entity) { - foreach ($results as &$result) { - // Remove inapplicable contact fields - if ($entity === 'Contact' && !empty($result['contact_type'])) { - \CRM_Utils_Array::remove($result, $this->contactFieldsToRemove($result['contact_type'])); - } - foreach ($result as $field => $value) { - $dataType = $fields[$field]['data_type'] ?? NULL; - if (!empty($fields[$field]['serialize'])) { - if (is_string($value)) { - $result[$field] = $value = \CRM_Core_DAO::unSerializeField($value, $fields[$field]['serialize']); - foreach ($value as $key => $val) { - $result[$field][$key] = $this->convertDataType($val, $dataType); - } - } - } - else { - $result[$field] = $this->convertDataType($value, $dataType); - } - } - } - } - - /** - * @param mixed $value - * @param string $dataType - * @return mixed - */ - protected function convertDataType($value, $dataType) { - if (isset($value)) { - switch ($dataType) { - case 'Boolean': - return (bool) $value; - - case 'Integer': - return (int) $value; - - case 'Money': - case 'Float': - return (float) $value; - } + FormattingUtil::formatOutputValues($joinResults, $fields, $join->getEntity()); } - return $value; } /** @@ -382,20 +329,4 @@ class PostSelectQuerySubscriber implements EventSubscriberInterface { return $subResults; } - /** - * @param string $contactType - * @return array - */ - private function contactFieldsToRemove($contactType) { - if (!isset($this->contactFieldsToRemove[$contactType])) { - $this->contactFieldsToRemove[$contactType] = []; - foreach (\CRM_Contact_DAO_Contact::fields() as $field) { - if (!empty($field['contactType']) && $field['contactType'] != $contactType) { - $this->contactFieldsToRemove[$contactType][] = $field['name']; - } - } - } - return $this->contactFieldsToRemove[$contactType]; - } - } diff --git a/Civi/Api4/Generic/Traits/DAOActionTrait.php b/Civi/Api4/Generic/Traits/DAOActionTrait.php index 3748daa4ab..941056a050 100644 --- a/Civi/Api4/Generic/Traits/DAOActionTrait.php +++ b/Civi/Api4/Generic/Traits/DAOActionTrait.php @@ -20,7 +20,6 @@ namespace Civi\Api4\Generic\Traits; -use CRM_Utils_Array as UtilsArray; use Civi\Api4\Utils\FormattingUtil; use Civi\Api4\Query\Api4SelectQuery; @@ -129,7 +128,7 @@ trait DAOActionTrait { $result = []; foreach ($items as $item) { - $entityId = UtilsArray::value('id', $item); + $entityId = $item['id'] ?? NULL; FormattingUtil::formatWriteParams($item, $this->getEntityName(), $this->entityFields()); $this->formatCustomParams($item, $entityId); $item['check_permissions'] = $this->getCheckPermissions(); @@ -167,6 +166,7 @@ trait DAOActionTrait { $result[] = $resultArray; } + FormattingUtil::formatOutputValues($result, $this->entityFields(), $this->getEntityName()); return $result; } @@ -180,7 +180,7 @@ trait DAOActionTrait { $baoName = $this->getBaoName(); $hook = empty($params['id']) ? 'create' : 'edit'; - \CRM_Utils_Hook::pre($hook, $this->getEntityName(), UtilsArray::value('id', $params), $params); + \CRM_Utils_Hook::pre($hook, $this->getEntityName(), $params['id'] ?? NULL, $params); /** @var \CRM_Core_DAO $instance */ $instance = new $baoName(); $instance->copyValues($params, TRUE); diff --git a/Civi/Api4/Query/Api4SelectQuery.php b/Civi/Api4/Query/Api4SelectQuery.php index 80ecaf55cf..3fb86d3607 100644 --- a/Civi/Api4/Query/Api4SelectQuery.php +++ b/Civi/Api4/Query/Api4SelectQuery.php @@ -280,7 +280,7 @@ class Api4SelectQuery extends SelectQuery { throw new \API_Exception("Invalid field '$key' in where clause."); } - FormattingUtil::formatValue($value, $fieldSpec, $this->getEntity()); + FormattingUtil::formatInputValue($value, $fieldSpec, $this->getEntity()); $sql_clause = \CRM_Core_DAO::createSQLFilter("`$table_name`.`$column_name`", [$operator => $value]); if ($sql_clause === NULL) { diff --git a/Civi/Api4/Utils/FormattingUtil.php b/Civi/Api4/Utils/FormattingUtil.php index 21c3d4e6b5..b4a55d493c 100644 --- a/Civi/Api4/Utils/FormattingUtil.php +++ b/Civi/Api4/Utils/FormattingUtil.php @@ -21,8 +21,6 @@ namespace Civi\Api4\Utils; -use CRM_Utils_Array as UtilsArray; - require_once 'api/v3/utils.php'; class FormattingUtil { @@ -43,7 +41,7 @@ class FormattingUtil { if ($value === 'null') { $value = 'Null'; } - FormattingUtil::formatValue($value, $field, $entity); + self::formatInputValue($value, $field, $entity); // Ensure we have an array for serialized fields if (!empty($field['serialize'] && !is_array($value))) { $value = (array) $value; @@ -80,18 +78,14 @@ class FormattingUtil { * Ex: 'Contact', 'Domain' * @throws \API_Exception */ - public static function formatValue(&$value, $fieldSpec, $entity) { + public static function formatInputValue(&$value, $fieldSpec, $entity) { if (is_array($value)) { foreach ($value as &$val) { - self::formatValue($val, $fieldSpec, $entity); + self::formatInputValue($val, $fieldSpec, $entity); } return; } - $fk = UtilsArray::value('fk_entity', $fieldSpec); - if ($fieldSpec['name'] == 'id') { - $fk = $entity; - } - $dataType = UtilsArray::value('data_type', $fieldSpec); + $fk = $fieldSpec['name'] == 'id' ? $entity : $fieldSpec['fk_entity'] ?? NULL; if ($fk === 'Domain' && $value === 'current_domain') { $value = \CRM_Core_Config::domainID(); @@ -104,7 +98,7 @@ class FormattingUtil { } } - switch ($dataType) { + switch ($fieldSpec['data_type'] ?? NULL) { case 'Timestamp': $value = date('Y-m-d H:i:s', strtotime($value)); break; @@ -120,4 +114,73 @@ class FormattingUtil { } } + /** + * Unserialize raw DAO values and convert to correct type + * + * @param array $results + * @param array $fields + * @param string $entity + * @throws \CRM_Core_Exception + */ + public static function formatOutputValues(&$results, $fields, $entity) { + foreach ($results as &$result) { + // Remove inapplicable contact fields + if ($entity === 'Contact' && !empty($result['contact_type'])) { + \CRM_Utils_Array::remove($result, self::contactFieldsToRemove($result['contact_type'])); + } + foreach ($result as $field => $value) { + $dataType = $fields[$field]['data_type'] ?? ($field == 'id' ? 'Integer' : NULL); + if (!empty($fields[$field]['serialize'])) { + if (is_string($value)) { + $result[$field] = $value = \CRM_Core_DAO::unSerializeField($value, $fields[$field]['serialize']); + foreach ($value as $key => $val) { + $result[$field][$key] = self::convertDataType($val, $dataType); + } + } + } + else { + $result[$field] = self::convertDataType($value, $dataType); + } + } + } + } + + /** + * @param mixed $value + * @param string $dataType + * @return mixed + */ + public static function convertDataType($value, $dataType) { + if (isset($value)) { + switch ($dataType) { + case 'Boolean': + return (bool) $value; + + case 'Integer': + return (int) $value; + + case 'Money': + case 'Float': + return (float) $value; + } + } + return $value; + } + + /** + * @param string $contactType + * @return array + */ + public static function contactFieldsToRemove($contactType) { + if (!isset(\Civi::$statics[__CLASS__][__FUNCTION__][$contactType])) { + \Civi::$statics[__CLASS__][__FUNCTION__][$contactType] = []; + foreach (\CRM_Contact_DAO_Contact::fields() as $field) { + if (!empty($field['contactType']) && $field['contactType'] != $contactType) { + \Civi::$statics[__CLASS__][__FUNCTION__][$contactType][] = $field['name']; + } + } + } + return \Civi::$statics[__CLASS__][__FUNCTION__][$contactType]; + } + } diff --git a/ang/api4Explorer/Explorer.js b/ang/api4Explorer/Explorer.js index 2bfc82c9e4..cd5d61a2d9 100644 --- a/ang/api4Explorer/Explorer.js +++ b/ang/api4Explorer/Explorer.js @@ -706,8 +706,8 @@ }); } else if (dataType === 'Boolean') { $el.attr('placeholder', ts('- select -')).crmSelect2({allowClear: false, multiple: multi, placeholder: ts('- select -'), data: [ - {id: '1', text: ts('Yes')}, - {id: '0', text: ts('No')} + {id: 'true', text: ts('Yes')}, + {id: 'false', text: ts('No')} ]}); } } else if (dataType === 'Integer' && !multi) { diff --git a/tests/phpunit/api/v3/UFGroupTest.php b/tests/phpunit/api/v3/UFGroupTest.php index 8d10ba5cf3..be06e412fe 100644 --- a/tests/phpunit/api/v3/UFGroupTest.php +++ b/tests/phpunit/api/v3/UFGroupTest.php @@ -115,8 +115,11 @@ class api_v3_UFGroupTest extends CiviUnitTestCase { if ($key == 'add_contact_to_group' or $key == 'group') { continue; } - $expected = $this->params[$key]; $received = $result['values'][$result['id']][$key]; + if ($key == 'group_type' && $version == 4) { + $received = implode(',', $received); + } + $expected = $this->params[$key]; $this->assertEquals($expected, $received, "The string '$received' does not equal '$expected' for key '$key' in line " . __LINE__); } } @@ -165,7 +168,11 @@ class api_v3_UFGroupTest extends CiviUnitTestCase { if ($key == 'add_contact_to_group' or $key == 'group') { continue; } - $this->assertEquals($result['values'][$result['id']][$key], $params[$key], $key . " doesn't match " . $value); + $received = $result['values'][$result['id']][$key]; + if ($key == 'group_type' && $version == 4) { + $received = implode(',', $received); + } + $this->assertEquals($received, $params[$key], $key . " doesn't match " . $value); } $this->assertEquals($result['values'][$this->_ufGroupId]['add_to_group_id'], $params['add_contact_to_group']);