diff options
Diffstat (limited to 'core/js/shareitemmodel.js')
-rw-r--r-- | core/js/shareitemmodel.js | 750 |
1 files changed, 750 insertions, 0 deletions
diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js new file mode 100644 index 00000000000..ff0d3a6d800 --- /dev/null +++ b/core/js/shareitemmodel.js @@ -0,0 +1,750 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + if(!OC.Share) { + OC.Share = {}; + OC.Share.Types = {}; + } + + /** + * @typedef {object} OC.Share.Types.LinkShareInfo + * @property {bool} isLinkShare + * @property {string} token + * @property {string|null} password + * @property {string} link + * @property {number} permissions + * @property {Date} expiration + * @property {number} stime share time + */ + + /** + * @typedef {object} OC.Share.Types.Collection + * @property {string} item_type + * @property {string} path + * @property {string} item_source TODO: verify + */ + + /** + * @typedef {object} OC.Share.Types.Reshare + * @property {string} uid_owner + * @property {number} share_type + * @property {string} share_with + * @property {string} displayname_owner + * @property {number} permissions + */ + + /** + * @typedef {object} OC.Share.Types.ShareInfo + * @property {number} share_type + * @property {number} permissions + * @property {number} file_source optional + * @property {number} item_source + * @property {string} token + * @property {string} share_with + * @property {string} share_with_displayname + * @property {string} share_mail_send + * @property {OC.Share.Types.Collection|undefined} collection + * @property {Date} expiration optional? + * @property {number} stime optional? + */ + + /** + * @typedef {object} OC.Share.Types.ShareItemInfo + * @property {OC.Share.Types.Reshare} reshare + * @property {OC.Share.Types.ShareInfo[]} shares + * @property {OC.Share.Types.LinkShareInfo|undefined} linkShare + */ + + /** + * @class OCA.Share.ShareItemModel + * @classdesc + * + * Represents the GUI of the share dialogue + * + * // FIXME: use OC Share API once #17143 is done + * + * // TODO: this really should be a collection of share item models instead, + * where the link share is one of them + */ + var ShareItemModel = OC.Backbone.Model.extend({ + initialize: function(attributes, options) { + if(!_.isUndefined(options.configModel)) { + this.configModel = options.configModel; + } + if(!_.isUndefined(options.fileInfoModel)) { + /** @type {OC.Files.FileInfo} **/ + this.fileInfoModel = options.fileInfoModel; + } + + _.bindAll(this, 'addShare'); + }, + + defaults: { + allowPublicUploadStatus: false, + permissions: 0, + linkShare: {} + }, + + /** + * Saves the current link share information. + * + * This will trigger an ajax call and refetch the model afterwards. + * + * TODO: this should be a separate model + */ + saveLinkShare: function(attributes, options) { + var model = this; + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + + // TODO: use backbone's default value mechanism once this is a separate model + var requiredAttributes = [ + { name: 'password', defaultValue: '' }, + { name: 'permissions', defaultValue: OC.PERMISSION_READ }, + { name: 'expiration', defaultValue: this.configModel.getDefaultExpirationDateString() } + ]; + + attributes = attributes || {}; + + // get attributes from the model and fill in with default values + _.each(requiredAttributes, function(attribute) { + // a provided options overrides a present value of the link + // share. If neither is given, the default value is used. + if(_.isUndefined(attribute[attribute.name])) { + attributes[attribute.name] = attribute.defaultValue; + var currentValue = model.get('linkShare')[attribute.name]; + if(!_.isUndefined(currentValue)) { + attributes[attribute.name] = currentValue; + } + } + }); + + OC.Share.share( + itemType, + itemSource, + OC.Share.SHARE_TYPE_LINK, + attributes.password, + attributes.permissions, + this.fileInfoModel.get('name'), + attributes.expiration, + function(result) { + if (!result || result.status !== 'success') { + model.fetch({ + success: function() { + if (options && _.isFunction(options.success)) { + options.success(model); + } + } + }); + } else { + if (options && _.isFunction(options.error)) { + options.error(model); + } + } + }, + function(result) { + var msg = t('core', 'Error'); + if (result.data && result.data.message) { + msg = result.data.message; + } + + if (options && _.isFunction(options.error)) { + options.error(model, msg); + } else { + OC.dialogs.alert(msg, t('core', 'Error while sharing')); + } + } + ); + }, + + removeLinkShare: function() { + this.removeShare(OC.Share.SHARE_TYPE_LINK, ''); + }, + + /** + * Sets the public upload flag + * + * @param {bool} allow whether public upload is allowed + */ + setPublicUpload: function(allow) { + var permissions = OC.PERMISSION_READ; + if(allow) { + permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ; + } + + this.get('linkShare').permissions = permissions; + }, + + /** + * Sets the expiration date of the public link + * + * @param {string} expiration expiration date + */ + setExpirationDate: function(expiration) { + this.get('linkShare').expiration = expiration; + }, + + /** + * Set password of the public link share + * + * @param {string} password + */ + setPassword: function(password) { + this.get('linkShare').password = password; + }, + + addShare: function(attributes, options) { + var shareType = attributes.shareType; + var shareWith = attributes.shareWith; + var fileName = this.fileInfoModel.get('name'); + options = options || {}; + + // Default permissions are Edit (CRUD) and Share + // Check if these permissions are possible + var permissions = OC.PERMISSION_READ; + if (shareType === OC.Share.SHARE_TYPE_REMOTE) { + permissions = OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE | OC.PERMISSION_READ; + } else { + if (this.updatePermissionPossible()) { + permissions = permissions | OC.PERMISSION_UPDATE; + } + if (this.createPermissionPossible()) { + permissions = permissions | OC.PERMISSION_CREATE; + } + if (this.deletePermissionPossible()) { + permissions = permissions | OC.PERMISSION_DELETE; + } + if (this.configModel.get('isResharingAllowed') && (this.sharePermissionPossible())) { + permissions = permissions | OC.PERMISSION_SHARE; + } + } + + var model = this; + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + OC.Share.share(itemType, itemSource, shareType, shareWith, permissions, fileName, options.expiration, function() { + model.fetch(); + }); + }, + + setPermissions: function(shareType, shareWith, permissions) { + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + + // TODO: in the future, only set the permissions on the model but don't save directly + OC.Share.setPermissions(itemType, itemSource, shareType, shareWith, permissions); + }, + + removeShare: function(shareType, shareWith) { + var model = this; + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + + OC.Share.unshare(itemType, itemSource, shareType, shareWith, function() { + model.fetch(); + }); + }, + + /** + * @returns {boolean} + */ + isPublicUploadAllowed: function() { + return this.get('allowPublicUploadStatus'); + }, + + /** + * @returns {boolean} + */ + isFolder: function() { + return this.get('itemType') === 'folder'; + }, + + /** + * @returns {boolean} + */ + isFile: function() { + return this.get('itemType') === 'file'; + }, + + /** + * whether this item has reshare information + * @returns {boolean} + */ + hasReshare: function() { + var reshare = this.get('reshare'); + return _.isObject(reshare) && !_.isUndefined(reshare.uid_owner); + }, + + /** + * whether this item has user share information + * @returns {boolean} + */ + hasUserShares: function() { + var shares = this.get('shares'); + return _.isArray(shares) && shares.length > 0; + }, + + /** + * Returns whether this item has a link share + * + * @return {bool} true if a link share exists, false otherwise + */ + hasLinkShare: function() { + var linkShare = this.get('linkShare'); + if (linkShare && linkShare.isLinkShare) { + return true; + } + return false; + }, + + /** + * @param {number} shareIndex + * @returns {string} + */ + getCollectionType: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } else if(_.isUndefined(share.collection)) { + throw "Share is not a collection"; + } + + return share.collection.item_type; + }, + + /** + * @param {number} shareIndex + * @returns {string} + */ + getCollectionPath: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } else if(_.isUndefined(share.collection)) { + throw "Share is not a collection"; + } + + return share.collection.path; + }, + + /** + * @param {number} shareIndex + * @returns {string} + */ + getCollectionSource: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } else if(_.isUndefined(share.collection)) { + throw "Share is not a collection"; + } + + return share.collection.item_source; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + isCollection: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + if(_.isUndefined(share.collection)) { + return false; + } + return true; + }, + + + /** + * @returns {string} + */ + getReshareOwner: function() { + return this.get('reshare').uid_owner; + }, + + /** + * @returns {string} + */ + getReshareOwnerDisplayname: function() { + return this.get('reshare').displayname_owner; + }, + + /** + * @returns {string} + */ + getReshareWith: function() { + return this.get('reshare').share_with; + }, + + /** + * @returns {number} + */ + getReshareType: function() { + return this.get('reshare').share_type; + }, + + /** + * @param shareIndex + * @returns {string} + */ + getShareWith: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + return share.share_with; + }, + + /** + * @param shareIndex + * @returns {string} + */ + getShareWithDisplayName: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + return share.share_with_displayname; + }, + + getShareType: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + return share.share_type; + }, + + /** + * whether a share from shares has the requested permission + * + * @param {number} shareIndex + * @param {number} permission + * @returns {boolean} + * @private + */ + _shareHasPermission: function(shareIndex, permission) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + if( share.share_type === OC.Share.SHARE_TYPE_REMOTE + && ( permission === OC.PERMISSION_SHARE + || permission === OC.PERMISSION_DELETE)) + { + return false; + } + return (share.permissions & permission) === permission; + }, + + notificationMailWasSent: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + return share.share_mail_send === '1'; + }, + + /** + * Sends an email notification for the given share + * + * @param {int} shareType share type + * @param {string} shareWith recipient + * @param {bool} state whether to set the notification flag or remove it + */ + sendNotificationForShare: function(shareType, shareWith, state) { + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + + return $.post( + OC.generateUrl('core/ajax/share.php'), + { + action: state ? 'informRecipients' : 'informRecipientsDisabled', + recipient: shareWith, + shareType: shareType, + itemSource: itemSource, + itemType: itemType + }, + function(result) { + if (result.status !== 'success') { + // FIXME: a model should not show dialogs + OC.dialogs.alert(t('core', result.data.message), t('core', 'Warning')); + } + } + ); + }, + + /** + * Send the link share information by email + * + * @param {string} recipientEmail recipient email address + */ + sendEmailPrivateLink: function(recipientEmail) { + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + var linkShare = this.get('linkShare'); + + return $.post( + OC.generateUrl('core/ajax/share.php'), { + action: 'email', + toaddress: recipientEmail, + link: linkShare.link, + itemType: itemType, + itemSource: itemSource, + file: this.fileInfoModel.get('name'), + expiration: linkShare.expiration || '' + }, + function(result) { + if (!result || result.status !== 'success') { + // FIXME: a model should not show dialogs + OC.dialogs.alert(result.data.message, t('core', 'Error while sending notification')); + } + }); + }, + + /** + * @returns {boolean} + */ + sharePermissionPossible: function() { + return (this.get('permissions') & OC.PERMISSION_SHARE) === OC.PERMISSION_SHARE; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + hasSharePermission: function(shareIndex) { + return this._shareHasPermission(shareIndex, OC.PERMISSION_SHARE); + }, + + /** + * @returns {boolean} + */ + createPermissionPossible: function() { + return (this.get('permissions') & OC.PERMISSION_CREATE) === OC.PERMISSION_CREATE; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + hasCreatePermission: function(shareIndex) { + return this._shareHasPermission(shareIndex, OC.PERMISSION_CREATE); + }, + + /** + * @returns {boolean} + */ + updatePermissionPossible: function() { + return (this.get('permissions') & OC.PERMISSION_UPDATE) === OC.PERMISSION_UPDATE; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + hasUpdatePermission: function(shareIndex) { + return this._shareHasPermission(shareIndex, OC.PERMISSION_UPDATE); + }, + + /** + * @returns {boolean} + */ + deletePermissionPossible: function() { + return (this.get('permissions') & OC.PERMISSION_DELETE) === OC.PERMISSION_DELETE; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + hasDeletePermission: function(shareIndex) { + return this._shareHasPermission(shareIndex, OC.PERMISSION_DELETE); + }, + + /** + * @returns {boolean} + */ + editPermissionPossible: function() { + return this.createPermissionPossible() + || this.updatePermissionPossible() + || this.deletePermissionPossible(); + }, + + /** + * @returns {boolean} + */ + hasEditPermission: function(shareIndex) { + return this.hasCreatePermission(shareIndex) + || this.hasUpdatePermission(shareIndex) + || this.hasDeletePermission(shareIndex); + }, + + fetch: function() { + var model = this; + OC.Share.loadItem(this.get('itemType'), this.get('itemSource'), function(data) { + model.set(model.parse(data)); + }); + }, + + /** + * Updates OC.Share.itemShares and OC.Share.statuses. + * + * This is required in case the user navigates away and comes back, + * the share statuses from the old arrays are still used to fill in the icons + * in the file list. + */ + _legacyFillCurrentShares: function(shares) { + var fileId = this.fileInfoModel.get('id'); + if (!shares || !shares.length) { + delete OC.Share.statuses[fileId]; + return; + } + + var currentShareStatus = OC.Share.statuses[fileId]; + if (!currentShareStatus) { + currentShareStatus = {link: false}; + OC.Share.statuses[fileId] = currentShareStatus; + } + currentShareStatus.link = false; + + OC.Share.currentShares = {}; + OC.Share.itemShares = []; + _.each(shares, + /** + * @param {OC.Share.Types.ShareInfo} share + */ + function(share) { + if (share.share_type === OC.Share.SHARE_TYPE_LINK) { + OC.Share.itemShares[share.share_type] = true; + currentShareStatus.link = true; + } else { + if (!OC.Share.itemShares[share.share_type]) { + OC.Share.itemShares[share.share_type] = []; + } + OC.Share.itemShares[share.share_type].push(share.share_with); + } + } + ); + }, + + parse: function(data) { + if(data === false) { + console.warn('no data was returned'); + trigger('fetchError'); + return {}; + } + + var permissions = this.get('possiblePermissions'); + if(!_.isUndefined(data.reshare) && !_.isUndefined(data.reshare.permissions) && data.reshare.uid_owner !== OC.currentUser) { + permissions = permissions & data.reshare.permissions; + } + + var allowPublicUploadStatus = false; + if(!_.isUndefined(data.shares)) { + $.each(data.shares, function (key, value) { + if (value.share_type === OC.Share.SHARE_TYPE_LINK) { + allowPublicUploadStatus = (value.permissions & OC.PERMISSION_CREATE) ? true : false; + return true; + } + }); + } + + /** @type {OC.Share.Types.ShareInfo[]} **/ + var shares = _.toArray(data.shares); + this._legacyFillCurrentShares(shares); + + var linkShare = { isLinkShare: false }; + // filter out the share by link + shares = _.reject(shares, + /** + * @param {OC.Share.Types.ShareInfo} share + */ + function(share) { + var isShareLink = + share.share_type === OC.Share.SHARE_TYPE_LINK + && ( share.file_source === this.get('itemSource') + || share.item_source === this.get('itemSource')); + + if (isShareLink) { + var link = window.location.protocol + '//' + window.location.host; + if (!share.token) { + // pre-token link + var fullPath = this.fileInfoModel.get('path') + '/' + + this.fileInfoModel.get('name'); + var location = '/' + OC.currentUser + '/files' + fullPath; + var type = this.fileInfoModel.isDirectory() ? 'folder' : 'file'; + link += OC.linkTo('', 'public.php') + '?service=files&' + + type + '=' + encodeURIComponent(location); + } else { + link += OC.generateUrl('/s/') + share.token; + } + linkShare = { + isLinkShare: true, + token: share.token, + password: share.share_with, + link: link, + permissions: share.permissions, + // currently expiration is only effective for link shares. + expiration: share.expiration, + stime: share.stime + }; + + return share; + } + }, + this + ); + + return { + reshare: data.reshare, + shares: shares, + linkShare: linkShare, + permissions: permissions, + allowPublicUploadStatus: allowPublicUploadStatus + }; + }, + + /** + * Parses a string to an valid integer (unix timestamp) + * @param time + * @returns {*} + * @internal Only used to work around a bug in the backend + */ + _parseTime: function(time) { + if (_.isString(time)) { + // skip empty strings and hex values + if (time === '' || (time.length > 1 && time[0] === '0' && time[1] === 'x')) { + return null; + } + time = parseInt(time, 10); + if(isNaN(time)) { + time = null; + } + } + return time; + } + }); + + OC.Share.ShareItemModel = ShareItemModel; +})(); |