Ext.namespace('Zarafa.core.data'); /** * #dependsFile client/zarafa/core/mapi/ObjectType.js */ /** * @class Zarafa.core.data.IPMAttachmentStore * @extends Zarafa.core.data.MAPISubStore */ Zarafa.core.data.IPMAttachmentStore = Ext.extend(Zarafa.core.data.MAPISubStore, { /** * @cfg {String} id ID to communicate with the server for the location of the cached attachments. */ id : null, /** * @cfg {Zarafa.core.ObjectType} attachmentRecordType to create a custom {Zarafa.core.data.IPMAttachmentRecord} */ attachmentRecordType : Zarafa.core.mapi.ObjectType.MAPI_ATTACH, /** * @constructor * @param config Configuration object */ constructor : function(config) { config = config || {}; Ext.applyIf(config, { id : Zarafa.generateId(32), // provide a default proxy proxy : new Zarafa.core.data.IPMAttachmentProxy({ listModuleName : 'attachments', itemModuleName : 'attachments' }), // provide a default writer writer : new Zarafa.core.data.JsonAttachmentWriter(), // provide a default reader reader : new Zarafa.core.data.JsonAttachmentReader({}, Zarafa.core.data.RecordFactory.getRecordClassByCustomType(this.attachmentRecordType)) }); Zarafa.core.data.IPMAttachmentStore.superclass.constructor.call(this, config); // Update the 'hasattach' property whenever the store changes this.on({ 'update' : this.onAttachmentsChange, 'add' : this.onAttachmentsChange, 'remove' : this.onAttachmentsChange, 'datachanged' : this.onAttachmentsChange, 'scope' : this }); // add the custom events of the Attachmentstore to the EventDispatcher Zarafa.core.data.ZarafaCustomEventDispatcher.addEvents( 'attachmentstorebeforecheck', 'attachmentstorebeforeupload', 'attachmentstorebeforedestroyrecord', 'attachmentstorebeforegetbaseurl' ); }, /** * Event handler which is fired when data in this store has changed. * * This will update hasattach property of parentrecord * If there are any attachments in store then, * it will set hasattach property of record to true, * otherwise it will set it to false. * * @param {Store} store * @param {Ext.data.Record/Ext.data.Record[]} record * @param {Number} index * @private */ onAttachmentsChange : function() { if(this.getParentRecord()){ if(this.getCount() > 0) { this.getParentRecord().set('hasattach', true); } else { this.getParentRecord().set('hasattach', false); } } }, /** * Obtain the ID to communicate with the * server for the location of cached attachments * @return {String} The ID of the server attachment location */ getId : function() { return this.id; }, /** * Set the ID to communicate with the server * for the location of cached attachments * @param {String} id The ID of the server attachment location */ setId : function(id) { this.id = id; }, /** * Builds and returns inline image URL to download inline images, * it uses {@link Zarafa.core.data.IPMRecord IPMRecord} to get store and message entryids. * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord Attachment record. * @return {String} URL for downloading inline images. */ getInlineImageUrl : function(attachmentRecord) { var url = this.getDownloadAttachmentUrl(attachmentRecord); return Ext.urlAppend(url, 'contentDispositionType=inline'); }, /** * Builds and returns attachment URL to download attachment, * it uses {@link Zarafa.core.data.IPMRecord IPMRecord} to get store and message entryids. * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord Attachment record. * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP * @return {String} URL for downloading attachment. */ getAttachmentUrl : function(attachmentRecord, allAsZip) { var url = this.getDownloadAttachmentUrl(attachmentRecord, allAsZip); return Ext.urlAppend(url, 'contentDispositionType=attachment'); }, /** * Builds and returns attachment URL to download inline images/attachments, * it uses {@link Zarafa.core.data.IPMRecord IPMRecord} to get store and message entryids. * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord Attachment record. * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP * @return {String} URL for downloading attachments. */ getDownloadAttachmentUrl : function(attachmentRecord, allAsZip) { var parentRecord = this.getParentRecord(); var isSubMessage = parentRecord.isSubMessage(); var url = this.getAttachmentBaseUrl(); url = Ext.urlAppend(url, 'load=download_attachment'); // If all the attachments are requested to be downloaded in a ZIP, then there is no need to // send 'attach_num' property. if(!allAsZip || (allAsZip && isSubMessage)){ var attachNum = []; // Don't go for attachNum property of parent in case the parent record is an exception of meeting request. // Because, exception of meeting request it self is an attachment. if(!Ext.isFunction(parentRecord.isRecurringException) || (Ext.isFunction(parentRecord.isRecurringException) && !parentRecord.isRecurringException())) { attachNum = attachmentRecord.getParentAttachNum(parentRecord); } if (attachmentRecord.get('attach_num') != -1) { // add attachment number of parent sub messages attachNum.push(attachmentRecord.get('attach_num')); for(var index = 0, len = attachNum.length; index < len; index++) { url = Ext.urlAppend(url, 'attachNum[]=' + attachNum[index]); } } else { url = Ext.urlAppend(url, 'attachNum[]=' + attachmentRecord.get('tmpname')); } } if(allAsZip) { url = Ext.urlAppend(url, 'AllAsZip=true'); if(isSubMessage){ url = Ext.urlAppend(url, 'isSubMessage=true'); } url = Ext.urlAppend(url, 'subject='+parentRecord.get('subject')); } // fire the 'attachmentstorebeforegetbaseurl' event. var eventData = { url: url }; Zarafa.core.data.ZarafaCustomEventDispatcher.fireEvent('attachmentstorebeforegetbaseurl', this, attachmentRecord, allAsZip, eventData); return eventData.url; }, /** * Function returns parent record entry id for the attachment. * If parent record has an entryid then return it. For phantom records i.e. forward mail, * parent record won't have entryid so get source_entryid from record's * message action data and return it. * @return {String} entryID parent record entryid. * @private */ getAttachmentParentRecordEntryId : function() { var parentRecord = this.getParentRecord(); var entryID = parentRecord.get('entryid') || ''; var messageAction = parentRecord.getMessageActions(); if (Ext.isEmpty(entryID) && messageAction) { switch(messageAction.action_type) { case 'forward': entryID = messageAction.source_entryid; break; case 'reply': case 'replyall': // @TODO: Need to check for inline images, whether we // should pass entryid or any need in future. } } return entryID; }, /** * Builds and returns base attachment URL. * @return {String} URL for attachments * @private */ getAttachmentBaseUrl : function() { var url = container.getBaseURL(); url = Ext.urlAppend(url, 'store=' + this.getParentRecord().get('store_entryid')); url = Ext.urlAppend(url, 'entryid=' + this.getAttachmentParentRecordEntryId()); url = Ext.urlAppend(url, 'dialog_attachments=' + this.getId()); return url; }, /** * Builds and returns attachment URL to upload files. * @return {String} URL for uploading attachments * @private */ getUploadAttachmentUrl : function() { return Ext.urlAppend(this.getAttachmentBaseUrl(), 'load=upload_attachment'); }, /** * Builds and returns attachment-URL to import attachments. * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord The attachment to import into given folder * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The selected folder * @return {String} URL for importing attachments to respective folder * @private */ getImportAttachmentUrl : function(attachmentRecord, folder) { var url = this.getDownloadAttachmentUrl(attachmentRecord); url = Ext.urlAppend(url, 'import=true'); url = Ext.urlAppend(url, 'destination_folder=' + folder.get('entryid')); return url; }, /** * Check if the given files can be added to this store, this is * only possible when none of the limits from the {@link Zarafa.core.data.ServerConfig ServerConfig} * configuration have been exceeded. * * @param {FileList|Array} files The array of Files objects from the file input field. * @param {Object} options (optional) Additional parameters for the call to * {@link Zarafa.core.ui.notifier.Notifier#notify} if one of the limits was * exceeded. * @return {Boolean} True if the files can be uploaded, false otherwise * @private */ canUploadFiles : function(files, options) { // If there are no files we can exit. Ext.isEmpty() // checks if files is null or undefined. If files is // a FileList, then Ext.isEmpty() will not perform // a proper check for the length of the list. if (Ext.isEmpty(files) || files.length === 0) { return false; } // fire the 'beforeuploadcheck' event. var eventData = { handledfiles: [], checkfailed: false }; Zarafa.core.data.ZarafaCustomEventDispatcher.fireEvent('attachmentstorebeforecheck', this, files, options, eventData); // do a fast check if one of the plugins failed checking the files // error handling (notifying the used for example is handled by the plugin) if(eventData.checkfailed) { return false; } var server = container.getServerConfig(); // Maximum number of attachments that can be uploaded in message. var max_attachments = server.getMaxAttachments(); // Maximum size of single file that can be uploaded in message. var max_attachment_size = server.getMaxAttachmentSize(); // Maximum size of attachments that can be uploaded in message. var max_attachment_total_size = server.getMaxAttachmentTotalSize(); /** * Here all three checks are regarding maximum upload attachments in message. * * first check is check's that maximum number of attachments are possible to attach in single message. * by default there is no limit for number of attachments in message. * * second check is check's that each attachment should not be more than 30 MB size. * * third check is check's that total maximum attachment size in single message. * by default there is no limit for maximum attachments in message. */ // 1) Check for the total number of attachments if (Ext.isDefined(max_attachments)) { if ((this.getCount() + files.length - eventData.handledfiles.length) >= max_attachments) { container.getNotifier().notify('error.attachment', _('Attachment error'), String.format(_('Cannot upload attachment, only {0} attachments are allowed to be added to the message'), max_attachments), options); return false; } } // total Post request size in single request. var totalPostSize = 0; var totalSize = this.sum('size'); for (var i = 0; i < files.length; i++) { var file = files[i]; var fileSize = file.fileSize || file.size; // check if file was already handled by any other plugin if(eventData.handledfiles.indexOf(file) !== -1) { continue; // skip already handled files } // Update totalPostSize totalPostSize += fileSize; // Update totalSize totalSize += fileSize; // 2) Check if the size exceeds the maximum upload size if (Ext.isDefined(max_attachment_size)) { if (fileSize > max_attachment_size) { container.getNotifier().notify('error.attachment', _('Attachment error'), String.format(_('Cannot upload attachment, attachment is {0} while the allowed maximum is {1}.'), Ext.util.Format.fileSize(fileSize), Ext.util.Format.fileSize(max_attachment_size)), options); return false; } } } // 3) Check if the size exceeds the total maximum attachment size if (Ext.isDefined(max_attachment_total_size)) { if (totalSize > max_attachment_total_size) { container.getNotifier().notify('error.attachment', _('Attachment error'), String.format(_('Cannot upload attachment, the total attachment size is {0} while the allowed maximum is {1}'), Ext.util.Format.fileSize(totalSize), Ext.util.Format.fileSize(max_attachment_total_size)), options); return false; } } // Maximum number of files that can be uploaded in a single request. var max_file_uploads = server.getMaxFileUploads(); // Maximum size of post request that can be send in a single request. var max_post_size = server.getMaxPostRequestSize(); /** * Here both the checks are for post request size in single message, * * first check is check that user can not * able to send post request more then 31 MB size in single request. * max_post_size has default configuration in php ini(Apache) settings. * * second check is check that user can not able to send post request * more then 20 files in single request. max_file_uploads has default * configuration in php ini (Apache) settings. */ // 1) Check if the size exceeds the maximum post size. if(Ext.isDefined(max_post_size)) { if (totalPostSize > max_post_size) { container.getNotifier().notify('error.attachment', _('Attachment error'), String.format(_('Cannot upload attachment, total attachment is {0} while the allowed maximum attachment in single request is {1}.'), Ext.util.Format.fileSize(totalPostSize), Ext.util.Format.fileSize(max_post_size)), options); return false; } } var totalFilesUploads = files.length; // 2) check if the maximum file uploads in single request. if(Ext.isDefined(max_file_uploads)) { if (totalFilesUploads > max_file_uploads) { container.getNotifier().notify('error.attachment', _('Attachment error'), String.format(_('Cannot upload attachment, total attachments are {0} files while the maximum {1} files are allowed in single request.'), totalFilesUploads , max_file_uploads), options); return false; } } return true; }, /** * Upload the given files to the server, this will generate new * {@link Zarafa.core.data.IPMAttachmentRecord attachment records} and * add those to the store. * @param {FileList/String} files The array of file objects to upload * @param {Ext.form.BasicForm} form (optional) If only filenames were provided in files * argument, then this form must be the form which contains all file input elements * @param {Boolean} isHidden if isHidden is true it will hide the attachments. * @param {Object} params (optional) the params which contains source type of the attachment, * i.e 'contactphoto', 'embedded' attachment or file attachment 'default' */ uploadFiles : function(files, form, isHidden, params) { var attachments = []; var uploadFiles = []; // For a proper instance check demanded by some of the browser, we must have to // use relevant FileList object which belongs to respective browser window. var activeBrowserWindow = Zarafa.core.BrowserWindowMgr.getActive() || window; var isFileList = files instanceof activeBrowserWindow.FileList; // fire the 'attachmentstorebeforeupload' event. // the eventhandler can not directly remove the handled files from the "files" FileList as this property is read // only. So we have to check the eventData for handled files. var eventData = { handledfiles: [] }; Zarafa.core.data.ZarafaCustomEventDispatcher.fireEvent('attachmentstorebeforeupload', this, files, form, isHidden, params, eventData); for (var i = 0; i < files.length; i++) { var file = files[i]; // check if file was already handled by any other plugin if(eventData.handledfiles.indexOf(file) !== -1) { continue; // skip already handled files } var attach; if (isFileList) { // add the file to the upload parameters uploadFiles.push(file); // Create an attachment record, with all the passed data, use record factory so default values will be applied attach = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(this.attachmentRecordType, { 'name' : file['name'], 'size' : file['size'], 'filetype' : file['type'], 'hidden' : Ext.isDefined(isHidden) ? isHidden : false, 'attach_method' : Zarafa.core.mapi.AttachMethod.ATTACH_BY_VALUE }); } else { // If the file is a String, it is the file name including a fake path // ('C:\fakepath\<filename>') which must be stripped of its silly path. attach = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(this.attachmentRecordType, { 'name' : Ext.util.Format.basename(file), 'hidden' : Ext.isDefined(isHidden) ? isHidden : false }); } attach.file = file; attachments.push(attach); } // Add it to the store this.add(attachments); // Prepare the upload var data = {}; // Add source type if attachment record is contact photo. data = Ext.apply(data, params); // If we received a FileList we can should provide // the files into the data so it can be added to the request. // Otherwise we expect the form object to hold the files. if (isFileList) { data['attachments'] = uploadFiles; } var options = { 'params' : data, 'requestUrl' : this.getUploadAttachmentUrl(), 'requestForm' : form }; if (this.batch) { this.addToBatch(++this.batchCounter); } // if the user has only uploaded files that where handled by external plugins then this array is empty if(attachments.length > 0) { var action = Ext.data.Api.actions['create']; var callback = this.createCallback(action, attachments, false); this.proxy.request(action, attachments, options.params, this.reader, callback, this, options); } }, /** * Add given {@link Zarafa.core.data.IPMRecord IPMRecord} as embedded attachment to {@link Zarafa.core.data.IPMAttachmentStore IPMAttachmentStore}, this will generate new * {@link Zarafa.core.data.IPMAttachmentRecord attachment record} and * add those to the store and will send request to server to save embedded attachment info to state file. * @param {Zarafa.core.data.IPMRecord} record The record which needs to be added as embedded attachment into store. */ addEmbeddedAttachment : function(record) { var data = { 'entryid' : record.get('entryid'), 'store_entryid' : record.get('store_entryid'), // attach method to indicate this is an embedded attachment 'attach_method' : Zarafa.core.mapi.AttachMethod.ATTACH_EMBEDDED_MSG }; // Some optional data which should be present only if it is not empty if(!Ext.isEmpty(record.get('subject'))) { data['name'] = record.get('subject'); } if(!Ext.isEmpty(record.get('message_class'))) { data['attach_message_class'] = record.get('message_class'); } if(!Ext.isEmpty(record.get('message_size'))) { data['size'] = record.get('message_size'); } // Create an attachment record, with all the passed data, use record factory so default values will be applied var attach = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.mapi.ObjectType.MAPI_ATTACH, data); // Add it to the store this.add(attach); var options = { 'params' : attach.data, 'requestUrl' : this.getUploadAttachmentUrl() }; var action = Ext.data.Api.actions['create']; var callback = this.createCallback(action, attach, false); this.proxy.request(action, attach, options.params, this.reader, callback, this, options); }, /** * Destroys a record or records. Should not be used directly. It's called by Store#remove automatically * @param {Store} store * @param {Ext.data.Record/Ext.data.Record[]} record * @param {Number} index * @private */ destroyRecord : function(store, record, index) { Zarafa.core.data.IPMAttachmentStore.superclass.destroyRecord.apply(this, arguments); var data = {}; if (record.isTmpFile()) { data['deleteattachment'] = true; data['attach_num'] = record.get('tmpname'); } else { data['deleteattachment'] = true; data['attach_num'] = record.get('attach_num'); } var options = { 'params' : data, 'requestUrl' : this.getUploadAttachmentUrl() }; // fire the 'attachmentstorebeforedestroyrecord' event that can change the event options. var eventData = { options: options }; Zarafa.core.data.ZarafaCustomEventDispatcher.fireEvent('attachmentstorebeforedestroyrecord', this, record, eventData); var action = Ext.data.Api.actions['destroy']; var callback = this.createCallback(action, record, false); this.proxy.request(action, record, options.params, this.reader, callback, this, eventData.options); }, /** * Imports an attachment into given folder. * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord The attachment to import into given folder * @param {Zarafa.core.data.IPMRecord IPMRecord} message The record to which given attachment belongs * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The selected folder * @private */ importRecord : function(attachmentRecord, message, folder) { var options = { 'params' : {}, 'requestUrl' : this.getImportAttachmentUrl(attachmentRecord, folder) }; var action = Ext.data.Api.actions['create']; this.proxy.request(action, message, options.params, this.reader, undefined, this, options); }, /** * Possibly temporary until Ext framework has an exception-handler. * See {@link Ext.data.Store}. * @protected */ handleException : Ext.data.Store.prototype.handleException, /** * callback-handler for remote CRUD actions. * Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead. * See {@link Ext.data.Store}. * @private */ createCallback : Ext.data.Store.prototype.createCallback, /** * Proxy callback for create action. * Callback function as created by {@Link #createCallback}. * See {@link Ext.data.Store}. * @protected */ onCreateRecords : Ext.data.Store.prototype.onCreateRecords, /** * Proxy callback for destroy action * Callback function as created by {@Link #createCallback}. * See {@link Ext.data.Store}. * @protected */ onDestroyRecords : Ext.data.Store.prototype.onDestroyRecords, /** * remap record ids in MixedCollection after records have been realized. See {@link Ext.dataStore#onCreateRecords}, and * {@link Ext.data.DataReader#realize} * @private */ reMap : Ext.data.Store.prototype.reMap, /** * Add request to batch * See {@link Ext.data.Store}. * @private */ addToBatch : Ext.emptyFn, /** * Remove request from batch * See {@link Ext.data.Store}. * @private */ removeFromBatch : Ext.emptyFn });