}
if ($groupID || !empty($group)) {
- $groups = CRM_Contact_BAO_Group::getGroupsHierarchy($ids);
+ $groups = CRM_Contact_BAO_Group::getGroupsHierarchy($ids, NULL, '- ');
$attributes['skiplabel'] = TRUE;
$elements = [];
// add select for groups
// Get hierarchical listing of groups, respecting ACLs for CRM-16836.
- $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($this->_group, NULL, ' ');
+ $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($this->_group, NULL, '- ');
if (!empty($searchOptions['groups'])) {
$this->addField('group', [
'entity' => 'group_contact',
// multiselect for groups
if ($form->_group) {
// Arrange groups into hierarchical listing (child groups follow their parents and have indentation spacing in title)
- $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($form->_group, NULL, ' ', TRUE);
+ $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($form->_group, NULL, '- ', TRUE);
$form->add('select', 'group', ts('Groups'), $groupHierarchy, FALSE,
['id' => 'group', 'multiple' => 'multiple', 'class' => 'crm-select2']
CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_Form::addRadio');
}
- if ($attributes && !is_array($attributes)) {
+ if ($type !== 'static' && $attributes && !is_array($attributes)) {
// The $attributes param used to allow for strings and would default to an
// empty string. However, now that the variable is heavily manipulated,
// we should expect it to always be an array.
FALSE
);
- $form->add('static', 'pdf_format_header', NULL, ts('Page Format: %1', [1 => '<span class="pdf-format-header-label"></span>']));
$form->addSelect('format_id', [
'label' => ts('Select Format'),
'placeholder' => ts('Default'),
FALSE,
['onChange' => "selectPaper( this.value ); showUpdateFormatChkBox();"]
);
- $form->add('static', 'paper_dimensions', NULL, ts('Width x Height'));
$form->add(
'select',
'orientation',
CRM_Event_BAO_Event::deleteEventLocBlock($this->_oldLocBlockId, $this->_id);
}
- $isUpdateToExistingLocationBlock = !empty($params['loc_event_id']) && (int) $params['loc_event_id'] === $this->locationBlock['loc_block_id'];
+ $isUpdateToExistingLocationBlock = !$deleteOldBlock && !empty($params['loc_event_id']) && (int) $params['loc_event_id'] === $this->locationBlock['loc_block_id'];
// It should be impossible for there to be no default location type. Consider removing this handling
$defaultLocationTypeID = CRM_Core_BAO_LocationType::getDefault()->id ?? 1;
namespace Civi\Api4\Action\Entity;
use Civi\Api4\CustomGroup;
+use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
/**
* Get the names & docblocks of all APIv4 entities.
->execute();
foreach ($customEntities as $customEntity) {
$fieldName = 'Custom_' . $customEntity['name'];
+ $baseEntity = '\Civi\Api4\\' . CustomGroupJoinable::getEntityFromExtends($customEntity['extends']);
$entities[$fieldName] = [
'name' => $fieldName,
'title' => $customEntity['title'],
- 'description' => 'Custom group - extends ' . $customEntity['extends'],
+ 'titlePlural' => $customEntity['title'],
+ 'description' => ts('Custom group for %1', [1 => $baseEntity::getInfo()['titlePlural']]),
'see' => [
'https://docs.civicrm.org/user/en/latest/organising-your-data/creating-custom-fields/#multiple-record-fieldsets',
'\\Civi\\Api4\\CustomGroup',
if ($loadOptions) {
$entity['optionsLoaded'] = TRUE;
}
- // Because multivalue custom pseudo-entities don't have titlePlural
- $entity['titlePlural'] = $entity['titlePlural'] ?? $entity['title'];
$entity['fields'] = civicrm_api4($entity['name'], 'getFields', [
'select' => $getFields,
'where' => [['permission', 'IS NULL']],
.factory('searchMeta', function() {
function getEntity(entityName) {
if (entityName) {
- entityName = entityName === true ? searchEntity : entityName;
return _.find(CRM.vars.search.schema, {name: entityName});
}
}
- function getField(name) {
- var dotSplit = name.split('.'),
+ function getField(fieldName, entityName) {
+ var dotSplit = fieldName.split('.'),
joinEntity = dotSplit.length > 1 ? dotSplit[0] : null,
- fieldName = _.last(dotSplit).split(':')[0],
- entityName = searchEntity;
+ name = _.last(dotSplit).split(':')[0];
// Custom fields contain a dot in their fieldname
// If 3 segments, the first is the joinEntity and the last 2 are the custom field
if (dotSplit.length === 3) {
- fieldName = dotSplit[1] + '.' + fieldName;
+ name = dotSplit[1] + '.' + name;
}
// If 2 segments, it's ambiguous whether this is a custom field or joined field. Search the main entity first.
if (dotSplit.length === 2) {
- var field = _.find(getEntity(true).fields, {name: dotSplit[0] + '.' + fieldName});
+ var field = _.find(getEntity(entityName).fields, {name: dotSplit[0] + '.' + name});
if (field) {
return field;
}
if (joinEntity) {
entityName = _.find(CRM.vars.search.links[entityName], {alias: joinEntity}).entity;
}
- return _.find(getEntity(entityName).fields, {name: fieldName});
+ return _.find(getEntity(entityName).fields, {name: name});
}
return {
getEntity: getEntity,
result.fn = _.find(CRM.vars.search.functions, {name: expr.substring(0, bracketPos)});
result.modifier = _.trim(parsed[1]);
}
- result.field = getField(fieldName);
- var split = fieldName.split(':'),
- prefixPos = split[0].lastIndexOf(result.field.name);
- result.path = split[0];
- result.prefix = prefixPos > 0 ? result.path.substring(0, prefixPos) : '';
- result.suffix = !split[1] ? '' : ':' + split[1];
+ result.field = expr ? getField(fieldName, searchEntity) : undefined;
+ if (result.field) {
+ var split = fieldName.split(':'),
+ prefixPos = split[0].lastIndexOf(result.field.name);
+ result.path = split[0];
+ result.prefix = prefixPos > 0 ? result.path.substring(0, prefixPos) : '';
+ result.suffix = !split[1] ? '' : ':' + split[1];
+ }
return result;
}
};
this.page = 1;
this.params = {};
// After a search this.results is an object of result arrays keyed by page,
- // Prior to searching it's an empty string because 1: falsey and 2: doesn't throw an error if you try to access undefined properties
+ // Initially this.results is an empty string because 1: it's falsey (unlike an empty object) and 2: it doesn't throw an error if you try to access undefined properties (unlike null)
this.results = '';
this.rowCount = false;
// Have the filters (WHERE, HAVING, GROUP BY, JOIN) changed?
return value;
}
- function getOption(field, value) {
- return _.find(field.options, function(option) {
- // Type coersion is intentional
- return option.id == value;
- });
- }
-
$scope.fieldsForGroupBy = function() {
return {results: getAllFields('', function(key) {
return _.contains(ctrl.params.groupBy, key);
};
function getDefaultSelect() {
- return _.filter(['id', 'display_name', 'label', 'title', 'location_type_id:label'], searchMeta.getField);
+ return _.filter(['id', 'display_name', 'label', 'title', 'location_type_id:label'], function(field) {
+ return !!searchMeta.getField(field, ctrl.entity);
+ });
}
function getAllFields(suffix, disabledIf) {
{{:: ts('Auto') }}
</button>
</div>
- <crm-search-actions entity="$ctrl.entity" ids="$ctrl.selectedRows"></crm-search-actions>
+ <crm-search-actions entity="$ctrl.entity" ids="$ctrl.selectedRows" refresh="$ctrl.refreshPage()"></crm-search-actions>
<div class="btn-group pull-right">
- <button type="button" class="btn form-control dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <button type="button" class="btn btn-default form-control dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="crm-i fa-save"></i> {{:: ts('Create')}}
<span class="caret"></span>
</button>
angular.module('search').component('crmSearchActions', {
bindings: {
entity: '<',
+ refresh: '&',
ids: '<'
},
- require: {
- search: '^crmSearch'
- },
templateUrl: '~/search/crmSearchActions.html',
controller: function($scope, crmApi4, dialogService, searchMeta) {
var ts = $scope.ts = CRM.ts(),
var path = $scope.$eval(action.crmPopup.path, data),
query = action.crmPopup.query && $scope.$eval(action.crmPopup.query, data);
CRM.loadForm(CRM.url(path, query))
- .on('crmFormSuccess', ctrl.search.refreshPage);
+ .on('crmFormSuccess', ctrl.refresh);
}
// If action uses dialogService
else if (action.uiDialog) {
title: action.title
});
dialogService.open('crmSearchAction', action.uiDialog.templateUrl, data, options)
- .then(ctrl.search.refreshPage);
+ .then(ctrl.refresh);
}
};
}
<div class="btn-group" title="{{:: ts('Perform action on selected items.') }}">
- <button type="button" ng-disabled="!$ctrl.ids.length" ng-click="$ctrl.init()" class="btn form-control dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <button type="button" ng-disabled="!$ctrl.ids.length" ng-click="$ctrl.init()" class="btn form-control dropdown-toggle btn-default" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{:: ts('Action') }} <span class="caret"></span>
</button>
<ul class="dropdown-menu" ng-if=":: $ctrl.actions">
this.availableFields = function() {
var results = _.transform(ctrl.entity.fields, function(result, item) {
- var formatted = {id: item.name, text: item.title, description: item.description};
+ var formatted = {id: item.name, text: item.label, description: item.description};
if (fieldInUse(item.name)) {
formatted.disabled = true;
}
<hr />
<div class="buttons pull-right">
<button type="button" ng-click="$ctrl.cancel()" class="btn btn-danger">{{:: ts('Cancel') }}</button>
- <button ng-click="$ctrl.save()" class="btn btn-primary" ng-disabled="!$ctrl.values.length">{{:: ts('Update %1 %2', {1: model.ids.length, 2: $ctrl.entity.title}) }}</button>
+ <button ng-click="$ctrl.save()" class="btn btn-primary" ng-disabled="!$ctrl.values.length">{{:: ts('Update %1 %2', {1: model.ids.length, 2: (model.ids.length === 1 ? $ctrl.entity.title : $ctrl.entity.titlePlural)}) }}</button>
</div>
</div>
</div>
<div class="crm-accordion-wrapper collapsed crm-pdf-format-accordion">
<div class="crm-accordion-header">
- {$form.pdf_format_header.html}
+ {ts}Page Format:{/ts} <span class="pdf-format-header-label"></span>
</div>
<div class="crm-accordion-body">
<div class="crm-block crm-form-block">
<td colspan="2"> </td>
</tr>
<tr>
- <td>{$form.paper_dimensions.html}</td><td id="paper_dimensions"> </td>
+ <td>{ts}Width x Height{/ts}</td><td id="paper_dimensions"> </td>
<td colspan="2"> </td>
</tr>
<tr>
$this->eventDelete($eventID);
}
+ /**
+ * Test updating a location block.
+ *
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ public function testUpdateLocationBlock() {
+ $eventID = (int) $this->eventCreate()['id'];
+ $this->submitForm([
+ 'address' => [
+ '1' => [
+ 'street_address' => 'Old address',
+ 'supplemental_address_1' => 'Hallmark Ct',
+ 'supplemental_address_2' => 'Jersey Village',
+ 'supplemental_address_3' => 'My Town',
+ 'city' => 'Newark',
+ 'postal_code' => '01903',
+ 'country_id' => 1228,
+ 'state_province_id' => 1029,
+ 'geo_code_1' => '18.219023',
+ 'geo_code_2' => '-105.00973',
+ 'is_primary' => 1,
+ 'location_type_id' => 1,
+ ],
+ ],
+ ], $eventID);
+
+ $this->submitForm([
+ 'location_option' => 1,
+ 'loc_event_id' => Event::get()->addWhere('id', '=', $eventID)->addSelect('loc_block_id')->execute()->first()['loc_block_id'],
+ 'address' => [
+ '1' => [
+ 'street_address' => 'New address',
+ 'supplemental_address_1' => 'Hallmark Ct',
+ 'supplemental_address_2' => 'Jersey Village',
+ 'supplemental_address_3' => 'My Town',
+ 'city' => 'Newark',
+ 'postal_code' => '01903',
+ 'country_id' => 1228,
+ 'state_province_id' => 1029,
+ 'geo_code_1' => '18.219023',
+ 'geo_code_2' => '-105.00973',
+ ],
+ ],
+ 'email' => [
+ '1' => [
+ 'email' => '',
+ ],
+ '2' => [
+ 'email' => '',
+ ],
+ ],
+ 'phone' => [
+ '1' => [
+ 'phone_type_id' => 1,
+ 'phone' => '',
+ 'phone_ext' => '',
+ ],
+ '2' => [
+ 'phone_type_id' => 1,
+ 'phone' => '',
+ 'phone_ext' => '',
+ ],
+ ],
+ ], $eventID);
+ // Cleanup.
+ $this->eventDelete($eventID);
+ }
+
/**
* Get the values to submit for the form.
*