) {
return TRUE;
}
+ // Proceed only if permissions are being enforced.'
+ // Anonymous users in permission-bypass mode should not be allowed to set arbitrary filters.
+ if ($this->getCheckPermissions()) {
+ $field = $this->getField($fieldName);
+ try {
+ civicrm_api4($field['entity'], 'getFields', [
+ 'action' => 'get',
+ 'where' => [['name', '=', $fieldName]],
+ ])->single();
+ return TRUE;
+ }
+ catch (\CRM_Core_Exception $e) {
+ return FALSE;
+ }
+ }
return FALSE;
}
}
if (is_array($this->savedSearch)) {
$this->savedSearch += ['api_params' => []];
- $this->savedSearch['api_params'] += ['select' => [], 'where' => []];
+ $this->savedSearch['api_params'] += ['version' => 4, 'select' => [], 'where' => []];
}
$this->_apiParams = ($this->savedSearch['api_params'] ?? []) + ['select' => [], 'where' => []];
}
};
})
+ // Render a crmAutocomplete APIv4 widget
+ // usage: <input crm-autocomplete="'Contact'" crm-autocomplete-params={savedSearch: 'mySearch', filters: {is_deceased: false}}" ng-model="myobj.field" />
+ .directive('crmAutocomplete', function () {
+ return {
+ require: {
+ ngModel: '?ngModel'
+ },
+ bindToController: {
+ crmAutocomplete: '<',
+ crmAutocompleteParams: '<'
+ },
+ controller: function($element, $timeout) {
+ var ctrl = this;
+ $timeout(function() {
+ $element.crmAutocomplete(ctrl.crmAutocomplete, ctrl.crmAutocompleteParams);
+ // Ensure widget is updated when model changes
+ if (ctrl.ngModel) {
+ ctrl.ngModel.$render = function() {
+ $element.val(ctrl.ngModel.$viewValue || '').change();
+ };
+ }
+ });
+ }
+ };
+ })
+
// validate multiple email text
// usage: <input crm-multiple-email type="text" ng-model="myobj.field" />
.directive('crmMultipleEmail', function ($parse, $timeout) {
// Add existing entity field
$idField = CoreUtil::getIdFieldName($entityName);
$fields[$idField]['readonly'] = FALSE;
- $fields[$idField]['input_type'] = 'EntityRef';
+ $fields[$idField]['input_type'] = 'Existing';
$fields[$idField]['is_id'] = TRUE;
$fields[$idField]['label'] = E::ts('Existing %1', [1 => CoreUtil::getInfoItem($entityName, 'title')]);
// Mix in alterations declared by afform entities
-<li>
+<li ng-if="$ctrl.fieldDefn.input_type !== 'Existing'">
<div href ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown">
<label>{{:: ts('Type:') }}</label>
<select class="form-control" ng-model="getSet('input_type')" ng-model-options="{getterSetter: true}" title="{{:: ts('Field type') }}">
</select>
</div>
</li>
+<li ng-if="$ctrl.fieldDefn.input_type === 'Existing'" title="{{:: ts('Use a saved search to filter the autocomplete results') }}">
+ <div href ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown">
+ <label>{{:: ts('Saved Search:') }}</label>
+ <input class="form-control" crm-entityref="{entity: 'SavedSearch', api: {id_field: 'name', params: {api_entity: $ctrl.getEntity().name}}}" ng-model="getSet('saved_search')" ng-model-options="{getterSetter: true}">
+ </div>
+</li>
<li>
<a href ng-click="toggleRequired(); $event.stopPropagation(); $event.target.blur();" title="{{:: ts('Require this field') }}">
<i class="crm-i fa-{{ getProp('required') ? 'check-' : '' }}square-o"></i>
{{:: ts('Required') }}
</a>
</li>
-<li>
+<li ng-if="$ctrl.fieldDefn.input_type === 'Existing'">
+ <a href ng-click="toggleSkipPermissions(); $event.stopPropagation(); $event.target.blur();" title="{{:: ts('Allows non-permissioned users to access any record returned by the saved search') }}">
+ <i class="crm-i fa-{{ getProp('skip_permissions') ? 'check-' : '' }}square-o"></i>
+ {{:: ts('Disable Permission Checks') }}
+ </a>
+</li>
+<li ng-if="$ctrl.fieldDefn.input_type !== 'Existing'">
<a href ng-click="toggleDefaultValue(); $event.stopPropagation(); $event.target.blur();" title="{{:: ts('Pre-fill this field with a value') }}">
<i class="crm-i fa-{{ $ctrl.hasDefaultValue ? 'check-' : '' }}square-o"></i>
{{:: ts('Default value') }}
</a>
</li>
-<li ng-if="$ctrl.hasDefaultValue">
+<li ng-if="$ctrl.fieldDefn.input_type !== 'Existing' && $ctrl.hasDefaultValue">
<div ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown form-inline">
<input class="form-control" af-gui-field-value="$ctrl.fieldDefn" ng-model="getSet('afform_default')" ng-model-options="{getterSetter: true}" >
</div>
return defn;
};
+ // Get the api entity this field belongs to
+ this.getEntity = function() {
+ return afGui.getEntity(ctrl.container.getFieldEntityType(ctrl.node.name));
+ };
+
$scope.getOriginalLabel = function() {
+ // Use afform entity if available (e.g. "Individual1")
if (ctrl.container.getEntityName()) {
return ctrl.editor.getEntity(ctrl.container.getEntityName()).label + ': ' + ctrl.getDefn().label;
}
- return afGui.getEntity(ctrl.container.getFieldEntityType(ctrl.node.name)).label + ': ' + ctrl.getDefn().label;
+ // Use generic entity (e.g. "Contact")
+ return ctrl.getEntity().label + ': ' + ctrl.getDefn().label;
};
$scope.hasOptions = function() {
getSet('required', !getSet('required'));
};
+ $scope.toggleSkipPermissions = function() {
+ getSet('skip_permissions', !getSet('skip_permissions'));
+ };
+
$scope.toggleHelp = function(position) {
getSet('help_' + position, $scope.propIsset('help_' + position) ? null : (ctrl.getDefn()['help_' + position] || ts('Enter text')));
};
--- /dev/null
+<div class="form-inline">
+ <div class="input-group">
+ <input autocomplete="off" type="text" class="form-control" placeholder="{{:: ts('Select %1', {1: $ctrl.getFkEntity().label}) }}" title="{{:: ts('Click to add placeholder text') }}" ng-model="getSet('input_attrs.placeholder')" ng-model-options="$ctrl.editor.debounceWithGetterSetter">
+ <div class="input-group-btn">
+ <button type="button" class="btn btn-default" disabled><i class="crm-i fa-search"></i></button>
+ </div>
+ </div>
+</div>
namespace Civi\Afform;
+use Civi\Api4\Utils\CoreUtil;
use CRM_Afform_ExtensionUtil as E;
/**
// Each field can be nested within a fieldset, a join or a block
foreach (pq('af-field', $doc) as $afField) {
/** @var \DOMElement $afField */
- $action = 'create';
+ $action = 'update';
$joinName = pq($afField)->parents('[af-join]')->attr('af-join');
if ($joinName) {
self::fillFieldMetadata($joinName, $action, $afField);
break;
}
}
+ // Id field for selecting existing entity
+ if ($field['name'] === CoreUtil::getIdFieldName($entityName)) {
+ $entityTitle = CoreUtil::getInfoItem($entityName, 'title');
+ $field['input_type'] = 'Existing';
+ $field['entity'] = $entityName;
+ $field['label'] = E::ts('Existing %1', [1 => $entityTitle]);
+ $field['input_attrs']['placeholder'] = E::ts('Select %1', [1 => $entityTitle]);
+ }
// If this is an implicit join, get new field from fk entity
if ($field['name'] !== $fieldName && $field['fk_entity']) {
$params['where'] = [['name', '=', substr($fieldName, 1 + strrpos($fieldName, '.'))]];
$apiRequest = $event->getApiRequest();
if (is_object($apiRequest) && is_a($apiRequest, 'Civi\Api4\Generic\AutocompleteAction')) {
$formName = $apiRequest->getFormName();
- if (!$formName || !str_starts_with('afform:', $formName) || !strpos(':', $apiRequest->getFieldName() ?: '')) {
+ if (!str_starts_with((string) $formName, 'afform:') || !strpos((string) $apiRequest->getFieldName(), ':')) {
return;
}
[$entityName, $fieldName] = explode(':', $apiRequest->getFieldName());
}
$formDataModel = new FormDataModel($afform['layout']);
$entity = $formDataModel->getEntity($entityName);
- $field = $entity['fields'][$fieldName] ?? NULL;
- if ($field) {
- $apiRequest->setCheckPermissions(empty($field['defn']['bypass_permission']));
- $apiRequest->setSavedSearch($field['defn']['saved_search'] ?? NULL);
+ $defn = [];
+ if (!empty($entity['fields'][$fieldName]['defn'])) {
+ $defn = \CRM_Utils_JS::decode($entity['fields'][$fieldName]['defn']);
}
+ $apiRequest->setCheckPermissions(empty($defn['skip_permissions']));
+ $apiRequest->setSavedSearch($defn['saved_search'] ?? NULL);
}
}
--- /dev/null
+<input class="form-control" id="{{:: fieldId }}" ng-model="getSetSelect" ng-model-options="{getterSetter: true}" crm-autocomplete="$ctrl.defn.entity" crm-autocomplete-params="{formName: 'afform:' + $ctrl.afFieldset.getFormName(), fieldName: $ctrl.afFieldset.modelName + ':' + $ctrl.fieldName}" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" >