/*
* Copyright 2016 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Tool which displays a grid with the assignments for users and groups on the profiles of the application, depending on a context object.
* @private
*/
Ext.define('Ametys.plugins.coreui.profiles.ProfileAssignmentsTool', {
extend: "Ametys.tool.Tool",
statics: {
/**
* @readonly
* @property {String} ACCESS_TYPE_ALLOW Type of access for an allowed access
*/
ACCESS_TYPE_ALLOW: 'allow',
/**
* @readonly
* @property {String} ACCESS_TYPE_DENY Type of access for an denied access
*/
ACCESS_TYPE_DENY: 'deny',
/**
* @readonly
* @property {String} ACCESS_TYPE_INHERITED_ALLOW Type of access for an allowed access by inheritance
*/
ACCESS_TYPE_INHERITED_ALLOW: 'inherited_allow',
/**
* @readonly
* @property {String} ACCESS_TYPE_DENIED Type of access for an denied access by inheritance
*/
ACCESS_TYPE_INHERITED_DENY: 'inherited_deny',
/**
* @readonly
* @property {String} ACCESS_TYPE_ALLOW_BY_GROUP Type of access for an allowed access through groups (client-side only)
*/
ACCESS_TYPE_ALLOW_BY_GROUP: 'allow_by_group',
/**
* @readonly
* @property {String} ACCESS_TYPE_DENY_BY_GROUP Type of access for a denied access through groups (client-side only)
*/
ACCESS_TYPE_DENY_BY_GROUP: 'deny_by_group',
/**
* @readonly
* @property {String} ACCESS_TYPE_ALLOW_BY_ANONYMOUS Type of access for an allowed access through anonymous (client-side only)
*/
ACCESS_TYPE_ALLOW_BY_ANONYMOUS: 'allow_by_anonymous',
/**
* @readonly
* @property {String} ACCESS_TYPE_ALLOW_BY_ANYCONNECTED Type of access for an allowed access through any connected user (client-side only)
*/
ACCESS_TYPE_ALLOW_BY_ANYCONNECTED: 'allow_by_anyconnected',
/**
* @readonly
* @property {String} ACCESS_TYPE_DENY_BY_ANYCONNECTED Type of access for a denied access through any connected user (client-side only)
*/
ACCESS_TYPE_DENY_BY_ANYCONNECTED: 'deny_by_anyconnected',
/**
* @readonly
* @property {String} ACCESS_TYPE_UNKNOWN Type of access for a undetermined access
*/
ACCESS_TYPE_UNKNOWN: 'unknown',
/**
* Function called when an assignment is clicked in order to change its value
* @param {String} recordId The id of the record
* @param {String} profileId The profile id (id of the column)
* @param {String} toolId The id of the tool
*/
onCellClick: function(recordId, profileId, toolId)
{
var tool = Ametys.tool.ToolsManager.getTool(toolId);
if (tool != null)
{
tool.onCellClick(recordId, profileId);
}
},
/**
* Compute the tooltip for a given record and access type
* @param {Ext.data.Model} record The record
* @param {String} accessType The access type
* @return The tooltip text
*/
computeTooltip: function (record, accessType)
{
var type = record.get('targetType');
if (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANONYMOUS)
{
return this._computeTooltipForAnonymous(record, accessType);
}
else if (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANYCONNECTEDUSER)
{
return this._computeTooltipForAnyconnectedUser(record, accessType);
}
else if (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER)
{
return this._computeTooltipForUser(record, accessType);
}
else if (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP)
{
return this._computeTooltipForGroup(record, accessType);
}
},
/**
* @private
* Compute the tooltip for the Anonymous record and access type
* @param {Ext.data.Model} record The Anonymous record
* @param {String} accessType The access type
* @return The tooltip text
*/
_computeTooltipForAnonymous: function (record, accessType)
{
switch (accessType)
{
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
// Anonymous is locally allowed
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANONYMOUS_LOCAL_ALLOWED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
// Anonymous is allowed by inheritance
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANONYMOUS_INHERIT_ALLOWED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
// Anonymous is locally denied
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANONYMOUS_LOCAL_DENIED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
// Anonymous is denied by inheritance
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANONYMOUS_INHERIT_DENIED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
default:
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_UNKNOWN}}";
}
},
/**
* @private
* Compute the tooltip for the anyconnected user record and access type
* @param {Ext.data.Model} record The anyconnected user record
* @param {String} accessType The access type
* @return The tooltip text
*/
_computeTooltipForAnyconnectedUser: function (record, accessType)
{
switch (accessType)
{
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS:
// Any connected users are allowed because Anonymous is allowed
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_DISABLED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
// Any connected users are locally allowed
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_LOCAL_ALLOWED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
// Any connected users are allowed by inheritance
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_INHERIT_ALLOWED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
// Any connected users are locally denied
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_LOCAL_DENIED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
// Any connected users are denied by inheritance
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_ANYCONNECTED_INHERIT_DENIED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
default:
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_UNKNOWN}}";
}
},
/**
* @private
* Compute the tooltip for a user record and access type
* @param {Ext.data.Model} record The user record
* @param {String} accessType The access type
* @return The tooltip text
*/
_computeTooltipForUser: function (record, accessType)
{
switch (accessType)
{
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS:
// The user is allowed because Anonymous is allowed
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_ALLOWED_BY_ANONYMOUS}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
// The user is locally allowed
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_LOCAL_ALLOWED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_GROUP:
// The user is allowed because he belongs to a allowed group locally
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_ALLOWED_BY_GROUP}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANYCONNECTED:
// The user is allowed because any connected users are allowed
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_ALLOWED_BY_ANYCONNECTED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
// The user is allowed by inheritance
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_INHERIT_ALLOWED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
// The user is denied
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_LOCAL_DENIED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_GROUP:
// The user is denied because he belongs to a denied group locally
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_DENIED_BY_GROUP}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_ANYCONNECTED:
// The user is denied because any connected users are denied
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_DENIED_BY_ANYCONNECTED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
// The user is denied by inheritance
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_USER_INHERIT_DENIED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
default:
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_UNKNOWN}}";
}
},
/**
* @private
* Compute the tooltip for a group record and access type
* @param {Ext.data.Model} record The group record
* @param {String} accessType The access type
* @return The tooltip text
*/
_computeTooltipForGroup: function (record, accessType)
{
switch (accessType)
{
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS:
// The group is allowed because Anonymous is allowed
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_ALLOWED_BY_ANONYMOUS}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
// The group is locally allowed
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_LOCAL_ALLOWED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANYCONNECTED:
// The group is allowed because any connected users are allowed
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_ALLOWED_BY_ANYCONNECTED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
// The group is allowed by inheritance
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_INHERIT_ALLOWED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
// The group is denied
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_LOCAL_DENIED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_ANYCONNECTED:
// The group is denied because any connected users are denied
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_DENIED_BY_ANYCONNECTED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
// The group is denied by inheritance
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_GROUP_INHERIT_DENIED}}";
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
default:
return "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_UNKNOWN}}";
}
}
},
/**
* @private
* @property {Ext.form.field.ComboBox} _contextCombobox The combobox displaying the right assignment contexts
*/
/**
* @private
* @property {Ext.panel.Panel} _contextPanel The panel on the left of the tool, with a card layout, showing the context panel corresponding to the currently selected right assignment context in the combobox
*/
/**
* @private
* @property {Object} _contextComponents An object containing the context {@link Ext.Component}s (the key is the right assignment context id)
*/
/**
* @private
* @property {Ext.grid.Panel} _assignmentsGrid The grid panel on the right of the tool, showing the assignment on current object context.
*/
/**
* @private
* @property {Ext.data.Store} _gridStore The store of the grid
*/
/**
* @private
* @property {Object|Object[]} _objectContext The current object context(s). Must be up-to-date before loading the grid store. If multiple (more than 1), the grid store is not displayed.
*/
/**
* @private
* @property {Object[]} _profiles The profiles of the application.
* @property {String} _profiles.id The id of the profile
* @property {String} _profiles.label The label of the profile
* @property {String[]} _profiles.rights The ids of the rights this profile contains
*/
_profiles: [],
/**
* @private
* @property {Object[]} The unsaved assignments induced by the removal of records
*/
_removedAssignments: [],
/**
* @private
* @property {Object} _storedValues The stored assignments by profile (server-side)
* The key is the profile id, the value is an object (where its key is the record id, the value is the server-side value (with no induced local changes)
*/
/**
* @cfg {String} [rightContextHintPrefix="PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_HINT1"] The prefix text to use for right context hint
*/
/**
* @private
* @property {String} _rightContextHintPrefix. See #cfg-rightContextHintPrefix
*/
/**
* @cfg {String} [profilesPluginName="core"] The name of the plugin used to get the list of profiles.
*/
/**
* @private
* @property {String} _profilesPluginName See #cfg-profilesPluginName
*/
/**
* @cfg {String} [profilesUrl="rights/profiles.json"] The plugin url used to get the the list of profiles.
*/
/**
* @private
* @property {String} See #cfg-profilesUrl
*/
constructor: function (config)
{
this._profilesPluginName = config.profilesPluginName || 'core';
this._profilesUrl = config.profilesUrl || 'rights/profiles.json';
this._rightContextHintPrefix = config.rightContextHintPrefix || "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_HINT1}}";
this._initializing = true;
Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onMessage, this);
Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onMessage, this);
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onMessage, this);
this.callParent(arguments);
},
createPanel: function()
{
this._contextCombobox = Ext.create('Ext.form.field.ComboBox', this._getContextComboCfg());
this._contextPanel = Ext.create('Ext.panel.Panel', {
minWidth: 300,
flex: 1,
scrollable: false,
split: true,
layout: 'card',
cls: 'context-panel',
dockedItems: [{
xtype: 'toolbar',
cls: 'context-toolbar',
layout: {
type: 'vbox',
align: 'stretch'
},
dock: 'top',
items: [{
xtype: 'component',
html: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CONTEXT}}",
style: {
paddingTop: '5px',
paddingBottom: '5px'
}
},
this._contextCombobox
]
}],
listeners: {
'objectcontextchange': Ext.bind(this._onObjectContextChange, this)
}
});
this._createContextPanels();
// Hide if only one context and it is hideable
var rightAssignmentContexts = this.getFactory()._rightAssignmentContexts,
rightAssignmentContextIds = Object.keys(rightAssignmentContexts);
if (rightAssignmentContextIds.length == 1 && rightAssignmentContexts[rightAssignmentContextIds[0]].isHideable())
{
this._contextPanel.hide();
}
var mainPanel = Ext.create("Ext.container.Container", {
layout: {
type: 'hbox',
align: 'stretch'
},
cls: 'uitool-profile-assignment',
items: [
this._contextPanel,
{
xtype: 'container',
itemId: 'right-card-container',
layout: 'card',
activeItem: 0,
split: true,
flex: 3,
items: [{
xtype: 'component',
cls: 'a-panel-text-empty',
border: false,
html: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_NO_OBJECT_CONTEXT}}"
},
{
xtype: 'container',
itemId: 'grid-wrapper',
layout: 'fit',
html: ''
}
]
}
]
});
return mainPanel;
},
/**
* @private
* Create the grid for assignments
* @param {Object[]} columns The columns
* @return the created grid
*/
_createGrid: function (columns)
{
this._gridStore = Ext.create('Ametys.plugins.coreui.profiles.PermissionTargetStore', {
proxy: {
type: 'ametys',
plugin: 'core-ui',
url: 'profileAssignments.json',
reader: {
type: 'json',
rootProperty: 'assignments'
}
},
listeners: {
'beforeload': Ext.bind(this._onBeforeLoadGrid, this),
'update': Ext.bind(this._onUpdateOrLoadGrid, this),
'load': Ext.bind(this._onUpdateOrLoadGrid, this)
}
});
return Ext.create('Ametys.plugins.coreui.profiles.PermissionTargetGrid', {
dockedItems: this._getGridDockedItemsCfg(),
enableColumnMove: true,
store: this._gridStore,
listeners: {
'selectionchange': Ext.bind(this.sendCurrentSelection, this)
},
stateful: true,
stateId: this.self.getName() + "$grid",
selModel: {
mode: 'MULTI'
},
columns: columns
});
},
/**
* @private
* Gets the configuration of the combobox for assignment contexts
* @return {Object} The config object
*/
_getContextComboCfg: function()
{
var data = [];
Ext.Object.each(this.getFactory()._rightAssignmentContexts, function(id, rightAssignmentContext) {
data.push({
value: id,
displayText: rightAssignmentContext.getLabel()
});
});
return {
store: {
fields: ['value', {name: 'displayText', type: 'string'}],
data: data,
sorters: [{property: 'displayText', direction: 'ASC'}]
},
autoSelect: false,
editable: false,
listeners: {
'change': Ext.bind(this._onComboboxChange, this)
},
queryMode: 'local',
allowBlank: false,
forceSelection: true,
triggerAction: 'all',
valueField: 'value',
displayField: 'displayText',
hideLabel: true,
flex: 0.5
};
},
/**
* @private
* Function called when the current object context has changed.
* @param {Object|Object[]} object The new object(s) context(s)
* @param {String} hintTextContext The hint text to update
* @param {Boolean} [readOnly] true if no modification is allowed on object context
* @param {Boolean} [inheritanceNotAvailable] true if inheritance is not available on object context
*/
_onObjectContextChange: function(object, hintTextContext, readOnly, inheritanceNotAvailable)
{
if (this.isDirty())
{
Ametys.Msg.show({
title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CHANGE_CONTEXT_BOX_TITLE}}",
msg: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CHANGE_CONTEXT_BOX_MESSAGE}}",
buttons: Ext.Msg.YESNO,
icon: Ext.MessageBox.QUESTION,
fn: Ext.bind(callback, this, [object, hintTextContext, readOnly, inheritanceNotAvailable], 1)
});
}
// Only change context if the grid is initialized
else if (this._gridStore != undefined)
{
this._internalChangeObjectContext(object, hintTextContext, readOnly, inheritanceNotAvailable);
}
function callback(btn, object, parentObjects, hintTextContext, readOnly, inheritanceNotAvailable)
{
if (btn == 'yes')
{
this._saveChanges(this._contextCombobox.getValue(), Ext.bind(this._internalChangeObjectContext, this, [object, hintTextContext, readOnly, inheritanceNotAvailable]));
}
else
{
this._internalChangeObjectContext(object, hintTextContext, readOnly, inheritanceNotAvailable);
}
}
},
/**
* @private
* Changes the internal representation of the object context, update the hint text of the grid and updates the grid.
* @param {Object|Object[]} object The new object(s) context(s)
* @param {String} hintTextContext The hint text to update
* @param {Boolean} [readOnly=false] true if no modification is allowed on object context
* @param {Boolean} [inheritanceNotAvailable=false] true if the inheritance of assignments is not supported on this context
*/
_internalChangeObjectContext: function(object, hintTextContext, readOnly, inheritanceNotAvailable)
{
this._clearFilters(); // avoid bugs in the grid store before loading it
this.getLogger().info("Right assignment context has changed to : " + object);
if (!object)
{
this._objectContext = null;
// no context selected
this.getContentPanel().down('#right-card-container').items.get(0).setHtml("{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_NO_OBJECT_CONTEXT}}");
this.getContentPanel().down('#right-card-container').getLayout().setActiveItem(0);
this.sendCurrentSelection();
}
else if (Ext.isArray(object) && object.length > 1)
{
// multi contexts selected
this._objectContext = [];
Ext.Array.each(object, function(oCtx) {
this._objectContext.push({
context: oCtx,
modifiable: !readOnly,
inheritanceAvailable: !inheritanceNotAvailable
})
}, this)
this.getContentPanel().down('#right-card-container').items.get(0).setHtml("{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_MULTI_OBJECT_CONTEXT}}")
this.getContentPanel().down('#right-card-container').getLayout().setActiveItem(0);
this.sendCurrentSelection();
}
else
{
object = Ext.isArray(object) ? object[0] : object;
this._objectContext = {
context: object,
modifiable: !readOnly,
inheritanceAvailable: !inheritanceNotAvailable
}
var me = this;
function callback(inheritanceDisallowed)
{
me._objectContext.inheritanceDisallowed = inheritanceDisallowed;
me.getContentPanel().down('#right-card-container').getLayout().setActiveItem(1);
me._assignmentsGrid.getDockedItems('#context-helper-text')[0].update(me._rightContextHintPrefix + hintTextContext);
me._updateGrid();
me._switchToReadOnlyMode(readOnly);
me._switchToInheritanceDisallowedMode(inheritanceDisallowed);
me.sendCurrentSelection();
}
if (!inheritanceNotAvailable)
{
// get status of inheritance from server
var parameters = [
this.getFactory()._rightAssignmentContexts[this._contextCombobox.getValue()].getServerId(),
this._objectContext.context
];
this.serverCall('isInheritanceDisallowed', parameters, callback, {waitMessage: false});
}
else
{
callback(false);
}
}
},
/**
* @private
* Enter or leave the read-only mode
* @param {Boolean} readOnly true to switch to read-only mode
*/
_switchToReadOnlyMode: function (readOnly)
{
if (this._assignmentsGrid.rendered)
{
var lockingGrid = this._assignmentsGrid.items.getRange()[0],
normalGrid = this._assignmentsGrid.items.getRange()[1];
if (readOnly)
{
this._assignmentsGrid.getDockedItems('#context-readonly-text')[0].show();
lockingGrid.getView().mask();
normalGrid.getView().mask();
}
else
{
this._assignmentsGrid.getDockedItems('#context-readonly-text')[0].hide();
lockingGrid.getView().unmask();
normalGrid.getView().unmask();
}
}
},
/**
* @private
* Enter or leave the "inheritance disallowed" mode
* @param {Boolean} disallow true to switch to the mode when inheritance is disallowed on the selected context
*/
_switchToInheritanceDisallowedMode: function (disallow)
{
if (disallow)
{
this._assignmentsGrid.getDockedItems('#context-inheritance-disallowed-text')[0].show();
}
else
{
this._assignmentsGrid.getDockedItems('#context-inheritance-disallowed-text')[0].hide();
}
},
/**
* @private
* Gets the configuration of the docked items of the grid
* @return {Object[]} the docked items
*/
_getGridDockedItemsCfg: function()
{
return [{
xtype: 'component',
itemId: 'context-helper-text',
ui: 'tool-hintmessage'
}, {
xtype: 'component',
itemId: 'context-inheritance-disallowed-text',
html: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CONTEXT_INHERITANCE_DISALLOWED}}",
ui: 'tool-hintmessage',
hidden: true
}, {
xtype: 'component',
itemId: 'context-readonly-text',
html: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CONTEXT_READONLY}}",
hidden: true,
ui: 'tool-hintmessage'
}, {
dock: 'top',
xtype: 'toolbar',
layout: {
type: 'hbox',
align: 'stretch'
},
border: false,
defaultType: 'textfield',
items: [{
xtype: 'component',
html: '{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_FILTERS}}'
}, {
xtype: 'edition.right',
itemId: 'profile-filter',
name: 'profile-filter',
cls: 'ametys',
allowBlank: true,
multiple: false,
stacked: "false",
width: 400,
emptyText: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_RIGHT_FILTER}}",
listeners: {change: Ext.bind(this._filterByRight, this)}
}, {
itemId: 'user-group-filter',
name: 'user-group-filter',
cls: 'ametys',
width: 400,
emptyText: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_USERGROUP_FILTER}}",
listeners: {change: Ext.Function.createBuffered(this._filterByUserOrGroup, 500, this)}
}]
}];
},
/**
* @private
* Filters the columns by right
* @param {Ametys.form.widget.Right} field The right field
*/
_filterByRight: function(field)
{
Ext.suspendLayouts();
var rightId = field.getValue();
if (Ext.isEmpty(rightId))
{
Ext.Array.forEach(this._assignmentsGrid.getColumns(), function(column, index) {
column.setVisible(true);
}, this);
}
else
{
// Computes the columns to let visible
var matchingProfiles = [];
Ext.Array.forEach(this._profiles, function(profile) {
if (Ext.Array.contains(profile.rights, rightId))
{
matchingProfiles.push(profile.id);
}
}, this);
// Hide the others (except the first column)
Ext.Array.forEach(this._assignmentsGrid.getColumns(), function(column, index) {
var visible = index == 0 || Ext.Array.contains(matchingProfiles, column.dataIndex);
column.setVisible(visible);
}, this);
}
Ext.resumeLayouts(true);
},
/**
* @private
* Filters the records by their user login/label or group id/label
* @param {Ext.form.field.Text} field The text field
*/
_filterByUserOrGroup: function(field)
{
this._gridStore.clearFilter();
var text = Ext.String.escapeRegex(field.getRawValue());
if (text.length == 0)
{
return;
}
var fn = function(record, text)
{
var regExp = new RegExp('.*' + text + '.*', 'i');
return regExp.test(record.get('login'))
|| regExp.test(record.get('groupId'))
|| regExp.test(record.get('sortableLabel'));
};
this._gridStore.filterBy(Ext.bind(fn, this, [text], 1), this);
},
/**
* @private
* Clear the filters
*/
_clearFilters: function()
{
this._gridStore.clearFilter(); // We cannot wait for the 'change' event to be fired after the #setValue("") because we created a 500ms buffer and it lead to bugs
this._assignmentsGrid.down('#user-group-filter').setValue("");
},
/**
* Function called when an assignment is clicked in order to change its value
* @param {String} recordId The id of the record
* @param {String} profileId The profile id (id of the column)
*/
onCellClick: function(recordId, profileId)
{
function callback (computedValue)
{
// Re-init other local values to be sure to not have previously induced values
this._reinitComputedLocalValues(profileId);
// Compute and update the induced values on other records of same column
var records = this._getUnfilteredRecords();
Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.computeAndUpdateLocalInducedAssignments (records, profileId);
}
// Compute and set the new value
var newValue = this._computeNewValue (recordId, profileId, callback, this);
},
/**
* @private
* Compute the new value for assignment and save the new value on record.
* The value is computed on following rotation : Allow <-> Deny <-> Unknown <-> Allow
* @param {String} recordId The id of record
* @param {String} profileId The id of profile
* @param {Function} callback The callback function invoked after computing the new value. The arguments are:
* @param {Function} callback.value The computed value
* @param {Object} [scope] The scope of callback function
*/
_computeNewValue: function(recordId, profileId, callback, scope)
{
var record = this._gridStore.getById(recordId);
var newValue;
// Local value (client-side)
var currentValue = record.get(profileId);
// Stored value (server-side)
var storedValue = this._storedValues[profileId][recordId] || Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
if (currentValue == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW)
{
this._setComputedValue(Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY, record, profileId, callback, scope);
}
else if (currentValue == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
{
// When go to 'unknown' value, the value is computed from parent contexts
var parameters = [
this.getFactory()._rightAssignmentContexts[this._contextCombobox.getValue()].getServerId(),
this._objectContext.context,
profileId,
record.get('targetType'),
this._getIdentity(record)
];
this.serverCall('getInheritedAssignment', parameters, Ext.bind(this._setComputedValue, this, [record, profileId, callback, scope], 1), {waitMessage: false});
}
else
{
this._setComputedValue(Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW, record, profileId, callback, scope);
}
},
/**
* @private
* Function invoked after computing the new assignment value. Set the new value.
* @param {String} newValue The computed value
* @param {String} record The record to update
* @param {String} profileId The id of profile
* @param {Function} callback The callback function invoked after computing the new value. The arguments are:
* @param {Function} callback.value The computed value
* @param {Object} [scope] The scope of callback function
*/
_setComputedValue: function(newValue, record, profileId, callback, scope)
{
var storedValue = this._storedValues[profileId][record.getId()] || Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
record.set(profileId, newValue, {dirty: storedValue != newValue});
if (Ext.isFunction(callback))
{
callback.call (scope, newValue);
}
},
/**
* @private
* Reinitialize the local values (client-side) of records with the last stored values (server-side) for the given profile (column) except for the given record
* @param {String} profileId The id of profile
*/
_reinitComputedLocalValues: function(profileId)
{
var storedValues = this._storedValues[profileId];
this._getUnfilteredRecords().each(function(record) {
if (!Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.isCellDirty(record, profileId))
{
var storedValue = storedValues[record.getId()] || Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
record.set(profileId, storedValue, {dirty: false})
}
});
},
/**
* Adds user records in the assignment grid.
* @param {Object[]} users The users to add
* @param {String} users.login The login of the user
* @param {String} users.populationId The id of the population of the user
* @param {String} users.populationName The label of the population of the user
* @param {String} users.fullName The full name of the user
*/
addUsers: function(users)
{
var usersToAdd = [],
addedRecords = [],
records = this._getUnfilteredRecords();
Ext.Array.forEach(users, function(user) {
if (!Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.findUserRecord(records, user.login, user.populationId))
{
usersToAdd.push(user);
}
}, this);
var total = usersToAdd.length,
count = 0;
function addInStore(groups, user)
{
addedRecords = Ext.Array.merge (addedRecords, this._gridStore.add({
targetType: Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER,
login: user.login,
populationId: user.populationId,
populationLabel: user.populationName,
userSortableName: user.fullName,
groups: groups,
isNew: true
}));
count++;
if (count == total)
{
this._updateAddedRecords(addedRecords);
}
}
Ext.Array.forEach(usersToAdd, function(user) {
// Need to know the groups the user belongs to
this.serverCall('getUserGroups', [user.login, user.populationId], Ext.bind(addInStore, this, [user], 1));
}, this);
},
/**
* Adds group records in the assignment grid.
* @param {Object[]} groups The groups to add
* @param {String} groups.id The id of the group
* @param {String} groups.groupDirectory The id of the group directory of the group
* @param {String} groups.groupDirectoryName The label of the group directory of the group
* @param {String} groups.label The label of the group
*/
addGroups: function(groups)
{
var addedRecords = [],
records = this._getUnfilteredRecords();
Ext.Array.forEach(groups, function(group) {
if (!Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.findGroupRecord(records, group.id, group.groupDirectory))
{
addedRecords = Ext.Array.merge (addedRecords, this._gridStore.add({
targetType: Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP,
groupId: group.id,
groupDirectory: group.groupDirectory,
groupDirectoryLabel: group.groupDirectoryName,
groupLabel: group.label,
isNew: true
}));
}
}, this);
if (addedRecords.length > 0)
{
this._updateAddedRecords(addedRecords);
}
},
/**
* @private
* This function is invoked when adding manually user or group records
* Update the assignment for each profile with the inherited assignment
* @param {Ext.data.Model[]} addedRecords The added records (users or groups)
*/
_updateAddedRecords: function (addedRecords)
{
var me = this,
count = 0;
total = addedRecords.length;
Ext.Array.forEach(addedRecords, function(record) {
function callback (inheritedValues)
{
for (var i in inheritedValues)
{
record.set(i, inheritedValues[i], {dirty: false});
}
count++;
if (count == total)
{
this._onStoreUpdated();
}
}
// Get the inherited assignment for each profile for the added user or group
var parameters = [
me.getFactory()._rightAssignmentContexts[me._contextCombobox.getValue()].getServerId(),
me._objectContext.context,
me._getProfileIds(),
record.get('targetType'),
me._getIdentity(record)
];
me.serverCall('getInheritedAssignments', parameters, Ext.bind(callback, me), {waitMessage: false});
});
},
/**
* @private
* Returns the id of current profiles
* @return the id of current profiles
*/
_getProfileIds: function()
{
var profileIds = [];
Ext.Array.forEach(this._profiles, function (profile) {
profileIds.push (profile.id)
});
return profileIds;
},
/**
* Removes the given assignments
* @param {Object[]} assignments The assignments to remove
* @param {String} assignments.id The record id
* @param {Object} assignments.context The context
*/
removeAssignments: function(assignments)
{
var me = this;
Ext.Array.forEach(assignments, function(assignment) {
var record = this._gridStore.getById(assignment.id);
if (!record.get('isNew')
&& assignment.context == this._objectContext.context
&& assignment != null
&& record.get('targetType') != Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANONYMOUS
&& record.get('targetType') != Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANYCONNECTEDUSER)
{
// Iterate through the profiles, only keep the ones with local assignments to avoid useless removal
Ext.Array.forEach(this._profiles, function(profile) {
var profileId = profile.id,
currentAssignment = record.get(profileId);
if (currentAssignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW || currentAssignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
{
var assignmentInfo = {
profileId: profileId,
targetType: record.get('targetType'),
assignment: null
};
if (record.get('targetType') == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER)
{
assignmentInfo.identity = {
login: record.get('login'),
populationId: record.get('populationId')
};
}
else
{
assignmentInfo.identity = {
groupId: record.get('groupId'),
groupDirectory: record.get('groupDirectory')
};
}
me._removedAssignments.push(assignmentInfo);
}
}, this);
}
// Remove record from the grid
this._gridStore.remove([record]);
if (this._removedAssignments.length > 0)
{
this.setDirty(true);
}
}, this);
},
/**
* Cancel the changes made in the grid
*/
cancelChanges: function ()
{
this._gridStore.rejectChanges();
this._updateGrid();
},
/**
* Saves the changes made in the grid
*/
saveChanges: function()
{
this._saveChanges(this._contextCombobox.getValue(), this._updateGrid);
},
/**
* @private
* Computes the assignments and make a server call to save changes.
* @param {String} rightAssignmentId The id of the right assignment context
* @param {Function} [callback] The callback function to call when the changes are saved.
*/
_saveChanges: function(rightAssignmentId, callback)
{
var assignmentsInfo = [];
Ext.Array.forEach(this._gridStore.getModifiedRecords(), function(record) {
Ext.Object.each(record.modified, function(profileId) {
var assignment = record.get(profileId);
if (assignment != Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW && assignment != Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
{
assignment = Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
}
assignmentsInfo.push({
profileId: profileId,
targetType: record.get('targetType'),
assignment: assignment,
identity: this._getIdentity (record)
});
}, this);
}, this);
// Add the assignment due to removed records
assignmentsInfo = Ext.Array.merge(this._removedAssignments, assignmentsInfo);
if (assignmentsInfo.length > 0)
{
var parameters = [this.getFactory()._rightAssignmentContexts[rightAssignmentId].getServerId(), this._objectContext.context, assignmentsInfo];
this.serverCall('saveChanges', parameters, callback);
}
},
/**
* Apply some assignments and inheritance status on given contexts
* @param {Object} contexts the contexts with:
* @param {String} contexts.context the JS context
* @param {Boolean} contexts.inheritanceAvailable true if inheritance is available for the context
* @param {Object} assignmentsInfo the assignments to apply:
* @param {Object[]} assignmentsInfo.assignments the assignments
* @param {Boolean} assignmentsInfo.inheritanceDisallowed the inheritance status to apply (will be applied on context with inheritance available only)
*/
applyAssignments: function(contexts, assignmentsInfo)
{
var assignments = assignmentsInfo.assignments
if (assignments.length > 0)
{
function doApply(btn)
{
if (btn == 'yes')
{
var rightAssignmentId = this._contextCombobox.getValue();
var parameters = [this.getFactory()._rightAssignmentContexts[rightAssignmentId].getServerId(), contexts, assignments, assignmentsInfo.inheritanceDisallowed];
this.serverCall('applyAssignments', parameters, this._applyAssignmentsCb, { arguments: { inheritanceDisallowed: assignmentsInfo.inheritanceDisallowed } });
}
}
Ametys.Msg.confirm ("{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_PASTE_ASSIGNMENTS_TITLE}}",
"{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_PASTE_ASSIGNMENTS_CONFIRM}}",
Ext.bind(doApply, this));
}
},
/**
* @private
* Callback function invoked after applying assignments on contexts
* @param {Boolean} success false if something went wrong
* @param {Object} args additionnal args
*/
_applyAssignmentsCb: function(success, args)
{
if (!success)
{
Ametys.Msg.show({
title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_PASTE_ASSIGNMENTS_TITLE}}",
msg: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_PASTE_ASSIGNMENTS_ERROR}}",
buttons: Ext.Msg.OK,
icon: Ext.MessageBox.ERROR
});
return;
}
// Update inheritance status if assignments were applied on a single selection (inheritance status is ignored in a multi-selection)
if (!Ext.isArray(this._objectContext))
{
this._objectContext.inheritanceDisallowed = args.inheritanceDisallowed
}
this._updateGrid();
var targets = this._getProfileContextTargetsConfig();
Ext.create('Ametys.message.Message', {
type: Ametys.message.Message.MODIFIED,
targets: targets
});
},
/**
* Get the current assignments stored on current context. All changes should be saved before calling this method.
* @return {Object[]} the stored assignments
*/
getCurrentAssignments: function()
{
if (this.isDirty())
{
// some changes have to be saved
Ametys.Msg.show({
title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_GET_ASSIGNMENTS_TITLE}}",
msg: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_GET_ASSIGNMENTS_NOSAVE_MESSAGE}}",
buttons: Ext.Msg.OK,
icon: Ext.MessageBox.ERROR
});
return null;
}
var assignmentsInfo = [];
var me = this;
var records = this._getUnfilteredRecords();
records.each(function(record) {
Ext.Array.each(me._getProfileIds(), function(profileId) {
var assignment = record.get(profileId);
if (assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW || assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
{
assignmentsInfo.push({
profileId: profileId,
targetType: record.get('targetType'),
assignment: assignment,
identity: me._getIdentity (record)
});
}
});
});
return assignmentsInfo;
},
/**
* Allow or disallow inheritance on current object context
* @param {Boolean} disallow true to disallow inheritance
*/
disallowInheritance: function(disallow)
{
var me = this;
function callback()
{
// update current object context
me._objectContext.inheritanceDisallowed = disallow;
// reload the assignments grid
me._updateGrid();
// hide/show warning on inheritance
me._switchToInheritanceDisallowedMode(disallow)
// update selection message
var targets = this._getProfileContextTargetsConfig();
Ext.create('Ametys.message.Message', {
type: Ametys.message.Message.MODIFIED,
targets: targets
});
}
var parameters = [
me.getFactory()._rightAssignmentContexts[me._contextCombobox.getValue()].getServerId(),
me._objectContext.context,
disallow
];
me.serverCall('disallowInheritance', parameters, Ext.bind(callback, me), {waitMessage: false});
},
/**
* @private
* Function called before loading the grid store
* @param {Ext.data.Store} store The grid store
* @param {Ext.data.operation.Operation} operation The object that will be passed to the Proxy to load the store
*/
_onBeforeLoadGrid: function(store, operation)
{
operation.setParams(Ext.apply(operation.getParams() || {}, {
rightAssignmentContextId: this.getFactory()._rightAssignmentContexts[this._contextCombobox.getValue()].getServerId(),
context: this._objectContext.context
}));
},
/**
* @private
* Listener when a Model instance of the grid store has been updated, or when the grid store is loaded
* @param {Ext.data.Store} store The store
*/
_onUpdateOrLoadGrid: function(store)
{
// store.getModifiedRecords().length > 0 is not sufficient as when adding a record, there is no dirty cell but the record is returned in this array anyway
var dirty = false;
Ext.Array.each(store.getModifiedRecords(), function(record) {
if (!Ext.Object.isEmpty(record.modified))
{
dirty = true;
return false;
}
}, this);
this.setDirty(dirty);
},
/**
* @private
* Listener when the value of the context combobox changes.
* @param {Ext.form.field.ComboBox} combo The combobox
* @param {Object} newValue The new value (the selected context id)
* @param {Object} oldValue The old value
*/
_onComboboxChange: function(combo, newValue, oldValue)
{
if (this.isDirty())
{
Ametys.Msg.show({
title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CHANGE_CONTEXT_BOX_TITLE}}",
msg: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CHANGE_CONTEXT_BOX_MESSAGE}}",
buttons: Ext.Msg.YESNO,
icon: Ext.MessageBox.QUESTION,
fn: Ext.bind(callback, this, [combo, newValue, oldValue], 1)
});
}
else
{
this._changeContextPanel(newValue, oldValue);
}
function callback(btn, combo, newValue, oldValue)
{
// Force dirty state to false to avoid a second dialog box if event 'objectcontextchange' is fired too soon
this.setDirty(false);
if (btn == 'yes')
{
this._saveChanges(oldValue, Ext.bind(this._changeContextPanel, this, [newValue, oldValue]));
}
else
{
this._changeContextPanel(newValue, oldValue);
}
}
},
/**
* @private
* Changes the active context panel
* @param {String} rightAssignmentContextId The id of the right assignment context to display
* @param {String} oldRightAssignmentContextId The id of the old right assignment context to display
*/
_changeContextPanel: function(rightAssignmentContextId, oldRightAssignmentContextId)
{
// Dispose old assigment context id if exist
var oldRightAssignmentContext = this.getFactory()._rightAssignmentContexts[oldRightAssignmentContextId];
if (oldRightAssignmentContext)
{
oldRightAssignmentContext.dispose();
}
// Clear filters
this._clearFilters();
// Change the current panel displayed in the context panel
this._contextPanel.getLayout().setActiveItem(this._contextComponents[rightAssignmentContextId]);
// Call its initialize() method
var rightAssignmentContext = this.getFactory()._rightAssignmentContexts[rightAssignmentContextId];
if (rightAssignmentContext)
{
rightAssignmentContext.initialize();
}
},
/**
* @private
* Updates the grid cells from the context.
* @param {Object} result Result sent by the server
* @param {Boolean} result.success If every assignment is successfull
* @param {Object[]} result.errorInfos list of affectations in error
* @param {String} result.errorInfos.assignment action (allow, deny, unknown, etc...)
* @param {Object} result.errorInfos.identity User or group impacted or null
* @param {String} result.errorInfos.targetType user, group, anyconnected_user
* @param {String} result.errorInfos.profileId id of the profile
* @param {String} result.errorInfos.profileLabel label of the profile
* @param {Object[]} result.successInfos list of affectations successfull
* @param {String} result.successInfos.assignment action (allow, deny, unknown, etc...)
* @param {Object} result.successInfos.identity User or group impacted or null
* @param {String} result.successInfos.targetType user, group, anyconnected_user
* @param {String} result.successInfos.profileId id of the profile
* @param {String} result.successInfos.profileLabel label of the profile
*/
_updateGrid: function(result)
{
if (result && !result.success)
{
var details = "";
if (result.errorInfos)
{
var haveNegativeRights = false;
var unallowedProfiles = [];
for (var i = 0; i < result.errorInfos.length; i++)
{
var info = result.errorInfos[i];
if (info.assignment != "allow")
{
haveNegativeRights = true;
}
else
{
var profileDisplayed = "- " + Ext.String.escapeHtml(info.profileLabel) + " (" + info.profileId + ")";
if (!unallowedProfiles.includes(profileDisplayed))
{
unallowedProfiles.push(profileDisplayed);
}
}
}
if (haveNegativeRights)
{
details += "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_DELEGATION_SAVE_ERROR_ONLY_ADD_PROFILE}}";
if (unallowedProfiles.length != 0)
{
details += "\n";
}
}
if (unallowedProfiles.length != 0)
{
details += "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_DELEGATION_SAVE_ERROR_UNALLOWED_PROFILES}}\n";
details += unallowedProfiles.join('\n');
}
}
Ametys.log.ErrorDialog.display({
title: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_DELEGATION_SAVE_ERROR_TITLE}}",
text: "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TOOLTIP_DELEGATION_SAVE_ERROR}}",
details: details,
category: this.self.getName()
});
}
this._removedAssignments = [];
this._gridStore.load({
callback: this._onStoreUpdated,
scope: this
});
},
/**
* @private
* This function has to be invoked after the store is modified
* For instance, callback of the store loading, or after adding a new user/group record.
*/
_onStoreUpdated: function()
{
var me = this;
// Update the server-side values
this._reinitStoredValues();
// Update local assignments for each profiles
var records = this._getUnfilteredRecords();
Ext.Array.forEach(this._profiles, function(profile) {
Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.AssignmentHelper.computeAndUpdateLocalInducedAssignments (records, profile.id);
});
// Clear the selection
this._assignmentsGrid.getSelectionModel().deselectAll();
},
/**
* @private
* Create a columns object representing the columns
*/
_reinitStoredValues: function()
{
this._storedValues = {};
Ext.Array.forEach(this._profiles, function(profile) {
this._storedValues[profile.id] = {};
}, this);
var records = this._getUnfilteredRecords();
records.each(function(record) {
Ext.Object.each(this._storedValues, function(profileId, assignments) {
var assignment = record.get(profileId);
if (assignment && (assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW
|| assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY
|| assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW
|| assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY))
{
assignments[record.getId()] = record.get(profileId)
}
else
{
// All other assignment values can not be server-side values
assignments[record.getId()] = Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN;
}
}, this)
}, this);
},
setParams: function(params)
{
this.callParent(arguments);
this._objectContext = null;
this.showOutOfDate();
},
close: function(manual)
{
var assigmentContext = this.getFactory()._rightAssignmentContexts[this._contextCombobox.getValue()];
if (assigmentContext)
{
assigmentContext.dispose();
}
if (this.isDirty())
{
Ametys.form.SaveHelper.promptBeforeQuit("{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CLOSE_BOX_TITLE}}",
"{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_CLOSE_BOX_MESSAGE}}",
null,
Ext.bind(this._closeCb, this));
}
else
{
this.callParent(arguments);
}
},
/**
* @private
* Callback function after the user clicked on one of the three choices in the "Prompt before quit" dialog box
* @param {Boolean} doSave true means the user want to save. false means the user does not want to save. null means the user does not want to save nor quit.
*/
_closeCb: function(doSave)
{
if (doSave === true)
{
this._saveChanges(this._contextCombobox.getValue(), Ext.bind(this._closeWithoutPrompt, this));
}
else if (doSave === false)
{
this._closeWithoutPrompt();
}
},
/**
* @private
* Calls the close method on superclass
*/
_closeWithoutPrompt: function()
{
Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.superclass.close.call(this);
},
/**
* @private
* Creates the context panels
*/
_createContextPanels: function()
{
this._contextComponents = {};
Ext.Object.each(this.getFactory()._rightAssignmentContexts, function(contextId, rightAssignmentContext) {
var cmp = rightAssignmentContext.getComponent();
this._contextComponents[contextId] = cmp;
// Add the component in the context panel (which has a card layout)
this._contextPanel.add(cmp);
// Give the reference to the context panel
rightAssignmentContext.setContextPanel(this._contextPanel);
}, this);
},
refresh: function()
{
this._initializing = true;
this.showRefreshing();
// First, retrieve the profiles to reconfigure the grid panel (every profile is a column)
Ametys.data.ServerComm.send({
plugin: this._profilesPluginName,
url: this._profilesUrl,
parameters: {
limit: null,
context: this._getProfilesContext()
},
priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
callback: {
handler: this._getProfilesCb,
scope: this,
arguments: []
},
errorMessage: true,
waitMessage: false,
responseType: 'text'
});
},
/**
* @protected
* Get the context to display profiles (additionnaly to null context)
* @returns {String} the context to display or empty
*/
_getProfilesContext: function()
{
return "";
},
/**
* @private
* Callback function after retrieving the profiles
* @param {Object} response The server response
*/
_getProfilesCb: function(response)
{
// The server returned a string json, decode it into an array of object
var stringData = response.firstChild.textContent;
var data = Ext.decode(stringData, true);
this._profiles = data.profiles;
var profilesColumn = Ametys.plugins.coreui.profiles.ProfilesGridHelper.getProfilesColumns(this._profiles, this._renderAssignment, this);
this._assignmentsGrid = this._createGrid(profilesColumn);
var newFields = Ext.Array.map(this._profiles, function(profile) {
return {name: profile.id};
}, this);
newFields.push({name: 'isNew', type: 'boolean', defaultValue: false});
this._gridStore.getModel().addFields(newFields);
this.getContentPanel().down('#grid-wrapper').removeAll();
this.getContentPanel().down('#grid-wrapper').add(this._assignmentsGrid);
if (this._contextCombobox.getStore().getCount() == 1)
{
this._contextPanel.down("toolbar").hide();
}
if (this._profiles.length < 2)
{
this.getContentPanel().down('#profile-filter').hide();
}
// Select the default context
this._selectDefaultContext();
this.showRefreshed();
},
/**
* @private
* Initializing the default context from the current selection on the message bus.
* If several contexts are supported, this of highest priority will be selected.
*/
_selectDefaultContext: function ()
{
var message = Ametys.message.MessageBus.getCurrentSelectionMessage();
var initContextFound = false;
// Get the right contexts which support the current selection
var supportedContexts = {};
Ext.Object.each(this.getFactory()._rightAssignmentContexts, function(contextId, rightAssignmentContext) {
if (rightAssignmentContext.isSupported(message))
{
supportedContexts[contextId] = rightAssignmentContext;
initContextFound = true;
}
}, this);
if (Ext.Object.isEmpty(supportedContexts))
{
// If no contexts support the current selection, re-init with all contexts
supportedContexts = this.getFactory()._rightAssignmentContexts;
}
// Select the context of max priority among the supported context
var maxPriority = -1;
var contextIdToSelect = null;
Ext.Object.each(supportedContexts, function(contextId, rightAssignmentContext) {
if (rightAssignmentContext.getPriority() > maxPriority)
{
contextIdToSelect = contextId;
maxPriority = rightAssignmentContext.getPriority();
}
}, this);
// Clear the value to force the trigger of a change event
this._contextCombobox.clearValue();
this._contextCombobox.select(contextIdToSelect);
this._initializing = false;
if (initContextFound)
{
this.getFactory()._rightAssignmentContexts[contextIdToSelect].initContext (message);
}
else
{
this.sendCurrentSelection();
}
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
sendCurrentSelection: function()
{
if (this._initializing)
{
return;
}
var me = this;
function hasLocalAssignments(record)
{
var result = false;
Ext.Array.each(me._profiles, function(profile) {
var profileId = profile.id,
assignment = record.get(profileId);
if (assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW || assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
{
result = true;
return false;
}
}, me);
return result;
}
var selection = this._assignmentsGrid ? this._assignmentsGrid.getSelection() : [];
var targets = [];
if (this._objectContext != null)
{
targets = this._getProfileContextTargetsConfig();
}
Ext.create('Ametys.message.Message', {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: targets
});
},
/**
* Get the configuration of profile context targets from current object context
* @return {Object[]} the targets' configuration
*/
_getProfileContextTargetsConfig: function()
{
var contexts = Ext.Array.from(this._objectContext);
var targets = [];
var me = this;
Ext.Array.each(contexts, function (oCtx) {
var params = {
context: oCtx.context,
modifiable: oCtx.modifiable,
inheritanceAvailable: oCtx.inheritanceAvailable
}
var subtargets = [];
if (contexts.length == 1)
{
params.inheritanceDisallowed = oCtx.inheritanceDisallowed;
function hasLocalAssignments(record)
{
var result = false;
Ext.Array.each(me._profiles, function(profile) {
var profileId = profile.id,
assignment = record.get(profileId);
if (assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW || assignment == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY)
{
result = true;
return false;
}
}, me);
return result;
}
var selection = me._assignmentsGrid ? me._assignmentsGrid.getSelection() : [];
var subtargets = Ext.Array.map(selection, function(record) {
var type = record.get('targetType'),
removable = (type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER || type == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP)
&& hasLocalAssignments(record);
return {
id: Ametys.message.MessageTarget.PROFILE_ASSIGNMENT,
parameters: {
id: record.get('id'),
type: type,
context: oCtx.context,
removable: removable
}
};
}, me);
}
targets.push({
id: Ametys.message.MessageTarget.PROFILE_CONTEXT,
parameters: params,
subtargets: subtargets
});
});
return targets;
},
/**
* @private
* Renderer for the first column, depending on the type of assignement
* @param {Object} value The data value (the user sortable name or the group label)
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.Entry} record The record
* @return {String} The html representation
*/
_renderWho: function(value, metaData, record)
{
var type = record.get('targetType');
switch (type) {
case Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANONYMOUS:
var text = "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TARGET_TYPE_ANONYMOUS}}";
return '<span class="ametysicon-carnival23"></span> ' + text;
case Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_ANYCONNECTEDUSER:
var text = "{{i18n PLUGINS_CORE_UI_TOOL_PROFILE_ASSIGNMENTS_TARGET_TYPE_ANYCONNECTEDUSERS}}";
return '<span class="ametysicon-key162"></span> ' + text;
case Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER:
return Ametys.grid.GridColumnHelper.renderUser.apply(this, arguments);
case Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP:
var text = value + ' (' + record.get('groupId') + ', ' + record.get('groupDirectoryLabel') + ')';
return '<span class="ametysicon-multiple25"></span> ' + text;
default:
return value;
}
},
/**
* @private
* Renderer for the assignment cells, which draws a clickable icon representing the assignement
* @param {Object} value The data value for the current cell
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.Entry} record The record for the current row
* @return {String} The HTML string to be rendered
*/
_renderAssignment: function(value, metaData, record)
{
var glyph, suffix;
switch (value)
{
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS:
glyph = "ametysicon-check";
suffix = "allowed disabled";
break;
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW:
glyph = "ametysicon-check-1";
suffix = "allowed";
break;
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_GROUP:
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANYCONNECTED:
glyph = "ametysicon-check";
suffix = "allowed";
break;
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_ALLOW:
glyph = "ametysicon-check decorator-ametysicon-up-arrow";
suffix = "allowed";
break;
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY:
glyph = "ametysicon-cross-1";
suffix = "denied";
break;
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_GROUP:
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_DENY_BY_ANYCONNECTED:
glyph = "ametysicon-cross";
suffix = "denied";
break;
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_INHERITED_DENY:
// Denied by inheritance
glyph = "ametysicon-cross decorator-ametysicon-up-arrow";
suffix = "denied";
break;
case Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_UNKNOWN:
default:
// Undetermined
glyph = "ametysicon-minus-symbol";
suffix = "unknown";
}
var tooltip = Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.computeTooltip(record, value);
metaData.tdAttr = 'data-qtip="' + tooltip + '"';
var onclickFn = "Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.onCellClick('" + record.get('id') + "', '" + metaData.column.dataIndex + "', '" + this.id + "')";
if (value == Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.ACCESS_TYPE_ALLOW_BY_ANONYMOUS)
{
// Disable cell (no action available)
// We need to reinitialize onclick to empty otherwise the last set onclick function remains attached to the span (strange ...)
return '<span class="a-grid-assignment-glyph ' + glyph + ' ' + suffix + '" onclick=""/>';
}
else
{
return '<span class="a-grid-assignment-glyph ' + glyph + ' ' + suffix + '" onclick="' + onclickFn + '"/>';
}
},
/**
* @private
* Gets all records in the assignment grid store, even the unfiltered items if there is a filter in this store.
* @return {Ext.util.Collection} All records in the assignment grid store
*/
_getUnfilteredRecords: function()
{
var collection = this._gridStore.getData(),
unfilteredItems = collection.getSource();
if (unfilteredItems != null)
{
// there is a filter
return unfilteredItems;
}
else
{
return collection;
}
},
/**
* @private
* Returns a Object representing the identity of the record
* @param {Ext.data.Model} record The target record
* @return {Object} record The identity
*/
_getIdentity: function (record)
{
if (record.get('targetType') == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_USER)
{
return {
login: record.get('login'),
populationId: record.get('populationId')
}
}
else if (record.get('targetType') == Ametys.plugins.coreui.profiles.PermissionTargetStore.TARGET_TYPE_GROUP)
{
return {
groupId: record.get('groupId'),
groupDirectory: record.get('groupDirectory')
}
}
return null;
},
/**
* @private
* Listener for bus message
* @param {Ametys.message.Message} message The message received
*/
_onMessage: function(message)
{
var targets = message.getTargets(Ametys.message.MessageTarget.PROFILE);
if (targets.length > 0 || message.getTarget(Ametys.message.MessageTarget.RIGHT))
{
// if it was already on default context, the grid will be bugged, so force it before to null
this._contextCombobox.select(null);
this.showOutOfDate();
}
}
});
/**
* This class is the data model for profile assignment grid entries.
* @private
*/
Ext.define('Ametys.plugins.coreui.profiles.ProfileAssignmentsTool.Entry', {
extend: 'Ext.data.Model',
fields: [
/* For user entries */
{name: 'login'},
{name: 'populationId'},
{name: 'populationLabel'},
{name: 'userSortableName'},
{name: 'groups'},
{name: 'isNew', type: 'boolean', defaultValue: false},
/* For group entries */
{name: 'groupId'},
{name: 'groupDirectory'},
{name: 'groupDirectoryLabel'},
{name: 'groupLabel'},
/* For grouping feature */
{name: 'targetType'},
/* For sorting */
{
name: 'sortableLabel',
type: 'string',
type: 'string',
convert: function(value, record) // using convert and not calculate because for an unknown reason, it doesn't work when closing and reopening the tool
{
if (record.get('userSortableName') != null)
{
return record.get('userSortableName');
}
else if (record.get('groupLabel') != null)
{
return record.get('groupLabel');
}
return "";
}
}
]
});
Ext.define("Ametys.message.ProfileAssignmentMessageTarget",{
override: "Ametys.message.MessageTarget",
statics:
{
/**
* @member Ametys.message.MessageTarget
* @readonly
* @property {String} PROFILE_ASSIGNMENT The target of the message is a profile assignment
* @property {String} PROFILE_ASSIGNMENT.id The id of the record
* @property {String} PROFILE_ASSIGNMENT.type The type of assignment
* @property {Object} PROFILE_ASSIGNMENT.context The object context of the assignment
* @property {Boolean} PROFILE_ASSIGNMENT.removable true if the record is removable
*/
PROFILE_ASSIGNMENT: "profileAssignment",
/**
* @member Ametys.message.MessageTarget
* @readonly
* @property {String} PROFILE_CONTEXT The target of the message is a profile assignment
* @property {Object} PROFILE_CONTEXT.context The object context of the assignment
* @property {Boolean} PROFILE_CONTEXT.modifiable false if it is a read-only context
* @property {Boolean} PROFILE_CONTEXT.inheritanceAvailable true if inheritance can be allow/disallow on this context
* @property {Boolean} [PROFILE_CONTEXT.inheritanceDisallowed] true if inheritance is disallowed on this context
*/
PROFILE_CONTEXT: "profileContext"
}
});