*/
protected $select = [];
+ /**
+ * Field(s) by which to group the results.
+ *
+ * @var array
+ */
+ protected $groupBy = [];
+
public function _run(Result $result) {
$this->setDefaultWhereClause();
$this->expandSelectClauseWildcards();
return $result;
}
+ /**
+ * @return array
+ */
+ public function getGroupBy(): array {
+ return $this->groupBy;
+ }
+
+ /**
+ * @param array $groupBy
+ * @return $this
+ */
+ public function setGroupBy(array $groupBy) {
+ $this->groupBy = $groupBy;
+ return $this;
+ }
+
+ /**
+ * @param string $field
+ * @return $this
+ */
+ public function addGroupBy(string $field) {
+ $this->groupBy[] = $field;
+ return $this;
+ }
+
}
*/
public $debugOutput = NULL;
+ /**
+ * @var array
+ */
+ public $groupBy = [];
+
/**
* @param \Civi\Api4\Generic\DAOGetAction $apiGet
*/
$this->checkPermissions = $apiGet->getCheckPermissions();
$this->select = $apiGet->getSelect();
$this->where = $apiGet->getWhere();
+ $this->groupBy = $apiGet->getGroupBy();
$this->orderBy = $apiGet->getOrderBy();
$this->limit = $apiGet->getLimit();
$this->offset = $apiGet->getOffset();
$this->buildWhereClause();
$this->buildOrderBy();
$this->buildLimit();
+ $this->buildGroupBy();
return $this->query->toSQL();
}
$this->debugOutput['sql'][] = $sql;
}
$query = \CRM_Core_DAO::executeQuery($sql);
-
+ $i = 0;
while ($query->fetch()) {
+ $id = $query->id ?? $i++;
if (in_array('row_count', $this->select)) {
$results[]['row_count'] = (int) $query->c;
break;
}
- $results[$query->id] = [];
+ $results[$id] = [];
foreach ($this->select as $alias) {
$returnName = $alias;
if ($this->isOneToOneField($alias)) {
$alias = str_replace('.', '_', $alias);
- $results[$query->id][$returnName] = property_exists($query, $alias) ? $query->$alias : NULL;
+ $results[$id][$returnName] = property_exists($query, $alias) ? $query->$alias : NULL;
}
- };
+ }
}
$event = new PostSelectQueryEvent($results, $this);
\Civi::dispatcher()->dispatch(Events::POST_SELECT_QUERY, $event);
return;
}
else {
- // Always select id field
- $this->select = array_merge(['id'], $this->select);
+ // Always select ID (unless we're doing groupBy).
+ if (!$this->groupBy) {
+ $this->select = array_merge(['id'], $this->select);
+ }
// Expand wildcards in joins (the api wrapper already expanded non-joined wildcards)
$wildFields = array_filter($this->select, function($item) {
}
}
+ /**
+ *
+ */
+ protected function buildGroupBy() {
+ foreach ($this->groupBy as $field) {
+ if ($this->isOneToOneField($field) && $this->getField($field)) {
+ $this->query->groupBy($field['sql_name']);
+ }
+ else {
+ throw new \API_Exception("Invalid field. Cannot group by $field");
+ }
+ }
+ }
+
/**
* Recursively validate and transform a branch or leaf clause array to SQL.
*
<div class="api4-input form-inline">
<input class="collapsible-optgroups form-control" ng-model="controls[name]" crm-ui-select="{formatResult: formatSelect2Item, formatSelection: formatSelect2Item, data: fieldList(name), placeholder: ts('Add %1', {1: name.slice(0, -1)})}"/>
</div>
- </fieldset>
+ </fieldset><fieldset ng-if="availableParams.groupBy" ng-mouseenter="help('groupBy', availableParams.groupBy)" ng-mouseleave="help()">
+ <legend>groupBy<span class="crm-marker" ng-if="availableParams.groupBy.required"> *</span></legend>
+ <div ng-model="params.groupBy" ui-sortable="{axis: 'y'}">
+ <div class="api4-input form-inline" ng-repeat="(pos, field) in params.groupBy">
+ <i class="crm-i fa-arrows"></i>
+ <input class="collapsible-optgroups form-control" ng-model="params.groupBy[pos]" crm-ui-select="{data: fieldsAndJoins, allowClear: true, placeholder: 'Field'}" />
+ </div>
+ </div>
+ <div class="api4-input form-inline">
+ <input class="collapsible-optgroups form-control" ng-model="controls.groupBy" crm-ui-select="{data: fieldsAndJoins}" placeholder="Add groupBy" />
+ </div>
+ </fieldset>
<fieldset ng-if="availableParams.orderBy" ng-mouseenter="help('orderBy', availableParams.orderBy)" ng-mouseleave="help()">
<legend>orderBy<span class="crm-marker" ng-if="availableParams.orderBy.required"> *</span></legend>
- <div class="api4-input form-inline" ng-repeat="clause in params.orderBy">
- <input class="collapsible-optgroups form-control" ng-model="clause[0]" crm-ui-select="{data: fieldsAndJoins, allowClear: true, placeholder: 'Field'}" />
- <select class="form-control" ng-model="clause[1]">
- <option value="ASC">ASC</option>
- <option value="DESC">DESC</option>
- </select>
+ <div ng-model="params.orderBy" ui-sortable="{axis: 'y'}">
+ <div class="api4-input form-inline" ng-repeat="clause in params.orderBy">
+ <i class="crm-i fa-arrows"></i>
+ <input class="collapsible-optgroups form-control" ng-model="clause[0]" crm-ui-select="{data: fieldsAndJoins, allowClear: true, placeholder: 'Field'}" />
+ <select class="form-control" ng-model="clause[1]">
+ <option value="ASC">ASC</option>
+ <option value="DESC">DESC</option>
+ </select>
+ </div>
</div>
<div class="api4-input form-inline">
<input class="collapsible-optgroups form-control" ng-model="controls.orderBy" crm-ui-select="{data: fieldsAndJoins}" placeholder="Add orderBy" />
</div>
</fieldset>
+
<fieldset ng-if="availableParams.chain" ng-mouseenter="help('chain', availableParams.chain)" ng-mouseleave="help()">
<legend>chain</legend>
<div class="api4-input form-inline" ng-repeat="clause in params.chain" api4-exp-chain="clause" entities="entities" main-entity="entity" >
};
$scope.isSpecial = function(name) {
- var specialParams = ['select', 'fields', 'action', 'where', 'values', 'defaults', 'orderBy', 'chain'];
+ var specialParams = ['select', 'fields', 'action', 'where', 'values', 'defaults', 'orderBy', 'chain', 'groupBy'];
return _.contains(specialParams, name);
};
deep: format === 'json'
});
}
- if (typeof objectParams[name] !== 'undefined') {
+ if (typeof objectParams[name] !== 'undefined' || name === 'groupBy') {
$scope.$watch('params.' + name, function(values) {
// Remove empty values
_.each(values, function(clause, index) {
var field = value;
$timeout(function() {
if (field) {
- var defaultOp = _.cloneDeep(objectParams[name]);
- if (name === 'chain') {
- var num = $scope.params.chain.length;
- defaultOp[0] = field;
- field = 'name_me_' + num;
+ if (name === 'groupBy') {
+ $scope.params[name].push(field);
+ } else {
+ var defaultOp = _.cloneDeep(objectParams[name]);
+ if (name === 'chain') {
+ var num = $scope.params.chain.length;
+ defaultOp[0] = field;
+ field = 'name_me_' + num;
+ }
+ $scope.params[name].push([field, defaultOp]);
}
- $scope.params[name].push([field, defaultOp]);
$scope.controls[name] = null;
}
});