1/* 2 * Copyright (c) 2015 3 * 4 * This file is licensed under the Affero General Public License version 3 5 * or later. 6 * 7 * See the COPYING-README file. 8 * 9 */ 10 11(function() { 12 if(!OC.Share) { 13 OC.Share = {}; 14 OC.Share.Types = {}; 15 } 16 17 /** 18 * @typedef {object} OC.Share.Types.LinkShareInfo 19 * @property {bool} isLinkShare 20 * @property {string} token 21 * @property {string|null} password 22 * @property {string} link 23 * @property {number} permissions 24 * @property {Date} expiration 25 * @property {number} stime share time 26 */ 27 28 /** 29 * @typedef {object} OC.Share.Types.Reshare 30 * @property {string} uid_owner 31 * @property {number} share_type 32 * @property {string} share_with 33 * @property {string} displayname_owner 34 * @property {number} permissions 35 */ 36 37 /** 38 * @typedef {object} OC.Share.Types.ShareInfo 39 * @property {number} share_type 40 * @property {number} permissions 41 * @property {number} file_source optional 42 * @property {number} item_source 43 * @property {string} token 44 * @property {string} share_with 45 * @property {string} share_with_displayname 46 * @property {string} mail_send 47 * @property {Date} expiration optional? 48 * @property {number} stime optional? 49 * @property {string} uid_owner 50 */ 51 52 /** 53 * @typedef {object} OC.Share.Types.ShareItemInfo 54 * @property {OC.Share.Types.Reshare} reshare 55 * @property {OC.Share.Types.ShareInfo[]} shares 56 * @property {OC.Share.Types.LinkShareInfo|undefined} linkShare 57 */ 58 59 /** 60 * These properties are sometimes returned by the server as strings instead 61 * of integers, so we need to convert them accordingly... 62 */ 63 var SHARE_RESPONSE_INT_PROPS = [ 64 'id', 'file_parent', 'mail_send', 'file_source', 'item_source', 'permissions', 65 'storage', 'share_type', 'parent', 'stime' 66 ]; 67 68 /** 69 * @class OCA.Share.ShareItemModel 70 * @classdesc 71 * 72 * Represents the GUI of the share dialogue 73 * 74 * // FIXME: use OC Share API once #17143 is done 75 * 76 * // TODO: this really should be a collection of share item models instead, 77 * where the link share is one of them 78 */ 79 var ShareItemModel = OC.Backbone.Model.extend({ 80 /** 81 * @type share id of the link share, if applicable 82 */ 83 _linkShareId: null, 84 85 _linkSharesCollection: null, 86 87 initialize: function(attributes, options) { 88 if(!_.isUndefined(options.configModel)) { 89 this.configModel = options.configModel; 90 } 91 if(!_.isUndefined(options.fileInfoModel)) { 92 /** @type {OC.Files.FileInfo} **/ 93 this.fileInfoModel = options.fileInfoModel; 94 } 95 96 this._linkSharesCollection = new OC.Share.SharesCollection(); 97 98 _.bindAll(this, 'addShare'); 99 }, 100 101 defaults: { 102 permissions: 0, 103 linkShare: {} 104 }, 105 106 /** 107 * Returns the collection of link shares 108 * 109 * @return {OC.Share.SharesCollection} shares collection 110 */ 111 getLinkSharesCollection: function() { 112 return this._linkSharesCollection; 113 }, 114 115 /** 116 * Returns the file info for the file/folder on which this share information relate to 117 * 118 * @return {OC.Files.FileInfoModel} file info model 119 */ 120 getFileInfo: function() { 121 return this.fileInfoModel; 122 }, 123 124 addShare: function(attributes, options) { 125 var shareType = attributes.shareType; 126 options = options || {}; 127 attributes = _.extend({}, attributes); 128 129 // Default permissions are Edit (CRUD) and Share 130 // Check if these permissions are possible 131 var permissions = OC.PERMISSION_READ; 132 if (this.updatePermissionPossible()) { 133 permissions = permissions | OC.PERMISSION_UPDATE; 134 } 135 if (this.createPermissionPossible()) { 136 permissions = permissions | OC.PERMISSION_CREATE; 137 } 138 if (this.deletePermissionPossible()) { 139 permissions = permissions | OC.PERMISSION_DELETE; 140 } 141 if (this.configModel.get('isResharingAllowed') && (this.sharePermissionPossible())) { 142 permissions = permissions | OC.PERMISSION_SHARE; 143 } 144 145 attributes.permissions = permissions; 146 if (_.isUndefined(attributes.path)) { 147 attributes.path = this.fileInfoModel.getFullPath(); 148 } 149 150 var self = this; 151 return $.ajax({ 152 type: 'POST', 153 url: this._getUrl('shares'), 154 data: attributes, 155 dataType: 'json' 156 }).done(function() { 157 self.fetch().done(function() { 158 if (_.isFunction(options.success)) { 159 options.success(self); 160 } 161 }); 162 }).fail(function(xhr) { 163 var msg = t('core', 'Error'); 164 var result = xhr.responseJSON; 165 if (result && result.ocs && result.ocs.meta) { 166 msg = result.ocs.meta.message; 167 } 168 169 if (_.isFunction(options.error)) { 170 options.error(self, msg); 171 } else { 172 OC.dialogs.alert(msg, t('core', 'Error while sharing')); 173 } 174 }); 175 }, 176 177 updateShare: function(shareId, attrs, options) { 178 var self = this; 179 options = options || {}; 180 return $.ajax({ 181 type: 'PUT', 182 url: this._getUrl('shares/' + encodeURIComponent(shareId)), 183 data: attrs, 184 dataType: 'json' 185 }).done(function() { 186 self.fetch({ 187 success: function() { 188 if (_.isFunction(options.success)) { 189 options.success(self); 190 } 191 } 192 }); 193 }).fail(function(xhr) { 194 var msg = t('core', 'Error'); 195 var result = xhr.responseJSON; 196 if (result.ocs && result.ocs.meta) { 197 msg = result.ocs.meta.message; 198 } 199 200 if (_.isFunction(options.error)) { 201 options.error(self, msg); 202 } else { 203 OC.dialogs.alert(msg, t('core', 'Error while sharing')); 204 } 205 }); 206 }, 207 208 /** 209 * Deletes the share with the given id 210 * 211 * @param {int} shareId share id 212 * @return {jQuery} 213 */ 214 removeShare: function(shareId, options) { 215 var self = this; 216 options = options || {}; 217 return $.ajax({ 218 type: 'DELETE', 219 url: this._getUrl('shares/' + encodeURIComponent(shareId)), 220 }).done(function() { 221 self.fetch({ 222 success: function() { 223 if (_.isFunction(options.success)) { 224 options.success(self); 225 } 226 } 227 }); 228 }).fail(function(xhr) { 229 var msg = t('core', 'Error'); 230 var result = xhr.responseJSON; 231 if (result.ocs && result.ocs.meta) { 232 msg = result.ocs.meta.message; 233 } 234 235 if (_.isFunction(options.error)) { 236 options.error(self, msg); 237 } else { 238 OC.dialogs.alert(msg, t('core', 'Error removing share')); 239 } 240 }); 241 }, 242 243 /** 244 * @returns {boolean} 245 */ 246 isFolder: function() { 247 return this.get('itemType') === 'folder'; 248 }, 249 250 /** 251 * @returns {boolean} 252 */ 253 isFile: function() { 254 return this.get('itemType') === 'file'; 255 }, 256 257 /** 258 * whether this item has reshare information 259 * @returns {boolean} 260 */ 261 hasReshare: function() { 262 var reshare = this.get('reshare'); 263 return _.isObject(reshare) && !_.isUndefined(reshare.uid_owner); 264 }, 265 266 /** 267 * whether this item has user share information 268 * @returns {boolean} 269 */ 270 hasUserShares: function() {
271 return this.getSharesWithCurrentItem().length > 0; 272 }, 273 274 /** 275 * Returns whether this item has a link share 276 * 277 * @return {bool} true if a link share exists, false otherwise 278 */ 279 hasLinkShare: function() { 280 return this._linkSharesCollection.length > 0; 281 }, 282 283 /** 284 * @returns {string} 285 */ 286 getReshareOwner: function() { 287 return this.get('reshare').uid_owner; 288 }, 289 290 /** 291 * @returns {string} 292 */ 293 getReshareOwnerDisplayname: function() { 294 return this.get('reshare').displayname_owner; 295 }, 296 297 /** 298 * @returns {string} 299 */ 300 getReshareWith: function() { 301 return this.get('reshare').share_with; 302 }, 303 304 /** 305 * @returns {string} 306 */ 307 getReshareWithDisplayName: function() { 308 var reshare = this.get('reshare'); 309 return reshare.share_with_displayname || reshare.share_with; 310 }, 311 312 /** 313 * @returns {number} 314 */ 315 getReshareType: function() { 316 return this.get('reshare').share_type; 317 }, 318 319 /** 320 * Returns all share entries that only apply to the current item 321 * (file/folder) 322 * 323 * @return {Array.<OC.Share.Types.ShareInfo>} 324 */ 325 getSharesWithCurrentItem: function() { 326 var shares = this.get('shares') || []; 327 var fileId = this.fileInfoModel.get('id'); 328 return _.filter(shares, function(share) { 329 return share.item_source === fileId; 330 }); 331 }, 332 333 /** 334 * @param shareIndex 335 * @returns {string} 336 */ 337 getShareWith: function(shareIndex) { 338 /** @type OC.Share.Types.ShareInfo **/ 339 var share = this.get('shares')[shareIndex]; 340 if(!_.isObject(share)) { 341 throw "Unknown Share"; 342 } 343 return share.share_with; 344 }, 345 346 /** 347 * @param shareIndex 348 * @returns {string} 349 */ 350 getShareWithDisplayName: function(shareIndex) { 351 /** @type OC.Share.Types.ShareInfo **/ 352 var share = this.get('shares')[shareIndex]; 353 if(!_.isObject(share)) { 354 throw "Unknown Share"; 355 } 356 return share.share_with_displayname; 357 }, 358 359 getShareType: function(shareIndex) { 360 /** @type OC.Share.Types.ShareInfo **/ 361 var share = this.get('shares')[shareIndex]; 362 if(!_.isObject(share)) { 363 throw "Unknown Share"; 364 } 365 return share.share_type; 366 }, 367 368 /** 369 * whether a share from shares has the requested permission 370 * 371 * @param {number} shareIndex 372 * @param {number} permission 373 * @returns {boolean} 374 * @private 375 */ 376 _shareHasPermission: function(shareIndex, permission) { 377 /** @type OC.Share.Types.ShareInfo **/ 378 var share = this.get('shares')[shareIndex]; 379 if(!_.isObject(share)) { 380 throw "Unknown Share"; 381 } 382 return (share.permissions & permission) === permission; 383 }, 384 385 notificationMailWasSent: function(shareIndex) { 386 /** @type OC.Share.Types.ShareInfo **/ 387 var share = this.get('shares')[shareIndex]; 388 if(!_.isObject(share)) { 389 throw "Unknown Share"; 390 } 391 return share.mail_send === 1; 392 }, 393 394 /** 395 * Sends an email notification for the given share 396 * 397 * @param {int} shareType share type 398 * @param {string} shareWith recipient 399 * @param {bool} state whether to set the notification flag or remove it 400 */ 401 sendNotificationForShare: function(shareType, shareWith, state) { 402 var itemType = this.get('itemType'); 403 var itemSource = this.get('itemSource'); 404 405 return $.post( 406 OC.generateUrl('core/ajax/share.php'), 407 { 408 action: state ? 'informRecipients' : 'informRecipientsDisabled', 409 recipient: shareWith, 410 shareType: shareType, 411 itemSource: itemSource, 412 itemType: itemType 413 }, 414 function(result) { 415 if (result.status !== 'success') { 416 // FIXME: a model should not show dialogs 417 OC.dialogs.alert(t('core', result.data.message), t('core', 'Warning')); 418 } 419 } 420 ); 421 }, 422 423 /** 424 * @returns {boolean} 425 */ 426 sharePermissionPossible: function() { 427 return (this.get('permissions') & OC.PERMISSION_SHARE) === OC.PERMISSION_SHARE; 428 }, 429 430 /** 431 * @param {number} shareIndex 432 * @returns {boolean} 433 */ 434 hasSharePermission: function(shareIndex) { 435 return this._shareHasPermission(shareIndex, OC.PERMISSION_SHARE); 436 }, 437 438 /** 439 * @returns {boolean} 440 */ 441 createPermissionPossible: function() { 442 return (this.get('permissions') & OC.PERMISSION_CREATE) === OC.PERMISSION_CREATE; 443 }, 444 445 /** 446 * @param {number} shareIndex 447 * @returns {boolean} 448 */ 449 hasCreatePermission: function(shareIndex) { 450 return this._shareHasPermission(shareIndex, OC.PERMISSION_CREATE); 451 }, 452 453 /** 454 * @returns {boolean} 455 */ 456 updatePermissionPossible: function() { 457 return (this.get('permissions') & OC.PERMISSION_UPDATE) === OC.PERMISSION_UPDATE; 458 }, 459 460 /** 461 * @param {number} shareIndex 462 * @returns {boolean} 463 */ 464 hasUpdatePermission: function(shareIndex) { 465 return this._shareHasPermission(shareIndex, OC.PERMISSION_UPDATE); 466 }, 467 468 /** 469 * @returns {boolean} 470 */ 471 deletePermissionPossible: function() {
472 return (this.get('permissions') & OC.PERMISSION_DELETE) === OC.PERMISSION_DELETE; 473 }, 474 475 /** 476 * @param {number} shareIndex 477 * @returns {boolean} 478 */ 479 hasDeletePermission: function(shareIndex) { 480 return this._shareHasPermission(shareIndex, OC.PERMISSION_DELETE); 481 }, 482 483 /** 484 * @returns {boolean} 485 */ 486 editPermissionPossible: function() { 487 return this.createPermissionPossible() 488 || this.updatePermissionPossible() 489 || this.deletePermissionPossible(); 490 }, 491 492 /** 493 * @returns {boolean} 494 */ 495 hasEditPermission: function(shareIndex) { 496 return this.hasCreatePermission(shareIndex) 497 || this.hasUpdatePermission(shareIndex) 498 || this.hasDeletePermission(shareIndex); 499 }, 500 501 _getUrl: function(base, params) { 502 params = _.extend({format: 'json'}, params || {}); 503 return OC.linkToOCS('apps/files_sharing/api/v1', 2) + base + '?' + OC.buildQueryString(params); 504 }, 505 506 _fetchShares: function() { 507 var path = this.fileInfoModel.getFullPath(); 508 return $.ajax({ 509 type: 'GET', 510 url: this._getUrl('shares', {path: path, reshares: true}) 511 }); 512 }, 513 514 _fetchReshare: function() { 515 // only fetch original share once 516 if (!this._reshareFetched) { 517 var path = this.fileInfoModel.getFullPath(); 518 this._reshareFetched = true; 519 return $.ajax({ 520 type: 'GET', 521 url: this._getUrl('shares', {path: path, shared_with_me: true}) 522 }); 523 } else { 524 return $.Deferred().resolve([{ 525 ocs: { 526 data: [this.get('reshare')] 527 } 528 }]); 529 } 530 }, 531 532 /** 533 * Group reshares into a single super share element. 534 * Does this by finding the most precise share and 535 * combines the permissions to be the most permissive. 536 * 537 * @param {Array} reshares 538 * @return {Object} reshare 539 */ 540 _groupReshares: function(reshares) { 541 if (!reshares || !reshares.length) { 542 return false; 543 } 544 545 var superShare = reshares.shift(); 546 var combinedPermissions = superShare.permissions; 547 _.each(reshares, function(reshare) { 548 // use share have higher priority than group share 549 if (reshare.share_type === OC.Share.SHARE_TYPE_USER && superShare.share_type === OC.Share.SHARE_TYPE_GROUP) { 550 superShare = reshare; 551 } 552 combinedPermissions |= reshare.permissions; 553 }); 554 555 superShare.permissions = combinedPermissions; 556 return superShare; 557 }, 558 559 fetch: function() { 560 var model = this; 561 this.trigger('request', this); 562 563 var deferred = $.when( 564 this._fetchShares(), 565 this._fetchReshare() 566 ); 567 deferred.done(function(data1, data2) { 568 model.trigger('sync', 'GET', this); 569 var sharesMap = {}; 570 _.each(data1[0].ocs.data, function(shareItem) { 571 sharesMap[shareItem.id] = shareItem; 572 }); 573 574 var reshare = false; 575 if (data2[0].ocs.data.length) { 576 reshare = model._groupReshares(data2[0].ocs.data); 577 } 578 579 model.set(model.parse({ 580 shares: sharesMap, 581 reshare: reshare 582 })); 583 }); 584 585 return deferred; 586 }, 587 588 /** 589 * Updates OC.Share.itemShares and OC.Share.statuses. 590 * 591 * This is required in case the user navigates away and comes back, 592 * the share statuses from the old arrays are still used to fill in the icons 593 * in the file list. 594 */ 595 _legacyFillCurrentShares: function(shares) { 596 var fileId = this.fileInfoModel.get('id'); 597 if (!shares || !shares.length) { 598 delete OC.Share.statuses[fileId]; 599 OC.Share.currentShares = {}; 600 OC.Share.itemShares = []; 601 return; 602 } 603 604 var currentShareStatus = OC.Share.statuses[fileId]; 605 if (!currentShareStatus) { 606 currentShareStatus = {link: false}; 607 OC.Share.statuses[fileId] = currentShareStatus; 608 } 609 currentShareStatus.link = false; 610 611 OC.Share.currentShares = {}; 612 OC.Share.itemShares = []; 613 _.each(shares, 614 /** 615 * @param {OC.Share.Types.ShareInfo} share 616 */ 617 function(share) { 618 if (share.share_type === OC.Share.SHARE_TYPE_LINK) { 619 OC.Share.itemShares[share.share_type] = true; 620 currentShareStatus.link = true; 621 } else { 622 if (!OC.Share.itemShares[share.share_type]) { 623 OC.Share.itemShares[share.share_type] = []; 624 }
625 OC.Share.itemShares[share.share_type].push(share.share_with); 626 } 627 } 628 ); 629 }, 630 631 parse: function(data) { 632 if(data === false) { 633 console.warn('no data was returned'); 634 this.trigger('fetchError'); 635 return {}; 636 } 637 638 var permissions = this.get('possiblePermissions'); 639 if(!_.isUndefined(data.reshare) && !_.isUndefined(data.reshare.permissions) && data.reshare.uid_owner !== OC.currentUser) { 640 permissions = permissions & data.reshare.permissions; 641 } 642 643 /** @type {OC.Share.Types.ShareInfo[]} **/ 644 var shares = _.map(data.shares, function(share) { 645 // properly parse some values because sometimes the server 646 // returns integers as string... 647 var i; 648 for (i = 0; i < SHARE_RESPONSE_INT_PROPS.length; i++) { 649 var prop = SHARE_RESPONSE_INT_PROPS[i]; 650 if (!_.isUndefined(share[prop])) { 651 share[prop] = parseInt(share[prop], 10); 652 } 653 } 654 return share; 655 }); 656 657 this._legacyFillCurrentShares(shares); 658 659 var linkShares = []; 660 // filter out the share by link 661 shares = _.reject(shares, 662 /** 663 * @param {OC.Share.Types.ShareInfo} share 664 */ 665 function(share) { 666 var isShareLink = 667 share.share_type === OC.Share.SHARE_TYPE_LINK 668 && ( share.file_source === this.get('itemSource') 669 || share.item_source === this.get('itemSource')); 670 671 if (isShareLink) { 672 /* 673 * Ignore reshared link shares for now 674 * FIXME: Find a way to display properly 675 */ 676 if (share.uid_owner !== OC.currentUser) { 677 return share; 678 } 679 680 linkShares.push(share); 681 return share; 682 } 683 }, 684 this 685 ); 686 687 // populate link shares collection with found link shares 688 this._linkSharesCollection.set(linkShares, {parse: true}); 689 690 // use the old crappy way for other shares for now 691 return { 692 reshare: data.reshare, 693 shares: shares, 694 permissions: permissions 695 }; 696 }, 697 698 /** 699 * Parses a string to an valid integer (unix timestamp) 700 * @param time 701 * @returns {*} 702 * @internal Only used to work around a bug in the backend 703 */ 704 _parseTime: function(time) { 705 if (_.isString(time)) { 706 // skip empty strings and hex values 707 if (time === '' || (time.length > 1 && time[0] === '0' && time[1] === 'x')) { 708 return null; 709 } 710 time = parseInt(time, 10); 711 if(isNaN(time)) { 712 time = null; 713 } 714 } 715 return time; 716 }, 717 718 /** 719 * Returns a list of share types from the existing shares. 720 * 721 * @return {Array.<int>} array of share types 722 */ 723 getShareTypes: function() { 724 var result; 725 result = _.pluck(this.getSharesWithCurrentItem(), 'share_type'); 726 if (this.hasLinkShare()) { 727 result.push(OC.Share.SHARE_TYPE_LINK); 728 } 729 return _.uniq(result); 730 } 731 }); 732 733 OC.Share.ShareItemModel = ShareItemModel; 734})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.