Source: services/actions/CopyMoveService.js

/**
 * Copyright (C) 2005-2016 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * This service handles requests to copy or move nodes from one location to another. It does this by displaying
 * a [Dialog]{@link module:alfresco/dialogs/AlfDialog} containing a [ContainerPicker]{@link module:alfresco/pickers/ContainerPicker}.
 * The [Dialog]{@link module:alfresco/dialogs/AlfDialog} is created via the [DialogService]{@link module:alfresco/services/DialogService}
 * so it is imperative that is also included on the page (or an alternative service that handles the same publications).
 *
 * @module alfresco/services/actions/CopyMoveService
 * @extends module:alfresco/services/BaseService
 * @mixes module:alfresco/core/CoreXhr
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "alfresco/services/BaseService",
        "alfresco/core/CoreXhr",
        "alfresco/core/topics",
        "service/constants/Default",
        "dojo/_base/lang",
        "dojo/_base/array",
        "dojo/when",
        "alfresco/core/NodeUtils"],
        function(declare, BaseService, AlfCoreXhr, topics, AlfConstants, lang, array, when, NodeUtils) {

   return declare([BaseService, AlfCoreXhr], {

      /**
       * An array of the i18n files to use with this widget.
       *
       * @instance
       * @type {object[]}
       * @default [{i18nFile: "./i18n/CopyMoveService.properties"}]
       */
      i18nRequirements: [{i18nFile: "./i18n/CopyMoveService.properties"}],

      /**
       * This should be set to the NodeRef to be used to represent the root of the Repository. By default
       * this is "alfresco://company/home" but can be configured to be any other value. When used in Alfresco
       * Share this should be set to the configured value of <root-node>
       *
       * @instance
       * @type {string}
       * @default
       */
      repoNodeRef: "alfresco://company/home",

      /**
       * Indicates whether or not [CopyMoveService]{@link module:alfresco/services/CopyMoveService} should support the
       * creation of links for files and folders
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.92
       */
      supportLinkCreation: false,

      /**
       * URL to call to copy a document
       *
       * @instance
       * @type {string}
       * @default
       */
      copyAPI: "slingshot/doclib/action/copy-to/node/",

      /**
       * URL to call to move a document
       *
       * @instance
       * @type {string}
       * @default
       */
      moveAPI: "slingshot/doclib/action/move-to/node/",

      /**
       * URL to call to create a link to a document
       *
       * @instance
       * @type {string}
       * @default
       * @since 1.0.92
       */
      createLinkAPI: "api/node/doclink/",


      /**
       * Sets up the service using the configuration provided.
       *
       * @instance
       * @since 1.0.32
       */
      registerSubscriptions: function alfresco_services_actions_CopyMoveService__registerSubscriptions() {
         this.alfSubscribe(topics.COPY_OR_MOVE, lang.hitch(this, this.createCopyMoveDialog));
      },

      /**
       * Create a typeDef for the createCopyMoveDialogConfig type used by {@link alfresco/services/ActionService:createCopyMoveDialog}
       *
       * @typedef {Object} createCopyMoveDialogConfig
       * @property [urlPrefix] {String} - The URL prefix for the action
       * @property [dialogTitle] {string} - The title for the dialog
       * @property [confirmButtonLabel] {String} - The label for the confirmation button
       * @property [singleItemMode] {Bool} - {@link alfresco/pickers/PickedItems:singleItemMode}
       */

      /* This function handles the creation of dialogs for both copy and move actions because
       * they are identical apart from the config object (which can be optionally passed in).
       * It defaults to copy mode if missing.
       *
       * @instance
       * @param {object} payload The action payload
       * @param {object} documents The documents selected for copy or move
       * @param {createCopyMoveDialogConfig} [config] The config object.
       * @fires module:alfresco/core/topics#CREATE_DIALOG
       */
      createCopyMoveDialog: function alfresco_services_ActionService__createCopyMoveDialog(payload) {
         var documents = payload.documents || [];

         var urlPrefix = payload.copy ? this.copyAPI : this.moveAPI, // Default to copy API.
             propertyType = payload.copy ? "copyTo" : "moveTo",
             dialogTitle = payload.dialogTitle || "services.ActionService." + propertyType + ".title", // Default to copy title
             confirmButtonLabel = payload.confirmButtonLabel || "services.ActionService." + propertyType + ".ok", // Default to copy confirmation
             singleItemMode = payload.singleItemMode !== false;

         var responseTopic = this.generateUuid() + "_ALF_MOVE_LOCATION_PICKED",
            nodes = NodeUtils.nodeRefArray(documents),
            publishPayload = {
               nodes: nodes,
               documents: documents,
               responseScope: payload.alfResponseScope
            };
         var createLinkButtonLabel = payload.createLinkButtonLabel || "services.ActionService.createLink",
             createLinkResponseTopic = this.generateUuid() + "_ALF_CREATE_LINK_LOCATION_PICKED",
             createLinkPublishPayload = {
                nodes: nodes,
                documents: documents,
                responseScope: payload.alfResponseScope
             };

         var firstFileName = documents.length ? documents[0].fileName : null;
         var fileName = (nodes.length === 1 && firstFileName) ? firstFileName : this.message("services.ActionService.copyMoveTo.multipleFiles");
         this._actionHandles = [
             this.alfSubscribe(responseTopic, lang.hitch(this, this.onLocationSelected, urlPrefix, payload.copy, this.performAction), true),
             this.alfSubscribe(createLinkResponseTopic, lang.hitch(this, this.onLocationSelected, urlPrefix, payload.copy, this.performCreateLinkAction), true)
          ];

         this.alfPublish(topics.CREATE_DIALOG, {
            dialogId: "ALF_COPY_MOVE_DIALOG",
            dialogTitle: this.message(dialogTitle, { 0: fileName}),
            handleOverflow: false,
            widgetsContent: [
               {
                  name: "alfresco/pickers/ContainerPicker",
                  config: {
                     singleItemMode: singleItemMode,
                     generatePubSubScope: true,
                     repoNodeRef: this.repoNodeRef || "alfresco://company/home",
                     supportLinkCreation : this.supportLinkCreation
                  }
               }
            ],
            widgetsButtons: [
               {
                  name: "alfresco/buttons/AlfButton",
                  config: {
                     label: createLinkButtonLabel,
                     publishTopic: createLinkResponseTopic,
                     publishPayload: createLinkPublishPayload,
                     disableOnInvalidControls: true,
                     validTopic: "ALF_PICKER_VALID",
                     invalidTopic: "ALF_PICKER_INVALID",
                     additionalCssClasses: "call-to-action",
                     visibilityConfig: {
                        initialValue: this.supportLinkCreation && payload.copy
                     }
                  }
               },
               {
                  name: "alfresco/buttons/AlfButton",
                  config: {
                     label: confirmButtonLabel,
                     publishTopic: responseTopic,
                     publishPayload: publishPayload,
                     disableOnInvalidControls: true,
                     validTopic: "ALF_PICKER_VALID",
                     invalidTopic: "ALF_PICKER_INVALID",
                     additionalCssClasses: "call-to-action"
                  }
               },
               {
                  name: "alfresco/buttons/AlfButton",
                  config: {
                     label: "picker.cancel.label",
                     publishTopic: "NO_OP"
                  }
               }
            ]
         }, true);
      },

      /**
       * Handles the actual copy,move or create link XHR call
       *
       * @instance
       * @param {string} urlPrefix The specific URL prefix to use for the action (e.g. either move or copy)
       * @param {boolean} copy A boolean indicating if this is a copy action or not
       * @param {object} payload The payload from confirmation of the action dialog
       * @param {function} function to call: create link or copy/move
       */
      onLocationSelected: function alfresco_services_ActionService__onLocationSelected(urlPrefix, copy, actionToCall, payload) {
         this.alfUnsubscribeSaveHandles(this._actionHandles);

         // Get the locations to copy/move/create link to and the documents to them...
         if (payload.dialogContent)
         {
            when(payload.dialogContent, lang.hitch(this, function(content) {
               if (content && content.length)
               {
                  var locations = lang.getObject("pickedItemsWidget.currentData.items", false, content[0]);
                  var documents = lang.getObject("documents", false, payload);
                  if (!locations || locations.length === 0)
                  {
                     this.alfLog("error", "copyMoveTarget not specified");
                  }
                  else if (!documents || documents.length === 0)
                  {
                     this.alfLog("error", "Documents to copy/move not specified.");
                  }
                  else
                  {
                     var nodeRefs = NodeUtils.nodeRefArray(documents);
                     array.forEach(locations, lang.hitch(this, actionToCall, nodeRefs, urlPrefix, copy, payload.alfResponseScope));
                  }
               }
            }));
         }
      },

      /**
       * Makes an XHR call to move or copy the selected documents to the location provided.
       *
       * @instance
       * @param {array} nodeRefs An array of the nodes to move or copy
       * @param {string} urlPrefix The prefix to use in the action URL
       * @param {boolean} copy A boolean indicating if this is a copy action or not
       * @param {array} location  The location to move or copy the documents to
       */
      performAction: function alfresco_services_actions_CopyMoveService__performAction(nodeRefs, urlPrefix, copy, responseScope, location) {
         var responseTopic = this.generateUuid();
         var successSubscription = this.alfSubscribe(responseTopic + "_SUCCESS", lang.hitch(this, this.onActionSuccess), true);
         var failureSubscription = this.alfSubscribe(responseTopic + "_FAILURE", lang.hitch(this, this.onActionFailure), true);
         this.serviceXhr({
            alfTopic: responseTopic,
            subscriptionHandles: [successSubscription,failureSubscription],
            copy: copy,
            responseScope: responseScope,
            url: AlfConstants.PROXY_URI + urlPrefix + location.nodeRef.replace("://", "/"),
            method: "POST",
            data: {
               nodeRefs: nodeRefs,
               parentId: location
            }
         });
      },

      /**
       * Makes an XHR call to create links for the selected documents to the location provided.
       *
       * @instance
       * @param {array} nodeRefs An array of the nodes to create links for
       * @param {string} urlPrefix The prefix to use in the action URL
       * @param {boolean} copy A boolean indicating if this is a copy action or not
       * @param {array} location  The location where links will be created
       * @since 1.0.92
       */
      performCreateLinkAction: function alfresco_services_actions_CopyMoveService__performCreateLinkAction(nodeRefs, urlPrefix, copy, responseScope, location) {
         var responseTopic = this.generateUuid();
         var successSubscription = this.alfSubscribe(responseTopic + "_SUCCESS", lang.hitch(this, this.onCreateLinkActionSuccess), true);
         var failureSubscription = this.alfSubscribe(responseTopic + "_FAILURE", lang.hitch(this, this.onCreateLinkActionFailure), true);
         this.serviceXhr({
            alfTopic: responseTopic,
            subscriptionHandles: [successSubscription,failureSubscription],
            responseScope: responseScope,
            url: AlfConstants.PROXY_URI +  this.createLinkAPI + location.nodeRef.replace("://", "/"),
            method: "POST",
            data: {
               destinationNodeRef: location.nodeRef,
               multipleFiles: nodeRefs
            }
         });
      },


      /**
       * Handles successful actions in one of two ways. When an attempt is made to move or copy more than one node
       * it is possible that only some of the nodes will be moved/copied successfully. If all the nodes were moved or
       * copied successfully then a simple notification is displayed indicating a successful action was completed, however
       * if a partial success was reported then a prompt is displayed listing the nodes that could not be copied or moved.
       *
       * @instance
       * @param {object} payload
       * @fires module:alfresco/core/topics#DISPLAY_NOTIFICATION
       * @fires module:alfresco/core/topics#DISPLAY_PROMPT
       * @fires module:alfresco/core/topics#RELOAD_DATA_TOPIC
       */
      onActionSuccess: function alfresco_services_actions_CopyMoveService__onActionSuccess(payload) {
         // jshint unused:false, maxcomplexity:false
         var subscriptionHandles = lang.getObject("requestConfig.subscriptionHandles", false, payload);
         if (subscriptionHandles)
         {
            this.alfUnsubscribeSaveHandles(subscriptionHandles);
         }
         if (payload.response.successCount === 0)
         {
            // See AKU-1068 - total failure returned as success...
            var failureMessage;
            if (payload.requestConfig.copy)
            {
               failureMessage = payload.response.totalResults === 1 ? "copyMoveService.copy.failure" : "copyMoveService.copy.multiple.failure";
            }
            else
            {
               failureMessage = payload.response.totalResults === 1 ? "copyMoveService.move.failure" : "copyMoveService.move.multiple.failure";
            }

            this.alfServicePublish(topics.DISPLAY_PROMPT, {
               title: payload.requestConfig.copy ? this.message("copyMoveService.copy.failure.title") : this.message("copyMoveService.move.failure.title"),
               message: this.message(failureMessage)
            });
         }
         else if (payload.response.overallSuccess === true)
         {
            this.alfServicePublish(topics.DISPLAY_NOTIFICATION, {
               message: payload.requestConfig.copy ? this.message("copyMoveService.copy.completeSuccess") : this.message("copyMoveService.move.completeSuccess")
            });
         }
         else
         {
            var failures = "";
            array.forEach(payload.response.results, function(result) {
               if (!result.success)
               {
                  failures += result.id + ", ";
               }
            });
            if (failures.length > 2)
            {
               failures = failures.substring(0, failures.length - 2);
            }
            var messageKey = payload.requestConfig.copy ? "copyMoveService.copy.multiple.failure" : "copyMoveService.move.multiple.failure";
            var message = this.message(messageKey);
            this.alfPublish(topics.DISPLAY_PROMPT, {
               title: payload.requestConfig.copy ? this.message("copyMoveService.copy.failure.title") : this.message("copyMoveService.move.failure.title"),
               message: message
            });
         }
         this.alfPublish(topics.RELOAD_DATA_TOPIC, {}, false, false, payload.requestConfig.responseScope);
      },

      /**
       * Handles successful actions. When an attempt is made to create link to more than one node it is possible that
       * only for some of the nodes it will be created successfully. If for all the nodes the link was created
       * successfully then a simple notification is displayed indicating a successful action was completed, or partial
       * completed.
       *
       * @instance
       * @param {object} payload
       * @fires module:alfresco/core/topics#DISPLAY_NOTIFICATION
       * @fires module:alfresco/core/topics#DISPLAY_PROMPT
       * @fires module:alfresco/core/topics#RELOAD_DATA_TOPIC
       * @since 1.0.92
       */
      onCreateLinkActionSuccess: function alfresco_services_actions_CopyMoveService__onCreateLinkActionSuccess(payload) {
         // jshint unused:false
         var subscriptionHandles = lang.getObject("requestConfig.subscriptionHandles", false, payload);
         if (subscriptionHandles)
         {
            this.alfUnsubscribeSaveHandles(subscriptionHandles);
         }
         if (payload.response.successCount === 0)
         {
            // See AKU-1068 - total failure returned as success...
            var failureMessage;
            failureMessage = payload.response.failureCount === 1 ? "copyMoveService.createLink.failure" : "copyMoveService.createLink.multiple.failure";

            this.alfServicePublish(topics.DISPLAY_PROMPT, {
               title: this.message("copyMoveService.createLink.failure.title"),
               message: this.message(failureMessage)
            });
         }
         else if (payload.response.overallSuccess === "true")
         {
            this.alfServicePublish(topics.DISPLAY_NOTIFICATION, {
               message: this.message("copyMoveService.createLink.completeSuccess")
            });
         }
         this.alfPublish(topics.RELOAD_DATA_TOPIC, {}, false, false, payload.requestConfig.responseScope);
      },

      /**
       * Handles failed actions by displaying a notification indicating that the action was not successful.
       *
       * @instance
       * @param {object} payload
       * @fires module:alfresco/core/topics#DISPLAY_PROMPT
       */
      onActionFailure: function alfresco_services_actions_CopyMoveService__onActionFailure(payload) {
         // jshint unused:false
         // TODO: Not all success is success. Need to check response.overallSuccess rather than just response status.
         var subscriptionHandles = lang.getObject("requestConfig.subscriptionHandles", false, payload);
         if (subscriptionHandles)
         {
            this.alfUnsubscribeSaveHandles(subscriptionHandles);
         }
         this.alfServicePublish(topics.DISPLAY_PROMPT, {
            title: payload.requestConfig.copy ? this.message("copyMoveService.copy.failure.title") : this.message("copyMoveService.move.failure.title"),
            message: payload.requestConfig.copy ? this.message("copyMoveService.copy.failure") : this.message("copyMoveService.move.failure")
         });
      },

      /**
       * Handles failed actions by displaying a notification indicating that the action was not successful.
       *
       * @instance
       * @param {object} payload
       * @fires module:alfresco/core/topics#DISPLAY_PROMPT
       * @since 1.0.92
       */
      onCreateLinkActionFailure: function alfresco_services_actions_CopyMoveService__onCreateLinkActionFailure(payload) {
         // jshint unused:false
         var subscriptionHandles = lang.getObject("requestConfig.subscriptionHandles", false, payload);
         if (subscriptionHandles)
         {
            this.alfUnsubscribeSaveHandles(subscriptionHandles);
         }
         this.alfServicePublish(topics.DISPLAY_PROMPT, {
            title: this.message("copyMoveService.createLink.failure.title"),
            message: this.message("copyMoveService.createLink.failure")
         });
      }
   });
});