public function entityFields() {
if (!$this->_entityFields) {
$allowedTypes = ['Field', 'Filter', 'Extra'];
- if (method_exists($this, 'getCustomGroup')) {
- $allowedTypes[] = 'Custom';
- }
$getFields = \Civi\API\Request::create($this->getEntityName(), 'getFields', [
'version' => 4,
'checkPermissions' => FALSE,
$joinEntityFields = $joinEntityGet->entityFields();
foreach ($joinEntityFields as $field) {
$field['sql_name'] = '`' . $alias . '`.`' . $field['column_name'] . '`';
+ $field['explicit_join'] = $alias;
$this->addSpecField($alias . '.' . $field['name'], $field);
}
$tableName = CoreUtil::getTableName($entity);
// Save join info to be retrieved by $this->getExplicitJoin()
$this->explicitJoins[$alias] = [
'entity' => $entity,
+ 'alias' => $alias,
'table' => $tableName,
'bridge' => NULL,
];
// For INNER joins, these fields get a sql alias pointing to the bridge entity,
// but an api alias pretending they belong to the join entity.
$field['sql_name'] = '`' . ($side === 'LEFT' ? $alias : $bridgeAlias) . '`.`' . $field['column_name'] . '`';
+ $field['explicit_join'] = $alias;
$this->addSpecField($alias . '.' . $name, $field);
if ($field['type'] === 'Field') {
$fakeFields[$field['column_name']] = '`' . $bridgeAlias . '`.`' . $field['column_name'] . '`';
else {
$fieldArray['sql_name'] = '`' . $baseTableAlias . '`.`' . $link->getBaseColumn() . '`';
}
+ $fieldArray['implicit_join'] = $link->getBaseColumn();
+ $fieldArray['explicit_join'] = $explicitJoin ? $explicitJoin['alias'] : NULL;
// Custom fields will already have the group name prefixed
$fieldName = $isCustom ? explode('.', $fieldArray['name'])[1] : $fieldArray['name'];
$this->addSpecField($joinTreeNode[$joinName]['#path'] . $fieldName, $fieldArray);
$this->apiFieldSpec[$path] = FALSE;
return;
}
- $this->apiFieldSpec[$path] = $field;
+ $this->apiFieldSpec[$path] = $field + [
+ 'implicit_join' => NULL,
+ 'explicit_join' => NULL,
+ ];
}
/**
return \CRM_Utils_System::url($path, NULL, $absolute, NULL, FALSE);
}
+ /**
+ * @param $column
+ * @param $data
+ * @return array{entity: string, input_type: string, data_type: string, options: bool, serialize: bool, fk_entity: string, value_key: string, record: array, value: mixed}|null
+ */
private function formatEditableColumn($column, $data) {
+ $editable = $this->getEditableInfo($column['key']);
+ if (!empty($data[$editable['id_path']])) {
+ $editable['record'] = [
+ $editable['id_key'] => $data[$editable['id_path']],
+ ];
+ $editable['value'] = $data[$editable['value_path']];
+ \CRM_Utils_Array::remove($editable, 'id_key', 'id_path', 'value_path');
+ return $editable;
+ }
+ return NULL;
+ }
+ /**
+ * @param $key
+ * @return array{entity: string, input_type: string, data_type: string, options: bool, serialize: bool, fk_entity: string, value_key: string, value_path: string, id_key: string, id_path: string}|null
+ */
+ private function getEditableInfo($key) {
+ [$key] = explode(':', $key);
+ $field = $this->getField($key);
+ // If field is an implicit join, use the original fk field
+ if (!empty($field['implicit_join'])) {
+ return $this->getEditableInfo(substr($key, 0, -1 - strlen($field['name'])));
+ }
+ if ($field) {
+ $idKey = CoreUtil::getIdFieldName($field['entity']);
+ $idPath = ($field['explicit_join'] ? $field['explicit_join'] . '.' : '') . $idKey;
+ // Hack to support editing relationships
+ if ($field['entity'] === 'RelationshipCache') {
+ $field['entity'] = 'Relationship';
+ $idPath = ($field['explicit_join'] ? $field['explicit_join'] . '.' : '') . 'relationship_id';
+ }
+ return [
+ 'entity' => $field['entity'],
+ 'input_type' => $field['input_type'],
+ 'data_type' => $field['data_type'],
+ 'options' => !empty($field['options']),
+ 'serialize' => !empty($field['serialize']),
+ 'fk_entity' => $field['fk_entity'],
+ 'value_key' => $field['name'],
+ 'value_path' => $key,
+ 'id_key' => $idKey,
+ 'id_path' => $idPath,
+ ];
+ }
+ return NULL;
}
+ /**
+ * @param $column
+ * @param $data
+ * @return array{url: string, width: int, height: int}
+ */
private function formatImage($column, $data) {
$tokenExpr = $column['rewrite'] ?: '[' . $column['key'] . ']';
return [
$possibleTokens .= implode('', array_column($column['links'], 'text'));
}
- // Select value fields for in-place editing
- if (isset($column['editable']['value'])) {
- $additions[] = $column['editable']['value'];
- $additions[] = $column['editable']['id'];
+ // Select id & value for in-place editing
+ if (!empty($column['editable'])) {
+ $editable = $this->getEditableInfo($column['key']);
+ if ($editable) {
+ $additions[] = $editable['value_path'];
+ $additions[] = $editable['id_path'];
+ }
}
}
// Add fields referenced via token
$field['fieldName'] = $field['name'];
// Hack for RelationshipCache to make Relationship fields editable
if ($entity['name'] === 'RelationshipCache') {
- $entity['primary_key'] = ['relationship_id'];
if (in_array($field['name'], ['is_active', 'start_date', 'end_date'])) {
$field['readonly'] = FALSE;
}
this.toggleEditable = function(col) {
if (col.editable) {
delete col.editable;
- return;
- }
-
- var info = searchMeta.parseExpr(col.key),
- arg = _.findWhere(info.args, {type: 'field'}) || {},
- value = col.key.split(':')[0];
- if (!arg.field || info.fn) {
- delete col.editable;
- return;
- }
- // If field is an implicit join, use the original fk field
- if (arg.field.name !== arg.field.fieldName) {
- value = value.substr(0, value.lastIndexOf('.'));
- info = searchMeta.parseExpr(value);
- arg = info.args[0];
+ } else {
+ col.editable = true;
}
- col.editable = {
- // Hack to support editing relationships
- entity: arg.field.entity.replace('RelationshipCache', 'Relationship'),
- input_type: arg.field.input_type,
- data_type: arg.field.data_type,
- options: !!arg.field.options,
- serialize: !!arg.field.serialize,
- fk_entity: arg.field.fk_entity,
- id: arg.prefix + searchMeta.getEntity(arg.field.entity).primary_key[0],
- name: arg.field.name,
- value: value
- };
};
- this.isEditable = function(col) {
+ this.canBeEditable = function(col) {
var expr = ctrl.getExprFromSelect(col.key),
info = searchMeta.parseExpr(expr);
return !col.image && !col.rewrite && !col.link && !info.fn && info.args[0] && info.args[0].field && !info.args[0].field.readonly;
if (column.link) {
ctrl.onChangeLink(column, column.link.path, '');
} else {
+ delete column.editable;
var defaultLink = ctrl.getLinks(column.key)[0];
column.link = {path: defaultLink ? defaultLink.path : 'civicrm/'};
ctrl.onChangeLink(column, null, column.link.path);
<crm-search-admin-token-select ng-if="col.rewrite" model="col" field="rewrite" suffix=":label"></crm-search-admin-token-select>
</div>
<div class="form-inline">
- <label ng-if="$ctrl.parent.isEditable(col)" title="{{:: ts('Users will be able to click to edit this field.') }}">
+ <label ng-if="$ctrl.parent.canBeEditable(col)" title="{{:: ts('Users will be able to click to edit this field.') }}">
<input type="checkbox" ng-checked="col.editable" ng-click="$ctrl.parent.toggleEditable(col)">
{{:: ts('In-Place Edit') }}
</label>
- <label ng-if="!$ctrl.parent.isEditable(col)" class="disabled" title="{{:: ts('Read-only or rewritten fields cannot be editable.') }}">
+ <label ng-if="!$ctrl.parent.canBeEditable(col)" class="disabled" title="{{:: ts('Read-only or rewritten fields cannot be editable.') }}">
<input type="checkbox" disabled>
{{:: ts('In-Place Edit') }}
</label>
-<crm-search-display-editable row="row" col="$ctrl.settings.columns[colIndex]" on-success="$ctrl.runSearch(row)" cancel="$ctrl.editing = null;" ng-if="col.editable && $ctrl.editing && $ctrl.editing[0] === rowIndex && $ctrl.editing[1] === col.key"></crm-search-display-editable>
-<span ng-if="::!colData.links && !colData.img" ng-class="{'crm-editable-enabled': colData.edit && !$ctrl.editing}" ng-click="col.edit && !$ctrl.editing && ($ctrl.editing = [rowIndex, col.key])">
+<crm-search-display-editable row="row" col="colData" on-success="$ctrl.runSearch(row)" cancel="$ctrl.editing = null;" ng-if="colData.edit && $ctrl.editing && $ctrl.editing[0] === rowIndex && $ctrl.editing[1] === colIndex"></crm-search-display-editable>
+<span ng-if="::!colData.links && !colData.img" ng-class="{'crm-editable-enabled': colData.edit && !$ctrl.editing}" ng-click="colData.edit && !$ctrl.editing && ($ctrl.editing = [rowIndex, colIndex])">
{{:: $ctrl.formatFieldValue(colData) }}
</span>
<span ng-if="::colData.links">
this.$onInit = function() {
col = this.col;
- this.value = _.cloneDeep(this.row[col.editable.value].raw);
- initialValue = _.cloneDeep(this.row[col.editable.value].raw);
+ this.value = _.cloneDeep(col.edit.value);
+ initialValue = _.cloneDeep(col.edit.value);
this.field = {
- data_type: col.editable.data_type,
- input_type: col.editable.input_type,
- name: col.editable.name,
- options: col.editable.options,
- fk_entity: col.editable.fk_entity,
- serialize: col.editable.serialize,
+ data_type: col.edit.data_type,
+ input_type: col.edit.input_type,
+ name: col.edit.value_key,
+ options: col.edit.options,
+ fk_entity: col.edit.fk_entity,
+ serialize: col.edit.serialize,
};
$(document).on('keydown.crmSearchDisplayEditable', function(e) {
ctrl.cancel();
return;
}
- var values = {id: ctrl.row[col.editable.id].raw};
- values[col.editable.name] = ctrl.value;
+ var record = _.cloneDeep(col.edit.record);
+ record[col.edit.value_key] = ctrl.value;
$('input', $element).attr('disabled', true);
- crmStatus({}, crmApi4(col.editable.entity, 'update', {
- values: values
+ crmStatus({}, crmApi4(col.edit.entity, 'update', {
+ values: record
})).then(ctrl.onSuccess);
};
function loadOptions() {
- var cacheKey = col.editable.entity + ' ' + ctrl.field.name;
+ var cacheKey = col.edit.entity + ' ' + ctrl.field.name;
if (optionsCache[cacheKey]) {
ctrl.field.options = optionsCache[cacheKey];
return;
}
- crmApi4(col.editable.entity, 'getFields', {
+ crmApi4(col.edit.entity, 'getFields', {
action: 'update',
select: ['options'],
loadOptions: ['id', 'name', 'label', 'description', 'color', 'icon'],