diff --git a/CRM/Contact/BAO/SavedSearch.php b/CRM/Contact/BAO/SavedSearch.php index 5dcc905d8a4..9fc320aff14 100644 --- a/CRM/Contact/BAO/SavedSearch.php +++ b/CRM/Contact/BAO/SavedSearch.php @@ -251,6 +251,21 @@ public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event): // Set by mysql unset($event->params['modified_date']); + // Delete empty form values and save as null if completely empty + if (isset($event->params['form_values']) && is_array($event->params['form_values'])) { + // Exclude legacy smart groups by checking if api_entity is set + if (!empty($event->params['api_entity'])) { + foreach ($event->params['form_values'] as $key => $value) { + if (is_array($value) && !$value) { + unset($event->params['form_values'][$key]); + } + } + if (!$event->params['form_values']) { + $event->params['form_values'] = ''; + } + } + } + // Flush angular caches to refresh search displays if (isset($event->params['api_params'])) { Civi::container()->get('angular')->clear(); diff --git a/CRM/Upgrade/Incremental/SmartGroups.php b/CRM/Upgrade/Incremental/SmartGroups.php index 862eefe79d2..d4e52135d13 100644 --- a/CRM/Upgrade/Incremental/SmartGroups.php +++ b/CRM/Upgrade/Incremental/SmartGroups.php @@ -231,11 +231,15 @@ public function renameFields($pairs) { * @return mixed */ protected function getSearchesWithField($field) { - return civicrm_api3('SavedSearch', 'get', [ - 'options' => ['limit' => 0], - 'form_values' => ['LIKE' => "%{$field}%"], - 'return' => ['id', 'form_values'], - ])['values']; + $apiGet = \Civi\Api4\SavedSearch::get(FALSE); + $apiGet->addSelect('id', 'form_values'); + $apiGet->addWhere('form_values', 'LIKE', "%{$field}%"); + // Avoid error if column hasn't been added yet by pending upgrades + if (version_compare(\CRM_Core_BAO_Domain::version(), '5.24', '>=')) { + // Exclude SearchKit searches + $apiGet->addWhere('api_entity', 'IS NULL'); + } + return $apiGet->execute()->column(NULL, 'id'); } /** diff --git a/Civi/Api4/Generic/Traits/SavedSearchInspectorTrait.php b/Civi/Api4/Generic/Traits/SavedSearchInspectorTrait.php index b44649e9b4e..e0518dc5fb9 100644 --- a/Civi/Api4/Generic/Traits/SavedSearchInspectorTrait.php +++ b/Civi/Api4/Generic/Traits/SavedSearchInspectorTrait.php @@ -445,7 +445,7 @@ protected function getJoinLabel($joinAlias) { $joinCount[$entityName] = 1; } $label = CoreUtil::getInfoItem($entityName, 'title'); - $this->_joinMap[$alias] = $label . $num; + $this->_joinMap[$alias] = $this->savedSearch['form_values']['join'][$alias] ?? "$label$num"; } } return $this->_joinMap[$joinAlias]; diff --git a/ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php b/ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php index fcebff5fa33..1005a3788b0 100644 --- a/ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php +++ b/ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php @@ -173,7 +173,7 @@ public function _run(\Civi\Api4\Generic\Result $result) { ->setSavedSearch($displayTag['search-name']); } $display = $displayGet - ->addSelect('*', 'type:name', 'type:icon', 'saved_search_id.name', 'saved_search_id.label', 'saved_search_id.api_entity', 'saved_search_id.api_params') + ->addSelect('*', 'type:name', 'type:icon', 'saved_search_id.name', 'saved_search_id.label', 'saved_search_id.api_entity', 'saved_search_id.api_params', 'saved_search_id.form_values') ->execute()->first(); if (!$display) { continue; diff --git a/ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js b/ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js index 7cd0eb8b870..1228c8e8302 100644 --- a/ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js +++ b/ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js @@ -128,21 +128,23 @@ countEntity(mainEntity.entity); _.each(ctrl.display.settings['saved_search_id.api_params'].join, function(join) { - var joinInfo = join[0].split(' AS '), - entity = afGui.getEntity(joinInfo[0]), - joinEntity = afGui.getEntity(join[2]); + const joinInfo = join[0].split(' AS '); + const entity = afGui.getEntity(joinInfo[0]); + const bridgeEntity = afGui.getEntity(join[2]); + const defaultLabel = entity.label + countEntity(entity.entity); + const formValues = ctrl.display.settings['saved_search_id.form_values'] || {}; entities.push({ name: entity.entity, prefix: joinInfo[1] + '.', - label: entity.label + countEntity(entity.entity), + label: (formValues && formValues.join && formValues.join[joinInfo[1]]) || defaultLabel, fields: entity.fields, }); - if (joinEntity) { + if (bridgeEntity) { entities.push({ - name: joinEntity.entity, + name: bridgeEntity.entity, prefix: joinInfo[1] + '.', - label: joinEntity.label + countEntity(joinEntity.entity), - fields: _.omit(joinEntity.fields, _.keys(entity.fields)), + label: bridgeEntity.label + countEntity(bridgeEntity.entity), + fields: _.omit(bridgeEntity.fields, _.keys(entity.fields)), }); } }); diff --git a/ext/search_kit/ang/crmSearchAdmin.module.js b/ext/search_kit/ang/crmSearchAdmin.module.js index cd43783846d..c0211129bbc 100644 --- a/ext/search_kit/ang/crmSearchAdmin.module.js +++ b/ext/search_kit/ang/crmSearchAdmin.module.js @@ -29,7 +29,7 @@ savedSearch: function($route, crmApi4) { var params = $route.current.params; return crmApi4('SavedSearch', 'get', { - select: ['id', 'name', 'label', 'description', 'api_entity', 'api_params', 'is_template', 'expires_date', 'GROUP_CONCAT(DISTINCT entity_tag.tag_id) AS tag_id'], + select: ['id', 'name', 'label', 'description', 'api_entity', 'api_params', 'form_values', 'is_template', 'expires_date', 'GROUP_CONCAT(DISTINCT entity_tag.tag_id) AS tag_id'], where: [['id', '=', params.id]], join: [ ['EntityTag AS entity_tag', 'LEFT', ['entity_tag.entity_table', '=', '"civicrm_saved_search"'], ['id', '=', 'entity_tag.entity_id']], @@ -51,7 +51,7 @@ savedSearch: function($route, crmApi4) { var params = $route.current.params; return crmApi4('SavedSearch', 'get', { - select: ['label', 'description', 'api_entity', 'api_params', 'is_template', 'expires_date', 'GROUP_CONCAT(DISTINCT entity_tag.tag_id) AS tag_id'], + select: ['label', 'description', 'api_entity', 'api_params', 'form_values', 'is_template', 'expires_date', 'GROUP_CONCAT(DISTINCT entity_tag.tag_id) AS tag_id'], where: [['id', '=', params.id]], join: [ ['EntityTag AS entity_tag', 'LEFT', ['entity_tag.entity_table', '=', '"civicrm_saved_search"'], ['id', '=', 'entity_tag.entity_id']], @@ -160,11 +160,11 @@ } } // Get join metadata matching a given expression like "Email AS Contact_Email_contact_id_01" - function getJoin(fullNameOrAlias) { + function getJoin(savedSearch, fullNameOrAlias) { var alias = _.last(fullNameOrAlias.split(' AS ')), path = alias, baseEntity = searchEntity, - label = [], + labels = [], join, result; while (path.length) { @@ -177,13 +177,20 @@ } path = path.replace(join.alias + '_', ''); var num = parseInt(path.substr(0, 2), 10); - label.push(join.label + (num > 1 ? ' ' + num : '')); + labels.push(join.label + (num > 1 ? ' ' + num : '')); path = path.replace(/^\d\d_?/, ''); if (path.length) { baseEntity = join.entity; } } - result = _.assign(_.cloneDeep(join), {label: label.join(' - '), alias: alias, baseEntity: baseEntity}); + const defaultLabel = labels.join(' - '); + result = _.assign(_.cloneDeep(join), { + label: (savedSearch && savedSearch.form_values && savedSearch.form_values.join && savedSearch.form_values.join[alias]) || defaultLabel, + defaultLabel: defaultLabel, + alias: alias, + baseEntity: baseEntity, + icon: getEntity(join.entity).icon, + }); // Add the numbered suffix to the join conditions // If this is a deep join, also add the base entity prefix var prefix = alias.replace(new RegExp('_?' + join.alias + '_?\\d?\\d?$'), ''); @@ -212,7 +219,7 @@ field; // If 2 or more segments, the first might be the name of a join if (dotSplit.length > 1) { - join = getJoin(dotSplit[0]); + join = getJoin(null, dotSplit[0]); if (join) { dotSplit.shift(); entityName = join.entity; @@ -361,7 +368,7 @@ } return info; } - function getDefaultLabel(col) { + function getDefaultLabel(col, savedSearch) { var info = parseExpr(col), label = ''; if (info.fn) { @@ -369,7 +376,8 @@ } _.each(info.args, function(arg) { if (arg.join) { - label += (label ? ' ' : '') + arg.join.label + ':'; + let join = getJoin(savedSearch, arg.join.alias); + label += (label ? ' ' : '') + join.label + ':'; } if (arg.field) { label += (label ? ' ' : '') + arg.field.label; @@ -379,7 +387,7 @@ }); return label; } - function fieldToColumn(fieldExpr, defaults) { + function fieldToColumn(fieldExpr, defaults, savedSearch) { var info = parseExpr(fieldExpr), field = (_.findWhere(info.args, {type: 'field'}) || {}).field || {}, values = _.merge({ @@ -388,7 +396,7 @@ dataType: (info.fn && info.fn.data_type) || field.data_type }, defaults); if (defaults.label === true) { - values.label = getDefaultLabel(fieldExpr); + values.label = getDefaultLabel(fieldExpr, savedSearch); } if (defaults.sortable) { values.sortable = field.type && field.type !== 'Pseudo'; @@ -448,11 +456,11 @@ return null; }, // Find all possible search columns that could serve as contact_id for a smart group - getSmartGroupColumns: function(api_entity, api_params) { - var joins = _.pluck((api_params.join || []), 0); - return _.transform([api_entity].concat(joins), function(columns, joinExpr) { + getSmartGroupColumns: function(savedSearch) { + var joins = _.pluck((savedSearch.api_params.join || []), 0); + return _.transform([savedSearch.api_entity].concat(joins), function(columns, joinExpr) { var joinName = joinExpr.split(' AS '), - joinInfo = joinName[1] ? getJoin(joinName[1]) : {entity: joinName[0]}, + joinInfo = joinName[1] ? getJoin(savedSearch, joinName[1]) : {entity: joinName[0]}, entity = getEntity(joinInfo.entity), prefix = joinInfo.alias ? joinInfo.alias + '.' : ''; _.each(entity.fields, function(field) { diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearch-for.html b/ext/search_kit/ang/crmSearchAdmin/crmSearch-for.html index deaa53466d6..26f39623c82 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearch-for.html +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearch-for.html @@ -6,7 +6,10 @@