From d574bb2cd96911ccdb88f82b812716746d45d344 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Thu, 13 Oct 2022 15:14:42 +0100 Subject: [PATCH] APIv4 - Enable getFields to report dynamic FKs if a value is supplied for the entity type E.g. many tables have a DFK with a pair of `entity_table` and `entity_id` columns; If you supply a value for `entity_table`, then getFields will output the correct `fk_entity` for the `entity_id` field. This allows the "Assigned Financial Accounts" display to add the financial type in the page title. --- Civi/Api4/Service/Spec/SpecGatherer.php | 32 +++++++++++++++-- .../SearchDisplay/AbstractRunAction.php | 35 +++++++++++++++---- tests/phpunit/api/v4/Action/GetFieldsTest.php | 17 +++++++++ 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/Civi/Api4/Service/Spec/SpecGatherer.php b/Civi/Api4/Service/Spec/SpecGatherer.php index 3a79e2cd70..849f90b758 100644 --- a/Civi/Api4/Service/Spec/SpecGatherer.php +++ b/Civi/Api4/Service/Spec/SpecGatherer.php @@ -46,7 +46,7 @@ class SpecGatherer extends AutoService { // Real entities if (strpos($entity, 'Custom_') !== 0) { - $this->addDAOFields($entity, $action, $specification); + $this->addDAOFields($entity, $action, $specification, $values); if ($includeCustom) { $this->addCustomFields($entity, $specification); } @@ -83,8 +83,9 @@ class SpecGatherer extends AutoService { * @param string $entity * @param string $action * @param \Civi\Api4\Service\Spec\RequestSpec $spec + * @param array $values */ - private function addDAOFields($entity, $action, RequestSpec $spec) { + private function addDAOFields($entity, $action, RequestSpec $spec, array $values) { $DAOFields = $this->getDAOFields($entity); foreach ($DAOFields as $DAOField) { @@ -100,11 +101,38 @@ class SpecGatherer extends AutoService { if ($DAOField['name'] == 'is_active' && empty($DAOField['default'])) { $DAOField['default'] = '1'; } + $this->setDynamicFk($DAOField, $entity, $values); $field = SpecFormatter::arrayToField($DAOField, $entity); $spec->addFieldSpec($field); } } + /** + * Cleverly enables getFields to report dynamic FKs if a value is supplied for the entity type. + * + * E.g. many tables have a DFK with a pair of `entity_table` and `entity_id` columns. + * If you supply a value for `entity_table`, then getFields will output the correct `fk_entity` for the `entity_id` field. + * + * @param array $DAOField + * @param string $entityName + * @param array $values + */ + private function setDynamicFk(array &$DAOField, string $entityName, array $values): void { + if (empty($field['FKClassName']) && $values) { + $bao = CoreUtil::getBAOFromApiName($entityName); + // Check all dynamic FKs for entity for a match with this field and a supplied value + foreach ($bao::getReferenceColumns() ?? [] as $reference) { + if ($reference instanceof \CRM_Core_Reference_Dynamic + && $reference->getReferenceKey() === $DAOField['name'] + && array_key_exists($reference->getTypeColumn(), $values) + ) { + $DAOField['FKClassName'] = \CRM_Core_DAO_AllCoreTables::getClassForTable($values[$reference->getTypeColumn()]); + break; + } + } + } + } + /** * Get custom fields that extend this entity * diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php index 9857025a08..25034eb1be 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php @@ -807,12 +807,16 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { return; } + // Add all filters to the WHERE or HAVING clause foreach ($filters as $key => $value) { $fieldNames = explode(',', $key); if (in_array($key, $allowedFilters, TRUE) || !array_diff($fieldNames, $allowedFilters)) { $this->applyFilter($fieldNames, $value); } - // Filter labels are used to set the page title for drilldown forms + } + // After adding filters, set filter labels + // Filter labels are used to set the page title for drilldown forms + foreach ($filters as $key => $value) { if (in_array($key, $directiveFilters, TRUE)) { $this->addFilterLabel($key, $value); } @@ -1196,15 +1200,19 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { if ($field['name'] === $idField) { $field['fk_entity'] = $field['entity']; } - if (!empty($field['options'])) { - $options = civicrm_api4($field['entity'], 'getFields', [ + else { + // Reload field with options and any dynamic FKs based on values (e.g. entity_table) + $field = civicrm_api4($field['entity'], 'getFields', [ 'loadOptions' => TRUE, 'checkPermissions' => FALSE, + 'values' => $this->getWhereClauseValues(), 'where' => [['name', '=', $field['name']]], - ])->first()['options'] ?? []; + ])->first(); + } + if (!empty($field['options'])) { foreach ((array) $value as $val) { - if (!empty($options[$val])) { - $this->filterLabels[] = $options[$val]; + if (!empty($field['options'][$val])) { + $this->filterLabels[] = $field['options'][$val]; } } } @@ -1226,6 +1234,21 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { } } + /** + * Returns any key/value pairs in the WHERE clause (those using the `=` operator) + * + * @return array + */ + private function getWhereClauseValues(): array { + $values = []; + foreach ($this->_apiParams['where'] as $clause) { + if (count($clause) > 2 && $clause[1] === '=' && empty($clause[3]) && strpos('(', $clause[0]) === FALSE) { + $values[$clause[0]] = $clause[2]; + } + } + return $values; + } + /** * Loads display if not already an array */ diff --git a/tests/phpunit/api/v4/Action/GetFieldsTest.php b/tests/phpunit/api/v4/Action/GetFieldsTest.php index d8f653f71b..840e9ceebb 100644 --- a/tests/phpunit/api/v4/Action/GetFieldsTest.php +++ b/tests/phpunit/api/v4/Action/GetFieldsTest.php @@ -24,6 +24,7 @@ use Civi\Api4\Activity; use Civi\Api4\Campaign; use Civi\Api4\Contact; use Civi\Api4\Contribution; +use Civi\Api4\EntityTag; use Civi\Test\TransactionalInterface; /** @@ -122,4 +123,20 @@ class GetFieldsTest extends Api4TestBase implements TransactionalInterface { $this->assertEquals(['name', 'label', 'description', 'color'], $actFields['tags']['suffixes']); } + public function testDynamicFks() { + $tagFields = EntityTag::getFields(FALSE) + ->execute()->indexBy('name'); + $this->assertEmpty($tagFields['entity_id']['fk_entity']); + + $tagFields = EntityTag::getFields(FALSE) + ->addValue('entity_table', 'civicrm_activity') + ->execute()->indexBy('name'); + $this->assertEquals('Activity', $tagFields['entity_id']['fk_entity']); + + $tagFields = EntityTag::getFields(FALSE) + ->addValue('entity_table:name', 'Contact') + ->execute()->indexBy('name'); + $this->assertEquals('Contact', $tagFields['entity_id']['fk_entity']); + } + } -- 2.25.1