*
* Generated from xml/schema/CRM/Pledge/Pledge.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:de226da85d80eda7f8ffef798215ad2a)
+ * (GenCodeChecksum:91fbbe8481e26df491af63fd07594f20)
*/
/**
'localizable' => 0,
'html' => [
'type' => 'Select',
+ 'label' => ts("Status"),
],
'pseudoconstant' => [
'optionGroupName' => 'pledge_status',
* @see https://docs.civicrm.org/user/en/latest/organising-your-data/contacts/
* @searchable primary
* @orderBy sort_name
+ * @searchFields sort_name
* @iconField contact_sub_type:icon,contact_type:icon
* @since 5.19
* @package Civi\Api4
'name' => 'label_field',
'description' => 'Field to show when displaying a record',
],
+ [
+ 'name' => 'search_fields',
+ 'data_type' => 'Array',
+ 'description' => 'Fields to show in search context',
+ ],
[
'name' => 'icon_field',
'data_type' => 'Array',
$info[$field['name']] = $val;
}
}
-
+ // search_fields defaults to label_field
+ if (empty($info['search_fields']) && !empty($info['label_field'])) {
+ $info['search_fields'] = [$info['label_field']];
+ }
if ($dao) {
$info['description'] = $dao::getEntityDescription() ?? $info['description'] ?? NULL;
}
*
* @see https://docs.civicrm.org/user/en/latest/organising-your-data/relationships/
* @searchable none
+ * @searchFields contact_id_a.sort_name,relationship_type_id.label_a_b,contact_id_b.sort_name
* @since 5.19
* @package Civi\Api4
*/
* RelationshipCache - readonly table to facilitate joining and finding contacts by relationship.
*
* @searchable secondary
+ * @searchFields near_contact_id.sort_name,near_relation:label,far_contact_id.sort_name
* @see \Civi\Api4\Relationship
* @ui_join_filters near_relation
* @since 5.29
'id',
'subject',
'activity_date_time',
- 'Activity_ActivityContact_Contact_01.display_name',
+ 'Activity_ActivityContact_Contact_01.sort_name',
'activity_type_id:label',
],
'orderBy' => [],
[$entity, $contactAlias] = explode(' AS ', $join[0]);
if ($entity === 'Contact') {
array_unshift($e->display['settings']['sort'], ["$contactAlias.sort_name", 'ASC']);
- $e->display['settings']['columns'][0]['rewrite'] = "[$contactAlias.display_name] - [subject]";
- $e->display['settings']['columns'][0]['empty_value'] = "[$contactAlias.display_name] (" . ts('no subject') . ')';
+ $e->display['settings']['columns'][0]['rewrite'] = "[$contactAlias.sort_name] - [subject]";
+ $e->display['settings']['columns'][0]['empty_value'] = "[$contactAlias.sort_name] (" . ts('no subject') . ')';
break;
}
}
'select' => [
'id',
'subject',
- 'Case_CaseContact_Contact_01.display_name',
+ 'Case_CaseContact_Contact_01.sort_name',
'case_type_id:label',
'status_id:label',
'start_date',
[$entity, $contactAlias] = explode(' AS ', $join[0]);
if ($entity === 'Contact') {
array_unshift($e->display['settings']['sort'], ["$contactAlias.sort_name", 'ASC']);
- $e->display['settings']['columns'][0]['rewrite'] = "[$contactAlias.display_name] - [subject]";
- $e->display['settings']['columns'][0]['empty_value'] = "[$contactAlias.display_name] (" . ts('no subject') . ')';
+ $e->display['settings']['columns'][0]['rewrite'] = "[$contactAlias.sort_name] - [subject]";
+ $e->display['settings']['columns'][0]['empty_value'] = "[$contactAlias.sort_name] (" . ts('no subject') . ')';
break;
}
}
'columns' => [
[
'type' => 'field',
- 'key' => 'display_name',
+ 'key' => 'sort_name',
'icons' => [
['field' => 'contact_sub_type:icon'],
['field' => 'contact_type:icon'],
'version' => 4,
'select' => [
'id',
- 'contact_id.display_name',
+ 'contact_id.sort_name',
'total_amount',
'receive_date',
'financial_type_id:label',
'columns' => [
[
'type' => 'field',
- 'key' => 'contact_id.display_name',
- 'rewrite' => '[contact_id.display_name] - [total_amount]',
+ 'key' => 'contact_id.sort_name',
+ 'rewrite' => '[contact_id.sort_name] - [total_amount]',
],
[
'type' => 'field',
'version' => 4,
'select' => [
'id',
- 'contact_id.display_name',
+ 'contact_id.sort_name',
'frequency_unit:label',
'frequency_interval',
'amount',
'columns' => [
[
'type' => 'field',
- 'key' => 'contact_id.display_name',
- 'rewrite' => '[contact_id.display_name] - [amount]',
+ 'key' => 'contact_id.sort_name',
+ 'rewrite' => '[contact_id.sort_name] - [amount]',
],
[
'type' => 'field',
+++ /dev/null
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved. |
- | |
- | This work is published under the GNU AGPLv3 license with some |
- | permitted exceptions and without any warranty. For full license |
- | and copyright information, see https://civicrm.org/licensing |
- +--------------------------------------------------------------------+
- */
-
-namespace Civi\Api4\Service\Autocomplete;
-
-use Civi\Core\Event\GenericHookEvent;
-use Civi\Core\HookInterface;
-
-/**
- * @service
- * @internal
- */
-class ParticipantAutocompleteProvider extends \Civi\Core\Service\AutoService implements HookInterface {
-
- /**
- * Provide default SearchDisplay for Participant autocompletes
- *
- * @param \Civi\Core\Event\GenericHookEvent $e
- */
- public static function on_civi_search_defaultDisplay(GenericHookEvent $e) {
- if ($e->display['settings'] || $e->display['type'] !== 'autocomplete' || $e->savedSearch['api_entity'] !== 'Participant') {
- return;
- }
- $e->display['settings'] = [
- 'sort' => [
- ['contact_id.sort_name', 'ASC'],
- ['event_id.title', 'ASC'],
- ],
- 'columns' => [
- [
- 'type' => 'field',
- 'key' => 'contact_id.display_name',
- 'rewrite' => '[contact_id.display_name] - [event_id.title]',
- ],
- [
- 'type' => 'field',
- 'key' => 'role_id:label',
- 'rewrite' => '#[id] [role_id:label]',
- ],
- [
- 'type' => 'field',
- 'key' => 'status_id:label',
- ],
- ],
- ];
- }
-
-}
'version' => 4,
'select' => [
'id',
- 'contact_id.display_name',
+ 'contact_id.sort_name',
'amount',
'start_date',
'end_date',
'columns' => [
[
'type' => 'field',
- 'key' => 'contact_id.display_name',
- 'rewrite' => '[contact_id.display_name] - [amount]',
+ 'key' => 'contact_id.sort_name',
+ 'rewrite' => '[contact_id.sort_name] - [amount]',
],
[
'type' => 'field',
[
'type' => 'field',
'key' => 'relationship_type_id.label_a_b',
- 'rewrite' => '[contact_id_a.display_name] [relationship_type_id.label_a_b] [contact_id_b.display_name]',
+ 'rewrite' => '[contact_id_a.sort_name] [relationship_type_id.label_a_b] [contact_id_b.sort_name]',
],
[
'type' => 'field',
return self::getInfoItem($entityName, 'primary_key')[0] ?? 'id';
}
+ /**
+ * Get name of field(s) to display in search context
+ * @param string $entityName
+ * @return array
+ */
+ public static function getSearchFields(string $entityName): array {
+ return self::getInfoItem($entityName, 'search_fields') ?: [];
+ }
+
/**
* Get table name of given entity
*
->execute();
$this->assertCount(2, $result);
- $this->assertEquals('A ' . $lastName, $result[0]['label']);
- $this->assertEquals('B ' . $lastName, $result[1]['label']);
+ $this->assertEquals($lastName . ', A', $result[0]['label']);
+ $this->assertEquals($lastName . ', B', $result[1]['label']);
// Ensure form validates submission, restricting it to contacts A & B
$values = [
->execute();
$this->assertCount(2, $result);
- $this->assertEquals('A ' . $lastName, $result[0]['label']);
- $this->assertEquals('B ' . $lastName, $result[1]['label']);
+ $this->assertEquals($lastName . ', A', $result[0]['label']);
+ $this->assertEquals($lastName . ', B', $result[1]['label']);
// Ensure form validates submission, restricting it to contacts A & B
$values = [
->execute();
$this->assertCount(2, $result);
- $this->assertEquals('A ' . $lastName, $result[0]['label']);
- $this->assertEquals('C ' . $lastName, $result[1]['label']);
+ $this->assertEquals($lastName . ', A', $result[0]['label']);
+ $this->assertEquals($lastName . ', C', $result[1]['label']);
// Ensure form validates submission, restricting it to contacts A & C
$values = [
* Contribution entity.
*
* @searchable primary
+ * @searchFields contact_id.sort_name,total_amount
* @since 5.19
* @package Civi\Api4
*/
* Participant entity, stores the participation record of a contact in an event.
*
* @searchable primary
+ * @searchFields contact_id.sort_name,event_id.title
* @since 5.19
* @package Civi\Api4
*/
* Membership entity.
*
* @searchable primary
+ * @searchFields contact_id.sort_name
* @since 5.42
* @package Civi\Api4
*/
*
* @see https://docs.civicrm.org/user/en/latest/pledges/what-is-civipledge/
* @searchable primary
+ * @searchFields contact_id.display_name,amount
* @since 5.35
* @package Civi\Api4
*/
* @see https://docs.civicrm.org/user/en/latest/grants/what-is-civigrant/
*
* @searchable primary
+ * @searchFields contact_id.sort_name,grant_type_id:label
* @since 5.33
* @package Civi\Api4
*/
+++ /dev/null
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved. |
- | |
- | This work is published under the GNU AGPLv3 license with some |
- | permitted exceptions and without any warranty. For full license |
- | and copyright information, see https://civicrm.org/licensing |
- +--------------------------------------------------------------------+
- */
-
-namespace Civi\Api4\Service\Autocomplete;
-
-use Civi\Core\Event\GenericHookEvent;
-use Civi\Core\HookInterface;
-
-/**
- * @service
- * @internal
- */
-class GrantAutocompleteProvider extends \Civi\Core\Service\AutoService implements HookInterface {
-
- /**
- * Provide default SavedSearch for Grant autocompletes
- *
- * @param \Civi\Core\Event\GenericHookEvent $e
- */
- public static function on_civi_search_autocompleteDefault(GenericHookEvent $e) {
- if (!is_array($e->savedSearch) || $e->savedSearch['api_entity'] !== 'Grant') {
- return;
- }
- $e->savedSearch['api_params'] = [
- 'version' => 4,
- 'select' => [
- 'id',
- 'contact_id.display_name',
- 'grant_type_id:label',
- 'financial_type_id:label',
- 'status_id:label',
- ],
- 'orderBy' => [],
- 'where' => [],
- 'groupBy' => [],
- 'join' => [],
- 'having' => [],
- ];
- }
-
- /**
- * Provide default SearchDisplay for Grant autocompletes
- *
- * @param \Civi\Core\Event\GenericHookEvent $e
- */
- public static function on_civi_search_defaultDisplay(GenericHookEvent $e) {
- if ($e->display['settings'] || $e->display['type'] !== 'autocomplete' || $e->savedSearch['api_entity'] !== 'Grant') {
- return;
- }
- $e->display['settings'] = [
- 'sort' => [
- ['contact_id.sort_name', 'ASC'],
- ['application_received_date', 'DESC'],
- ],
- 'columns' => [
- [
- 'type' => 'field',
- 'key' => 'contact_id.display_name',
- 'rewrite' => '[contact_id.display_name] - [grant_type_id:label]',
- ],
- [
- 'type' => 'field',
- 'key' => 'financial_type_id:label',
- 'rewrite' => '#[id] [status_id:label]',
- ],
- [
- 'type' => 'field',
- 'key' => 'financial_type_id:label',
- ],
- ],
- ];
- }
-
-}
if ($clause['expr'] instanceof SqlField || $clause['expr'] instanceof SqlFunctionGROUP_CONCAT) {
$field = \CRM_Utils_Array::first($clause['fields'] ?? []);
if ($field &&
- CoreUtil::getInfoItem($field['entity'], 'label_field') === $field['name'] &&
+ in_array($field['name'], array_merge(CoreUtil::getSearchFields($field['entity']), [CoreUtil::getInfoItem($field['entity'], 'label_field')]), TRUE) &&
!empty(CoreUtil::getInfoItem($field['entity'], 'paths')['view'])
) {
$col['link'] = [
throw new \CRM_Core_Exception("Entity name is required to get autocomplete default display.");
}
$idField = CoreUtil::getIdFieldName($entityName);
- $labelField = CoreUtil::getInfoItem($entityName, 'label_field');
- if (!$labelField) {
+ $searchFields = CoreUtil::getSearchFields($entityName);
+ if (!$searchFields) {
throw new \CRM_Core_Exception("Entity $entityName has no default label field.");
}
$apiGet = Request::create($entityName, 'get', ['version' => 4]);
$fields = $apiGet->entityFields();
- $columns = [$labelField];
+ $columns = array_slice($searchFields, 0, 1);
// Add grouping fields like "event_type_id" in the description
- $grouping = (array) (CoreUtil::getCustomGroupExtends($entityName)['grouping'] ?? []);
+ $grouping = (array) (CoreUtil::getCustomGroupExtends($entityName)['grouping'] ?? ['financial_type_id']);
foreach ($grouping as $fieldName) {
- $columns[] = "$fieldName:label";
+ if (!empty($fields[$fieldName]['options']) && !in_array("$fieldName:label", $searchFields)) {
+ $columns[] = "$fieldName:label";
+ }
+ }
+ $statusField = $fields['status_id'] ?? $fields[strtolower($entityName) . '_status_id'] ?? NULL;
+ if (!empty($statusField['options']) && !in_array("{$statusField['name']}:label", $searchFields)) {
+ $columns[] = "{$statusField['name']}:label";
}
if (isset($fields['description'])) {
$columns[] = 'description';
'key' => $columnField,
];
}
+ if (count($searchFields) > 1) {
+ $e->display['settings']['columns'][0]['rewrite'] = '[' . implode('] - [', $searchFields) . ']';
+ }
// Include entity id on the second line
$e->display['settings']['columns'][1] = [
'type' => 'field',
- 'key' => $idField,
+ 'key' => $columns[1] ?? $idField,
'rewrite' => "#[$idField]" . (isset($columns[1]) ? " [$columns[1]]" : ''),
+ 'empty_value' => "#[$idField]",
];
// Default icons
* @return array
*/
protected static function getDefaultSort($entityName) {
- $sortField = CoreUtil::getInfoItem($entityName, 'order_by') ?: CoreUtil::getInfoItem($entityName, 'label_field');
- return $sortField ? [[$sortField, 'ASC']] : [];
+ $result = [];
+ $sortFields = (array) (CoreUtil::getInfoItem($entityName, 'order_by') ?: CoreUtil::getSearchFields($entityName));
+ foreach ($sortFields as $sortField) {
+ $result[] = [$sortField, 'ASC'];
+ }
+ return $result;
}
}
public static function getSchema(): array {
$schema = [];
$entities = Entity::get()
- ->addSelect('name', 'title', 'title_plural', 'bridge_title', 'type', 'primary_key', 'description', 'label_field', 'icon', 'dao', 'bridge', 'ui_join_filters', 'searchable', 'order_by')
+ ->addSelect('name', 'title', 'title_plural', 'bridge_title', 'type', 'primary_key', 'description', 'label_field', 'search_fields', 'icon', 'dao', 'bridge', 'ui_join_filters', 'searchable', 'order_by')
->addWhere('searchable', '!=', 'none')
->addOrderBy('title_plural')
->setChain([
'select' => ['name', 'title', 'label', 'description', 'type', 'options', 'input_type', 'input_attrs', 'data_type', 'serialize', 'entity', 'fk_entity', 'readonly', 'operators', 'suffixes', 'nullable'],
'where' => [['deprecated', '=', FALSE], ['name', 'NOT IN', ['api_key', 'hash']]],
'orderBy' => ['label'],
- ]);
+ ])->indexBy('name');
}
catch (\CRM_Core_Exception $e) {
\Civi::log()->warning('Entity could not be loaded', ['entity' => $entity['name']]);
}
$entity['fields'][] = $field;
}
+ $defaultColumns = CoreUtil::getSearchFields($entity['name']);
+ // Add grouping fields like "event_type_id" + status_id + description if available
+ $grouping = (array) (CoreUtil::getCustomGroupExtends($entity['name'])['grouping'] ?? ['financial_type_id']);
+ foreach ($grouping as $fieldName) {
+ if (!empty($getFields[$fieldName]['options'])) {
+ $defaultColumns[] = "$fieldName:label";
+ }
+ }
+ $statusField = $getFields['status_id'] ?? $getFields[strtolower($entity['name']) . '_status_id'] ?? NULL;
+ if (!empty($statusField['options'])) {
+ $defaultColumns[] = $statusField['name'] . ':label';
+ }
+ if (isset($getFields['description'])) {
+ $defaultColumns[] = 'description';
+ }
+ $entity['default_columns'] = array_values(array_unique($defaultColumns));
$params = $entity['get'][0];
// Entity must support at least these params or it is too weird for search kit
if (!array_diff(['select', 'where', 'orderBy', 'limit', 'offset'], array_keys($params))) {
foreach ($schema as &$entity) {
if ($entity['searchable'] !== 'bridge') {
foreach (array_reverse($entity['fields'] ?? [], TRUE) as $index => $field) {
- if (!empty($field['fk_entity']) && !$field['options'] && !$field['suffixes'] && !empty($schema[$field['fk_entity']]['label_field'])) {
- $isCustom = strpos($field['name'], '.');
- // Custom fields: append "Contact ID" etc. to original field label
- if ($isCustom) {
- $idField = array_column($schema[$field['fk_entity']]['fields'], NULL, 'name')['id'];
- $entity['fields'][$index]['label'] .= ' ' . $idField['title'];
- }
- // DAO fields: use title instead of label since it represents the id (title usually ends in ID but label does not)
- else {
- $entity['fields'][$index]['label'] = $field['title'];
+ if (!empty($field['fk_entity']) && !$field['options'] && !$field['suffixes'] && !empty($schema[$field['fk_entity']]['search_fields'])) {
+ $labelFields = array_unique(array_merge($schema[$field['fk_entity']]['search_fields'], (array) ($schema[$field['fk_entity']]['label_field'] ?? [])));
+ foreach ($labelFields as $labelField) {
+ $isCustom = strpos($field['name'], '.');
+ // Custom fields: append "Contact ID" etc. to original field label
+ if ($isCustom) {
+ $idField = array_column($schema[$field['fk_entity']]['fields'], NULL, 'name')['id'];
+ $entity['fields'][$index]['label'] .= ' ' . $idField['title'];
+ }
+ // DAO fields: use title instead of label since it represents the id (title usually ends in ID but label does not)
+ else {
+ $entity['fields'][$index]['label'] = $field['title'];
+ }
+ // Add the label field from the other entity to this entity's list of fields
+ $newField = \CRM_Utils_Array::findAll($schema[$field['fk_entity']]['fields'], ['name' => $labelField])[0] ?? NULL;
+ if ($newField) {
+ $newField['name'] = $field['name'] . '.' . $labelField;
+ $newField['label'] = $field['label'] . ' ' . $newField['label'];
+ array_splice($entity['fields'], $index + 1, 0, [$newField]);
+ }
}
- // Add the label field from the other entity to this entity's list of fields
- $newField = \CRM_Utils_Array::findAll($schema[$field['fk_entity']]['fields'], ['name' => $schema[$field['fk_entity']]['label_field']])[0];
- $newField['name'] = $field['name'] . '.' . $schema[$field['fk_entity']]['label_field'];
- $newField['label'] = $field['label'] . ' ' . $newField['label'];
- array_splice($entity['fields'], $index, 0, [$newField]);
}
}
// Useful address fields (see ContactSchemaMapSubscriber)
if (!this.savedSearch.id) {
var defaults = {
version: 4,
- select: getDefaultSelect(),
+ select: searchMeta.getEntity(ctrl.savedSearch.api_entity).default_columns,
orderBy: {},
where: [],
};
params.push(condition);
});
ctrl.savedSearch.api_params.join.push(params);
- if (entity.label_field && $scope.controls.joinType !== 'EXCLUDE') {
- ctrl.savedSearch.api_params.select.push(join.alias + '.' + entity.label_field);
+ if (entity.search_fields && $scope.controls.joinType !== 'EXCLUDE') {
+ // Add columns for newly-joined entity
+ entity.search_fields.forEach((fieldName) => {
+ // Try to avoid adding duplicate columns
+ const simpleName = _.last(fieldName.split('.'));
+ if (!ctrl.savedSearch.api_params.select.join(',').includes(simpleName)) {
+ ctrl.savedSearch.api_params.select.push(join.alias + '.' + fieldName);
+ }
+ });
}
loadFieldOptions();
}
return {results: ctrl.getSelectFields()};
};
- // Sets the default select clause based on commonly-named fields
- function getDefaultSelect() {
- var entity = searchMeta.getEntity(ctrl.savedSearch.api_entity);
- return _.transform(entity.fields, function(defaultSelect, field) {
- if (field.name === 'id' || field.name === entity.label_field) {
- defaultSelect.push(field.name);
- }
- });
- }
-
this.getAllFields = function(suffix, allowedTypes, disabledIf, topJoin) {
disabledIf = disabledIf || _.noop;
allowedTypes = allowedTypes || ['Field', 'Custom', 'Extra', 'Filter'];
sort: ctrl.parent.getDefaultSort(),
columns: []
};
- var labelField = searchMeta.getEntity(ctrl.apiEntity).label_field;
- _.each([labelField, 'description'], function(field) {
+ var searchFields = searchMeta.getEntity(ctrl.apiEntity).search_fields || [];
+ searchFields.push('description');
+ searchFields.forEach((field) => {
if (_.includes(ctrl.parent.savedSearch.api_params.select, field)) {
ctrl.display.settings.columns.push(searchMeta.fieldToColumn(field, {}));
}
->execute();
// Contacts will be returned in order by sort_name
- $this->assertStringStartsWith('Both', $result[0]['label']);
+ $this->assertStringEndsWith('Both', $result[0]['label']);
$this->assertEquals('fa-star', $result[0]['icon']);
- $this->assertStringStartsWith('No icon', $result[1]['label']);
+ $this->assertStringEndsWith('No icon', $result[1]['label']);
$this->assertEquals('fa-user', $result[1]['icon']);
- $this->assertStringStartsWith('Starry', $result[2]['label']);
+ $this->assertStringEndsWith('Starry', $result[2]['label']);
$this->assertEquals('fa-star', $result[2]['icon']);
}
class EntityTest extends Api4TestBase {
public function testEntityGet() {
+ \CRM_Core_BAO_ConfigSetting::enableAllComponents();
$result = Entity::get(FALSE)
->execute()
->indexBy('name');
- $this->assertArrayHasKey('Entity', $result,
- "Entity::get missing itself");
+ $this->assertArrayHasKey('Entity', $result, "Entity::get missing itself");
$this->assertEquals('CRM_Contact_DAO_Contact', $result['Contact']['dao']);
$this->assertEquals(['DAOEntity'], $result['Contact']['type']);
$this->assertEquals(['id'], $result['Contact']['primary_key']);
// Contact icon fields
$this->assertEquals(['contact_sub_type:icon', 'contact_type:icon'], $result['Contact']['icon_field']);
+ // Label fields
+ $this->assertEquals('display_name', $result['Contact']['label_field']);
+ $this->assertEquals('title', $result['Event']['label_field']);
+ // Search fields
+ $this->assertEquals(['sort_name'], $result['Contact']['search_fields']);
+ $this->assertEquals(['title'], $result['Event']['search_fields']);
+ $this->assertEquals(['contact_id.sort_name', 'event_id.title'], $result['Participant']['search_fields']);
}
public function testEntity() {
<type>int unsigned</type>
<html>
<type>Select</type>
+ <label>Status</label>
</html>
<pseudoconstant>
<optionGroupName>pledge_status</optionGroupName>