*
* Generated from xml/schema/CRM/Contact/Contact.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:6c4b31481898fef1b087265d096c65f6)
+ * (GenCodeChecksum:1a988e976c3347c4050d0b6aad955a09)
*/
/**
'table' => 'civicrm_contact_type',
'keyColumn' => 'name',
'labelColumn' => 'label',
+ 'iconColumn' => 'icon',
'condition' => 'parent_id IS NULL',
],
'readonly' => TRUE,
'table' => 'civicrm_contact_type',
'keyColumn' => 'name',
'labelColumn' => 'label',
+ 'iconColumn' => 'icon',
'condition' => 'parent_id IS NOT NULL',
],
'add' => '1.5',
'nameColumn',
// Column to fetch in "abbreviate" context
'abbrColumn',
+ // Supported by APIv4 suffixes
+ 'colorColumn',
+ 'iconColumn',
// Where clause snippet (will be joined to the rest of the query with AND operator)
'condition',
// callback function incase of static arrays
/**
* Retrieve $ENTITIES for an autocomplete form field.
+ *
+ * @method $this setInput(string $input) Set input term.
+ * @method string getInput()
+ * @method $this setIds(array $ids) Set array of ids.
+ * @method array getIds()
+ * @method $this setPage(int $page) Set current page.
+ * @method array getPage()
+ * @method $this setFormName(string $formName) Set formName.
+ * @method string getFormName()
+ * @method $this setFieldName(string $fieldName) Set fieldName.
+ * @method string getFieldName()
+ * @method $this setClientFilters(array $clientFilters) Set array of untrusted filter values.
+ * @method array getClientFilters()
*/
class AutocompleteAction extends AbstractAction {
use Traits\SavedSearchInspectorTrait;
// Adding one extra result allows us to see if there are any more
$this->_apiParams['limit'] = $resultsPerPage + 1;
$this->_apiParams['offset'] = ($this->page - 1) * $resultsPerPage;
+
+ $orderBy = CoreUtil::getInfoItem($this->getEntityName(), 'order_by') ?: $labelField;
+ $this->_apiParams['orderBy'] = [$orderBy => 'ASC'];
if (strlen($this->input)) {
$prefix = \Civi::settings()->get('includeWildCardInName') ? '%' : '';
$this->_apiParams['where'][] = [$labelField, 'LIKE', $prefix . $this->input . '%'];
foreach ($map as $key => $fieldName) {
$mapped[$key] = $row[$fieldName];
}
+ // Get icon in order of priority
foreach ($iconFields as $fieldName) {
if (!empty($row[$fieldName])) {
- $mapped['icon'] = $row[$fieldName];
+ // Icon field may be multivalued e.g. contact_sub_type
+ $icon = \CRM_Utils_Array::first(array_filter((array) $row[$fieldName]));
+ if ($icon) {
+ $mapped['icon'] = $icon;
+ }
break;
}
}
$returnFormat = array_diff($returnFormat, ['id', 'name', 'label']);
// CRM_Core_Pseudoconstant doesn't know how to fetch extra stuff like icon, description, color, etc., so we have to invent that wheel here...
if ($returnFormat) {
- $optionIds = implode(',', array_column($options, 'id'));
$optionIndex = array_flip(array_column($options, 'id'));
if ($spec instanceof CustomFieldSpec) {
$optionGroupId = \CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $spec->getCustomFieldId(), 'option_group_id');
$returnFormat = array_diff($returnFormat, ['abbr']);
}
// Fetch anything else (color, icon, description)
- if ($returnFormat && !empty($pseudoconstant['table']) && \CRM_Utils_Rule::commaSeparatedIntegers($optionIds)) {
- $sql = "SELECT * FROM {$pseudoconstant['table']} WHERE id IN (%1)";
- $query = \CRM_Core_DAO::executeQuery($sql, [1 => [$optionIds, 'CommaSeparatedIntegers']]);
+ if ($returnFormat && !empty($pseudoconstant['table'])) {
+ $idCol = $pseudoconstant['keyColumn'] ?? 'id';
+ $optionIds = \CRM_Core_DAO::escapeStrings(array_column($options, 'id'));
+ $sql = "SELECT * FROM {$pseudoconstant['table']} WHERE `$idCol` IN ($optionIds)";
+ $query = \CRM_Core_DAO::executeQuery($sql);
while ($query->fetch()) {
foreach ($returnFormat as $ret) {
- if (property_exists($query, $ret)) {
+ $retCol = $pseudoconstant[$ret . 'Column'] ?? $ret;
+ if (property_exists($query, $retCol)) {
// Note: our schema is inconsistent about whether `description` fields allow html,
// but it's usually assumed to be plain text, so we strip_tags() to standardize it.
- $options[$optionIndex[$query->id]][$ret] = $ret === 'description' ? strip_tags($query->$ret ?? '') : $query->$ret;
+ $options[$optionIndex[$query->$idCol]][$ret] = isset($query->$retCol) ? strip_tags($query->$retCol) : NULL;
}
}
}
namespace Civi\Api4;
+use Civi\Api4\Generic\AutocompleteAction;
use Civi\Api4\Generic\BasicGetFieldsAction;
/**
* The `prefill` and `submit` actions are used for preparing forms and processing submissions.
*
* @see https://lab.civicrm.org/extensions/afform
+ * @labelField title
+ * @iconField type:icon
* @searchable none
* @package Civi\Api4
*/
->setCheckPermissions($checkPermissions);
}
+ /**
+ * @param bool $checkPermissions
+ * @return \Civi\Api4\Generic\AutocompleteAction
+ */
+ public static function autocomplete($checkPermissions = TRUE) {
+ return (new AutocompleteAction('Afform', __FUNCTION__))
+ ->setCheckPermissions($checkPermissions);
+ }
+
/**
* @param bool $checkPermissions
* @return Action\Afform\Convert
$this->assertArrayNotHasKey('base_module', $result);
}
+ public function testAfformAutocomplete(): void {
+ $title = uniqid();
+ Afform::create()
+ ->addValue('name', $this->formName)
+ ->addValue('title', $title)
+ ->addValue('type', 'form')
+ ->execute();
+
+ $result = Afform::autocomplete()
+ ->setInput(substr($title, 0, 9))
+ ->execute();
+
+ $this->assertEquals($this->formName, $result[0]['id']);
+ $this->assertEquals($title, $result[0]['label']);
+ $this->assertEquals('fa-list-alt', $result[0]['icon']);
+ }
+
public function testGetSearchDisplays() {
Afform::create()
->addValue('name', $this->formName)
--- /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 |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ */
+
+
+namespace api\v4\Action;
+
+use api\v4\Api4TestBase;
+use Civi\Api4\Contact;
+use Civi\Api4\MockBasicEntity;
+use Civi\Core\Event\GenericHookEvent;
+use Civi\Test\HookInterface;
+use Civi\Test\TransactionalInterface;
+
+/**
+ * @group headless
+ */
+class AutocompleteTest extends Api4TestBase implements HookInterface, TransactionalInterface {
+
+ /**
+ * Listens for civi.api4.entityTypes event to manually add this nonstandard entity
+ *
+ * @param \Civi\Core\Event\GenericHookEvent $e
+ */
+ public function on_civi_api4_entityTypes(GenericHookEvent $e): void {
+ $e->entities['MockBasicEntity'] = MockBasicEntity::getInfo();
+ }
+
+ public function setUp(): void {
+ // Ensure MockBasicEntity gets added via above listener
+ \Civi::cache('metadata')->clear();
+ MockBasicEntity::delete(FALSE)->addWhere('identifier', '>', 0)->execute();
+ \Civi::settings()->set('includeWildCardInName', 1);
+ parent::setUp();
+ }
+
+ public function testMockEntityAutocomplete(): void {
+ $sampleData = [
+ ['foo' => 'White', 'color' => 'ffffff'],
+ ['foo' => 'Gray', 'color' => '777777'],
+ ['foo' => 'Black', 'color' => '000000'],
+ ];
+ $entities = MockBasicEntity::save(FALSE)
+ ->setRecords($sampleData)
+ ->execute();
+
+ $result = MockBasicEntity::autocomplete()
+ ->setInput('a')
+ ->execute();
+ $this->assertCount(2, $result);
+ $this->assertEquals('Black', $result[0]['label']);
+ $this->assertEquals('777777', $result[1]['color']);
+
+ $result = MockBasicEntity::autocomplete()
+ ->setInput('ite')
+ ->execute();
+ $this->assertCount(1, $result);
+ $this->assertEquals($entities[0]['identifier'], $result[0]['id']);
+ $this->assertEquals('ffffff', $result[0]['color']);
+ $this->assertEquals('White', $result[0]['label']);
+ }
+
+ public function testContactIconAutocomplete(): void {
+ $this->createTestRecord('ContactType', [
+ 'label' => 'Star',
+ 'name' => 'Star',
+ 'parent_id:name' => 'Individual',
+ 'icon' => 'fa-star',
+ ]);
+ $this->createTestRecord('ContactType', [
+ 'label' => 'None',
+ 'name' => 'None',
+ 'parent_id:name' => 'Individual',
+ 'icon' => NULL,
+ ]);
+
+ $lastName = uniqid(__FUNCTION__);
+ $sampleData = [
+ [
+ 'first_name' => 'Starry',
+ 'contact_sub_type' => ['Star'],
+ ],
+ [
+ 'first_name' => 'No icon',
+ 'contact_sub_type' => ['None'],
+ ],
+ [
+ 'first_name' => 'Both',
+ 'contact_sub_type' => ['None', 'Star'],
+ ],
+ ];
+ $records = $this->saveTestRecords('Contact', [
+ 'records' => $sampleData,
+ 'defaults' => ['last_name' => $lastName],
+ ]);
+
+ $result = Contact::autocomplete()
+ ->setInput($lastName)
+ ->execute();
+
+ // Contacts will be returned in order by sort_name
+ $this->assertStringStartsWith('Both', $result[0]['label']);
+ $this->assertEquals('fa-star', $result[0]['icon']);
+ $this->assertStringStartsWith('No icon', $result[1]['label']);
+ $this->assertEquals('fa-user', $result[1]['icon']);
+ $this->assertStringStartsWith('Starry', $result[2]['label']);
+ $this->assertEquals('fa-star', $result[2]['icon']);
+ }
+
+}
$this->assertFalse($fields['first_name']['options']);
}
- public function testTableAndColumnReturned() {
+ public function testContactGetFields() {
$fields = Contact::getFields(FALSE)
->execute()
->indexBy('name');
+ // Ensure table & column are returned
$this->assertEquals('civicrm_contact', $fields['display_name']['table_name']);
$this->assertEquals('display_name', $fields['display_name']['column_name']);
+
+ // Check suffixes
+ $this->assertEquals(['name', 'label', 'icon'], $fields['contact_type']['suffixes']);
+ $this->assertEquals(['name', 'label', 'icon'], $fields['contact_sub_type']['suffixes']);
}
public function testComponentFields() {
"Entity::get missing itself");
$this->assertArrayHasKey('Participant', $result,
"Entity::get missing Participant");
+
+ $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']);
}
public function testEntity() {
/**
* MockBasicEntity entity.
*
+ * @labelField foo
* @package Civi\Api4
*/
class MockBasicEntity extends Generic\BasicEntity {
<table>civicrm_contact_type</table>
<keyColumn>name</keyColumn>
<labelColumn>label</labelColumn>
+ <iconColumn>icon</iconColumn>
<condition>parent_id IS NULL</condition>
</pseudoconstant>
<html>
<table>civicrm_contact_type</table>
<keyColumn>name</keyColumn>
<labelColumn>label</labelColumn>
+ <iconColumn>icon</iconColumn>
<condition>parent_id IS NOT NULL</condition>
</pseudoconstant>
<html>