Skip to content

Commit

Permalink
Search ext: Extend api4 smart groups to work with HAVING
Browse files Browse the repository at this point in the history
  • Loading branch information
colemanw committed Sep 30, 2020
1 parent 9b96638 commit 579c860
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 11 deletions.
16 changes: 10 additions & 6 deletions CRM/Contact/BAO/GroupContactCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
+--------------------------------------------------------------------+
*/

use Civi\Api4\Query\SqlExpression;

/**
*
* @package CRM
Expand Down Expand Up @@ -701,16 +703,18 @@ public static function invalidateGroupContactCache($groupID) {
*/
protected static function getApiSQL(array $savedSearch, string $addSelect, string $excludeClause) {
$apiParams = $savedSearch['api_params'] + ['select' => ['id'], 'checkPermissions' => FALSE];
list($idField) = explode(' AS ', $apiParams['select'][0]);
$apiParams['select'] = [
$addSelect,
$idField,
];
$idField = SqlExpression::convert($apiParams['select'][0], TRUE)->getAlias();
// Unless there's a HAVING clause, we don't care about other columns
if (empty($apiParams['having'])) {
$apiParams['select'] = array_slice($apiParams['select'], 0, 1);
}
$api = \Civi\API\Request::create($savedSearch['api_entity'], 'get', $apiParams);
$query = new \Civi\Api4\Query\Api4SelectQuery($api);
$query->forceSelectId = FALSE;
$query->getQuery()->having("$idField $excludeClause");
return $query->getSql();
$sql = $query->getSql();
// Place sql in a nested sub-query, otherwise HAVING is impossible on any field other than contact_id
return "SELECT $addSelect, $idField FROM ($sql) api_query";
}

/**
Expand Down
2 changes: 2 additions & 0 deletions CRM/Utils/API/HTMLInputCoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public function getSkipFields() {
'header',
// https://lab.civicrm.org/dev/core/issues/1286
'footer',
// SavedSearch entity
'api_params',
];
$custom = CRM_Core_DAO::executeQuery('SELECT id FROM civicrm_custom_field WHERE html_type = "RichTextEditor"');
while ($custom->fetch()) {
Expand Down
4 changes: 2 additions & 2 deletions ext/search/ang/search/SaveSmartGroup.ctrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@
}

// Pick the first applicable column for contact id
model.api_params.select[0] = _.intersection(model.api_params.select, _.pluck($scope.columns, 'id'))[0] || $scope.columns[0].name;
model.api_params.select.length = 1;
model.api_params.select.unshift(_.intersection(model.api_params.select, _.pluck($scope.columns, 'id'))[0] || $scope.columns[0].id);

if (!CRM.checkPerm('administer reserved groups')) {
$scope.groupEntityRefParams.api.params.is_reserved = 0;
Expand Down Expand Up @@ -76,6 +75,7 @@
group.visibility = model.visibility;
group.group_type = model.group_type;
group.saved_search_id = '$id';
model.api_params.select = _.unique(model.api_params.select);
var savedSearch = {
api_entity: model.api_entity,
api_params: model.api_params
Expand Down
4 changes: 2 additions & 2 deletions ext/search/ang/search/crmSearch.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,8 @@
api_params: _.cloneDeep(angular.extend({}, ctrl.params, {version: 4}))
};
delete model.api_params.orderBy;
if (ctrl.load && ctrl.load.api_params) {
model.api_params.select = ctrl.load.api_params.select;
if (ctrl.load && ctrl.load.api_params && ctrl.load.api_params.select && ctrl.load.api_params.select[0]) {
model.api_params.select.unshift(ctrl.load.api_params.select[0]);
}
var options = CRM.utils.adjustDialogDefaults({
autoOpen: false,
Expand Down
1 change: 0 additions & 1 deletion ext/search/ang/search/saveSmartGroup.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<form id="bootstrap-theme">
<div ng-controller="SaveSmartGroup">
<div crm-ui-debug="model"></div>
<input class="form-control" id="api-save-search-select-group" ng-model="model.id" crm-entityref="groupEntityRefParams" >
<label ng-show="!model.id">{{:: ts('Or') }}</label>
<input class="form-control" placeholder="{{:: ts('Create new group') }}" ng-model="model.title" ng-show="!model.id">
Expand Down
32 changes: 32 additions & 0 deletions tests/phpunit/api/v4/Entity/SavedSearchTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,36 @@ public function testEmailSmartGroup() {
$this->assertArrayNotHasKey($out['id'], $ins['values']);
}

public function testSmartGroupWithHaving() {
$in = Contact::create(FALSE)->addValue('first_name', 'yes')->addValue('last_name', 'siree')->execute()->first();
$in2 = Contact::create(FALSE)->addValue('first_name', 'yessir')->addValue('last_name', 'ee')->execute()->first();
$out = Contact::create(FALSE)->addValue('first_name', 'yess')->execute()->first();

$savedSearch = civicrm_api4('SavedSearch', 'create', [
'values' => [
'api_entity' => 'Contact',
'api_params' => [
'version' => 4,
'select' => ['id', 'CONCAT(first_name, last_name) AS whole_name'],
'where' => [
['id', '>=', $in['id']],
],
'having' => [
['whole_name', '=', 'yessiree'],
],
],
],
'chain' => [
'group' => ['Group', 'create', ['values' => ['title' => 'Having Test', 'saved_search_id' => '$id']], 0],
],
])->first();

// Oops we don't have an api4 syntax yet for selecting contacts in a group.
$ins = civicrm_api3('Contact', 'get', ['group' => $savedSearch['group']['name'], 'options' => ['limit' => 0]]);
$this->assertCount(2, $ins['values']);
$this->assertArrayHasKey($in['id'], $ins['values']);
$this->assertArrayHasKey($in2['id'], $ins['values']);
$this->assertArrayNotHasKey($out['id'], $ins['values']);
}

}

0 comments on commit 579c860

Please sign in to comment.