Source: services/ContentService.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 should be used for the creation, uploading, updating and deletion of nodes from an
 * Alfresco Repository. When using for deleting nodes it is important to ensure that the 
 * [DialogService]{@link module:alfresco/services/DialogService} is included on the page (or an 
 * alternative service that handles dialog creation requests). When uploading new content or updating
 * existing content then it is important to ensure that the [UploadService]{@link module:alfresco/services/UploadService}
 * is included on the page.
 * 
 * @module alfresco/services/ContentService
 * @extends module:alfresco/services/BaseService
 * @mixes module:alfresco/core/CoreXhr
 * @mixes module:alfresco/documentlibrary/_AlfDocumentListTopicMixin
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "alfresco/services/BaseService",
        "alfresco/core/CoreXhr",
        "alfresco/core/topics",
        "service/constants/Default",
        "alfresco/documentlibrary/_AlfDocumentListTopicMixin",
        "dojo/_base/lang",
        "dojo/_base/array",
        "alfresco/core/NodeUtils"],
        function(declare, BaseService, CoreXhr, topics, AlfConstants, _AlfDocumentListTopicMixin, lang, array, NodeUtils) {
   
   return declare([BaseService, CoreXhr, _AlfDocumentListTopicMixin], {
      
      /**
       * An array of the i18n files to use with this widget.
       *
       * @instance
       * @type {object[]}
       * @default [{i18nFile: "./i18n/ContentService.properties"}]
       */
      i18nRequirements: [{i18nFile: "./i18n/ContentService.properties"}],

      /**
       * Sets up the subscriptions for the ContentService
       * 
       * @instance
       * @since 1.0.32
       * @listens module:alfresco/core/topics#CREATE_CONTENT_REQUEST
       * @listens module:alfresco/core/topics#DELETE_CONTENT
       * @listens module:alfresco/core/topics#UPLOAD_TO_UNKNOWN_LOCATION
       */
      registerSubscriptions: function alfresco_services_ContentService__registerSubscriptions() {
         this.alfSubscribe(this.metadataChangeTopic, lang.hitch(this, this.handleCurrentNodeChange));
         this.alfSubscribe("ALF_SHOW_UPLOADER", lang.hitch(this, this.showUploader));
         this.alfSubscribe("ALF_CONTENT_SERVICE_UPLOAD_REQUEST_RECEIVED", lang.hitch(this, this.onFileUploadRequest));
         this.alfSubscribe(topics.CREATE_CONTENT_REQUEST, lang.hitch(this, this.onCreateContent));
         this.alfSubscribe("ALF_UPDATE_CONTENT_REQUEST", lang.hitch(this, this.onUpdateContent));
         this.alfSubscribe(topics.DELETE_CONTENT, lang.hitch(this, this.onDeleteContent));
         this.alfSubscribe("ALF_EDIT_BASIC_METADATA_REQUEST", lang.hitch(this, this.onEditBasicMetadataRequest));
         this.alfSubscribe("ALF_BASIC_METADATA_SUCCESS", lang.hitch(this, this.onEditBasicMetadataReceived));
         this.alfSubscribe(topics.UPLOAD_TO_UNKNOWN_LOCATION, lang.hitch(this, this.showUploadLocationPicker));
      },
      
      /**
       * This handles requests to create content.
       *
       * @instance
       * @param {object} payload The details of the content to create
       */
      onCreateContent: function alfresco_services_ContentService__onCreateContent(payload) {
         var parentNodeRef = lang.getObject("currentNode.parent.nodeRef", false, payload);
         if (!parentNodeRef)
         {
            parentNodeRef = lang.getObject("parent.nodeRef", false, this._currentNode);
         }
         if (!payload.alf_destination)
         {
            payload.alf_destination = parentNodeRef;
         }
         var type = null;
         if (!payload.type)
         {
            type = "cm%3acontent";
         }
         else
         {
            type = payload.type;
         }
         var url = AlfConstants.PROXY_URI + "api/type/" + type + "/formprocessor",
            data = lang.clone(payload);
         delete data.currentNode;
         delete data.type;
         this.serviceXhr({url : url,
                          data: data,
                          method: "POST",
                          successCallback: this.onContentCreationSuccess,
                          failureCallback: this.onContentCreationFailure,
                          callbackScope: this});
      },

      /**
       * This is a generic success callback handler for content creation that simply publishes a request to
       * reload the current data.
       *
       * @instance
       * @param {object} response The response from the request
       * @param {object} originalRequestConfig The configuration passed on the original request
       *
       * @fires module:alfresco/core/topics#CONTENT_CREATED
       * @fires module:alfresco/core/topics#RELOAD_DATA_TOPIC
       */
      onContentCreationSuccess: function alfresco_services_ContentService__onContentCreationSuccess(response, originalRequestConfig) {
         var responseScope = lang.getObject("data.alfResponseScope", false, originalRequestConfig) || "";

         this.alfPublish(topics.CONTENT_CREATED, {
            parentNodeRef: originalRequestConfig.data.alf_destination,
            nodeRef: response.persistedObject
         }, false, false, responseScope);
         this.alfPublish(this.reloadDataTopic, {}, false, false, responseScope);
      },

       /**
       * This is a generic failure callback for content creation.
       *
       * @instance
       * @param {object} response The response from the request
       * @param {object} originalRequestConfig The configuration passed on the original request
       * @since 1.0.64
       *
       * @fires module:alfresco/core/topics#CONTENT_CREATION_FAILED
       * @fires module:alfresco/core/topics#DISPLAY_PROMPT
       */
      onContentCreationFailure: function alfresco_services_ContentService__onContentCreationFailure(response, originalRequestConfig) {
         var responseScope = lang.getObject("data.alfResponseScope", false, originalRequestConfig) || "";
         this.alfPublish(topics.CONTENT_CREATION_FAILED, response, false, false, responseScope);

         try
         {
            var message = JSON.parse(response.response.data).message;
            this.alfServicePublish(topics.DISPLAY_PROMPT, {
               message: message
            });
         }
         catch(e)
         {
            this.alfLog("error", "Could not extract error message from content creation failure response", response);
         }
         
      },

      /**
       * This handles requests to update content
       *
       * @instance
       * @param {object} payload The details of the content to update
       */
      onUpdateContent: function alfresco_services_ContentService__onUpdateContent(payload) {
         if (!payload.nodeRef)
         {
            this.alfLog("warn", "A request was made to update content but no 'nodeRef' attribute was provided", payload, this);
         }
         else
         {
            var nodeRef = NodeUtils.processNodeRef(payload.nodeRef);
            var url = AlfConstants.PROXY_URI + "api/node/" + nodeRef.uri + "/formprocessor";
            delete payload.nodeRef;
            this.serviceXhr({url : url,
                             data: payload,
                             method: "POST",
                             successCallback: this.onContentCreationSuccess,
                             callbackScope: this});
         }
      },

      /**
       * This handles requests to delete content
       *
       * @instance
       * @param {object} payload The details of the content to update
       */
      onDeleteContent: function alfresco_services_ContentService__onDeleteContent(payload) {
         var responseTopic = this.generateUuid();
         this._actionDeleteHandle = this.alfSubscribe(responseTopic, lang.hitch(this, this.onActionDeleteConfirmation), true);

         var nodes = payload.documents || (payload.document ? [payload.document] : [payload]);
         this.alfPublish(topics.CREATE_DIALOG, {
            dialogId: "ALF_DELETE_CONTENT_DIALOG",
            dialogTitle: this.message("contentService.delete.dialog.title"),
            widgetsContent: [
               {
                  name: "alfresco/lists/views/AlfListView",
                  config: {
                     additionalCssClasses: "no-highlight",
                     currentData: {
                        items: nodes
                     },
                     widgets: [
                        {
                           name: "alfresco/lists/views/layouts/Row",
                           config: {
                              widgets: [
                                 {
                                    name: "alfresco/lists/views/layouts/Cell",
                                    config: {
                                       width: "40px",
                                       widgets: [
                                          {
                                             name: "alfresco/renderers/SmallThumbnail"
                                          }
                                       ]
                                    }
                                 },
                                 {
                                    name: "alfresco/lists/views/layouts/Cell",
                                    config: {
                                       widgets: [
                                          {
                                             name: "alfresco/renderers/Property",
                                             config: {
                                                propertyToRender: "displayName",
                                                renderFilter: [
                                                   {
                                                      property: "displayName",
                                                      values: [""],
                                                      negate: true
                                                   }
                                                ]
                                             }
                                          },
                                          {
                                             name: "alfresco/renderers/Property",
                                             config: {
                                                propertyToRender: "node.properties.cm:name",
                                                renderFilter: [
                                                   {
                                                      property: "displayName",
                                                      renderOnAbsentProperty: true
                                                   }
                                                ]
                                             }
                                          }
                                       ]
                                    }
                                 }
                              ]
                           }
                        }
                     ]
                  }
               }
            ],
            widgetsButtons: [
               {
                  id: "ALF_DELETE_CONTENT_DIALOG_CONFIRMATION",
                     name: "alfresco/buttons/AlfButton",
                  config: {
                     label: this.message("contentService.delete.confirmation"),
                     publishTopic: responseTopic,
                     publishPayload: {
                        nodes: nodes,
                        responseScope: payload.alfResponseScope
                     }
                  }
               },
               {
                  id: "ALF_DELETE_CONTENT_DIALOG_CANCELLATION",
                  name: "alfresco/buttons/AlfButton",
                  config: {
                     label: this.message("contentService.delete.cancellation"),
                     publishTopic: "close"
                  }
               }
            ]
         });
      },

      /**
       * This function is called when the user confirms that they wish to delete a document
       *
       * @instance
       * @param {object} payload An object containing the details of the document(s) to be deleted.
       */
      onActionDeleteConfirmation: function alfresco_services_ContentService__onActionDeleteConfirmation(payload) {
         this.alfUnsubscribeSaveHandles([this._actionDeleteHandle]);

         var nodeRefs = array.map(payload.nodes, function(item) {
            return item.nodeRef || item.node.nodeRef;
         });
         var responseTopic = this.generateUuid();
         var subscriptionHandle = this.alfSubscribe(responseTopic + "_SUCCESS", lang.hitch(this, this.onActionDeleteSuccess), true);

         this.serviceXhr({
            alfTopic: responseTopic,
            responseScope: payload.alfResponseScope,
            subscriptionHandle: subscriptionHandle,
            url: AlfConstants.PROXY_URI + "slingshot/doclib/action/files?alf_method=delete",
            method: "POST",
            data: {
               nodeRefs: nodeRefs
            }
         });
      },

      /**
       * This action will be called when documents are successfully deleted
       *
       * @instance
       * @param {object} payload
       * @fires module:alfresco/core/topics#DISPLAY_NOTIFICATION
       * @fires module:alfresco/core/topics#CONTENT_DELETED
       * @fires module:alfresco/core/topics#RELOAD_DATA_TOPIC
       */
      onActionDeleteSuccess: function alfresco_services_ContentService__onActionDeleteSuccess(payload) {
         var subscriptionHandle = lang.getObject("requestConfig.subscriptionHandle", false, payload);
         if (subscriptionHandle)
         {
            this.alfUnsubscribe(subscriptionHandle);
         }
         this.alfServicePublish(topics.DISPLAY_NOTIFICATION, {
            message: this.message("contentService.delete.success.message")
         });
         this.alfPublish(topics.CONTENT_DELETED, {
            nodeRefs: lang.getObject("requestConfig.data.nodeRefs", false, payload)
         }, false, false, payload.requestConfig.responseScope);
         this.alfPublish(topics.RELOAD_DATA_TOPIC, {}, false, false, payload.requestConfig.responseScope);
      },

      /**
       * The current Node that content will be worked relative to.
       * @instance
       * @type {object}
       * @default
       */
      _currentNode: null,
      
      /**
       *
       * @instance
       */
      handleCurrentNodeChange: function alfresco_services_ContentService__handleCurrentNodeRefChange(payload) {
         if (payload && payload.node)
         {
            this.alfLog("log", "Updating current nodeRef to: ", payload.node);
            this._currentNode = payload.node;
         }
         else
         {
            this.alfLog("error", "A request was made to update the current NodeRef, but no 'node' property was provided in the payload: ", payload);
         }
      },
      
      /**
       * These are the widgets to render in a dialog for basic content upload (e.g. uploading new content
       * rather than updating the content of an existing Node). By default only a basic file selection
       * control will be displayed.
       *
       * @instance
       * @type {object[]}
       */
      widgetsForUpload: [
         {
            name: "alfresco/forms/controls/FileSelect",
            config: {
               label: "contentService.uploader.dialog.fileSelect.label",
               name: "files",
               requirementConfig: {
                  initialValue: true
               }
            }
         }
      ],

      /**
       * These are the widgets to render in a dialog when updating an existing Node. A file selection
       * control along with version increment radio buttons and comment text box are rendered.
       *
       * @instance
       * @type {object[]}
       */
      widgetsForUpdate: [
         {
            name: "alfresco/forms/controls/FileSelect",
            config: {
               label: "contentService.updater.dialog.fileSelect.label",
               name: "files",
               requirementConfig: {
                  initialValue: true
               }
            }
         },
         {
            name: "alfresco/forms/controls/RadioButtons",
            config: {
               label: "contentService.updater.dialog.majorVersion.label",
               name: "targetData.majorVersion",
               value: "false",
               optionsConfig: {
                  fixed: [
                     { label: "contentService.updater.dialog.majorVersion.false.label", value: "false" },
                     { label: "contentService.updater.dialog.majorVersion.true.label", value: "true" }
                  ]
               }
            }
         },
         {
            name: "alfresco/forms/controls/TextArea",
            config: {
               label: "contentService.updater.dialog.comments",
               name: "targetData.description",
               value: ""
            }
         }
      ],

      /**
       * This function will open a [AlfFormDialog]{@link module:alfresco/forms/AlfFormDialog} containing a 
       * [file select form control]{@link module:alfresco/forms/controls/FileSelect} so that the user can 
       * select one or more files to upload. When the dialog is confirmed the 
       * [onFileUploadRequest]{@link module:alfresco/services/ContentService#onFileUploadRequest}
       * function will be called to destroy the dialog and pass the upload request on.
       * 
       * @instance
       * @param {object} payload
       *
       * @fires module:alfresco/services/DialogService~event:ALF_CREATE_FORM_DIALOG_REQUEST
       */
      showUploader: function alfresco_services_ContentService__showUploader(/*jshint unused:false*/ payload) {
         // Check to see what we're uploading, either new content to a location or updating a 
         // specific node. Ideally we want to get the parent nodeRef from the payload provided
         // but if this information isn't availabel then we'll fall back to whatever this service
         // things is the "current" node.
         // 
         // This reason why this is important is that multiple Document Libraries can be displayed on the same
         // page and we want to make sure that they're not "competing" to set the _currentNode
         var parentNodeRef = lang.getObject("parent.nodeRef", false, payload);
         if (!parentNodeRef)
         {
            parentNodeRef = lang.getObject("document.parent.nodeRef", false, payload);
         }
         if (!parentNodeRef)
         {
            parentNodeRef = lang.getObject("parent.nodeRef", false, this._currentNode);
         }
         var updateNodeRef = lang.getObject("node.nodeRef", false, payload);
         if (!updateNodeRef)
         {
            updateNodeRef = lang.getObject("document.node.nodeRef", false, payload);
         }

         this.alfPublish(topics.CREATE_FORM_DIALOG, {
            dialogId: "ALF_UPLOAD_DIALOG",
            dialogTitle: (updateNodeRef ? "contentService.updater.dialog.title" : "contentService.uploader.dialog.title"),
            dialogConfirmationButtonTitle: "contentService.uploader.dialog.confirmation",
            dialogCancellationButtonTitle: "contentService.uploader.dialog.cancellation",
            formSubmissionTopic: "ALF_CONTENT_SERVICE_UPLOAD_REQUEST_RECEIVED",
            formSubmissionPayloadMixin: {
               targetData: {
                  destination: parentNodeRef,
                  siteId: null,
                  containerId: null,
                  uploadDirectory: null,
                  updateNodeRef: updateNodeRef,
                  description: "",
                  overwrite: false,
                  thumbnails: "doclib",
                  username: null
               }
            },
            responseScope: payload.alfResponseScope,
            widgets: (updateNodeRef ? lang.clone(this.widgetsForUpdate) : lang.clone(this.widgetsForUpload))
         });
      },

      /**
       * Publishes a request to create a form dialog containing a file picker and a location so that the user
       * can choose what to upload and where to upload it.
       * 
       * @instance
       * @param  {object} payload The payload from the upload request
       * @since 1.0.34
       * 
       * @fires module:alfresco/services/DialogService~event:ALF_CREATE_FORM_DIALOG_REQUEST
       */
      showUploadLocationPicker: function alfresco_services_ContentService__showUploadLocationPicker(payload) {
         this.alfPublish(topics.CREATE_FORM_DIALOG, {
            dialogId: "ALF_UPLOAD_TO_LOCATION_DIALOG",
            dialogTitle: "contentService.no-location.uploader.dialog.title",
            dialogConfirmationButtonTitle: "contentService.uploader.dialog.confirmation",
            dialogCancellationButtonTitle: "contentService.uploader.dialog.cancellation",
            formSubmissionTopic: "ALF_CONTENT_SERVICE_UPLOAD_REQUEST_RECEIVED",
            formSubmissionPayloadMixin: {
               targetData: {
                  siteId: null,
                  containerId: null,
                  uploadDirectory: null,
                  description: "",
                  overwrite: false,
                  thumbnails: "doclib",
                  username: null
               }
            },
            responseScope: payload.alfResponseScope,
            widgets: [
               {
                  id: "ALF_UPLOAD_TO_LOCATION_DIALOG_FILE_SELECT",
                  name: "alfresco/forms/controls/FileSelect",
                  config: {
                     name: "files",
                     label: "contentService.no-location.uploader.file.picker.label",
                     description: "contentService.no-location.uploader.file.picker.description",
                     requirementConfig: {
                        initialValue: true
                     }
                  }
               },
               {
                  id: "ALF_UPLOAD_TO_LOCATION_DIALOG_CONTAINER_PICKER",
                  name: "alfresco/forms/controls/ContainerPicker",
                  config: {
                     name: "targetData.destination",
                     label: "contentService.no-location.uploader.location.picker.label",
                     description: "contentService.no-location.uploader.location.picker.description",
                     requirementConfig: {
                        initialValue: true
                     }
                  }
               }
            ]
         });
      },

      /**
       * This function will be called whenever the [AlfFormDialog]{@link module:alfresco/forms/AlfFormDialog} created
       * by the [showUploader function]{@link module:alfresco/services/ContentService#showUploader} is confirmed to
       * trigger a dialog. This will destroy the dialog and pass the supplied payload onto the [AlfUpload]{@link module:alfresco/upload/AlfUpload}
       * module to actually perform the upload. It is necessary to destroy the dialog to ensure that all the subscriptions
       * are removed to prevent subsequent upload requests from processing old data.
       *
       * @instance
       * @param {object} payload The file upload data payload to pass on
       * @fires module:alfresco/core/topics#UPLOAD_REQUEST
       */
      onFileUploadRequest: function alfresco_services_ContentService__onFileUploadRequest(payload) {
         var responseTopic = this.generateUuid();
         this._uploadSubHandle = this.alfSubscribe(responseTopic, lang.hitch(this, this.onFileUploadComplete), true);
         payload.alfResponseTopic = responseTopic;
         this.alfPublish(topics.UPLOAD_REQUEST, payload);
      },

      /**
       * This function is called once the document upload is complete. It publishes a request to reload the
       * current document list data.
       * 
       * @instance
       */
      onFileUploadComplete: function alfresco_services_ContentService__onFileUploadComplete(payload) {
         this.alfLog("log", "Upload complete");
         this.alfUnsubscribeSaveHandles([this._uploadSubHandle]);
         this.alfPublish(this.reloadDataTopic, {}, false, false, payload.alfResponseScope);
      },

      /**
       * Handles requests to edit the basic metadata of the node provided.
       *
       * @instance
       * @param {object} payload An object containing the node to edit
       */
      onEditBasicMetadataRequest: function alfresco_services_ContentService__onEditBasicMetadataRequest(payload) {
         var node = lang.getObject("document.node", false, payload);
         if (node)
         {
            // Check to see if properties are already available (this would be expected when used with
            // some Alfresco APIs but not others, e.g. Document Library APIs, but not Search APIs)...
            if (node.properties)
            {
               this.onEditBasicMetadata(payload);
            }
            else if (node.nodeRef)
            {
               this.alfPublish("ALF_RETRIEVE_SINGLE_DOCUMENT_REQUEST", {
                  alfResponseTopic: "ALF_BASIC_METADATA",
                  responseScope: payload.alfResponseScope,
                  nodeRef: node.nodeRef,
                  rawData: true
               });
            }
         }
         else
         {
            this.alfLog("warn", "A request was made to edit the properties of a node but no node or 'nodeRef' was provided", payload, this);
         }
      },

      /**
       * This function will be called in response to a documents details being successfully retrieved.
       *
       * @instance
       * @param {object} payload
       */
      onEditBasicMetadataReceived: function alfresco_services_ContentService__onEditBasicMetadataReceived(payload) {
         if (lang.exists("response.item.node", payload)) 
         {
            this.onEditBasicMetadata(payload);
         }
      },

      /**
       * 
       * @instance
       * @param {object} node The node to display the metadata for
       */
      onEditBasicMetadata: function alfresco_services_ContentService__onEditBasicMetadata(payload) {
         var node = lang.getObject("document.node", false, payload);
         if (!node)
         {
            node = lang.getObject("response.item.node", false, payload);
         }
         if (node)
         {
            var dialogTitle = this.message("contentService.basicMetadata.dialog.title", {
               0: node.properties["cm:name"]
            });

            this.alfPublish("ALF_CREATE_FORM_DIALOG_REQUEST", {
               dialogId: "ALF_BASIC_METADATA_DIALOG",
               dialogTitle: dialogTitle,
               dialogConfirmationButtonTitle: "contentService.basicMetadata.confirmation",
               dialogCancellationButtonTitle: "contentService.basicMetadata.cancellation",
               formSubmissionTopic: "ALF_UPDATE_CONTENT_REQUEST",
               responseScope: payload.alfResponseScope,
               widgets: [
                  {
                     name: "alfresco/forms/controls/TextBox",
                     config: {
                        name: "nodeRef",
                        value: node.nodeRef,
                        visibilityConfig: {
                           initialValue: false
                        }
                     }
                  },
                  {
                     name: "alfresco/forms/controls/TextBox",
                     config: {
                        label: "contentService.basicMetadata.name.label",
                        description: "contentService.basicMetadata.name.description",
                        name: "prop_cm_name",
                        value: node.properties["cm:name"],
                        requirementConfig: {
                           initialValue: true
                        }
                     }
                  },
                  {
                     name: "alfresco/forms/controls/TextBox",
                     config: {
                        label: "contentService.basicMetadata.title.label",
                        description: "contentService.basicMetadata.title.description",
                        name: "prop_cm_title",
                        value: node.properties["cm:title"]
                     }
                  },
                  {
                     name: "alfresco/forms/controls/TextArea",
                     config: {
                        label: "contentService.basicMetadata.description.label",
                        description: "contentService.basicMetadata.description.description",
                        name: "prop_cm_description",
                        value: node.properties["cm:description"]
                     }
                  }
               ]
            });
         }
         else
         {
            this.alfLog("warn", "Node data not provided for editing metdata", payload, this);
         }
      }
   });
});