/* eslint-disable */ /* * 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 {string} token * @property {bool} hideDownload * @property {string|null} password * @property {bool} sendPasswordByTalk * @property {number} permissions * @property {Date} expiration * @property {number} stime share time */ /** * @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_with_avatar * @property {string} mail_send * @property {Date} expiration optional? * @property {number} stime optional? * @property {string} uid_owner * @property {string} displayname_owner */ /** * @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 */ /** * These properties are sometimes returned by the server as strings instead * of integers, so we need to convert them accordingly... */ var SHARE_RESPONSE_INT_PROPS = [ 'id', 'file_parent', 'mail_send', 'file_source', 'item_source', 'permissions', 'storage', 'share_type', 'parent', 'stime' ] /** * @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({ /** * share id of the link share, if applicable */ _linkShareId: null, 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, linkShares: [] }, /** * Saves the current link share information. * * This will trigger an ajax call and, if successful, refetch the model * afterwards. Callbacks "success", "error" and "complete" can be given * in the options object; "success" is called after a successful save * once the model is refetch, "error" is called after a failed save, and * "complete" is called both after a successful save and after a failed * save. Note that "complete" is called before "success" and "error" are * called (unlike in jQuery, in which it is called after them); this * ensures that "complete" is called even if refetching the model fails. * * TODO: this should be a separate model */ saveLinkShare: function(attributes, options) { options = options || {} attributes = _.extend({}, attributes) var shareId = null var call // oh yeah... if (attributes.expiration) { attributes.expireDate = attributes.expiration delete attributes.expiration } var linkShares = this.get('linkShares') var shareIndex = _.findIndex(linkShares, function(share) { return share.id === attributes.cid }) if (linkShares.length > 0 && shareIndex !== -1) { shareId = linkShares[shareIndex].id // note: update can only update a single value at a time call = this.updateShare(shareId, attributes, options) } else { attributes = _.defaults(attributes, { hideDownload: false, password: '', passwordChanged: false, sendPasswordByTalk: false, permissions: OC.PERMISSION_READ, expireDate: this.configModel.getDefaultExpirationDateString(), shareType: OC.Share.SHARE_TYPE_LINK }) call = this.addShare(attributes, options) } return call }, addShare: function(attributes, options) { var shareType = attributes.shareType attributes = _.extend({}, attributes) // get default permissions var defaultPermissions = OC.getCapabilities()['files_sharing']['default_permissions'] || OC.PERMISSION_ALL var possiblePermissions = OC.PERMISSION_READ if (this.updatePermissionPossible()) { possiblePermissions = possiblePermissions | OC.PERMISSION_UPDATE } if (this.createPermissionPossible()) { possiblePermissions = possiblePermissions | OC.PERMISSION_CREATE } if (this.deletePermissionPossible()) { possiblePermissions = possiblePermissions | OC.PERMISSION_DELETE } if (this.configModel.get('isResharingAllowed') && (this.sharePermissionPossible())) { possiblePermissions = possiblePermissions | OC.PERMISSION_SHARE } attributes.permissions = defaultPermissions & possiblePermissions if (_.isUndefined(attributes.path)) { attributes.path = this.fileInfoModel.getFullPath() } return this._addOrUpdateShare({ type: 'POST', url: this._getUrl('shares'), data: attributes, dataType: 'json' }, options) }, updateShare: function(shareId, attrs, options) { return this._addOrUpdateShare({ type: 'PUT', url: this._getUrl('shares/' + encodeURIComponent(shareId)), data: attrs, dataType: 'json' }, options) }, _addOrUpdateShare: function(ajaxSettings, options) { var self = this options = options || {} return $.ajax( ajaxSettings ).always(function() { if (_.isFunction(options.complete)) { options.complete(self) } }).done(function() { self.fetch().done(function() { if (_.isFunction(options.success)) { options.success(self) } }) }).fail(function(xhr) { var msg = t('core', 'Error') var result = xhr.responseJSON if (result && result.ocs && result.ocs.meta) { msg = result.ocs.meta.message } if (_.isFunction(options.error)) { options.error(self, msg) } else { OC.dialogs.alert(msg, t('core', 'Error while sharing')) } }) }, /** * Deletes the share with the given id * * @param {int} shareId share id * @returns {jQuery} */ removeShare: function(shareId, options) { var self = this options = options || {} return $.ajax({ type: 'DELETE', url: this._getUrl('shares/' + encodeURIComponent(shareId)) }).done(function() { self.fetch({ success: function() { if (_.isFunction(options.success)) { options.success(self) } } }) }).fail(function(xhr) { var msg = t('core', 'Error') var result = xhr.responseJSON if (result.ocs && result.ocs.meta) { msg = result.ocs.meta.message } if (_.isFunction(options.error)) { options.error(self, msg) } else { OC.dialogs.alert(msg, t('core', 'Error removing share')) } }) }, /** * @returns {boolean} */ isPublicUploadAllowed: function() { return this.get('allowPublicUploadStatus') }, isPublicEditingAllowed: function() { return this.get('allowPublicEditingStatus') }, /** * @returns {boolean} */ isHideFileListSet: function() { return this.get('hideFileListStatus') }, /** * @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() { return this.getSharesWithCurrentItem().length > 0 }, /** * Returns whether this item has link shares * * @returns {bool} true if a link share exists, false otherwise */ hasLinkShares: function() { var linkShares = this.get('linkShares') if (linkShares && linkShares.length > 0) { return true } return false }, /** * @returns {string} */ getReshareOwner: function() { return this.get('reshare').uid_owner }, /** * @returns {string} */ getReshareOwnerDisplayname: function() { return this.get('reshare').displayname_owner }, /** * @returns {string} */ getReshareNote: function() { return this.get('reshare').note }, /** * @returns {string} */ getReshareWith: function() { return this.get('reshare').share_with }, /** * @returns {string} */ getReshareWithDisplayName: function() { var reshare = this.get('reshare') return reshare.share_with_displayname || reshare.share_with }, /** * @returns {number} */ getReshareType: function() { return this.get('reshare').share_type }, getExpireDate: function(shareIndex) { return this._shareExpireDate(shareIndex) }, getNote: function(shareIndex) { return this._shareNote(shareIndex) }, /** * Returns all share entries that only apply to the current item * (file/folder) * * @returns {Array.} */ getSharesWithCurrentItem: function() { var shares = this.get('shares') || [] var fileId = this.fileInfoModel.get('id') return _.filter(shares, function(share) { return share.item_source === fileId }) }, /** * @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 }, /** * @param shareIndex * @returns {string} */ getShareWithAvatar: function(shareIndex) { /** @type OC.Share.Types.ShareInfo **/ var share = this.get('shares')[shareIndex] if (!_.isObject(share)) { throw 'Unknown Share' } return share.share_with_avatar }, /** * @param shareIndex * @returns {string} */ getSharedBy: function(shareIndex) { /** @type OC.Share.Types.ShareInfo **/ var share = this.get('shares')[shareIndex] if (!_.isObject(share)) { throw 'Unknown Share' } return share.uid_owner }, /** * @param shareIndex * @returns {string} */ getSharedByDisplayName: function(shareIndex) { /** @type OC.Share.Types.ShareInfo **/ var share = this.get('shares')[shareIndex] if (!_.isObject(share)) { throw 'Unknown Share' } return share.displayname_owner }, /** * @param shareIndex * @returns {string} */ getFileOwnerUid: function(shareIndex) { /** @type OC.Share.Types.ShareInfo **/ var share = this.get('shares')[shareIndex] if (!_.isObject(share)) { throw 'Unknown Share' } return share.uid_file_owner }, /** * returns the array index of a sharee for a provided shareId * * @param shareId * @returns {number} */ findShareWithIndex: function(shareId) { var shares = this.get('shares') if (!_.isArray(shares)) { throw 'Unknown Share' } for (var i = 0; i < shares.length; i++) { var shareWith = shares[i] if (shareWith.id === shareId) { return i } } throw 'Unknown Sharee' }, 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' } return (share.permissions & permission) === permission }, _shareExpireDate: function(shareIndex) { var share = this.get('shares')[shareIndex] if (!_.isObject(share)) { throw 'Unknown Share' } var date2 = share.expiration return date2 }, _shareNote: function(shareIndex) { var share = this.get('shares')[shareIndex] if (!_.isObject(share)) { throw 'Unknown Share' } return share.note }, /** * @returns {int} */ getPermissions: function() { return this.get('permissions') }, /** * @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) }, hasReadPermission: function(shareIndex) { return this._shareHasPermission(shareIndex, OC.PERMISSION_READ) }, /** * @returns {boolean} */ editPermissionPossible: function() { return this.createPermissionPossible() || this.updatePermissionPossible() || this.deletePermissionPossible() }, /** * @returns {string} * The state that the 'can edit' permission checkbox should have. * Possible values: * - empty string: no permission * - 'checked': all applicable permissions * - 'indeterminate': some but not all permissions */ editPermissionState: function(shareIndex) { var hcp = this.hasCreatePermission(shareIndex) var hup = this.hasUpdatePermission(shareIndex) var hdp = this.hasDeletePermission(shareIndex) if (this.isFile()) { if (hcp || hup || hdp) { return 'checked' } return '' } if (!hcp && !hup && !hdp) { return '' } if ((this.createPermissionPossible() && !hcp) || (this.updatePermissionPossible() && !hup) || (this.deletePermissionPossible() && !hdp)) { return 'indeterminate' } return 'checked' }, /** * @returns {int} */ linkSharePermissions: function(shareId) { var linkShares = this.get('linkShares') var shareIndex = _.findIndex(linkShares, function(share) { return share.id === shareId }) if (!this.hasLinkShares()) { return -1 } else if (linkShares.length > 0 && shareIndex !== -1) { return linkShares[shareIndex].permissions } return -1 }, _getUrl: function(base, params) { params = _.extend({ format: 'json' }, params || {}) return OC.linkToOCS('apps/files_sharing/api/v1', 2) + base + '?' + OC.buildQueryString(params) }, _fetchShares: function() { var path = this.fileInfoModel.getFullPath() return $.ajax({ type: 'GET', url: this._getUrl('shares', { path: path, reshares: true }) }) }, _fetchReshare: function() { // only fetch original share once if (!this._reshareFetched) { var path = this.fileInfoModel.getFullPath() this._reshareFetched = true return $.ajax({ type: 'GET', url: this._getUrl('shares', { path: path, shared_with_me: true }) }) } else { return $.Deferred().resolve([{ ocs: { data: [this.get('reshare')] } }]) } }, /** * Group reshares into a single super share element. * Does this by finding the most precise share and * combines the permissions to be the most permissive. * * @param {Array} reshares * @returns {Object} reshare */ _groupReshares: function(reshares) { if (!reshares || !reshares.length) { return false } var superShare = reshares.shift() var combinedPermissions = superShare.permissions _.each(reshares, function(reshare) { // use share have higher priority than group share if (reshare.share_type === OC.Share.SHARE_TYPE_USER && superShare.share_type === OC.Share.SHARE_TYPE_GROUP) { superShare = reshare } combinedPermissions |= reshare.permissions }) superShare.permissions = combinedPermissions return superShare }, fetch: function(options) { var model = this this.trigger('request', this) var deferred = $.when( this._fetchShares(), this._fetchReshare() ) deferred.done(function(data1, data2) { model.trigger('sync', 'GET', this) var sharesMap = {} _.each(data1[0].ocs.data, function(shareItem) { sharesMap[shareItem.id] = shareItem }) var reshare = false if (data2[0].ocs.data.length) { reshare = model._groupReshares(data2[0].ocs.data) } model.set(model.parse({ shares: sharesMap, reshare: reshare })) if (!_.isUndefined(options) && _.isFunction(options.success)) { options.success() } }) return deferred }, /** * 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] OC.Share.currentShares = {} OC.Share.itemShares = [] 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') this.trigger('fetchError') return {} } var permissions = this.fileInfoModel.get('permissions') 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)) return true } }) } var allowPublicEditingStatus = true if (!_.isUndefined(data.shares)) { $.each(data.shares, function(key, value) { if (value.share_type === OC.Share.SHARE_TYPE_LINK) { allowPublicEditingStatus = !!((value.permissions & OC.PERMISSION_UPDATE)) return true } }) } var hideFileListStatus = false if (!_.isUndefined(data.shares)) { $.each(data.shares, function(key, value) { if (value.share_type === OC.Share.SHARE_TYPE_LINK) { hideFileListStatus = !((value.permissions & OC.PERMISSION_READ)) return true } }) } /** @type {OC.Share.Types.ShareInfo[]} **/ var shares = _.map(data.shares, function(share) { // properly parse some values because sometimes the server // returns integers as string... var i for (i = 0; i < SHARE_RESPONSE_INT_PROPS.length; i++) { var prop = SHARE_RESPONSE_INT_PROPS[i] if (!_.isUndefined(share[prop])) { share[prop] = parseInt(share[prop], 10) } } return share }) this._legacyFillCurrentShares(shares) var linkShares = [] // 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) { /** * Ignore reshared link shares for now * FIXME: Find a way to display properly */ if (share.uid_owner !== OC.currentUser) { return } 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 } linkShares.push(_.extend({}, share, { // hide_download is returned as an int, so force it // to a boolean hideDownload: !!share.hide_download, password: share.share_with, sendPasswordByTalk: share.send_password_by_talk })) return share } }, this ) return { reshare: data.reshare, shares: shares, linkShares: linkShares, permissions: permissions, allowPublicUploadStatus: allowPublicUploadStatus, allowPublicEditingStatus: allowPublicEditingStatus, hideFileListStatus: hideFileListStatus } }, /** * 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 }, /** * Returns a list of share types from the existing shares. * * @returns {Array.} array of share types */ getShareTypes: function() { var result result = _.pluck(this.getSharesWithCurrentItem(), 'share_type') if (this.hasLinkShares()) { result.push(OC.Share.SHARE_TYPE_LINK) } return _.uniq(result) } }) OC.Share.ShareItemModel = ShareItemModel })()