if (array_key_exists('label', $fieldDefaults)) {
$field['label'] = $field['label'] ?? $field['title'] ?? $field['name'];
}
+ if (!empty($field['options']) && is_array($field['options']) && empty($field['suffixes']) && array_key_exists('suffixes', $field)) {
+ $this->setFieldSuffixes($field);
+ }
if (isset($defaults['options'])) {
$field['options'] = $this->formatOptionList($field['options']);
}
return $formatted;
}
+ /**
+ * Set supported field suffixes based on available option keys
+ * @param array $field
+ */
+ private function setFieldSuffixes(array &$field) {
+ // These suffixes are always supported if a field has options
+ $field['suffixes'] = ['name', 'label'];
+ $firstOption = reset($field['options']);
+ // If first option is an array, merge in those keys as available suffixes
+ if (is_array($firstOption)) {
+ // Remove 'id' because there is no practical reason to use it as a field suffix
+ $otherKeys = array_diff(array_keys($firstOption), ['id', 'name', 'label']);
+ $field['suffixes'] = array_merge($field['suffixes'], $otherKeys);
+ }
+ }
+
/**
* @return string
*/
'data_type' => 'Array',
'default_value' => FALSE,
],
+ [
+ 'name' => 'suffixes',
+ 'data_type' => 'Array',
+ 'default_value' => NULL,
+ 'options' => ['name', 'label', 'description', 'abbr', 'color', 'icon'],
+ 'description' => 'Available option transformations, e.g. :name, :label',
+ ],
[
'name' => 'operators',
'data_type' => 'Array',
->setType('Filter')
->setOperators(['IN', 'NOT IN'])
->addSqlFilter([__CLASS__, 'getContactGroupSql'])
+ ->setSuffixes(['id', 'name', 'label'])
->setOptionsCallback([__CLASS__, 'getGroupList']);
$spec->addFieldSpec($field);
}
->setType('Filter')
->setOperators(['IN', 'NOT IN'])
->addSqlFilter([__CLASS__, 'getTagFilterSql'])
+ ->setSuffixes(['id', 'name', 'label', 'description', 'color'])
->setOptionsCallback([__CLASS__, 'getTagList']);
$spec->addFieldSpec($field);
}
$field->setHelpPost($data['help_post'] ?? NULL);
if (self::customFieldHasOptions($data)) {
$field->setOptionsCallback([__CLASS__, 'getOptions']);
+ if (!empty($data['option_group_id'])) {
+ // Option groups support other stuff like description, icon & color,
+ // but at time of this writing, custom fields do not.
+ $field->setSuffixes(['id', 'name', 'label']);
+ }
}
$field->setReadonly($data['is_view']);
}
$field->setTitle($data['title'] ?? NULL);
$field->setLabel($data['html']['label'] ?? NULL);
if (!empty($data['pseudoconstant'])) {
- $field->setOptionsCallback([__CLASS__, 'getOptions']);
+ // Do not load options if 'prefetch' is explicitly FALSE
+ if ($data['pseudoconstant']['prefetch'] ?? TRUE) {
+ $field->setOptionsCallback([__CLASS__, 'getOptions']);
+ }
+ // These suffixes are always supported if a field has options
+ $suffixes = ['name', 'label'];
+ // Add other columns specified in schema (e.g. 'abbrColumn')
+ foreach (['description', 'abbr', 'icon', 'color'] as $suffix) {
+ if (isset($data['pseudoconstant'][$suffix . 'Column'])) {
+ $suffixes[] = $suffix;
+ }
+ }
+ $field->setSuffixes($suffixes);
}
$field->setReadonly(!empty($data['readonly']));
}
*/
public $options;
+ /**
+ * @var array|null
+ */
+ public $suffixes;
+
/**
* @var callable
*/
return $this;
}
+ /**
+ * @param array $suffixes
+ *
+ * @return $this
+ */
+ public function setSuffixes($suffixes) {
+ $this->suffixes = $suffixes;
+ return $this;
+ }
+
/**
* @param callable $callback
*
[
'name' => 'type',
'options' => $self->pseudoconstantOptions('afform_type'),
+ 'suffixes' => ['id', 'name', 'label', 'icon'],
],
[
'name' => 'requires',
$this->assertTrue($getFields['fruit']['options']);
$this->assertFalse($getFields['identifier']['options']);
+ // Getfields should figure out what suffixes are available based on option keys
+ $this->assertEquals(['name', 'label'], $getFields['group']['suffixes']);
+ $this->assertEquals(['name', 'label', 'color'], $getFields['fruit']['suffixes']);
+
// Load simple options
$getFields = MockBasicEntity::getFields()
->addWhere('name', 'IN', ['group', 'fruit'])
use api\v4\UnitTestCase;
use Civi\Api4\Contact;
+use Civi\Api4\Contribution;
/**
* @group headless
public function testInternalPropsAreHidden() {
// Public getFields should not contain @internal props
$fields = Contact::getFields(FALSE)
- ->execute()
- ->getArrayCopy();
+ ->execute();
foreach ($fields as $field) {
$this->assertArrayNotHasKey('output_formatters', $field);
}
}
}
+ public function testPreloadFalse() {
+ \CRM_Core_BAO_ConfigSetting::enableComponent('CiviContribute');
+ \CRM_Core_BAO_ConfigSetting::enableComponent('CiviCampaign');
+ // The campaign_id field has preload = false in the schema,
+ // Which means the options will NOT load but suffixes are still available
+ $fields = Contribution::getFields(FALSE)
+ ->setLoadOptions(['name', 'label'])
+ ->execute()->indexBy('name');
+ $this->assertFalse($fields['campaign_id']['options']);
+ $this->assertEquals(['name', 'label'], $fields['campaign_id']['suffixes']);
+ }
+
}
namespace api\v4\Action;
use Civi\Api4\Address;
+use Civi\Api4\Campaign;
use Civi\Api4\Contact;
use Civi\Api4\Activity;
+use Civi\Api4\Contribution;
use Civi\Api4\CustomField;
use Civi\Api4\CustomGroup;
use Civi\Api4\Email;
$this->assertArrayNotHasKey($participant['id'], (array) $search2);
}
+ public function testPreloadFalse() {
+ \CRM_Core_BAO_ConfigSetting::enableComponent('CiviContribute');
+ \CRM_Core_BAO_ConfigSetting::enableComponent('CiviCampaign');
+
+ $contact = $this->createEntity(['type' => 'Individual']);
+
+ $campaignTitle = uniqid('Test ');
+
+ $campaignId = Campaign::create(FALSE)
+ ->addValue('title', $campaignTitle)
+ ->addValue('campaign_type_id', 1)
+ ->execute()->first()['id'];
+
+ $contributionId = Contribution::create(FALSE)
+ ->addValue('campaign_id', $campaignId)
+ ->addValue('contact_id', $contact['id'])
+ ->addValue('financial_type_id', 1)
+ ->addValue('total_amount', .01)
+ ->execute()->first()['id'];
+
+ // Even though the option list of campaigns is not available (prefetch = false)
+ // We should still be able to get the title of the campaign as :label
+ $result = Contribution::get(FALSE)
+ ->addWhere('id', '=', $contributionId)
+ ->addSelect('campaign_id:label')
+ ->execute()->single();
+
+ $this->assertEquals($campaignTitle, $result['campaign_id:label']);
+
+ // Fetching the title via join ought to work too
+ $result = Contribution::get(FALSE)
+ ->addWhere('id', '=', $contributionId)
+ ->addSelect('campaign_id.title')
+ ->execute()->single();
+
+ $this->assertEquals($campaignTitle, $result['campaign_id.title']);
+ }
+
}