/*
* Copyright 2013 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.
*/
/**
* This class controls a ribbon button that will be responsible for a workflow action.
* The button will be enabled only if the workflow action is available for current selection.
* @private
*/
Ext.define('Ametys.plugins.cms.content.controller.SmartContentController', {
extend: 'Ametys.ribbon.element.ui.ButtonController',
/**
* @property {Boolean} _enabledOnUnlockOnly Not supported
* @protected
*/
/**
* @property {Number[]} _workflowActionId The workflow action id to execute
* @private
*/
/**
* @property {String[]} [_contentIds=[]] List of identifiers of contents concerned by the action of the controller
* @private
*/
constructor: function(config)
{
// Provide a default configuration for the no read description
config['noread-start-description'] = config['noread-start-description'] || "{{i18n PLUGINS_CMS_SMART_CONTENT_CONTROLLER_NO_READ_DEFAULT_DESCRIPTION_START}}";
config['noread-content-description'] = config['noread-content-description'] || "{{i18n PLUGINS_CMS_SMART_CONTENT_CONTROLLER_NO_READ_DEFAULT_DESCRIPTION_CONTENT}}";
config['noread-end-description'] = config['noread-end-description'] || "{{i18n PLUGINS_CMS_SMART_CONTENT_CONTROLLER_NO_READ_DEFAULT_DESCRIPTION_END}}";
this.callParent(arguments);
// Disable property to prevent conflict with common controller
this._enabledOnUnlockOnly = false;
Ametys.message.MessageBus.on(Ametys.message.Message.WORKFLOW_CHANGING, this._onWorkflowChanging, this);
Ametys.message.MessageBus.on(Ametys.message.Message.WORKFLOW_CHANGED, this._onWorkflowChanged, this);
Ametys.message.MessageBus.on(Ametys.message.Message.LOCK_CHANGED, this.refreshIfMatchingMessage, this);
},
/**
* @private
* Listener on workflow changing. Set buttons in refreshing state
* @param {Ametys.message.Message} message The bus message
*/
_onWorkflowChanging: function (message)
{
if (this.updateTargetsInCurrentSelectionTargets (message))
{
this.refreshing();
}
},
/**
* @private
* Listener on workflow changed. Stop the refreshing state and refresh
* @param {Ametys.message.Message} message The bus message
*/
_onWorkflowChanged: function (message)
{
if (this.updateTargetsInCurrentSelectionTargets (message))
{
this.stopRefreshing();
this.refresh(true);
}
},
/**
* Get the workflow action id to be executed by the action of the controller
* @return {Number} the workflow action id
*/
getWorkflowActionId: function ()
{
return this._workflowActionId;
},
/**
* Get the ids of contents concerned by the action of the controller
* @return {String[]} the ids of contents
*/
getContentIds: function ()
{
return this._contentIds;
},
updateState: function ()
{
this.stopRefreshing(true);
this._getStatus(this.getMatchingTargets());
},
/**
* Refresh the controller from the content informations state of given targets
* @param targets The content targets
* @protected
*/
_getStatus: function (targets)
{
var result = this._calculateStatus(targets);
this._getStatusCb(result, targets);
},
/**
* @protected
* Simulation of getStatus server side, done only client-side.
* In cases where the server side still needs to be called, see Ametys.plugins.cms.content.controller.SmartContentController#_getStatus
* @param {Object[]} targets see Ametys.ribbon.element.ui.CommonController#getMatchingTargets
* @return {Object} a map of arrays representing contents in different states. default is unmodifiable-contents, locked-contents, noright-contents, invalidworkflowaction-contents,invalidworkflowstep-contents, allright-contents
*/
_calculateStatus: function(targets)
{
var result = {};
result["unmodifiable-contents"] = [];
result["locked-contents"] = [];
result["noright-contents"] = [];
result["invalidworkflowaction-contents"] = [];
result["invalidworkflowstep-contents"] = [];
result["allright-contents"] = [];
var enabledOnModifiableOnly = this.getConfig("enabled-on-modifiable-only") == "true";
var enabledOnUnlockOnly = this.getConfig("enabled-on-unlock-only") == "true";
var enabledOnRightOnly = this.getConfig("enabled-on-right-only") == "true";
var enabledOnWorkflowActionOnly = this.getConfig("enabled-on-workflow-action-only");
var enabledOnWorkflowStepOnly = this.getConfig("enabled-on-workflow-step-only");
// Perform the same check than the server except for the read access
// There is no use in trying to hide information that is already there
Ext.Array.each(targets, function(matchingTarget)
{
var parameters = matchingTarget.getParameters();
if (parameters && parameters.content)
{
var content = parameters.content;
var error = false;
// "enabled-on-modifiable-only" == true && ! content instanceof ModifiableContent (isModifiable)
if (enabledOnModifiableOnly && !content.getIsModifiable())
{
var i18nStr = this.getConfig("nomodifiable-content-description");
var description = Ext.String.format(i18nStr, content.getTitle());
var contentParam = this._getContentDefaultParameters(content);
contentParam["description"] = description;
result["unmodifiable-contents"].push(contentParam);
error = true;
}
// "enabled-on-unlock-only" == true && isLocked
if (enabledOnUnlockOnly && content.getLocked() && !this._isLockOwner(content))
{
var i18nStr = this.getConfig("locked-content-description");
var lockOwner = content.getLockOwner();
var fullName = lockOwner != null ? lockOwner.fullname : "";
var login = lockOwner != null ? lockOwner.login : "Anonymous";
var description = Ext.String.format(i18nStr, content.getTitle(), fullName, login);
var contentParam = this._getContentDefaultParameters(content);
contentParam["description"] = description;
result["locked-contents"].push(contentParam);
error = true;
}
// "enabled-on-right-only" == true && !hasRight (rights empty or RIGHT_ALLOW on the requested rights)
if (enabledOnRightOnly && !this._hasRight(content.getRights()))
{
var i18nStr = this.getConfig("noright-content-description");
var description = Ext.String.format(i18nStr, content.getTitle());
var contentParam = this._getContentDefaultParameters(content);
contentParam["description"] = description;
result["noright-contents"].push(contentParam);
error = true;
}
// "enabled-on-workflow-action-only" not empty && this value is not in availableActions
if (enabledOnWorkflowActionOnly != null)
{
var actionId = this._workflowAction(content.getAvailableActions(), enabledOnWorkflowActionOnly);
if (actionId == -1)
{
var i18nStr = this.getConfig("workflowaction-content-description");
var description = Ext.String.format(i18nStr, content.getTitle());
var contentParam = this._getContentDefaultParameters(content);
contentParam["description"] = description;
result["invalidworkflowaction-contents"].push(contentParam);
error = true;
}
else
{
result["workflowaction-content-actionId"] = actionId;
}
}
// "enabled-on-workflow-step-only" not empty && this value is not in the current steps
if (enabledOnWorkflowStepOnly != null && !this._workflowStep(content.getWorkflowSteps(), enabledOnWorkflowStepOnly))
{
var i18nStr = this.getConfig("workflowstep-content-description");
var description = Ext.String.format(i18nStr, content.getTitle());
var contentParam = this._getContentDefaultParameters(content);
contentParam["description"] = description;
result["invalidworkflowstep-contents"].push(contentParam);
error = true;
}
if (!error)
{
var i18nStr = this.getConfig("allright-content-description");
var description = Ext.String.format(i18nStr, content.getTitle());
var contentParam = this._getContentDefaultParameters(content);
contentParam["description"] = description;
result["allright-contents"].push(contentParam);
}
}
}, this);
return result;
},
/**
* @protected
* Override to bypass rights's checking on selected targets (will be handled on #updateState)
* Indeed if #hasRightOnAny return false, the button state and tooltip is not updated as it considers that no selection matched.
*/
hasRightOnAny: function(targets)
{
return true;
},
/**
* @private
* Check if a least one required rights in is the available rights
* @param {String[]} rights A array of available rights
* @return {Boolean} true if a least one required rights in is the available rights
*/
_hasRight: function(rights)
{
var rightToCheck = this.getInitialConfig("rights");
if (!rightToCheck)
{
// No right is needed: ok.
return true;
}
var neededRights = rightToCheck.split('|');
for (var i=0; i < neededRights.length; i++)
{
if (Ext.Array.contains(rights, neededRights[i]))
{
return true;
}
}
// No right found
return false;
},
/**
* @private
* Check if a least one wanted step in is the available steps
* @param {Number[]} availableSteps A array of workflow steps available
* @param {String} wantedStepsString A comma separated list of required steps
* @return {Boolean} true if a least one wanted step in is the available steps
*/
_workflowStep: function(availableSteps, wantedStepsString)
{
var result = this._isInArray(availableSteps, wantedStepsString);
return result != null;
},
/**
* @private
* Get the first wanted action that is available
* @param {Number[]} availableActions A array of workflow actions available
* @param {String} wantedActionsString A comma separated list of required actions
* @return {Number} The first action wanted that is also available or -1 if non is matching.
*/
_workflowAction: function(availableActions, wantedActionsString)
{
var result = this._isInArray(availableActions, wantedActionsString);
return result != null ? result : -1;
},
/**
* @private
* Get the first wanted value that is available
* @param {Number[]} available A array of available values
* @param {String} wantedAsString A comma separated list of required values
* @return {Number} The first value wanted that is also available or null if non matches.
*/
_isInArray: function(available, wantedAsString)
{
var wanted = wantedAsString.split(",");
for (var i = 0; i < wanted.length; i++)
{
var iWanted = Ext.String.trim(wanted[i]);
if (!Ext.isEmpty(iWanted))
{
var id = parseInt(wanted[i]);
if (Ext.Array.contains(available, id))
{
return id;
}
}
}
return null;
},
/**
* @private
* Get the minimal informations: id and title.
* @param {Ametys.cms.content.Content} content The non null content to analyse
* @return {Object} A map with 'id' and 'title'
*/
_getContentDefaultParameters : function(content)
{
var contentParams = {};
contentParams["id"] = content.getId();
contentParams["title"] = content.getTitle();
return contentParams;
},
/**
* @protected
* Callback function called after retrieving the lock state of content targets
* @param params The JSON result
*/
_getStatusCb: function (params, targets)
{
this._contentIds = [];
var description = '';
var allRightContents = params['allright-contents'];
if (allRightContents.length > 0)
{
for (var i=0; i < allRightContents.length; i++)
{
this._contentIds.push(allRightContents[i].id)
}
this.enable();
}
else
{
description = this.getInitialConfig("error-description") || '';
this.disable();
}
// server check only returns id in case of no read
// we need to build the params ourself based on targets
if (params['noread-contents'])
{
params['noread-contents'] = this._convertIdsToParams(params['noread-contents'], this.getInitialConfig("noread-content-description"), targets || this.getMatchingTargets());
}
this._updateTooltipDescription (description, params);
this.toggle(allRightContents.length > 0 && this.getInitialConfig()["enable-toggle"] == "true");
this._workflowActionId = params['workflowaction-content-actionId'];
},
/**
* Use the current matching targets to transform a list of content ids
* into a list of parameters to generate a tooltip
*/
_convertIdsToParams: function(contentIds, contentDescription, targets)
{
let params = [];
if (contentIds && contentIds.length > 0)
{
// let's try to use the target to retrieve the title of faulty contents
// and build a fake param list to generate the description
for (var i=0; i < contentIds.length; i++)
{
let matchingContent = null;
for (var j=0; j < targets.length; j++)
{
if (targets[j].getParameters().id == contentIds[i])
{
matchingContent = targets[j].getParameters().content;
break;
}
}
if (matchingContent != null)
{
var contentParam = this._getContentDefaultParameters(matchingContent);
contentParam.description = Ext.String.format(contentDescription, matchingContent.getTitle());
params.push(contentParam);
}
// Couldn't use the target as fallback, provide basic params with the id
else
{
params.push({
id: contentIds[i],
description: Ext.String.format(contentDescription, contentIds[i])
})
}
}
}
return params;
},
/**
* @protected
* Update the tooltip description according state of the current selection
* @param description The initial description. Can be empty.
* @param params The JSON result received
*/
_updateTooltipDescription: function (description, params)
{
description = this._handlingMultiple (description, 'allright', params['allright-contents']);
description = this._handlingMultiple (description, 'noread', params['noread-contents']);
description = this._handlingMultiple(description, "locked", params['locked-contents']);
description = this._handlingMultiple(description, "noright", params['noright-contents']);
description = this._handlingMultiple(description, "nomodifiable", params['unmodifiable-contents']);
description = this._handlingMultiple(description, "workflowaction", params['invalidworkflowaction-contents']);
description = this._handlingMultiple(description, "workflowstep", params['invalidworkflowstep-contents']);
this.setDescription (description);
},
/**
* @private
* Add text to description
* @param description The initial description to concatenate. Can be empty.
* @param {String} prefix The parameters prefix to used to retrieve the start and end description. The start and end description are retrieved from initial configuration with [prefix]-start-description and [prefix]-end-description
* @param {Object[]} contents The concerned contents. If empty, no text will be concatenated
*/
_handlingMultiple: function(description, prefix, contents)
{
if (contents && contents.length > 0)
{
if (description != "")
{
description += "<br/><br/>";
}
description += this.getInitialConfig(prefix + "-start-description");
for (var i=0; i < contents.length; i++)
{
if (i != 0)
{
description += ", ";
}
description += contents[i].description;
}
description += this.getInitialConfig(prefix + "-end-description");
}
return description;
},
/**
* Test if the current user is the lock owner of the content
* @param {Ametys.cms.content.Content} content
* @return {boolean} containing id and title
*/
_isLockOwner: function (content)
{
var currentUser = Ametys.getAppParameter('user');
var lockOwner = content.getLockOwner();
return currentUser.login === lockOwner.login && currentUser.population === lockOwner.populationId;
},
});