/*
 *  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 handles the access to content properties. See {@link Ametys.cms.content.Content}
 */
Ext.define(
	"Ametys.cms.content.ContentDAO", 
	{
		singleton: true,
		
		/**
		 * @private 
		 * @property {String} _contentDAOClassName The java class name for contents DAO.
		 */
		_contentDAOClassname: 'org.ametys.cms.repository.ContentDAO',
		
		/**
		 * Retrieve a content by its id
		 * @param {String} id The content identifier. Cannot be null.
		 * @param {Function} callback The callback function called when the content is retrieved. Can be null for synchronous mode (not recommended!). Parameters are
		 * @param {Ametys.cms.content.Content} callback.content The content retrieved. Can be null if content does not exist.
		 * @param {String} [workspaceName] The JCR workspace name
         * @param {Object} [errorMessage] The error message
		 * @return {Ametys.cms.content.Content} The content retrieved if no callback function is specified or null otherwise.
		 */
		getContent: function (id, callback, workspaceName, errorMessage)
		{
			if (Ext.isEmpty(id))
			{
				callback(null);
				return;
			}
			this._sendRequest ([id], Ext.bind(this._getContentCb, this, [callback], 1), workspaceName, errorMessage);
		},
		/**
		 * @private
		 * Callback function called after #getContent is processed
		 * @param {Ametys.cms.content.Content[]} contents The contents retrieved
		 * @param {Function} callback The callback function called 
		 */
		_getContentCb: function (contents, callback)
		{
			callback((contents.length == 0) ? null : contents[0]);
		},
		
		/**
		 * Retrieve contents by their ids
		 * @param {String[]} ids The contents identifiers. Cannot be null.
		 * @param {Function} callback The callback function called when the content is retrieved. Can be null for synchronous mode (not recommended!). Parameters are
		 * @param {Ametys.cms.content.Content} callback.content The content retrieved. Can be null if content does not exist.
		 * @param {String} [workspaceName] The JCR workspace name
         * @param {Object} [errorMessage] The error message
		 * @return {Ametys.cms.content.Content} The content retrieved if no callback function is specified or null otherwise.
		 */
		getContents: function (ids, callback, workspaceName, errorMessage)
		{
			if (Ext.isEmpty(ids))
			{
				callback([]);
				return;
			}
			this._sendRequest (ids, Ext.bind(this._getContentsCb, this, [callback], 1), workspaceName, errorMessage);
		},
		/**
		 * @private
		 * Callback function called after #getContents is processed
		 * @param {Ametys.cms.content.Content[]} contents The contents retrieved
		 * @param {Function} callback The callback function called 
		 */
		_getContentsCb: function (contents, callback)
		{
			callback(contents);
		},
		
		/**
		 * @private
		 * Send request to server to retrieved contents
		 * @param {String[]} ids The id of contents to retrieve
		 * @param {Function} callback The callback function to be called after server process
		 * @param {String} [workspaceName] The JCR workspace name
         * @param {Object} [errorMessage] The error message to use
         */
		_sendRequest: function (ids, callback, workspaceName, errorMessage)
		{
            if (errorMessage === undefined)
            {
                errorMessage = "{{i18n DAOS_CONTENT_ERROR}}";
            }

            Ametys.data.ServerComm.callMethod({
				role: this._contentDAOClassname,
				methodName: "getContentsProperties",
				parameters: [ids, workspaceName],
				callback: {
					scope: this,
					handler: this._getContentsCB,
					arguments: [callback, errorMessage === false]
				},
				errorMessage: errorMessage
			});
		},
		
		/**
		 * @private
		 * Callback function called after #_sendRequest is processed
		 * @param {Object} result The server response
         * @param {String[]} result.contents Contents ids that were found
         * @param {String[]} result.contentsNotFound Contents ids that were not found
		 * @param {Array} arguments The callback arguments.
		 */
		_getContentsCB: function (result, arguments)
		{
			var contents = [];
			var contentsCfg = result.contents;
			
			if (this.getLogger().isDebugEnabled())
			{
				this.getLogger().debug(contentsCfg.length + " content(s) get from server and " + result.contentsNotFound.length + " content(s) where not found");
			}
			
			for (var i=0; i < contentsCfg.length; i++)
			{
				contents.push(Ext.create ('Ametys.cms.content.Content', contentsCfg[i]));
			}
			
			var contentsNotFound = result.contentsNotFound;
			if (contentsNotFound.length > 0
                && !arguments[1]) // skip error message
			{
				Ametys.log.ErrorDialog.display({
					title: "{{i18n DAOS_CONTENT_NOT_FOUND}}", 
					text: "{{i18n DAOS_CONTENT_NOT_FOUND_HINT}}",
		    		details: contentsNotFound.join(", "),
		    		category: this.self.getName()
				});
			}

			if (typeof arguments[0] == 'function')
			{
				arguments[0] (contents);
			}
		},
		
		/**
		 * Create a content
		 * @param {Object} config Parameters for content creation.
		 * @param {String} config.contentType (required) The type of the content to create.
		 * @param {String} config.contentTitle (required) The title of the content to create.
		 * @param {String} [config.contentName] The name of the content to create. If null #config.contentTitle will be used.
		 * @param {String} config.contentLanguage The language of the content to create.
		 * @param {Number} [config.initWorkflowActionId=1] The id of the workflow initialization action.
		 * @param {Number} [config.initAndEditWorkflowActionId=11] The id of the workflow initialization action followed by edition.
		 * @param {Number} [config.editWorkflowActionId=2] The id of the workflow edit action if creation is followed by edition.
		 * @param {String} [config.workflowName] The content workflow name. If empty the content type default worflow name will be used.
		 * @param {String} [config.rootContentPath] When creating a content under a specific root contents.
		 * @param {Object} [config.additionalWorkflowParameters] Additional workflow parameters to be added to the server request. All request parameters will be prefixed by "workflow-".
         * @param {String} [config.viewName=creation] The view to use for content creation.
		 * @param {Function} callback The callback function invoked when the content has been created. The callback function will received the following parameters:
		 * @param {String/Ametys.cms.content.Content} callback.content The created content as an id or a {@link Ametys.cms.content.Content}. See #config.fullContents
		 * @param {Object} [scope=window] The callback scope, default to window.
		 * @param {Boolean} [fullContents=false] If `true`, the callback function receives a Content object as argument. If `false`, only the content id is provided.
		 * @param {String} [contentMessageTargetType=Ametys.message.MessageTarget#CONTENT] The message target factory role to use for the event of the bus thrown after content creation
		 * @param {String} [parentContentMessageTargetType=Ametys.message.MessageTarget#CONTENT] The message target factory role to use for the event of the bus thrown after parent content modification if applyable
		 * @param {Ext.Component} [waitMsgCmp] The component that will be masked by the wait message
         * @param {Boolean} [auto] True if it is an automatic creation (ie. user did not choose the values of the content fields)
		 */
		createContent: function (config, callback, scope, fullContents, contentMessageTargetType, parentContentMessageTargetType, waitMsgCmp, auto)
		{
			// type, name and language are mandatory.
			if (!config.contentType || !config.contentLanguage || !config.contentTitle || (Ext.isObject(config.contentTitle) && !config.contentTitle[config.contentLanguage]))
			{
				Ametys.log.ErrorDialog.display({
					title: "{{i18n DAOS_CONTENT_CREATE_CONFIGURATION_ERROR_TITLE}}",
					text: "{{i18n DAOS_CONTENT_CREATE_CONFIGURATION_ERROR_MSG}}",
					category: Ext.getClassName(this) + '.createContent'
				});
				
				// Call the callback with a null argument.
				return (callback || Ext.emptyFn).call(scope || window, null);
			}
			
			var params = {
                'workflow-org.ametys.cms.workflow.CreateContentFunction$contentLanguage': config.contentLanguage,
				'workflow-org.ametys.cms.workflow.CreateContentFunction$contentType': config.contentType,
			};
            
            if (config.workflowName)
            {
                params['workflow-workflowName'] = config.workflowName;
            }
			
			if (Ext.isObject(config.contentTitle))
			{
			     params['workflow-org.ametys.cms.workflow.CreateContentFunction$contentTitleVariants'] = Ext.JSON.encode(config.contentTitle);
                 params['workflow-org.ametys.cms.workflow.CreateContentFunction$contentName'] = config.contentName || config.contentTitle[config.contentLanguage];
            }
			else
			{
			     params['workflow-org.ametys.cms.workflow.CreateContentFunction$contentTitle'] = config.contentTitle;
			     params['workflow-org.ametys.cms.workflow.CreateContentFunction$contentName'] = config.contentName || config.contentTitle;
			}
			
			if (config.rootContentPath)
		    {
			    params['workflow-org.ametys.cms.workflow.CreateContentFunction$rootContentPath'] = config.rootContentPath;
		    }
			
			Ext.Object.each(config.additionalWorkflowParameters, function(key, value) {
				params['workflow-' + key] = value;
			});

			var actionId = config.initWorkflowActionId || 1;
			var createContentCb = this._createContentCb;
			
			if (config.editFormValues && !Ext.Object.isEmpty(config.editFormValues))
			{
				actionId = config.initAndEditWorkflowActionId || 11;
				var editParams = {};
				
				editParams.values = config.editFormValues;
				editParams.quit = true;
				editParams['content.view'] = (config.viewName !== undefined ? config.viewName : 'creation'); // config.viewName can be null so a view will be created using the values
                
                // FIXME CMS-9700 Single workflow action for initialization and edition (for the future)
                Ext.applyIf(params, editParams);
                params.values = config.editFormValues;
                params.quit = true;
                params['content.view'] = (config.viewName !== undefined ? config.viewName : 'creation'); // config.viewName can be null so a view will be created using the values
                
				var editActionId = config.editWorkflowActionId || 2;
				createContentCb = Ext.bind(this._createContentCb2, this, [editActionId, editParams, waitMsgCmp], true);
			}
			
			Ametys.data.ServerComm.send({
				plugin: 'cms', 
				url: 'init-workflow/' + actionId, 
				parameters: params,
				
				priority: Ametys.data.ServerComm.PRIORITY_MAJOR, 
				waitMessage: {msg: "{{i18n DAOS_CONTENT_CREATE_REQUEST_WAIT}}", target: waitMsgCmp},
				errorMessage: {
					msg: "{{i18n DAOS_CONTENT_CREATE_REQUEST_ERROR}}",
					category: Ext.getClassName(this) + '.createContent'
				},
				
				callback: {
					scope: this,
					handler: createContentCb,
					arguments: {
						callback: callback || Ext.emptyFn, 
						scope: scope || window, 
						fullContents: fullContents || false,
						contentMessageTargetType: contentMessageTargetType || Ametys.message.MessageTarget.CONTENT,
						parentContentMessageTargetType: parentContentMessageTargetType || Ametys.message.MessageTarget.CONTENT,
						parentContentId: config.parentContentId,
                        auto: auto
					}
				}
			});
		},
		
		/**
		 * @private
		 * Callback function called after #createContent server request is processed
		 * @param {HTMLElement} response The XML document.
		 * @param {Object} params The callback arguments (see #createContent documentation).
		 */
		_createContentCb: function (response, params)
		{
			var success = Ext.dom.Query.selectValue ('> ActionResult > success', response) == "true";
			
			// Handle workflow errors 
			var workflowErrors = Ext.dom.Query.select ('> ActionResult > workflowValidation > error', response);
			if (!success && workflowErrors.length > 0)
			{
				var detailedMsg = '<ul>';
				for (var i=0; i < workflowErrors.length; i++)
				{
					var errorMsg = Ext.dom.Query.selectValue("", workflowErrors[i]);
					detailedMsg += '<li>' + errorMsg + '</li>';
				}
				detailedMsg += '</ul>';
				
				Ametys.Msg.show({
					title: "{{i18n DAOS_CONTENT_CREATE_REQUEST_ERROR}}",
					msg: "{{i18n DAOS_CONTENT_CREATE_REQUEST_ERROR}}" + detailedMsg,
					buttons: Ext.Msg.OK,
					icon: Ext.Msg.ERROR
				});
				return;
			}
			
			var contentId = Ext.dom.Query.selectValue("> ActionResult > contentId", response);
			
			if (params.parentContentId)
			{
				Ext.create("Ametys.message.Message", {
					type: Ametys.message.Message.MODIFIED,
					targets: {
						id: params.parentContentMessageTargetType,
						parameters: { ids: [params.parentContentId] }
					}
				});
			}
			
			var me = this;
			Ametys.cms.content.ContentDAO.getContent(contentId, function(content) {
				
			    // content notification
			    me._notifyContentCreation(content);
			    
				Ext.create("Ametys.message.Message", {
                    parameters: params.auto ? {auto: params.auto} : null,
					type: Ametys.message.Message.CREATED,
					targets: {
						id: params.contentMessageTargetType,
						parameters: { contents: [content] }
					}
				});
				
				if (Ext.isFunction (params.callback))
				{
					params.callback.call(params.scope, params.fullContents ? content : content.getId());
				}
			});
		},
		
		/**
		 * @private
		 * Callback function called after #createContent server request is processed in case of a creation followed by a edition
		 * @param {HTMLElement} response The XML document.
		 * @param {Object} params The callback arguments (see #createContent documentation)
		 * @param {Number} editActionId The edit workflow action id
		 * @param {Object} editParams The parameters for edition
		 * @param {Ext.Component} [waitMsgCmp] The component that will be masked by the wait message
		 */
		_createContentCb2: function (response, params, editActionId, editParams, waitMsgCmp)
		{
			var success = Ext.dom.Query.selectValue ('> ActionResult > success', response) == "true";
			
			// Handle workflow errors 
			var workflowErrors = Ext.dom.Query.select ('> ActionResult > workflowValidation > error', response);
			if (!success && workflowErrors.length > 0)
			{
				var detailedMsg = '<ul>';
				for (var i=0; i < workflowErrors.length; i++)
				{
					var errorMsg = Ext.dom.Query.selectValue("", workflowErrors[i]);
					detailedMsg += '<li>' + errorMsg + '</li>';
				}
				detailedMsg += '</ul>';
				
				Ametys.Msg.show({
					title: "{{i18n plugin.cms:DAOS_CONTENT_CREATE_REQUEST_ERROR}}",
					msg: "{{i18n plugin.cms:DAOS_CONTENT_CREATE_REQUEST_ERROR}}" + detailedMsg,
					buttons: Ext.Msg.OK,
					icon: Ext.Msg.ERROR
				});
				return;
			}
			
			var contentId = Ext.dom.Query.selectValue("> ActionResult > contentId", response);
			
			editParams.contentId = contentId;
	        
	        Ametys.data.ServerComm.send({
				plugin: 'cms', 
				url: 'do-action/' + editActionId, 
				parameters: editParams,
				waitMessage: {msg: "{{i18n DAOS_CONTENT_CREATE_REQUEST_WAIT}}", target: waitMsgCmp},
				errorMessage: {
					msg: "{{i18n DAOS_CONTENT_CREATE_REQUEST_ERROR}}",
					category: Ext.getClassName(this) + '.createContent'
				},
				priority: Ametys.data.ServerComm.PRIORITY_MAJOR, 
				
				callback: {
					scope: this,
					handler: this._editContentCb,
					arguments: {
						callback: params.callback || Ext.emptyFn, 
						scope: params.scope || window, 
						contentId: contentId,
						fullContents: params.fullContents || false,
						contentMessageTargetType: params.contentMessageTargetType || Ametys.message.MessageTarget.CONTENT,
						parentContentMessageTargetType: params.parentContentMessageTargetType || Ametys.message.MessageTarget.CONTENT,
						parentContentId: params.parentContentId
					}
				}
			});
		},
        
		/**
		 * @private
		 * Callback function called after edit content server request is processed
		 * @param {HTMLElement} response The XML document.
		 * @param {Object} params The callback arguments (see #createContent documentation).
		 */
		_editContentCb: function (response, params)
		{
			var contentId = params.contentId;
			
			var workflowMsgErrors = [];
			var fieldsInError = {};
			
			var workflowErrors = Ext.dom.Query.select ('> ActionResult > workflowValidation > error', response);
			var errors = Ext.dom.Query.select ('> ActionResult > * > fieldResult > error', response);
			
			if (errors.length > 0 || workflowErrors.length > 0)
			{
				// The content edition failed. Get the fields in error and/or the workflow errors
				for (var i=0; i < workflowErrors.length; i++)
				{
					workflowMsgErrors.push(Ext.dom.Query.selectValue("", workflowErrors[i]));
				}
				
				for (var i=0; i < errors.length; i++)
				{
                    var fieldData = errors[i].parentNode.parentNode;
					var errorMsg = Ext.dom.Query.selectValue("", errors[i]);
					
					var fieldPath = fieldData.tagName == '_global' ? fieldData.tagName : Ext.dom.Query.selectValue ('> fieldPath', fieldData);
					fieldsInError[fieldPath] = errorMsg;
				}
				
				// Delete quietly content
				Ametys.cms.content.ContentDAO.trashContentsQuietly ([contentId]);
				
				if (Ext.isFunction (params.callback))
				{
					// Call the callback function with content null
					params.callback.call(params.scope, null, fieldsInError, workflowMsgErrors);
				}
			}
			else
			{
				// The content edition succeeded
				if (params.parentContentId)
				{
					Ext.create("Ametys.message.Message", {
						type: Ametys.message.Message.MODIFIED,
						targets: {
							id: params.parentContentMessageTargetType,
							parameters: { ids: [params.parentContentId] }
						}
					});
				}
				
				var me = this;
				Ametys.cms.content.ContentDAO.getContent(contentId, function(content) {
				    
				    // content notification
				    me._notifyContentCreation(content);
					
					Ext.create("Ametys.message.Message", {
						type: Ametys.message.Message.CREATED,
						targets: {
							id: params.contentMessageTargetType,
							parameters: { contents: [content] }
						}
					});
					
					if (Ext.isFunction (params.callback))
					{
						params.callback.call(params.scope, params.fullContents ? content : content.getId());
					}
				});
			}
		},
		
		/**
		 * @protected
		 * Throw a {@link Ametys.ui.fluent.ribbon.Ribbon.Notificator.Notification} to indicate that a content has been created.
		 */
		_notifyContentCreation: function(content)
		{
            if (content.getIsSimple())
            {
                var cType = content.getTypes()[0];
                
                Ametys.notify({
                    type: 'info',
                    title: "{{i18n PLUGINS_CMS_NOTIFICATION_CREATE_CONTENT_TITLE}}",
                    iconGlyph: 'ametysicon-text70',
                    description: Ext.String.format("{{i18n PLUGINS_CMS_NOTIFICATION_CREATE_CONTENT_DESCRIPTION}}", Ext.String.escapeHtml(content.getTitle())),
                    action: Ext.bind(Ametys.tool.ToolsManager.openTool, Ametys.tool.ToolsManager, ['uitool-reference-tables', {id: 'reference-table-search-ui.' + cType, contentType: cType}], false)
                });
            }
            else
            {
                Ametys.notify({
	                type: 'info',
	                title: "{{i18n PLUGINS_CMS_NOTIFICATION_CREATE_CONTENT_TITLE}}",
	                iconGlyph: 'ametysicon-text70',
	                description: Ext.String.format("{{i18n PLUGINS_CMS_NOTIFICATION_CREATE_CONTENT_DESCRIPTION}}", Ext.String.escapeHtml(content.getTitle())),
	                action: Ext.bind(Ametys.tool.ToolsManager.openTool, Ametys.tool.ToolsManager, ['uitool-content', {id: content.getId()}], false)
	            });
            }
		    
		},
		
        /**
         * Edit a content
         * @param {Object} config Parameters for content edition.
         * @param {String} config.contentId (required) The id of content to edit
         * @param {Object} config.values (required) The values to edit.
         * @param {Object} [config.unlock=true] Set to 'false' to keep content locked after edition.
         * @param {String} [config.viewName] The view to edit.
         * @param {String} [config.fallbackViewName] The fallback view to edit if the requested view does not exist.
         * @param {Number} [config.editWorkflowActionId=2] The id of the workflow edit action
         * @param {Object} [config.bypassedWarnings] the warnings that have already been bypassed
         * @param {Boolean} [config.ignoreWarnings=false] True to ignore warnings
         * @param {Function} callback The callback function invoked when the content has been edited. The callback function will received the following parameters:
         * @param {Boolean} callback.success True if the edition ended successfully, false otherwise
         * @param {String/Ametys.cms.content.Content} callback.content The edited content as an id or a {@link Ametys.cms.content.Content}. See #config.fullContents
         * @param {String[]} callback.workflowErrors The workflow errors
         * @param {Object} callback.fieldsInError The fields in errors
         * @param {Object} callback.fieldsWithWarning The fields with warnings
         * @param {Object} callback.ingoreWarnings True to ignore warnings
         * @param {Object} [scope=window] The callback scope, default to window.
         * @param {Boolean} [fullContents=false] If `true`, the callback function receives a Content object as argument. If `false`, only the content id is provided.
         * @param {String} [contentMessageTargetType=Ametys.message.MessageTarget#CONTENT] The message target factory role to use for the event of the bus thrown after content creation
         * @param {String} [parentContentMessageTargetType=Ametys.message.MessageTarget#CONTENT] The message target factory role to use for the event of the bus thrown after parent content modification if applyable
         * @param {Ext.Component} [waitMsgCmp] The component that will be masked by the wait message
         */
        editContent: function (config, callback, scope, fullContents, contentMessageTargetType, parentContentMessageTargetType, waitMsgCmp)
        {
            contentMessageTargetType = contentMessageTargetType || Ametys.message.MessageTarget.CONTENT;
            
            Ext.create("Ametys.message.Message", {
                type: Ametys.message.Message.WORKFLOW_CHANGING,
                targets: {
                    id: contentMessageTargetType,
                    parameters: { ids: [config.contentId] }
                }
            });
                    
            var params = {
                values: config.values,
                contentId: config.contentId,
                quit: config.unlock !== false,
                'content.view': config.viewName,
                'content.fallback.view': config.fallbackViewName,
                'ignore.warnings': config.ignoreWarnings !== false
            };
            
            Ametys.data.ServerComm.send({
                plugin: 'cms', 
                url: 'do-action/' + (config.editWorkflowActionId || 2), 
                parameters: params,
                waitMessage: {msg: "{{i18n CONTENT_EDITION_SAVING}}", target: waitMsgCmp},
                errorMessage: {
                    msg: "{{i18n PLUGINS_CMS_SAVE_ACTION_ERROR}}",
                    category: Ext.getClassName(this) + '.editContent'
                },
                priority: Ametys.data.ServerComm.PRIORITY_MAJOR, 
                
                callback: {
                    scope: this,
                    handler: this._editContentCb2,
                    arguments: {
                        callback: callback || Ext.emptyFn, 
                        scope: scope || window, 
                        contentId: config.contentId,
                        fullContents: fullContents || false,
                        contentMessageTargetType: contentMessageTargetType,
                        unlock: config.unlock !== false,
                        bypassedWarnings: config.bypassedWarnings,
                        ignoreWarnings: config.ignoreWarnings !== false
                    }
                }
            });
        },
        
        /**
         * @private
         * Callback function called after edit content server request is processed
         * @param {HTMLElement} response The XML document.
         * @param {Object} params The callback arguments (see #createContent documentation).
         */
        _editContentCb2: function (response, params)
        {
            var contentId = params.contentId;
            
            if (Ametys.data.ServerComm.isBadResponse(response))
	        {
                // fail
                Ametys.cms.content.ContentDAO.getContent(contentId, function(content) {
    
                    // Workflow did not really changed, but WORKFLOW_CHANGING has been sent, elements could be waiting for update
                    Ext.create("Ametys.message.Message", {
                        type: Ametys.message.Message.WORKFLOW_CHANGED,
                        targets: {
                            id: params.contentMessageTargetType,
                            parameters: { contents: [content] }
                        }
                    });
                    
                    if (Ext.isFunction (params.callback))
                    {
                        params.callback.call(params.scope, false, contentId, [], {}, {}, false);
                    }
                });  
	            return;
	        }
            
            let me = this;
            let additionalFns = {
                successFn: function(contentId, params)
                {
                    Ametys.cms.content.ContentDAO.getContent(contentId, function(content) {
                        Ext.create("Ametys.message.Message", {
                            type: Ametys.message.Message.MODIFIED,
                            targets: {
                                id: params.contentMessageTargetType,
                                parameters: { contents: [content] }
                            }
                        });
                        
                        if (params.unlock)
                        {
                            Ext.create("Ametys.message.Message", {
                                type: Ametys.message.Message.LOCK_CHANGED,
                                targets: {
                                    id: params.contentMessageTargetType,
                                    parameters: { contents: [content] }
                                }
                            });
                        }
                        
                        Ext.create("Ametys.message.Message", {
                            type: Ametys.message.Message.WORKFLOW_CHANGED,
                            targets: {
                                id: params.contentMessageTargetType,
                                parameters: { contents: [content] }
                            }
                        });
                        
                        if (Ext.isFunction (params.callback))
                        {
                            params.callback.call(params.scope, true, params.fullContents ? content : contentId, [], {}, {}, false);
                        }
                    });
                },
                workflowValidationErrorFn: function(contentId, msg, detailedMsg, workflowMsgErrors, params) {
                    Ametys.form.SaveHelper.SaveErrorDialog.showErrorDialog (null, msg, detailedMsg);
                                    
                    Ametys.cms.content.ContentDAO.getContent(contentId, function(content) {
                        
                        // Workflow did not really changed, but WORKFLOW_CHANGING has been sent, elements could be waiting for update
                        Ext.create("Ametys.message.Message", {
                            type: Ametys.message.Message.WORKFLOW_CHANGED,
                            targets: {
                                id: params.contentMessageTargetType,
                                parameters: { contents: [content] }
                            }
                        });
                        
                        if (Ext.isFunction (params.callback))
                        {
                            // Call the callback function with success false and content null
                            params.callback.call(params.scope, false, contentId, workflowMsgErrors, {}, {}, false);
                        }
                    }); 
                },
                fieldResultErrorFn: function(contentId, msg, detailedMsg, fieldsInError, params) {
                    Ametys.form.SaveHelper.SaveErrorDialog.showErrorDialog (null, msg, detailedMsg);
                                    
                    Ametys.cms.content.ContentDAO.getContent(contentId, function(content) {
                        
                        // Workflow did not really changed, but WORKFLOW_CHANGING has been sent, elements could be waiting for update
                        Ext.create("Ametys.message.Message", {
                            type: Ametys.message.Message.WORKFLOW_CHANGED,
                            targets: {
                                id: params.contentMessageTargetType,
                                parameters: { contents: [content] }
                            }
                        });
                        
                        if (Ext.isFunction (params.callback))
                        {
                            // Call the callback function with success false and content null
                            params.callback.call(params.scope, false, contentId, [], fieldsInError, {}, false);
                        }
                    });
                },
                fieldResultWarningFn: function(contentId, msg, detailedMsg, question, fieldsWithWarning, params) {
                    if (msg) 
                    {
                        Ametys.form.SaveHelper.SaveErrorDialog.showWarningDialog (null, msg, detailedMsg, question, Ext.bind(me._showWarningsCb, me, [fieldsWithWarning, params], 1));
                    }
                    else
                    {
                        me._showWarningsCb(true, fieldsWithWarning, params);
                    }
                },
                fieldResultInfoFn: function(contentId, msg, detailedMsg, fieldsWithInfo, params) {
                    Ametys.notify({
                        title: "{{i18n PLUGINS_CMS_SAVE_ACTION_CONTENT_MODIFICATION}}",
                        description: detailedMsg
                    });
                },
                messageFn: function(contentId, messages, globalLevel, params) {
                    var popupItems = [];
                    for (var i=0; i < messages.length; i++)
                    {
                        var message = messages[i];
                        var cls = message.level == 'warning' ? 'x-message-box-warning' : 'x-message-box-info';
                        
                        popupItems.push({
                            xtype: 'container',
                            layout : {
                              type : "hbox",
                            },
                            anchor: "100%",
                                items:[{
                                    xtype: "component",
                                    cls: cls,
                                    width: 48,
                                    style: {
                                        'text-align': 'center'
                                    }
                                },
                                {
                                    xtype: 'component',
                                    cls: 'text',
                                    html: message.msg,
                                    flex: 1
                                }]
                        });
                        
                        if (i != messages.length-1)
                        {
                            popupItems.push({
                                xtype: 'component',
                                cls: 'text',
                                html: '<hr>',
                                flex: 1
                            });
                        }
                    }

                    var iconCls = globalLevel == 'warning' ? 'ametysicon-sign-caution' : 'ametysicon-sign-info';
                    var title = globalLevel == 'warning' ? "{{i18n PLUGINS_CMS_DUPLICATE_CONTENTS_SAVE_REPORT_MESSAGE_WARNING}}" : "{{i18n PLUGINS_CMS_DUPLICATE_CONTENTS_SAVE_REPORT_MESSAGE_INFO}}";
                    
                    var reportBox = Ext.create('Ametys.window.DialogBox', {
                        layout: 'anchor',
                        autoShow: true,
                        autoScroll: true,
                        maxWidth: 580,
                        maxHeight: 700,
                        iconCls: iconCls,
                        title: title,
                        
                        items: popupItems,
                        
                        closeAction: 'destroy',
                        defaultButton: 'buttonOk',
                        buttons: [{
                            itemId: 'button-ok',
                            reference: 'buttonOk',
                            text: "{{i18n PLUGINS_CMS_DUPLICATE_CONTENTS_SAVE_REPORT_BUTTON_OK}}",
                            handler: function() {
                                reportBox.close();
                            }
                        }
                        ]
                    });
                }
            };
            
            Ametys.cms.content.ContentErrorHelper.handleContentEditError(response, contentId, params, additionalFns)
        },
        
        /**
         * @private
         * Callback function called after user chose to ignore warnings or not
         * @param {Boolean} ignoreWarnings true if the user choose to ignore warnings and save anyway, false otherwise
         * @param {Object} fieldsWithWarnings the fields with warnings
         * @param {Object} params The callback arguments (see #createContent documentation).
         */
        _showWarningsCb: function (ignoreWarnings, fieldsWithWarnings, params)
        {
            var contentId = params.contentId;
            if (!ignoreWarnings)
            {
                Ametys.cms.content.ContentDAO.getContent(contentId, function(content) {
                
                    // Workflow did not really changed, but WORKFLOW_CHANGING has been sent, elements could be waiting for update
                    Ext.create("Ametys.message.Message", {
                        type: Ametys.message.Message.WORKFLOW_CHANGED,
                        targets: {
                            id: params.contentMessageTargetType,
                            parameters: { contents: [content] }
                        }
                    });
                });
            }
                
            if (Ext.isFunction (params.callback))
            {
                // Call the callback function with success false and content null
                params.callback.call(params.scope, false, contentId, [], {}, fieldsWithWarnings, ignoreWarnings);
            }
        },
        
		/**
		 * Delete contents
    	 * @param {String[]} contentIds The id of contents to delete
    	 * @param {Ametys.message.MessageTarget[]} contentTargets The content's targets
    	 * @param {Function} [callback] The function to call when the java process is over.
		 * @param {Object} callback.deleted-contents The deleted contents
		 * @param {Object} callback.referenced-contents The undeleted contents because they are referenced
		 * @param {Object} callback.undeleted-contents The undeleted contents
		 * @param {Object} [scope=window] The callback scope, default to window.
		 */
		trashContents: function (contentIds, contentTargets, callback, scope)
		{
			Ametys.data.ServerComm.callMethod({
				role: this._contentDAOClassname,
				methodName: "trashContents",
				parameters: [contentIds],
				callback: {
					scope: this,
					handler: this._trashContentsCb,
					arguments: {
						callback: callback,
						scope: scope,
						targets: contentTargets
					}
				},
				waitMessage: true,
				errorMessage: "{{i18n DAOS_CONTENT_DELETE_ERROR}}"
			});
		},
		
		/**
		 * Force delete contents even if it has references. If the content has references, these references will be deleted too and the referenced contents will be modified.
    	 * @param {String[]} contentIds The id of contents to delete
    	 * @param {Ametys.message.MessageTarget[]} contentTargets The content's targets
    	 * @param {Function} [callback] The function to call when the java process is over.
		 * @param {Object} callback.deleted-contents The deleted contents
		 * @param {Object} callback.undeleted-contents The undeleted contents
		 * @param {Object} [scope=window] The callback scope, default to window.
		 */
		forceTrashContents: function (contentIds, contentTargets, callback, scope)
		{
			Ametys.data.ServerComm.callMethod({
				role: this._contentDAOClassname,
				methodName: "forceTrashContents",
				parameters: [contentIds],
				callback: {
					scope: this,
					handler: this._trashContentsCb,
					arguments: {
						callback: callback,
						scope: scope,
						targets: contentTargets
					}
				},
				waitMessage: true,
				errorMessage: "{{i18n DAOS_CONTENT_DELETE_ERROR}}"
			});
		},
		
		/**
		 * Delete contents quietly, ie. without sending bus message or displaying the result of deletion
    	 * @param {String[]} contentIds The id of contents to delete
    	 * @param {Ametys.message.MessageTarget[]} contentTargets The content's targets
		 */
		trashContentsQuietly: function (contentIds, contentTargets)
		{
			Ametys.data.ServerComm.callMethod({
				role: this._contentDAOClassname,
				methodName: "trashContents",
				parameters: [contentIds],
				errorMessage: false
			});
		},
		
		/**
		 * @private
		 * Callback function called after contents were removed
		 * @param {Object} response The server result
		 * @param {Object} args The callback arguments :
		 * @param {Ametys.message.MessageTarget[]} args.targets The content's targets
		 * @param {Ametys.message.MessageTarget[]} [args.callback] The callback function
		 */
		_trashContentsCb: function (response, args)
		{
			var targets = args.targets;
			var deletedContents = response['deleted-contents'];
			
			if (deletedContents.length > 0)
			{
				var deletedContentTargets = [];
				
				for (var i=0; i < deletedContents.length; i++)
				{
					for (var j=0; j < targets.length; j++)
					{
			        	if (targets[j].getParameters().id == deletedContents[i].id)
						{
							deletedContentTargets.push(targets[j]);
						}
					}
					
					// Remove content from navigation history
					Ametys.navhistory.HistoryDAO.removeEntry (deletedContents[i].id);
					
					// Notify content deletion
			        Ametys.notify({
			            type: 'info',
			            iconGlyph: 'ametysicon-text70',
			            title: "{{i18n PLUGINS_CMS_NOTIFICATION_DELETE_CONTENT_TITLE}}",
			            description: Ext.String.format("{{i18n PLUGINS_CMS_NOTIFICATION_DELETE_CONTENT_DESCRIPTION}}", Ext.String.escapeHtml(deletedContents[i].title))
			        });
				}
				
				// Fires deleted event
				Ext.create("Ametys.message.Message", {
					type: Ametys.message.Message.DELETED,
                    // we assume that everything is trashed. The only case
                    // of actual deletion should be shared content but we will ignore it
                    // for simplicity
                    parameters : {trashed: true},
					targets: deletedContentTargets
				});
			}
			
	        var undeletedContents = response['undeleted-contents'];
	        var referencedContents = response['referenced-contents'];
	        var lockedContents = response['locked-contents'];
	        var unauthorizedContents = response['unauthorized-contents'];
	        if (undeletedContents.length > 0 || referencedContents.length > 0 || lockedContents.length > 0 || unauthorizedContents.length > 0)
	        {
	            var message = '';
	            
                var message = this._buildErrorMessage(message, undeletedContents, "{{i18n CONTENT_DELETE_FAILED_CONTENTS}}");
                message = this._buildErrorMessage(message, referencedContents, "{{i18n CONTENT_DELETE_REFERENCED_CONTENTS}}", ".<br/>{{i18n CONTENT_DELETE_REFERENCED_CONTENTS_END}}");
                message = this._buildErrorMessage(message, lockedContents, "{{i18n CONTENT_DELETE_LOCKED_CONTENTS}}");
                message = this._buildErrorMessage(message, unauthorizedContents, "{{i18n CONTENT_DELETE_UNAUTHORIZED_CONTENTS}}");
	            
	            Ametys.Msg.show({
	                   title: "{{i18n CONTENT_DELETE_LABEL}}",
	                   msg: message,
	                   buttons: Ext.Msg.OK,
	                   icon: Ext.MessageBox.ERROR
	            });
	        }
	        
	        if (Ext.isFunction (args.callback))
	        {
	        	args.callback.call(args.scope, response);
	        }
		},
        
        /**
         * @private
         * Build error message
         * @param {String} message The initial error message
         * @param {Object[]} contentsInError The contents in error
         * @param {String} startDesc The start description
         * @param {String} endDesc The end description
         */
        _buildErrorMessage: function(message, contentsInError, startDesc, endDesc)
	    {
            message = message || '';
	        if (contentsInError.length > 0)
	        {
	            if (message.length > 0)
	            {
	                message += '<br/><br/>';
	            }
	            
	            message += startDesc || '';
	            for (var i=0; i < contentsInError.length; i++)
	            {
	                message += (i > 0 ? ', ' : '') + Ext.String.escapeHtml(contentsInError[i].title);
	            }
	            message += endDesc || '';
	        }
            return message;
	    },
        
        /**
         * Lock the content
         * @param {String} id the id of content
         * @param {String} contentTarget the content target
         * @param {Function} [callback] Callback function to invoke after locking the content
         */
        lockContent: function(id, contentTarget, callback)
        {
            var content = Ext.create("Ametys.cms.content.Content", { 
                id: id,
                locked: false, 
                messageTarget: contentTarget 
            });
            
            content.lock(callback);
        },
        
        /**
         * Unlock the content
         * @param {String} id the id of content
         * @param {String} contentTarget the content target
         * @param {Function} [callback] Callback function to invoke after unlocking the content
         */
        unlockContent: function(id, contentTarget, callback)
        {
            var content = Ext.create("Ametys.cms.content.Content", { 
                id: id,
                locked: true, 
                messageTarget: contentTarget 
            });
            
            content.unlock(callback);
        }
	}
);