/**
* @var array
+ * [alias => expr][]
*/
protected $selectAliases = [];
// For WHERE clause, expr must be the name of a field.
if ($type === 'WHERE') {
$field = $this->getField($expr, TRUE);
- FormattingUtil::formatInputValue($value, $expr, $field, $this->getEntity());
+ FormattingUtil::formatInputValue($value, $expr, $field);
$fieldAlias = $field['sql_name'];
}
// For HAVING, expr must be an item in the SELECT clause
else {
+ // Expr references a fieldName or alias
if (isset($this->selectAliases[$expr])) {
$fieldAlias = $expr;
+ // Attempt to format if this is a real field
+ if (isset($this->apiFieldSpec[$expr])) {
+ FormattingUtil::formatInputValue($value, $expr, $this->apiFieldSpec[$expr]);
+ }
}
+ // Expr references a non-field expression like a function; convert to alias
elseif (in_array($expr, $this->selectAliases)) {
$fieldAlias = array_search($expr, $this->selectAliases);
}
+ // If either the having or select field contains a pseudoconstant suffix, match and perform substitution
else {
- throw new \API_Exception("Invalid expression in $type clause: '$expr'. Must use a value from SELECT clause.");
+ list($fieldName) = explode(':', $expr);
+ foreach ($this->selectAliases as $selectAlias => $selectExpr) {
+ list($selectField) = explode(':', $selectAlias);
+ if ($selectAlias === $selectExpr && $fieldName === $selectField && isset($this->apiFieldSpec[$fieldName])) {
+ FormattingUtil::formatInputValue($value, $expr, $this->apiFieldSpec[$fieldName]);
+ $fieldAlias = $selectAlias;
+ break;
+ }
+ }
+ }
+ if (!isset($fieldAlias)) {
+ throw new \API_Exception("Invalid expression in HAVING clause: '$expr'. Must use a value from SELECT clause.");
}
+ $fieldAlias = '`' . $fieldAlias . '`';
}
$sql_clause = \CRM_Core_DAO::createSQLFilter($fieldAlias, [$operator => $value]);
/**
* Massage values into the format the BAO expects for a write operation
*
- * @param $params
- * @param $entity
- * @param $fields
+ * @param array $params
+ * @param array $fields
* @throws \API_Exception
*/
- public static function formatWriteParams(&$params, $entity, $fields) {
+ public static function formatWriteParams(&$params, $fields) {
foreach ($fields as $name => $field) {
if (!empty($params[$name])) {
$value =& $params[$name];
if ($value === 'null') {
$value = 'Null';
}
- self::formatInputValue($value, $name, $field, $entity);
+ self::formatInputValue($value, $name, $field);
// Ensure we have an array for serialized fields
if (!empty($field['serialize'] && !is_array($value))) {
$value = (array) $value;
* @param $value
* @param string $fieldName
* @param array $fieldSpec
- * @param string $entity
- * Ex: 'Contact', 'Domain'
* @throws \API_Exception
*/
- public static function formatInputValue(&$value, $fieldName, $fieldSpec, $entity) {
+ public static function formatInputValue(&$value, $fieldName, $fieldSpec) {
// Evaluate pseudoconstant suffix
$suffix = strpos($fieldName, ':');
if ($suffix) {
}
elseif (is_array($value)) {
foreach ($value as &$val) {
- self::formatInputValue($val, $fieldName, $fieldSpec, $entity);
+ self::formatInputValue($val, $fieldName, $fieldSpec);
}
return;
}
- $fk = $fieldSpec['name'] == 'id' ? $entity : $fieldSpec['fk_entity'] ?? NULL;
+ $fk = $fieldSpec['name'] == 'id' ? $fieldSpec['entity'] : $fieldSpec['fk_entity'] ?? NULL;
if ($fk === 'Domain' && $value === 'current_domain') {
$value = \CRM_Core_Config::domainID();
return container;
}
- function getFieldList(action) {
+ // Returns field list formatted for select2
+ function getFieldList(action, addPseudoconstant) {
var fields = [],
fieldInfo = _.findWhere(getEntity().actions, {name: action}).fields;
+ if (addPseudoconstant) {
+ fieldInfo = _.cloneDeep(fieldInfo);
+ addPseudoconstants(fieldInfo, addPseudoconstant);
+ }
formatForSelect2(fieldInfo, fields, 'name', ['description', 'required', 'default_value']);
return fields;
}
- function addJoins(fieldList, addWildcard) {
+ // Note: this function expects fieldList to be select2-formatted already
+ function addJoins(fieldList, addWildcard, addPseudoconstant) {
var fields = _.cloneDeep(fieldList);
_.each(links[$scope.entity], function(link) {
var linkFields = _.cloneDeep(entityFields(link.entity)),
wildCard = addWildcard ? [{id: link.alias + '.*', text: link.alias + '.*', 'description': 'All core ' + link.entity + ' fields'}] : [];
if (linkFields) {
+ if (addPseudoconstant) {
+ addPseudoconstants(linkFields, addPseudoconstant);
+ }
fields.push({
text: link.alias,
description: 'Join to ' + link.entity,
return fields;
}
+ // Note: this function transforms a raw list a-la getFields; not a select2-formatted list
+ function addPseudoconstants(fieldList, toAdd) {
+ var optionFields = _.filter(fieldList, 'options');
+ _.each(optionFields, function(field) {
+ var pos = _.findIndex(fieldList, {name: field.name}) + 1;
+ _.each(toAdd, function(suffix) {
+ var newField = _.cloneDeep(field);
+ newField.name += ':' + suffix;
+ fieldList.splice(pos, 0, newField);
+ });
+ });
+ }
+
$scope.help = function(title, content) {
if (!content) {
$scope.helpTitle = helpTitle;
return info;
};
+ // Returns field list for write params (values, defaults)
$scope.fieldList = function(param) {
return function() {
- var fields = _.cloneDeep($scope.action === 'getFields' ? getFieldList($scope.params.action || 'get') : $scope.fields);
+ var fields = _.cloneDeep(getFieldList($scope.action === 'getFields' ? ($scope.params.action || 'get') : $scope.action, ['name']));
// Disable fields that are already in use
_.each($scope.params[param] || [], function(val) {
- (_.findWhere(fields, {id: val[0]}) || {}).disabled = true;
+ var usedField = val[0].replace(':name', '');
+ (_.findWhere(fields, {id: usedField}) || {}).disabled = true;
+ (_.findWhere(fields, {id: usedField + ':name'}) || {}).disabled = true;
});
return {results: fields};
};
var actionInfo = _.findWhere(actions, {id: $scope.action});
$scope.fields = getFieldList($scope.action);
if (_.contains(['get', 'update', 'delete', 'replace'], $scope.action)) {
- $scope.fieldsAndJoins = addJoins($scope.fields);
- var fieldsAndFunctions = _.cloneDeep($scope.fields);
+ $scope.fieldsAndJoins = addJoins(getFieldList($scope.action, ['name']));
+ var functions = [];
// SQL functions are supported if HAVING is
if (actionInfo.params.having) {
- fieldsAndFunctions.push({
+ functions.push({
text: ts('FUNCTION'),
description: ts('Calculate result of a SQL function'),
children: _.transform(CRM.vars.api4.functions, function(result, fn) {
})
});
}
- $scope.fieldsAndJoinsAndFunctions = addJoins(fieldsAndFunctions, true);
- $scope.fieldsAndJoinsAndFunctionsAndWildcards = addJoins(fieldsAndFunctions, true);
+ $scope.fieldsAndJoinsAndFunctions = addJoins($scope.fields.concat(functions), true);
+ $scope.fieldsAndJoinsAndFunctionsAndWildcards = addJoins(getFieldList($scope.action, ['name', 'label']).concat(functions), true, ['name', 'label']);
} else {
- $scope.fieldsAndJoins = $scope.fields;
+ $scope.fieldsAndJoins = getFieldList($scope.action, ['name']);
$scope.fieldsAndJoinsAndFunctions = $scope.fields;
- $scope.fieldsAndJoinsAndFunctionsAndWildcards = _.cloneDeep($scope.fields);
+ $scope.fieldsAndJoinsAndFunctionsAndWildcards = getFieldList($scope.action, ['name', 'label']);
}
$scope.fieldsAndJoinsAndFunctionsAndWildcards.unshift({id: '*', text: '*', 'description': 'All core ' + $scope.entity + ' fields'});
_.each(actionInfo.params, function (param, name) {
$scope.havingOptions.length = 0;
_.each(values, function(item) {
var pieces = item.split(' AS '),
- alias = _.trim(pieces[pieces.length - 1]);
+ alias = _.trim(pieces[pieces.length - 1]).replace(':label', ':name');
$scope.havingOptions.push({id: alias, text: alias});
});
});
$el.crmDatepicker({time: (field.input_attrs && field.input_attrs.time) || false});
}
} else if (_.includes(['=', '!=', '<>', 'IN', 'NOT IN'], op) && (field.fk_entity || field.options || dataType === 'Boolean')) {
- if (field.fk_entity) {
- $el.crmEntityRef({entity: field.fk_entity, select:{multiple: multi}});
- } else if (field.options) {
+ if (field.options) {
+ var id = field.pseudoconstant || 'id';
$el.addClass('loading').attr('placeholder', ts('- select -')).crmSelect2({multiple: multi, data: [{id: '', text: ''}]});
loadFieldOptions(field.entity || entity).then(function(data) {
- var options = [];
- _.each(_.findWhere(data, {name: field.name}).options, function(val, key) {
- options.push({id: key, text: val});
- });
- $el.removeClass('loading').select2({data: options, multiple: multi});
+ var options = _.transform(data[field.name].options, function(options, opt) {
+ options.push({id: opt[id], text: opt.label, description: opt.description, color: opt.color, icon: opt.icon});
+ }, []);
+ $el.removeClass('loading').crmSelect2({data: options, multiple: multi});
});
+ } else if (field.fk_entity) {
+ $el.crmEntityRef({entity: field.fk_entity, select:{multiple: multi}});
} else if (dataType === 'Boolean') {
$el.attr('placeholder', ts('- select -')).crmSelect2({allowClear: false, multiple: multi, placeholder: ts('- select -'), data: [
{id: 'true', text: ts('Yes')},
function loadFieldOptions(entity) {
if (!fieldOptions[entity + action]) {
fieldOptions[entity + action] = crmApi4(entity, 'getFields', {
- loadOptions: true,
+ loadOptions: ['id', 'name', 'label', 'description', 'color', 'icon'],
action: action,
- where: [["options", "!=", false]],
- select: ["name", "options"]
- });
+ where: [['options', '!=', false]],
+ select: ['options']
+ }, 'name');
}
return fieldOptions[entity + action];
}
}
function getField(fieldName, entity, action) {
+ var suffix = fieldName.split(':')[1];
+ fieldName = fieldName.split(':')[0];
var fieldNames = fieldName.split('.');
- return get(entity, fieldNames);
+ var field = get(entity, fieldNames);
+ if (field && suffix) {
+ field.pseudoconstant = suffix;
+ }
+ return field;
function get(entity, fieldNames) {
if (fieldNames.length === 1) {
$options = array_column($options, NULL, 'name');
$this->assertEquals('Fake Type', $options['Fake_Type']['label']);
+ Activity::create()
+ ->addValue('activity_type_id:name', 'Meeting')
+ ->addValue('source_contact_id', $cid)
+ ->addValue('subject', $subject)
+ ->execute();
+
Activity::create()
->addValue('activity_type_id:name', 'Fake_Type')
->addValue('source_contact_id', $cid)
$this->assertEquals('Fake Type', $act[0]['activity_type_id:label']);
$this->assertEquals('Fake_Type', $act[0]['activity_type_id:name']);
$this->assertTrue(is_numeric($act[0]['activity_type_id']));
+
+ $act = Activity::get()
+ ->addHaving('activity_type_id:name', '=', 'Fake_Type')
+ ->addHaving('subject', '=', $subject)
+ ->addSelect('activity_type_id:label')
+ ->addSelect('activity_type_id')
+ ->addSelect('subject')
+ ->execute();
+
+ $this->assertCount(1, $act);
+ $this->assertEquals('Fake Type', $act[0]['activity_type_id:label']);
+ $this->assertTrue(is_numeric($act[0]['activity_type_id']));
+
+ $act = Activity::get()
+ ->addHaving('activity_type_id:name', '=', 'Fake_Type')
+ ->addHaving('subject', '=', $subject)
+ ->addSelect('activity_type_id')
+ ->addSelect('subject')
+ ->execute();
+
+ $this->assertCount(1, $act);
+ $this->assertTrue(is_numeric($act[0]['activity_type_id']));
}
public function testAddressOptions() {