From: Coleman Watts Date: Wed, 3 Feb 2021 16:48:26 +0000 (-0500) Subject: APIv4 - Enable getFields to find fields across implicit FK joins X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=37d2f985c9b50ab716fdf0538df016f82569907f;p=civicrm-core.git APIv4 - Enable getFields to find fields across implicit FK joins Now it is possible to retrieve field metadata for a joined entity --- diff --git a/Civi/Api4/Generic/DAOGetFieldsAction.php b/Civi/Api4/Generic/DAOGetFieldsAction.php index 631e91652f..1ad7c3d3fb 100644 --- a/Civi/Api4/Generic/DAOGetFieldsAction.php +++ b/Civi/Api4/Generic/DAOGetFieldsAction.php @@ -40,15 +40,48 @@ class DAOGetFieldsAction extends BasicGetFieldsAction { * @return array */ protected function getRecords() { - $fields = $this->_itemsToGet('name'); + $fieldsToGet = $this->_itemsToGet('name'); /** @var \Civi\Api4\Service\Spec\SpecGatherer $gatherer */ $gatherer = \Civi::container()->get('spec_gatherer'); - // Any fields name with a dot in it is custom - if ($fields) { - $this->includeCustom = strpos(implode('', $fields), '.') !== FALSE; + // Any fields name with a dot in it is either custom or an implicit join + if ($fieldsToGet) { + $this->includeCustom = strpos(implode('', $fieldsToGet), '.') !== FALSE; } $spec = $gatherer->getSpec($this->getEntityName(), $this->getAction(), $this->includeCustom, $this->values); - return SpecFormatter::specToArray($spec->getFields($fields), $this->loadOptions, $this->values); + $fields = SpecFormatter::specToArray($spec->getFields($fieldsToGet), $this->loadOptions, $this->values); + foreach ($fieldsToGet ?? [] as $fieldName) { + if (empty($fields[$fieldName]) && strpos($fieldName, '.') !== FALSE) { + $fkField = $this->getFkFieldSpec($fieldName, $fields); + if ($fkField) { + $fkField['name'] = $fieldName; + $fields[] = $fkField; + } + } + } + return $fields; + } + + /** + * @param string $fieldName + * @param array $fields + * @return array|null + * @throws \API_Exception + */ + private function getFkFieldSpec($fieldName, $fields) { + $fieldPath = explode('.', $fieldName); + // Search for the first segment alone plus the first and second + // No field in the schema contains more than one dot in its name. + $searchPaths = [$fieldPath[0], $fieldPath[0] . '.' . $fieldPath[1]]; + $fkFieldName = array_intersect($searchPaths, array_keys($fields))[0] ?? NULL; + if ($fkFieldName && !empty($fields[$fkFieldName]['fk_entity'])) { + $newFieldName = substr($fieldName, 1 + strlen($fkFieldName)); + return civicrm_api4($fields[$fkFieldName]['fk_entity'], 'getFields', [ + 'checkPermissions' => $this->checkPermissions, + 'where' => [['name', '=', $newFieldName]], + 'loadOptions' => $this->loadOptions, + 'action' => $this->action, + ])->first(); + } } public function fields() { diff --git a/Civi/Api4/Service/Spec/RequestSpec.php b/Civi/Api4/Service/Spec/RequestSpec.php index f75a1f9e1c..dbf460a3ba 100644 --- a/Civi/Api4/Service/Spec/RequestSpec.php +++ b/Civi/Api4/Service/Spec/RequestSpec.php @@ -101,13 +101,15 @@ class RequestSpec { if (!$fieldNames) { return $this->fields; } - $fields = []; - foreach ($this->fields as $field) { - if (in_array($field->getName(), $fieldNames)) { - $fields[] = $field; + // Return all exact matches plus partial matches (to support retrieving fk fields) + return array_filter($this->fields, function($field) use($fieldNames) { + foreach ($fieldNames as $fieldName) { + if (strpos($fieldName, $field->getName()) === 0) { + return TRUE; + } } - } - return $fields; + return FALSE; + }); } /** diff --git a/tests/phpunit/api/v4/Action/GetExtraFieldsTest.php b/tests/phpunit/api/v4/Action/GetExtraFieldsTest.php index 0ac148059d..af4fe7d3ee 100644 --- a/tests/phpunit/api/v4/Action/GetExtraFieldsTest.php +++ b/tests/phpunit/api/v4/Action/GetExtraFieldsTest.php @@ -64,4 +64,18 @@ class GetExtraFieldsTest extends UnitTestCase { $this->assertContains('Alberta', $caOptions['options']); } + public function testGetFkFields() { + $fields = \Civi\Api4\Participant::getFields() + ->setLoadOptions(TRUE) + ->addWhere('name', 'IN', ['event_id', 'event_id.created_id', 'contact_id.gender_id', 'event_id.created_id.sort_name']) + ->execute() + ->indexBy('name'); + + $this->assertCount(4, $fields); + $this->assertEquals('Participant', $fields['event_id']['entity']); + $this->assertEquals('Event', $fields['event_id.created_id']['entity']); + $this->assertEquals('Contact', $fields['event_id.created_id.sort_name']['entity']); + $this->assertGreaterThan(1, count($fields['contact_id.gender_id']['options'])); + } + }