First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

View file

@ -0,0 +1,121 @@
(function($, _) {
if (!CRM.Designer) CRM.Designer = {};
// TODO Optimize this class
CRM.Designer.PaletteFieldModel = CRM.Backbone.Model.extend({
defaults: {
/**
* @var {string} required; a form-specific binding to an entity instance (eg 'student', 'mother')
*/
entityName: null,
/**
* @var {string}
*/
fieldName: null
},
initialize: function() {
},
getFieldSchema: function() {
return this.getRel('ufGroupModel').getFieldSchema(this.get('entityName'), this.get('fieldName'));
},
getLabel: function() {
// Note: if fieldSchema were a bit tighter, then we need to get a label from PaletteFieldModel at all
return this.getFieldSchema().title || this.get('fieldName');
},
getSectionName: function() {
// Note: if fieldSchema were a bit tighter, then we need to get a section from PaletteFieldModel at all
return this.getFieldSchema().section || 'default';
},
getSection: function() {
return this.getRel('ufGroupModel').getModelClass(this.get('entityName')).prototype.sections[this.getSectionName()];
},
/**
* Add a new UFField model to a UFFieldCollection (if doing so is legal).
* If it fails, display an alert.
*
* @param {int} ufGroupId
* @param {CRM.UF.UFFieldCollection} ufFieldCollection
* @param {Object} addOptions
* @return {CRM.UF.UFFieldModel} or null (if the field is not addable)
*/
addToUFCollection: function(ufFieldCollection, addOptions) {
var name, paletteFieldModel = this;
var ufFieldModel = paletteFieldModel.createUFFieldModel(ufFieldCollection.getRel('ufGroupModel'));
ufFieldModel.set('uf_group_id', ufFieldCollection.uf_group_id);
if (!ufFieldCollection.isAddable(ufFieldModel)) {
CRM.alert(
ts('The field "%1" is already included.', {
1: paletteFieldModel.getLabel()
}),
ts('Duplicate'),
'alert'
);
return null;
}
ufFieldCollection.add(ufFieldModel, addOptions);
// Load metadata and set defaults
// TODO: currently only works for custom fields
name = this.get('fieldName').split('_');
if (name[0] === 'custom') {
CRM.api('custom_field', 'getsingle', {id: name[1]}, {success: function(field) {
ufFieldModel.set(_.pick(field, 'help_pre', 'help_post', 'is_required'));
}});
}
return ufFieldModel;
},
createUFFieldModel: function(ufGroupModel) {
var model = new CRM.UF.UFFieldModel({
is_active: 1,
label: this.getLabel(),
entity_name: this.get('entityName'),
field_type: this.getFieldSchema().civiFieldType,
// For some reason the 'formatting' field gets a random number appended in core so we mimic that here.
// TODO: Why?
field_name: this.get('fieldName') == 'formatting' ? 'formatting_' + (Math.floor(Math.random() * 8999) + 1000) : this.get('fieldName')
});
return model;
}
});
/**
*
* options:
* - ufGroupModel: UFGroupModel
*/
CRM.Designer.PaletteFieldCollection = CRM.Backbone.Collection.extend({
model: CRM.Designer.PaletteFieldModel,
initialize: function(models, options) {
this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models);
},
/**
* Look up a palette-field
*
* @param entityName
* @param fieldName
* @return {CRM.Designer.PaletteFieldModel}
*/
getFieldByName: function(entityName, fieldName) {
if (fieldName.indexOf('formatting') === 0) {
fieldName = 'formatting';
}
return this.find(function(paletteFieldModel) {
return ((!entityName || paletteFieldModel.get('entityName') == entityName) && paletteFieldModel.get('fieldName') == fieldName);
});
},
/**
* Get a list of all fields, grouped into sections by "entityName+sectionName".
*
* @return {Object} keys are sections ("entityName+sectionName"); values are CRM.Designer.PaletteFieldModel
*/
getFieldsByEntitySection: function() {
// TODO cache
var fieldsByEntitySection = this.groupBy(function(paletteFieldModel) {
return paletteFieldModel.get('entityName') + '-' + paletteFieldModel.getSectionName();
});
return fieldsByEntitySection;
}
});
})(CRM.$, CRM._);

View file

@ -0,0 +1,9 @@
(function($, _) {
if (!CRM.ProfileSelector) CRM.ProfileSelector = {};
CRM.ProfileSelector.DummyModel = CRM.Backbone.Model.extend({
defaults: {
profile_id: null
}
});
})(CRM.$, CRM._);

View file

@ -0,0 +1,47 @@
/**
* Dynamically-generated alternative to civi.core.js
*/
(function($, _) {
if (!CRM.Schema) CRM.Schema = {};
/**
* Data models used by the Civi form designer require more attributes than basic Backbone models:
* - sections: array of field-groupings
* - schema: array of fields, keyed by field name, per backbone-forms; extra attributes:
* + section: string, index to the 'sections' array
* + civiFieldType: string
*
* @see https://github.com/powmedia/backbone-forms
*/
CRM.Schema.BaseModel = CRM.Backbone.Model.extend({
initialize: function() {
}
});
CRM.Schema.loadModels = function(civiSchema) {
_.each(civiSchema, function(value, key, list) {
CRM.Schema[key] = CRM.Schema.BaseModel.extend(value);
});
};
CRM.Schema.reloadModels = function(options) {
return $
.ajax({
url: CRM.url("civicrm/profile-editor/schema"),
data: {
'entityTypes': _.keys(CRM.civiSchema).join(',')
},
type: 'POST',
dataType: 'json',
success: function(data) {
if (data) {
CRM.civiSchema = data;
CRM.Schema.loadModels(CRM.civiSchema);
}
}
});
};
CRM.Schema.loadModels(CRM.civiSchema);
})(CRM.$, CRM._);

View file

@ -0,0 +1,51 @@
(function($, _) {
if (!CRM.Schema) CRM.Schema = {};
/**
* Civi data models require more attributes than basic Backbone models:
* - sections: array of field-groupings
* - schema: array of fields, keyed by field name, per backbone-forms
*
* @see https://github.com/powmedia/backbone-forms
*/
CRM.Schema.IndividualModel = CRM.Backbone.Model.extend({
sections: {
'default': {title: 'Individual'},
'custom1': {title: 'Individual: Favorite Things', is_addable: true},
'custom2': {title: 'Individual: Custom Things', is_addable: true}
},
schema: {
first_name: { type: 'Text', title: 'First name', civiFieldType: 'Individual' },
last_name: { type: 'Text', title: 'Last name', civiFieldType: 'Individual' },
legal_name: { type: 'Text', title: 'Legal name', civiFieldType: 'Contact' },
street_address: { validators: ['required', 'email'], title: 'Email', civiFieldType: 'Contact', civiIsLocation: true, civiIsPhone: false },
email: { validators: ['required', 'email'], title: 'Email', civiFieldType: 'Contact', civiIsLocation: true, civiIsPhone: true },
custom_123: { type: 'Checkbox', section: 'custom1', title: 'Likes whiskers on kittens', civiFieldType: 'Individual'},
custom_456: { type: 'Checkbox', section: 'custom1', title: 'Likes dog bites', civiFieldType: 'Individual' },
custom_789: { type: 'Checkbox', section: 'custom1', title: 'Likes bee stings', civiFieldType: 'Individual' },
custom_012: { type: 'Text', section: 'custom2', title: 'Pass phrase', civiFieldType: 'Contact' }
},
initialize: function() {
}
});
CRM.Schema.ActivityModel = CRM.Backbone.Model.extend({
sections: {
'default': {title: 'Activity'},
'custom3': {title: 'Activity: Questions', is_addable: true}
},
schema: {
subject: { type: 'Text', title: 'Subject', civiFieldType: 'Activity' },
location: { type: 'Text', title: 'Location', civiFieldType: 'Activity' },
activity_date_time: { type: 'DateTime', title: 'Date-Time', civiFieldType: 'Activity' },
custom_789: { type: 'Select', section: 'custom3', title: 'How often do you eat cheese?',
options: ['Never', 'Sometimes', 'Often'],
civiFieldType: 'Activity'
}
},
initialize: function() {
}
});
})(CRM.$, CRM._);

View file

@ -0,0 +1,848 @@
(function($, _) {
if (!CRM.UF) CRM.UF = {};
var YESNO = [
{val: 0, label: ts('No')},
{val: 1, label: ts('Yes')}
];
var VISIBILITY = [
{val: 'User and User Admin Only', label: ts('User and User Admin Only'), isInSelectorAllowed: false},
{val: 'Public Pages', label: ts('Expose Publicly'), isInSelectorAllowed: true},
{val: 'Public Pages and Listings', label: ts('Expose Publicly and for Listings'), isInSelectorAllowed: true}
];
var LOCATION_TYPES = _.map(CRM.PseudoConstant.locationType, function(value, key) {
return {val: key, label: value};
});
LOCATION_TYPES.unshift({val: '', label: ts('Primary')});
var DEFAULT_LOCATION_TYPE_ID = '';
var PHONE_TYPES = _.map(CRM.PseudoConstant.phoneType, function(value, key) {
return {val: key, label: value};
});
var WEBSITE_TYPES = _.map(CRM.PseudoConstant.websiteType, function(value, key) {
return {val: key, label: value};
});
var DEFAULT_PHONE_TYPE_ID = PHONE_TYPES[0].val;
var DEFAULT_WEBSITE_TYPE_ID = WEBSITE_TYPES[0].val;
/**
* Add a help link to a form label
*/
function addHelp(title, options) {
return title + ' <a href="#" onclick=\'CRM.help("' + title + '", ' + JSON.stringify(options) + '); return false;\' title="' + ts('%1 Help', {1: title}) + '" class="helpicon"></a>';
}
function watchChanges() {
CRM.designerApp.vent.trigger('ufUnsaved', true);
}
/**
* Parse a "group_type" expression
*
* @param string groupTypeExpr example: "Individual,Activity\0ActivityType:2:28"
* Note: I've seen problems where HTML "&#00;" != JS '\0', so we support ';;' as an equivalent delimiter
* @return Object example: {coreTypes: {"Individual":true,"Activity":true}, subTypes: {"ActivityType":{2: true, 28:true}]}}
*/
CRM.UF.parseTypeList = function(groupTypeExpr) {
var typeList = {coreTypes: {}, subTypes:{}};
// The API may have automatically converted a string with '\0' to an array
var parts = _.isArray(groupTypeExpr) ? groupTypeExpr : groupTypeExpr.replace(';;','\0').split('\0');
var coreTypesExpr = parts[0];
var subTypesExpr = parts[1];
if (!_.isEmpty(coreTypesExpr)) {
_.each(coreTypesExpr.split(','), function(coreType){
typeList.coreTypes[coreType] = true;
});
}
//CRM-15427 Allow Multiple subtype filtering
if (!_.isEmpty(subTypesExpr)) {
if (subTypesExpr.indexOf(';;') !== -1) {
var subTypeparts = subTypesExpr.replace(/;;/g,'\0').split('\0');
_.each(subTypeparts, function(subTypepart) {
var subTypes = subTypepart.split(':');
var subTypeKey = subTypes.shift();
typeList.subTypes[subTypeKey] = {};
_.each(subTypes, function(subTypeId) {
typeList.subTypes[subTypeKey][subTypeId] = true;
});
});
}
else {
var subTypes = subTypesExpr.split(':');
var subTypeKey = subTypes.shift();
typeList.subTypes[subTypeKey] = {};
_.each(subTypes, function(subTypeId) {
typeList.subTypes[subTypeKey][subTypeId] = true;
});
}
}
return typeList;
};
/**
* This function is a hack for generating simulated values of "entity_name"
* in the form-field model.
*
* @param {string} field_type
* @return {string}
*/
CRM.UF.guessEntityName = function(field_type) {
switch (field_type) {
case 'Contact':
case 'Individual':
case 'Organization':
case 'Household':
case 'Formatting':
return 'contact_1';
case 'Activity':
return 'activity_1';
case 'Contribution':
return 'contribution_1';
case 'Membership':
return 'membership_1';
case 'Participant':
return 'participant_1';
case 'Case':
return 'case_1';
default:
if (CRM.contactSubTypes.length && ($.inArray(field_type,CRM.contactSubTypes) > -1)) {
return 'contact_1';
}
else {
throw "Cannot guess entity name for field_type=" + field_type;
}
}
};
/**
* Represents a field in a customizable form.
*/
CRM.UF.UFFieldModel = CRM.Backbone.Model.extend({
/**
* Backbone.Form description of the field to which this refers
*/
defaults: {
help_pre: '',
help_post: '',
/**
* @var bool, non-persistent indication of whether this field is unique or duplicate
* within its UFFieldCollection
*/
is_duplicate: false
},
schema: {
'id': {
type: 'Number'
},
'uf_group_id': {
type: 'Number'
},
'entity_name': {
// pseudo-field
type: 'Text'
},
'field_name': {
type: 'Text'
},
'field_type': {
type: 'Select',
options: ['Contact', 'Individual', 'Organization', 'Contribution', 'Membership', 'Participant', 'Activity']
},
'help_post': {
title: addHelp(ts('Field Post Help'), {id: "help", file:"CRM/UF/Form/Field"}),
type: 'TextArea'
},
'help_pre': {
title: addHelp(ts('Field Pre Help'), {id: "help", file:"CRM/UF/Form/Field"}),
type: 'TextArea'
},
'in_selector': {
title: addHelp(ts('Results Columns?'), {id: "in_selector", file:"CRM/UF/Form/Field"}),
type: 'Select',
options: YESNO
},
'is_active': {
title: addHelp(ts('Active?'), {id: "is_active", file:"CRM/UF/Form/Field"}),
type: 'Select',
options: YESNO
},
'is_multi_summary': {
title: ts("Include in multi-record listing?"),
type: 'Select',
options: YESNO
},
'is_required': {
title: addHelp(ts('Required?'), {id: "is_required", file:"CRM/UF/Form/Field"}),
type: 'Select',
options: YESNO
},
'is_reserved': {
type: 'Select',
options: YESNO
},
'is_searchable': {
title: addHelp(ts("Searchable"), {id: "is_searchable", file:"CRM/UF/Form/Field"}),
type: 'Select',
options: YESNO
},
'is_view': {
title: addHelp(ts('View Only?'), {id: "is_view", file:"CRM/UF/Form/Field"}),
type: 'Select',
options: YESNO
},
'label': {
title: ts('Field Label'),
type: 'Text',
editorAttrs: {maxlength: 255}
},
'location_type_id': {
title: ts('Location Type'),
type: 'Select',
options: LOCATION_TYPES
},
'website_type_id': {
title: ts('Website Type'),
type: 'Select',
options: WEBSITE_TYPES
},
'phone_type_id': {
title: ts('Phone Type'),
type: 'Select',
options: PHONE_TYPES
},
'visibility': {
title: addHelp(ts('Visibility'), {id: "visibility", file:"CRM/UF/Form/Field"}),
type: 'Select',
options: VISIBILITY
},
'weight': {
type: 'Number'
}
},
initialize: function() {
if (this.get('field_name').indexOf('formatting') === 0) {
this.schema.help_pre.title = ts('Markup');
}
this.set('entity_name', CRM.UF.guessEntityName(this.get('field_type')));
this.on("rel:ufGroupModel", this.applyDefaults, this);
this.on('change', watchChanges);
},
applyDefaults: function() {
var fieldSchema = this.getFieldSchema();
if (fieldSchema && fieldSchema.civiIsLocation && !this.get('location_type_id')) {
this.set('location_type_id', DEFAULT_LOCATION_TYPE_ID);
}
if (fieldSchema && fieldSchema.civiIsWebsite && !this.get('website_type_id')) {
this.set('website_type_id', DEFAULT_WEBSITE_TYPE_ID);
}
if (fieldSchema && fieldSchema.civiIsPhone && !this.get('phone_type_id')) {
this.set('phone_type_id', DEFAULT_PHONE_TYPE_ID);
}
},
isInSelectorAllowed: function() {
var visibility = _.first(_.where(VISIBILITY, {val: this.get('visibility')}));
if (visibility) {
return visibility.isInSelectorAllowed;
}
else {
return false;
}
},
getFieldSchema: function() {
return this.getRel('ufGroupModel').getFieldSchema(this.get('entity_name'), this.get('field_name'));
},
/**
* Create a uniqueness signature. Ideally, each UFField in a UFGroup should
* have a unique signature.
*
* @return {String}
*/
getSignature: function() {
return this.get("entity_name") +
'::' + this.get("field_name") +
'::' + (this.get("location_type_id") ? this.get("location_type_id") : this.get("website_type_id") ? this.get("website_type_id") : '') +
'::' + (this.get("phone_type_id") ? this.get("phone_type_id") : '');
},
/**
* This is like destroy(), but it only destroys the item on the client-side;
* it does not trigger REST or Backbone.sync() operations.
*
* @return {Boolean}
*/
destroyLocal: function() {
this.trigger('destroy', this, this.collection, {});
return false;
}
});
/**
* Represents a list of fields in a customizable form
*
* options:
* - uf_group_id: int
*/
CRM.UF.UFFieldCollection = CRM.Backbone.Collection.extend({
model: CRM.UF.UFFieldModel,
uf_group_id: null, // int
initialize: function(models, options) {
options = options || {};
this.uf_group_id = options.uf_group_id;
this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models);
this.on('add', this.watchDuplicates, this);
this.on('remove', this.unwatchDuplicates, this);
this.on('change', watchChanges);
this.on('add', watchChanges);
this.on('remove', watchChanges);
},
getFieldsByName: function(entityName, fieldName) {
return this.filter(function(ufFieldModel) {
return (ufFieldModel.get('entity_name') == entityName && ufFieldModel.get('field_name') == fieldName);
});
},
toSortedJSON: function() {
var fields = this.map(function(ufFieldModel){
return ufFieldModel.toStrictJSON();
});
return _.sortBy(fields, function(ufFieldJSON){
return parseInt(ufFieldJSON.weight);
});
},
isAddable: function(ufFieldModel) {
var entity_name = ufFieldModel.get('entity_name'),
field_name = ufFieldModel.get('field_name'),
fieldSchema = this.getRel('ufGroupModel').getFieldSchema(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name'));
if (field_name.indexOf('formatting') === 0) {
return true;
}
if (! fieldSchema) {
return false;
}
var fields = this.getFieldsByName(entity_name, field_name);
var limit = 1;
if (fieldSchema.civiIsLocation) {
limit *= LOCATION_TYPES.length;
}
if (fieldSchema.civiIsWebsite) {
limit *= WEBSITE_TYPES.length;
}
if (fieldSchema.civiIsPhone) {
limit *= PHONE_TYPES.length;
}
return fields.length < limit;
},
watchDuplicates: function(model, collection, options) {
model.on('change:location_type_id', this.markDuplicates, this);
model.on('change:website_type_id', this.markDuplicates, this);
model.on('change:phone_type_id', this.markDuplicates, this);
this.markDuplicates();
},
unwatchDuplicates: function(model, collection, options) {
model.off('change:location_type_id', this.markDuplicates, this);
model.off('change:website_type_id', this.markDuplicates, this);
model.off('change:phone_type_id', this.markDuplicates, this);
this.markDuplicates();
},
hasDuplicates: function() {
var firstDupe = this.find(function(ufFieldModel){
return ufFieldModel.get('is_duplicate');
});
return firstDupe ? true : false;
},
/**
*
*/
markDuplicates: function() {
var ufFieldModelsByKey = this.groupBy(function(ufFieldModel) {
return ufFieldModel.getSignature();
});
this.each(function(ufFieldModel){
var is_duplicate = ufFieldModelsByKey[ufFieldModel.getSignature()].length > 1;
if (is_duplicate != ufFieldModel.get('is_duplicate')) {
ufFieldModel.set('is_duplicate', is_duplicate);
}
});
}
});
/**
* Represents an entity in a customizable form
*/
CRM.UF.UFEntityModel = CRM.Backbone.Model.extend({
schema: {
'id': {
// title: ts(''),
type: 'Number'
},
'entity_name': {
title: ts('Entity Name'),
help: ts('Symbolic name which referenced in the fields'),
type: 'Text'
},
'entity_type': {
title: ts('Entity Type'),
type: 'Select',
options: ['IndividualModel', 'ActivityModel']
},
'entity_sub_type': {
// Use '*' to match all subtypes; use an int to match a specific type id; use empty-string to match none
title: ts('Sub Type'),
type: 'Text'
}
},
defaults: {
entity_sub_type: '*'
},
initialize: function() {
},
/**
* Get a list of all fields that can be used with this entity.
*
* @return {Object} keys are field names; values are fieldSchemas
*/
getFieldSchemas: function() {
var ufEntityModel = this;
var modelClass= this.getModelClass();
if (this.get('entity_sub_type') == '*') {
return _.clone(modelClass.prototype.schema);
}
var result = {};
_.each(modelClass.prototype.schema, function(fieldSchema, fieldName){
var section = modelClass.prototype.sections[fieldSchema.section];
if (ufEntityModel.isSectionEnabled(section)) {
result[fieldName] = fieldSchema;
}
});
return result;
},
isSectionEnabled: function(section) {
//CRM-15427
return (!section || !section.extends_entity_column_value || _.contains(section.extends_entity_column_value, this.get('entity_sub_type')) || this.get('entity_sub_type') == '*');
},
getSections: function() {
var ufEntityModel = this;
var result = {};
_.each(ufEntityModel.getModelClass().prototype.sections, function(section, sectionKey){
if (ufEntityModel.isSectionEnabled(section)) {
result[sectionKey] = section;
}
});
return result;
},
getModelClass: function() {
return CRM.Schema[this.get('entity_type')];
}
});
/**
* Represents a list of entities in a customizable form
*
* options:
* - ufGroupModel: UFGroupModel
*/
CRM.UF.UFEntityCollection = CRM.Backbone.Collection.extend({
model: CRM.UF.UFEntityModel,
byName: {},
initialize: function(models, options) {
options = options || {};
this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models);
},
/**
*
* @param name
* @return {UFEntityModel} if found; otherwise, null
*/
getByName: function(name) {
// TODO consider indexing
return this.find(function(ufEntityModel){
return ufEntityModel.get('entity_name') == name;
});
}
});
/**
* Represents a customizable form
*/
CRM.UF.UFGroupModel = CRM.Backbone.Model.extend({
defaults: {
title: ts('Unnamed Profile'),
is_active: 1
},
schema: {
'id': {
// title: ts(''),
type: 'Number'
},
'name': {
// title: ts(''),
type: 'Text'
},
'title': {
title: ts('Profile Name'),
help: ts(''),
type: 'Text',
editorAttrs: {maxlength: 64},
validators: ['required']
},
'group_type': {
// For a description of group_type, see CRM_Core_BAO_UFGroup::updateGroupTypes
// title: ts(''),
type: 'Text'
},
'add_captcha': {
title: ts('Include reCAPTCHA?'),
help: ts('FIXME'),
type: 'Select',
options: YESNO
},
'add_to_group_id': {
title: ts('Add new contacts to a Group?'),
help: ts('Select a group if you are using this profile for adding new contacts, AND you want the new contacts to be automatically assigned to a group.'),
type: 'Number'
},
'cancel_URL': {
title: ts('Cancel Redirect URL'),
help: ts('If you are using this profile as a contact signup or edit form, and want to redirect the user to a static URL if they click the Cancel button - enter the complete URL here. If this field is left blank, the built-in Profile form will be redisplayed.'),
type: 'Text'
},
'created_date': {
//title: ts(''),
type: 'Text'// FIXME
},
'created_id': {
//title: ts(''),
type: 'Number'
},
'help_post': {
title: ts('Post-form Help'),
help: ts('Explanatory text displayed at the end of the form.') +
ts('Note that this help text is displayed on profile create/edit screens only.'),
type: 'TextArea'
},
'help_pre': {
title: ts('Pre-form Help'),
help: ts('Explanatory text displayed at the beginning of the form.') +
ts('Note that this help text is displayed on profile create/edit screens only.'),
type: 'TextArea'
},
'is_active': {
title: ts('Is this CiviCRM Profile active?'),
type: 'Select',
options: YESNO
},
'is_cms_user': {
title: ts('Drupal user account registration option?'),// FIXME
help: ts('FIXME'),
type: 'Select',
options: YESNO // FIXME
},
'is_edit_link': {
title: ts('Include profile edit links in search results?'),
help: ts('Check this box if you want to include a link in the listings to Edit profile fields. Only users with permission to edit the contact will see this link.'),
type: 'Select',
options: YESNO
},
'is_map': {
title: ts('Enable mapping for this profile?'),
help: ts('If enabled, a Map link is included on the profile listings rows and detail screens for any contacts whose records include sufficient location data for your mapping provider.'),
type: 'Select',
options: YESNO
},
'is_proximity_search': {
title: ts('Proximity Search'),
help: ts('FIXME'),
type: 'Select',
options: YESNO // FIXME
},
'is_reserved': {
// title: ts(''),
type: 'Select',
options: YESNO
},
'is_uf_link': {
title: ts('Include Drupal user account information links in search results?'), // FIXME
help: ts('FIXME'),
type: 'Select',
options: YESNO
},
'is_update_dupe': {
title: ts('What to do upon duplicate match'),
help: ts('FIXME'),
type: 'Select',
options: YESNO // FIXME
},
'limit_listings_group_id': {
title: ts('Limit listings to a specific Group?'),
help: ts('Select a group if you are using this profile for search and listings, AND you want to limit the listings to members of a specific group.'),
type: 'Number'
},
'notify': {
title: ts('Notify when profile form is submitted?'),
help: ts('If you want member(s) of your organization to receive a notification email whenever this Profile form is used to enter or update contact information, enter one or more email addresses here. Multiple email addresses should be separated by a comma (e.g. jane@example.org, paula@example.org). The first email address listed will be used as the FROM address in the notifications.'),
type: 'TextArea'
},
'post_URL': {
title: ts('Redirect URL'),
help: ts("If you are using this profile as a contact signup or edit form, and want to redirect the user to a static URL after they've submitted the form, you can also use contact tokens in URL - enter the complete URL here. If this field is left blank, the built-in Profile form will be redisplayed with a generic status message - 'Your contact information has been saved.'"),
type: 'Text'
},
'weight': {
title: ts('Order'),
help: ts('Weight controls the order in which profiles are presented when more than one profile is included in User Registration or My Account screens. Enter a positive or negative integer - lower numbers are displayed ahead of higher numbers.'),
type: 'Number'
// FIXME positive int
}
},
initialize: function() {
var ufGroupModel = this;
if (!this.getRel('ufEntityCollection')) {
var ufEntityCollection = new CRM.UF.UFEntityCollection([], {
ufGroupModel: this,
silent: false
});
this.setRel('ufEntityCollection', ufEntityCollection);
}
if (!this.getRel('ufFieldCollection')) {
var ufFieldCollection = new CRM.UF.UFFieldCollection([], {
uf_group_id: this.id,
ufGroupModel: this
});
this.setRel('ufFieldCollection', ufFieldCollection);
}
if (!this.getRel('paletteFieldCollection')) {
var paletteFieldCollection = new CRM.Designer.PaletteFieldCollection([], {
ufGroupModel: this
});
paletteFieldCollection.sync = function(method, model, options) {
if (!options) options = {};
// console.log(method, model, options);
switch (method) {
case 'read':
var success = options.success;
options.success = function(resp, status, xhr) {
if (success) success(resp, status, xhr);
model.trigger('sync', model, resp, options);
};
success(ufGroupModel.buildPaletteFields());
break;
case 'create':
case 'update':
case 'delete':
throw 'Unsupported method: ' + method;
default:
throw 'Unsupported method: ' + method;
}
};
this.setRel('paletteFieldCollection', paletteFieldCollection);
}
this.getRel('ufEntityCollection').on('reset', this.resetEntities, this);
this.resetEntities();
this.on('change', watchChanges);
},
/**
* Generate a copy of this UFGroupModel and its fields, with all ID's removed. The result
* is suitable for a new, identical UFGroup.
*
* @return {CRM.UF.UFGroupModel}
*/
deepCopy: function() {
var copy = new CRM.UF.UFGroupModel(_.omit(this.toStrictJSON(), ['id','created_id','created_date','is_reserved','group_type']));
copy.getRel('ufEntityCollection').reset(
this.getRel('ufEntityCollection').toJSON()
// FIXME: for configurable entities, omit ['id', 'uf_group_id']
);
copy.getRel('ufFieldCollection').reset(
this.getRel('ufFieldCollection').map(function(ufFieldModel) {
return _.omit(ufFieldModel.toStrictJSON(), ['id', 'uf_group_id']);
})
);
var copyLabel = ' ' + ts('(Copy)');
copy.set('title', copy.get('title').slice(0, 64 - copyLabel.length) + copyLabel);
return copy;
},
getModelClass: function(entity_name) {
var ufEntity = this.getRel('ufEntityCollection').getByName(entity_name);
if (!ufEntity) throw 'Failed to locate entity: ' + entity_name;
return ufEntity.getModelClass();
},
getFieldSchema: function(entity_name, field_name) {
if (field_name.indexOf('formatting') === 0) {
field_name = 'formatting';
}
var modelClass = this.getModelClass(entity_name);
var fieldSchema = modelClass.prototype.schema[field_name];
if (!fieldSchema) {
CRM.console('warn', 'Failed to locate field: ' + entity_name + "." + field_name);
return null;
}
return fieldSchema;
},
/**
* Check that the group_type contains *only* the types listed in validTypes
*
* @param string validTypesExpr
* @param bool allowAllSubtypes
* @return {Boolean}
*/
//CRM-15427
checkGroupType: function(validTypesExpr, allowAllSubtypes, usedByFilter) {
var allMatched = true;
allowAllSubtypes = allowAllSubtypes || false;
usedByFilter = usedByFilter || null;
if (_.isEmpty(this.get('group_type'))) {
return true;
}
if (usedByFilter && _.isEmpty(this.get('module'))) {
return false;
}
var actualTypes = CRM.UF.parseTypeList(this.get('group_type'));
var validTypes = CRM.UF.parseTypeList(validTypesExpr);
// Every actual.coreType is a valid.coreType
_.each(actualTypes.coreTypes, function(ignore, actualCoreType) {
if (! validTypes.coreTypes[actualCoreType]) {
allMatched = false;
}
});
// CRM-16915 - filter with usedBy module if specified.
if (usedByFilter && this.get('module') != usedByFilter) {
allMatched = false;
}
//CRM-15427 allow all subtypes
if (!$.isEmptyObject(validTypes.subTypes) && !allowAllSubtypes) {
// Every actual.subType is a valid.subType
_.each(actualTypes.subTypes, function(actualSubTypeIds, actualSubTypeKey) {
if (!validTypes.subTypes[actualSubTypeKey]) {
allMatched = false;
return;
}
// actualSubTypeIds is a list of all subtypes which can be used by group,
// so it's sufficient to match any one of them
var subTypeMatched = false;
_.each(actualSubTypeIds, function(ignore, actualSubTypeId) {
if (validTypes.subTypes[actualSubTypeKey][actualSubTypeId]) {
subTypeMatched = true;
}
});
allMatched = allMatched && subTypeMatched;
});
}
return allMatched;
},
calculateContactEntityType: function() {
var ufGroupModel = this;
// set proper entity model based on selected profile
var contactTypes = ['Individual', 'Household', 'Organization'];
var profileType = ufGroupModel.get('group_type') || '';
// check if selected profile have subtype defined eg: ["Individual,Contact,Case", "caseType:7"]
if (_.isArray(profileType) && profileType[0]) {
profileType = profileType[0];
}
profileType = profileType.split(',');
var ufEntityModel;
_.each(profileType, function (ptype) {
if ($.inArray(ptype, contactTypes) > -1) {
ufEntityModel = ptype + 'Model';
return true;
}
});
return ufEntityModel;
},
setUFGroupModel: function(entityType, allEntityModels) {
var ufGroupModel = this;
var newUfEntityModels = [];
_.each(allEntityModels, function (values) {
if (entityType && values.entity_name == 'contact_1') {
values.entity_type = entityType;
}
newUfEntityModels.push(new CRM.UF.UFEntityModel(values));
});
ufGroupModel.getRel('ufEntityCollection').reset(newUfEntityModels);
},
resetEntities: function() {
var ufGroupModel = this;
var deleteFieldList = [];
ufGroupModel.getRel('ufFieldCollection').each(function(ufFieldModel){
if (!ufFieldModel.getFieldSchema()) {
CRM.alert(ts('This profile no longer includes field "%1"! All references to the field have been removed.', {
1: ufFieldModel.get('label')
}), '', 'alert', {expires: false});
deleteFieldList.push(ufFieldModel);
}
});
_.each(deleteFieldList, function(ufFieldModel) {
ufFieldModel.destroyLocal();
});
this.getRel('paletteFieldCollection').reset(this.buildPaletteFields());
// reset to redraw the cancel after entity type is updated.
ufGroupModel.getRel('ufFieldCollection').reset(ufGroupModel.getRel('ufFieldCollection').toJSON());
},
/**
*
* @return {Array} of PaletteFieldModel
*/
buildPaletteFields: function() {
// rebuild list of fields; reuse old instances of PaletteFieldModel and create new ones
// as appropriate
// Note: The system as a whole is ill-defined in cases where we have an existing
// UFField that references a model field that disappears.
var ufGroupModel = this;
var oldPaletteFieldModelsBySig = {};
this.getRel('paletteFieldCollection').each(function(paletteFieldModel){
oldPaletteFieldModelsBySig[paletteFieldModel.get("entityName") + '::' + paletteFieldModel.get("fieldName")] = paletteFieldModel;
});
var newPaletteFieldModels = [];
this.getRel('ufEntityCollection').each(function(ufEntityModel){
var modelClass = ufEntityModel.getModelClass();
_.each(ufEntityModel.getFieldSchemas(), function(value, key, list) {
var model = oldPaletteFieldModelsBySig[ufEntityModel.get('entity_name') + '::' + key];
if (!model) {
model = new CRM.Designer.PaletteFieldModel({
modelClass: modelClass,
entityName: ufEntityModel.get('entity_name'),
fieldName: key
});
}
newPaletteFieldModels.push(model);
});
});
return newPaletteFieldModels;
}
});
/**
* Represents a list of customizable form
*/
CRM.UF.UFGroupCollection = CRM.Backbone.Collection.extend({
model: CRM.UF.UFGroupModel
});
})(CRM.$, CRM._);