/*
* Copyright 2014 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 is a relation handler between:
* * source : content
* * destination : content
*
* It will establish a relation between those contents by set the attribute of type compatible.
* E.g. when you drag a content on another content
* @private
*/
Ext.define('Ametys.plugins.cms.relations.SetContentAttributeRelationHandler', {
extend: 'Ametys.relation.RelationHandler',
/**
* @cfg {Number} [workflow-action-ids=2] The workflow action ids to use when modifying contents. A comma separated list of ids, that will be tested in that order. Should not be empty.
*/
/**
* @property {String[]} _workflowActionIds See #cfg-workflow-action-ids
*/
/**
* @protected
* @property {RegExp} sourceTargetType The message target type (of type content) of the source to handle
*/
sourceTargetType: /^(simple-)?content$/,
/**
* @protected
* @property {RegExp} destTargetType The message target type (of type content) of the target to handle
*/
destTargetType: /^(simple-)?content$/,
constructor: function(config)
{
this.callParent(arguments);
this._workflowActionIds = (config['workflow-action-ids'] || "2").split(",");
},
/**
* @private
* Revert the #targetId test
* @param {Object/Ametys.message.MessageTarget} target The target to test
* @return {Boolean} True if the target type does not match #targetId
*/
_nonMatchingSource: function(target)
{
return !this.sourceTargetType.test(target.isMessageTarget ? target.getId() : target.id)
},
/**
* @private
* Revert the #targetId test
* @param {Object/Ametys.message.MessageTarget} target The target to test
* @return {Boolean} True if the target type does not match #targetId
*/
_nonMatchingDest: function(target)
{
return !this.destTargetType.test(target.isMessageTarget ? target.getId() : target.id)
},
/**
* Get the workflow actions ids. See #cfg-workflow-action-ids
* @return {String[]} A non-null and non empty array of workflow action ids.
*/
getWorkflowActionIds: function()
{
return this._workflowActionIds;
},
supportedRelations: function(source, target)
{
// Are source contents and only contents ?
var sourceContentTargets = Ametys.message.MessageTargetHelper.findTargets(source.targets, this.sourceTargetType, 1);
if (sourceContentTargets.length == 0 || Ametys.message.MessageTargetHelper.findTargets(source.targets, Ext.bind(this._nonMatchingSource, this)).length > 0)
{
return [];
}
// Are target contents and only contents ?
var destContentTargets = Ametys.message.MessageTargetHelper.findTargets(target.targets, this.destTargetType, 1);
if (destContentTargets.length == 0 || Ametys.message.MessageTargetHelper.findTargets(target.targets, Ext.bind(this._nonMatchingDest, this)).length > 0)
{
return [];
}
// Is there at least one target editable ?
var authorized = false;
for (var i = 0; i < destContentTargets.length; i++)
{
if (destContentTargets[i].isMessageTarget && destContentTargets[i].getParameters().availableActions)
{
for (var w=0; w < this._workflowActionIds.length; w++)
{
if (Ext.Array.contains(destContentTargets[i].getParameters().availableActions, parseInt(this._workflowActionIds[w])))
{
authorized = true;
}
}
}
else
{
// Can not check available actions, in doubt authorize the relationship (it will be eventually blocked later)
authorized = true;
}
}
if (!authorized)
{
return [];
}
// Are all sources movable ?
var movable = true;
for (var i = 0; movable && i < sourceContentTargets.length; i++)
{
if (sourceContentTargets[i].isMessageTarget
&& (sourceContentTargets[i].getParameters()['parentNode'] == null || sourceContentTargets[i].getParameters()['parentMetadataPath'] == null))
{
movable = false;
}
}
var relations = [];
relations.push(
Ext.create('Ametys.relation.Relation', {
type: Ametys.relation.Relation.REFERENCE,
label: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_LABEL}}",
description: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_DESCRIPTION}}",
smallIcon: null,
mediumIcon: null,
largeIcon: null
})
);
if (movable)
{
relations.push(
Ext.create('Ametys.relation.Relation', {
type: Ametys.relation.Relation.MOVE,
label: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_MOVE_LABEL}}",
description: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_MOVE_DESCRIPTION}}",
smallIcon: null,
mediumIcon: null,
largeIcon: null
})
);
}
return relations;
},
link: function(source, target, callback, relationType)
{
var positionInTargets = target.positionInTargets,
targetNodeId = target.getTarget(this.destTargetType, 1).getParameters().id;
var contentIdsToEditToRemove = [];
if (relationType == Ametys.relation.Relation.MOVE)
{
Ext.Array.each(source.getTargets(this.sourceTargetType, 1), function(sourceMessageTarget) {
var sourceMessageTargetParams = sourceMessageTarget.getParameters(),
parentNode = sourceMessageTargetParams.parentNode;
if (positionInTargets < 0 || targetNodeId != parentNode/* it is not a reorder (dropPosition=="append"), OR it is a reorder (dropPosition=="after"/"before") but with a change of parentNode*/)
{
contentIdsToEditToRemove.push({
referencingAttributePath: sourceMessageTargetParams.parentMetadataPath,
contentId: parentNode,
valueToRemove: sourceMessageTargetParams.id
});
}
});
}
var contentIdsToReference = [];
Ext.Array.each(source.getTargets(this.sourceTargetType, 1), function(sourceMessageTarget) {
contentIdsToReference.push(sourceMessageTarget.getParameters().id);
});
var contentIdsToEditByType = {};
var contentIdsToEdit = [];
Ext.Array.each(target.getTargets(this.destTargetType, 1), function(targetMessageTarget) {
contentIdsToEdit.push(targetMessageTarget.getParameters().id);
contentIdsToEditByType[targetMessageTarget.getId()] = contentIdsToEditByType[targetMessageTarget.getId()] || [];
contentIdsToEditByType[targetMessageTarget.getId()].push(targetMessageTarget.getParameters().id)
});
this.serverCall("getCompatibleAttributes",
[contentIdsToReference, contentIdsToEdit],
this._linkCb,
{
waitMessage: true,
ignoreCallbackOnError: false,
arguments: [callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove, source, target]
}
);
},
/**
* @private
* The callback when #link is called
* @param {Object} compatibleAttributes The compatible attributes as JSON (response from server)
* @param {Object[]} args The passed arguments
*/
_linkCb: function(compatibleAttributes, args)
{
var callback = args[0];
var contentIdsToReference = args[1];
var contentIdsToEdit = args[2];
var positionInTargets = args[3];
var contentIdsToEditByType = args[4];
var contentIdsToEditToRemove = args[5];
var source = args[6];
var target = args[7];
var attributeForced = false;
// Should attributes be limited by target?
if (compatibleAttributes.length > 0)
{
// Compute the authorized attributes
var childrenAttributePaths = [];
Ext.Array.each(target.targets, function(oneTarget) {
childrenAttributePaths = Ext.Array.merge(childrenAttributePaths, oneTarget.getParameters()['childrenMetadataPaths'] || []);
});
childrenAttributePaths = Ext.Array.unique(childrenAttributePaths);
// Filter the authorized metadata
if (childrenAttributePaths.length > 0)
{
compatibleAttributes = Ext.Array.filter(compatibleAttributes, function(item) {
return Ext.Array.contains(childrenAttributePaths, item.name);
});
attributeForced = true;
}
}
if (compatibleAttributes.length == 1)
{
if (attributeForced)
{
this._linkChoiceCb(compatibleAttributes[0].path, callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove);
}
else
{
Ametys.Msg.confirm("{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_CONFIRM_TITLE}}",
"{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_CONFIRM_TEXT1}} '" + compatibleAttributes[0].label + "' {{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_CONFIRM_TEXT2}}",
Ext.bind(this._linkChoiceCb, this, [compatibleAttributes[0].path, callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove], 0));
}
}
else if (compatibleAttributes.length > 1)
{
Ametys.plugins.cms.relations.setcontentattributerelationhandler.ChooseAttribute.display(compatibleAttributes, Ext.bind(this._linkChoiceCb, this, [callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove], true));
}
else
{
// No attribute can be used
Ametys.log.ErrorDialog.display({
title: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_NOATTRIBUTE_TITLE}}",
text: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_NOATTRIBUTE_TEXT}}",
category: this.self.getName()
});
callback(false);
}
},
/**
* @protected
* When the contributor needs to choose an attribute, this is the callback function
* @param {String} attributePath The path of the chosen attribute or null if no choice was made
* @param {Function} callback The initial callback
* @param {String[]} contentIdsToReference The content ids to reference
* @param {String[]} contentIdsToEdit The content ids to edit
* @param {Number} positionInTargets The position in target if it is a reorder
* @param {Object} contentIdsToEditByType The contentIdsToEdit to edit classified by message target type ('content', 'reference-table-content'...)
* @param {Object} contentIdsToEditToRemove The contents that were referencing and that should not anymore
* @param {String} contentIdsToEditToRemove.contentId The content id to remove
* @param {String} contentIdsToEditToRemove.valueToRemove The value referenced to remove
* @param {String} contentIdsToEditToRemove.referencingMetadataPath The attribute path referencing the value
* @param {String} button If called from a confirm dialog, check this value
*/
_linkChoiceCb: function(attributePath, callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove, button)
{
if (attributePath == null || (button && (button == 'no' || button == 'cancel')))
{
callback(false);
return;
}
var contentIdsToEditAsObj = {};
Ext.Array.forEach(contentIdsToEdit, function(contentId) {
contentIdsToEditAsObj[contentId] = positionInTargets;
});
this.serverCall("setContentAttribute",
[contentIdsToReference, contentIdsToEditAsObj, contentIdsToEditToRemove, attributePath, this._workflowActionIds],
this._linkCb2,
{
waitMessage: true,
ignoreCallbackOnError: false,
arguments: [callback, contentIdsToReference, contentIdsToEdit, contentIdsToEditByType, contentIdsToEditToRemove]
}
);
},
/**
* @private
* The callback when #link is called
* @param {Object} response The JSON java response
* @param {Object[]} args The passed arguments
*/
_linkCb2: function(response, args)
{
var callback = args[0];
var contentIdsToReference = args[1];
var contentIdsToEdit = Ext.Object.getKeys(args[2]);
var contentIdsToEditByType = args[3];
var contentIdsToEditToRemove = args[4];
if (response == null)
{
callback(false);
}
else
{
var errors = response['errorMessages'];
if (errors && errors.length > 0)
{
var detailedMsg = '<ul>';
for (var i=0; i < errors.length; i++)
{
detailedMsg += '<li>' + errors[i] + '</li>';
}
detailedMsg += '</ul>';
Ametys.form.SaveHelper.SaveErrorDialog.showErrorDialog ("{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_ERROR_TITLE}}", "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_ERROR_TEXT}}", detailedMsg);
}
callback(response['success'] == true);
var errorLessContents = [];
for (var contentId in contentIdsToEditToRemove)
{
if (!response['errorIds'] || !Ext.Array.contains(response['errorIds'], contentIdsToEditToRemove[contentId]))
{
errorLessContents.push(contentIdsToEditToRemove[contentId].contentId);
}
}
for (var type in contentIdsToEditByType)
{
for (var contentId in contentIdsToEditByType[type])
{
if (!response['errorIds'] || !Ext.Array.contains(response['errorIds'], contentIdsToEditByType[type][contentId]))
{
errorLessContents.push(contentIdsToEditByType[type][contentId]);
}
}
}
if (errorLessContents.length > 0)
{
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.MODIFIED,
targets: {
id: type,
parameters: { ids: errorLessContents}
},
parameters: {
major: false
}
});
}
}
}
});