Skip to content

Commit

Permalink
Merge pull request #20191 from colemanw/apiSettingsFixes
Browse files Browse the repository at this point in the history
APIv4 - Setting api misc fixes & tests
  • Loading branch information
eileenmcnaughton authored May 5, 2021
2 parents f746ec8 + 6fb8554 commit eeeb9b4
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 50 deletions.
6 changes: 4 additions & 2 deletions CRM/Core/BAO/Setting.php
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,15 @@ public static function validateSettingsInput($params, &$fields, $createMode = TR
* value of the setting to be set
* @param array $fieldSpec
* Metadata for given field (drawn from the xml)
* @param bool $convertToSerializedString
* Deprecated mode
*
* @return bool
* @throws \API_Exception
*/
public static function validateSetting(&$value, array $fieldSpec) {
public static function validateSetting(&$value, array $fieldSpec, $convertToSerializedString = TRUE) {
// Deprecated guesswork - should use $fieldSpec['serialize']
if ($fieldSpec['type'] == 'String' && is_array($value)) {
if ($convertToSerializedString && $fieldSpec['type'] == 'String' && is_array($value)) {
$value = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $value) . CRM_Core_DAO::VALUE_SEPARATOR;
}
if (empty($fieldSpec['validate_callback'])) {
Expand Down
2 changes: 1 addition & 1 deletion CRM/Core/SelectValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ public static function geoProvider() {
*
* @throws \CRM_Core_Exception
*/
public function taxDisplayOptions() {
public static function taxDisplayOptions() {
return [
'Do_not_show' => ts('Do not show breakdown, only show total - i.e %1', [
1 => CRM_Utils_Money::format(120),
Expand Down
46 changes: 43 additions & 3 deletions Civi/Api4/Action/Setting/AbstractSettingAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,24 @@ public function _run(Result $result) {
*/
protected function validateSettings($domain) {
$meta = \Civi\Core\SettingsMetadata::getMetadata([], $domain);
$names = isset($this->values) ? array_keys($this->values) : $this->select;
$names = array_map(function($name) {
return explode(':', $name)[0];
}, isset($this->values) ? array_keys($this->values) : $this->select);
$invalid = array_diff($names, array_keys($meta));
if ($invalid) {
throw new \API_Exception("Unknown settings for domain $domain: " . implode(', ', $invalid));
}
if (isset($this->values)) {
foreach ($this->values as $name => &$value) {
\CRM_Core_BAO_Setting::validateSetting($value, $meta[$name]);
foreach ($this->values as $name => $value) {
[$name, $suffix] = array_pad(explode(':', $name), 2, NULL);
// Replace pseudoconstants in values array
if ($suffix) {
$value = $this->matchPseudoconstant($name, $value, $suffix, 'id', $domain);
unset($this->values["$name:$suffix"]);
$this->values[$name] = $value;
}
\CRM_Core_BAO_Setting::validateSetting($this->values[$name], $meta[$name], FALSE);

}
}
return $meta;
Expand All @@ -88,4 +98,34 @@ protected function findDomains() {
}
}

/**
* @param string $name
* @param mixed $value
* @param string $from
* @param string $to
* @param int $domain
* @return mixed
*/
protected function matchPseudoconstant(string $name, $value, $from, $to, $domain) {
if ($value === NULL) {
return NULL;
}
if ($from === $to) {
return $value;
}
$meta = \Civi\Core\SettingsMetadata::getMetadata(['name' => [$name]], $domain, [$from, $to]);
$options = $meta[$name]['options'] ?? [];
$map = array_column($options, $to, $from);
$translated = [];
foreach ((array) $value as $key) {
if (isset($map[$key])) {
$translated[] = $map[$key];
}
}
if (!is_array($value)) {
return \CRM_Utils_Array::first($translated);
}
return $translated;
}

}
20 changes: 13 additions & 7 deletions Civi/Api4/Action/Setting/Get.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,33 @@ class Get extends AbstractSettingAction {
protected function processSettings(Result $result, $settingsBag, $meta, $domain) {
if ($this->select) {
foreach ($this->select as $name) {
[$name, $suffix] = array_pad(explode(':', $name), 2, NULL);
$value = $settingsBag->get($name);
if (isset($value) && !empty($meta[$name]['serialize'])) {
$value = \CRM_Core_DAO::unSerializeField($value, $meta[$name]['serialize']);
}
if ($suffix) {
$value = $this->matchPseudoconstant($name, $value, 'id', $suffix, $domain);
}
$result[] = [
'name' => $name,
'value' => $settingsBag->get($name),
'name' => $suffix ? "$name:$suffix" : $name,
'value' => $value,
'domain_id' => $domain,
];
}
}
else {
foreach ($settingsBag->all() as $name => $value) {
if (isset($value) && !empty($meta[$name]['serialize'])) {
$value = \CRM_Core_DAO::unSerializeField($value, $meta[$name]['serialize']);
}
$result[] = [
'name' => $name,
'value' => $value,
'domain_id' => $domain,
];
}
}
foreach ($result as &$setting) {
if (isset($setting['value']) && !empty($meta[$setting['name']]['serialize'])) {
$setting['value'] = \CRM_Core_DAO::unSerializeField($setting['value'], $meta[$setting['name']]['serialize']);
}
}
}

/**
Expand Down
41 changes: 24 additions & 17 deletions Civi/Api4/Action/Setting/GetFields.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,25 @@ class GetFields extends \Civi\Api4\Generic\BasicGetFieldsAction {
protected $domainId;

protected function getRecords() {
// TODO: Waiting for filter handling to get fixed in core
// $names = $this->_itemsToGet('name');
// $filter = $names ? ['name' => $names] : [];
$filter = [];
return \Civi\Core\SettingsMetadata::getMetadata($filter, $this->domainId, $this->loadOptions);
$names = $this->_itemsToGet('name');
$filter = $names ? ['name' => $names] : [];
$settings = \Civi\Core\SettingsMetadata::getMetadata($filter, $this->domainId, $this->loadOptions);
$getReadonly = $this->_isFieldSelected('readonly');
foreach ($settings as $index => $setting) {
// Unserialize default value
if (!empty($setting['serialize']) && !empty($setting['default']) && is_string($setting['default'])) {
$setting['default'] = \CRM_Core_DAO::unSerializeField($setting['default'], $setting['serialize']);
}
if (!isset($setting['options'])) {
$setting['options'] = !empty($setting['pseudoconstant']);
}
if ($getReadonly) {
$setting['readonly'] = \Civi::settings()->getMandatory($setting['name']) !== NULL;
}
// Filter out deprecated properties
$settings[$index] = array_intersect_key($setting, array_column($this->fields(), NULL, 'name'));
}
return $settings;
}

public function fields() {
Expand All @@ -57,22 +71,10 @@ public function fields() {
'name' => 'default',
'data_type' => 'String',
],
[
'name' => 'pseudoconstant',
'data_type' => 'String',
],
[
'name' => 'options',
'data_type' => 'Array',
],
[
'name' => 'group_name',
'data_type' => 'String',
],
[
'name' => 'group',
'data_type' => 'String',
],
[
'name' => 'html_type',
'data_type' => 'String',
Expand All @@ -89,6 +91,11 @@ public function fields() {
'name' => 'data_type',
'data_type' => 'Integer',
],
[
'name' => 'readonly',
'data_type' => 'Boolean',
'description' => 'True if value is set in code and cannot be overridden.',
],
];
}

Expand Down
1 change: 1 addition & 0 deletions Civi/Api4/Generic/BasicGetFieldsAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ public function fields() {
[
'name' => 'readonly',
'data_type' => 'Boolean',
'description' => 'True for auto-increment, calculated, or otherwise non-editable fields.',
],
[
'name' => 'output_formatters',
Expand Down
45 changes: 35 additions & 10 deletions Civi/Core/SettingsMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class SettingsMetadata {
*
* @param array $filters
* @param int $domainID
* @param bool $loadOptions
* @param bool|array $loadOptions
*
* @return array
* the following information as appropriate for each setting
Expand Down Expand Up @@ -70,7 +70,7 @@ public static function getMetadata($filters = [], $domainID = NULL, $loadOptions

self::_filterSettingsSpecification($filters, $settingsMetadata);
if ($loadOptions) {
self::loadOptions($settingsMetadata);
self::fillOptions($settingsMetadata, $loadOptions);
}

return $settingsMetadata;
Expand Down Expand Up @@ -98,7 +98,7 @@ protected static function loadSettingsMetaDataFolders($metaDataFolders) {
/**
* Load up settings metadata from files.
*
* @param array $metaDataFolder
* @param string $metaDataFolder
*
* @return array
*/
Expand Down Expand Up @@ -141,30 +141,55 @@ protected static function _filterSettingsSpecification($filters, &$settingSpec)
* Retrieve options from settings metadata
*
* @param array $settingSpec
* @param bool|array $optionsFormat
* TRUE for a flat array; otherwise an array of keys to return
*/
protected static function loadOptions(&$settingSpec) {
protected static function fillOptions(&$settingSpec, $optionsFormat) {
foreach ($settingSpec as &$spec) {
if (empty($spec['pseudoconstant'])) {
continue;
}
$pseudoconstant = $spec['pseudoconstant'];
$spec['options'] = [];
// It would be nice if we could leverage CRM_Core_PseudoConstant::get() somehow,
// but it's tightly coupled to DAO/field. However, if you really need to support
// more pseudoconstant types, then probably best to refactor it. For now, KISS.
if (!empty($pseudoconstant['callback'])) {
$spec['options'] = Resolver::singleton()->call($pseudoconstant['callback'], []);
}
elseif (!empty($pseudoconstant['optionGroupName'])) {
if (!empty($pseudoconstant['optionGroupName'])) {
$keyColumn = \CRM_Utils_Array::value('keyColumn', $pseudoconstant, 'value');
$spec['options'] = \CRM_Core_OptionGroup::values($pseudoconstant['optionGroupName'], FALSE, FALSE, TRUE, NULL, 'label', TRUE, FALSE, $keyColumn);
if (is_array($optionsFormat)) {
$optionValues = \CRM_Core_OptionValue::getValues(['name' => $pseudoconstant['optionGroupName']]);
foreach ($optionValues as $option) {
$option['id'] = $option['value'];
$spec['options'][] = $option;
}
}
else {
$spec['options'] = \CRM_Core_OptionGroup::values($pseudoconstant['optionGroupName'], FALSE, FALSE, TRUE, NULL, 'label', TRUE, FALSE, $keyColumn);
}
continue;
}
if (!empty($pseudoconstant['callback'])) {
$options = Resolver::singleton()->call($pseudoconstant['callback'], []);
}
if (!empty($pseudoconstant['table'])) {
$params = [
'condition' => $pseudoconstant['condition'] ?? [],
'keyColumn' => $pseudoconstant['keyColumn'] ?? NULL,
'labelColumn' => $pseudoconstant['labelColumn'] ?? NULL,
];
$spec['options'] = \CRM_Core_PseudoConstant::renderOptionsFromTablePseudoconstant($pseudoconstant, $params, ($spec['localize_context'] ?? NULL), 'get');
$options = \CRM_Core_PseudoConstant::renderOptionsFromTablePseudoconstant($pseudoconstant, $params, ($spec['localize_context'] ?? NULL), 'get');
}
if (is_array($optionsFormat)) {
foreach ($options as $key => $value) {
$spec['options'][] = [
'id' => $key,
'name' => $value,
'label' => $value,
];
}
}
else {
$spec['options'] = $options;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion api/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function civicrm_api4(string $entity, string $action, array $params = [], $index
$removeIndexField = FALSE;

// If index field is not part of the select query, we add it here and remove it below (except for oddball "Setting" api)
if ($indexField && !empty($params['select']) && is_array($params['select']) && $entity !== 'Setting' && !\Civi\Api4\Utils\SelectUtil::isFieldSelected($indexField, $params['select'])) {
if ($indexField && !empty($params['select']) && is_array($params['select']) && !($entity === 'Setting' && $action === 'get') && !\Civi\Api4\Utils\SelectUtil::isFieldSelected($indexField, $params['select'])) {
$params['select'][] = $indexField;
$removeIndexField = TRUE;
}
Expand Down
10 changes: 4 additions & 6 deletions tests/phpunit/api/v3/SettingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,9 @@ public function testGetFieldsFilters($version) {

/**
* Test that getfields will filter on group.
* @param int $version
* @dataProvider versionThreeAndFour
*/
public function testGetFieldsGroupFilters($version) {
$this->_apiversion = $version;
public function testGetFieldsGroupFilters() {
$this->_apiversion = 3;
$params = ['filters' => ['group' => 'multisite']];
$result = $this->callAPISuccess('setting', 'getfields', $params);
$this->assertArrayNotHasKey('customCSSURL', $result['values']);
Expand Down Expand Up @@ -363,11 +361,11 @@ public function testGetExtensionSetting($version) {
$this->hookClass->setHook('civicrm_alterSettingsFolders', [$this, 'setExtensionMetadata']);
$data = NULL;
Civi::cache('settings')->flush();
$fields = $this->callAPISuccess('setting', 'getfields', ['filters' => ['group_name' => 'Test Settings']]);
$fields = $this->callAPISuccess('setting', 'getfields');
$this->assertArrayHasKey('test_key', $fields['values']);
$this->callAPISuccess('setting', 'create', ['test_key' => 'keyset']);
$this->assertEquals('keyset', Civi::settings()->get('test_key'));
$result = $this->callAPISuccess('setting', 'getvalue', ['name' => 'test_key', 'group' => 'Test Settings']);
$result = $this->callAPISuccess('setting', 'getvalue', ['name' => 'test_key']);
$this->assertEquals('keyset', $result);
}

Expand Down
52 changes: 49 additions & 3 deletions tests/phpunit/api/v4/Entity/SettingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,24 @@ public function testInvalidSetting() {
}

public function testSerailizedSetting() {
$settings = \Civi\Api4\Setting::get(FALSE)
->addSelect('contact_edit_options')
$set = \Civi\Api4\Setting::set(FALSE)
->addValue('contact_edit_options:name', [
'CommunicationPreferences',
'CustomData',
])
->execute();
$this->assertTrue(is_array($settings[0]['value']));

$setting = \Civi\Api4\Setting::get(FALSE)
->addSelect('contact_edit_options')
->execute()->first();
$this->assertTrue(is_array($setting['value']));
$this->assertTrue(is_numeric($setting['value'][0]));

$setting = \Civi\Api4\Setting::get(FALSE)
->addSelect('contact_edit_options:label')
->execute()->first();
$this->assertEquals(['Communication Preferences', 'Custom Data'], $setting['value']);
$this->assertEquals('contact_edit_options:label', $setting['name']);
}

/**
Expand All @@ -78,4 +92,36 @@ public function testSettingsWithIndexParam() {
$this->assertTrue($arrayValues);
}

/**
* Make sure options load from getFields.
*/
public function testSettingGetFieldsOptions() {
$setting = civicrm_api4('Setting', 'getFields', [
'select' => ['options'],
'loadOptions' => FALSE,
], 'name');
$this->assertTrue($setting['contact_edit_options']['options']);

$setting = civicrm_api4('Setting', 'getFields', [
'select' => ['options'],
'where' => [['name', '=', 'contact_edit_options']],
'loadOptions' => TRUE,
], 'name');
$this->assertContains('Custom Data', $setting['contact_edit_options']['options']);

$setting = civicrm_api4('Setting', 'getFields', [
'select' => ['options'],
'loadOptions' => ['id', 'name', 'label'],
], 'name');
$this->assertTrue(is_array($setting['contact_edit_options']['options'][0]));
}

/**
* Ensure settings default values unserialize.
*/
public function testSettingUnserializeDefaults() {
$setting = civicrm_api4('Setting', 'getFields', ['where' => [['name', '=', 'contact_view_options']]], 0);
$this->assertTrue(is_array($setting['default']));
}

}

0 comments on commit eeeb9b4

Please sign in to comment.