1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates a preferences page for displaying shares. 26 * @constructor 27 * @class 28 * This class contains a {@link ZmSharingView}, which shows shares in two list views. 29 * 30 * @author Conrad Damon 31 * 32 * @param {DwtControl} parent the containing widget 33 * @param {Object} section the page 34 * @param {ZmPrefController} controller the prefs controller 35 * 36 * @extends ZmPreferencesPage 37 * 38 * @private 39 */ 40 ZmSharingPage = function(parent, section, controller) { 41 ZmPreferencesPage.apply(this, arguments); 42 }; 43 44 ZmSharingPage.prototype = new ZmPreferencesPage; 45 ZmSharingPage.prototype.constructor = ZmSharingPage; 46 47 ZmSharingPage.prototype.isZmSharingPage = true; 48 ZmSharingPage.prototype.toString = function () { return "ZmSharingPage"; }; 49 50 ZmSharingPage.prototype.getShares = 51 function(type, owner, callback) { 52 53 var jsonObj = {GetShareInfoRequest:{_jsns:"urn:zimbraAccount"}}; 54 var request = jsonObj.GetShareInfoRequest; 55 if (type && type != ZmShare.TYPE_ALL) { 56 request.grantee = {type:type}; 57 } 58 if (owner) { 59 request.owner = {by:"name", _content:owner}; 60 } 61 var respCallback = new AjxCallback(this, this._handleGetSharesResponse, [callback]); 62 appCtxt.getAppController().sendRequest({jsonObj: jsonObj, 63 asyncMode: true, 64 callback: respCallback}); 65 }; 66 67 ZmSharingPage.prototype._handleGetSharesResponse = 68 function(callback, result) { 69 70 var resp = result.getResponse().GetShareInfoResponse; 71 if (callback) { 72 callback.run(resp.share); 73 } 74 }; 75 76 ZmSharingPage.prototype._createControls = 77 function() { 78 ZmPreferencesPage.prototype._createControls.call(this); 79 80 this.view = new ZmSharingView({parent:this, pageId:this._htmlElId}); 81 this.view.showMounts(); 82 this.view.findShares(); 83 this.view.showGrants(); 84 85 if (appCtxt.multiAccounts && this._acAddrSelectList) { 86 this._acAddrSelectList.setActiveAccount(appCtxt.getActiveAccount()); 87 } 88 }; 89 90 ZmSharingPage.prototype.hasResetButton = 91 function() { 92 return false; 93 }; 94 95 96 /** 97 * Creates a sharing view. 98 * @constructor 99 * @class 100 * <p>Manages a view composed of two sections. The first section is for showing information about 101 * folders shared with the user. The user can look for shares that came via their membership in 102 * a distribution list, or shares directly from a particular user. The shares are displayed in two 103 * lists: one for shares that have not been accepted, and one for shares that have been accepted, 104 * which have mountpoints.</p> 105 * <p> 106 * Internally, shares are standardized into ZmShare objects, with a few additional fields. Shares 107 * are converted into those from several different forms: share info JSON from GetShareInfoResponse, 108 * ZmShare's on folders that have been shared by the user, and folders that have been mounted by the 109 * user. 110 * 111 * @param {Hash} params a hash of parameters 112 * @param {ZmSharingPage} params.parent the owning prefs page 113 * @param {String} params.pageId the ID of prefs page's HTML element 114 * 115 * @extends DwtComposite 116 * 117 * @private 118 */ 119 ZmSharingView = function(params) { 120 121 DwtComposite.apply(this, arguments); 122 123 this._pageId = params.pageId; 124 this._shareByKey = {}; 125 this._shareByDomId = {}; 126 127 this._initialize(); 128 ZmFolderTree.createAllDeferredFolders(); 129 }; 130 131 ZmSharingView.prototype = new DwtComposite; 132 ZmSharingView.prototype.constructor = ZmSharingView; 133 134 ZmSharingView.ID_RADIO = "radio"; 135 ZmSharingView.ID_GROUP = "group"; 136 ZmSharingView.ID_USER = "user"; 137 ZmSharingView.ID_OWNER = "owner"; 138 ZmSharingView.ID_FIND_BUTTON = "findButton"; 139 ZmSharingView.ID_FOLDER_TYPE = "folderType"; 140 ZmSharingView.ID_SHARE_BUTTON = "shareButton"; 141 142 ZmSharingView.PENDING = "PENDING"; 143 ZmSharingView.MOUNTED = "MOUNTED"; 144 145 ZmSharingView.F_ACTIONS = "ac"; 146 ZmSharingView.F_FOLDER = "fo"; 147 ZmSharingView.F_ITEM = "it"; 148 ZmSharingView.F_OWNER = "ow"; 149 ZmSharingView.F_ROLE = "ro"; 150 ZmSharingView.F_TYPE = "ty"; 151 ZmSharingView.F_WITH = "wi"; 152 153 ZmSharingView.prototype.toString = function() { return "ZmSharingView"; }; 154 155 /** 156 * Makes a request to the server for group shares or shares from a particular user. 157 * 158 * @param owner [string]* address of account to check for shares from 159 * @param userButtonClicked [boolean]* if true, user pressed "Find Shares" button 160 * 161 * @private 162 */ 163 ZmSharingView.prototype.findShares = 164 function(owner, userButtonClicked) { 165 166 var errorMsg; 167 // check if button was actually clicked, since missing owner is fine when form 168 // goes through rote validation on display 169 if (userButtonClicked && !owner) { 170 errorMsg = ZmMsg.sharingErrorOwnerMissing; 171 } else if (!this._shareForm.validate(ZmSharingView.ID_OWNER)) { 172 errorMsg = ZmMsg.sharingErrorOwnerSelf; 173 } 174 if (errorMsg) { 175 appCtxt.setStatusMsg({msg: errorMsg, level: ZmStatusView.LEVEL_INFO}); 176 return; 177 } 178 179 var respCallback = new AjxCallback(this, this.showPendingShares); 180 var type = owner ? null : ZmShare.TYPE_GROUP; 181 this._curOwner = owner; 182 var shares = this.parent.getShares(type, owner, respCallback); 183 }; 184 /** 185 * Displays a list of shares that have been accepted/mounted by the user. 186 * 187 * @private 188 */ 189 ZmSharingView.prototype.showMounts = 190 function() { 191 192 var folderTree = appCtxt.getFolderTree(); 193 var folders = folderTree && folderTree.asList({remoteOnly:true}); 194 if (!folders) { return; } 195 196 var ownerHash = {}; 197 for (var i = 0; i < folders.length; i++) { 198 var folder = folders[i]; 199 if (folder.isMountpoint || folder.link) { 200 if (folder.owner) { 201 ownerHash[folder.owner] = true; 202 } 203 } 204 } 205 206 var owners = AjxUtil.keys(ownerHash); 207 if (owners.length > 0) { 208 var jsonObj = {BatchRequest:{_jsns:"urn:zimbra", onerror:"continue"}}; 209 var br = jsonObj.BatchRequest; 210 var requests = br.GetShareInfoRequest = []; 211 for (var i = 0; i < owners.length; i++) { 212 var req = {_jsns: "urn:zimbraAccount"}; 213 req.owner = {by:"name", _content:owners[i]}; 214 requests.push(req); 215 } 216 217 var respCallback = new AjxCallback(this, this._handleResponseGetShares); 218 appCtxt.getRequestMgr().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback}); 219 } 220 }; 221 222 ZmSharingView.prototype._handleResponseGetShares = 223 function(result) { 224 225 var mounts = []; 226 var resp = result.getResponse().BatchResponse.GetShareInfoResponse; 227 for (var i = 0; i < resp.length; i++) { 228 var shares = resp[i].share; 229 if (!(shares && shares.length)) { continue; } 230 for (var j = 0; j < shares.length; j++) { 231 var share = ZmShare.getShareFromShareInfo(shares[j]); 232 if (share.mounted) { 233 mounts.push(share); 234 } 235 } 236 } 237 238 mounts.sort(ZmSharingView.sortCompareShare); 239 this._mountedShareListView.set(AjxVector.fromArray(mounts)); 240 241 }; 242 243 /** 244 * Displays shares that are pending (have not yet been mounted). 245 * 246 * @param shares [array] list of JSON share info objects from GetShareInfoResponse 247 * 248 * @private 249 */ 250 ZmSharingView.prototype.showPendingShares = 251 function(shares) { 252 253 var pending = []; 254 if (shares && shares.length) { 255 for (var i = 0; i < shares.length; i++) { 256 // convert share info to ZmShare 257 var share = ZmShare.getShareFromShareInfo(shares[i]); 258 if (!share.mounted) { 259 pending.push(share); 260 } 261 } 262 } 263 pending.sort(ZmSharingView.sortCompareShare); 264 this._pendingShareListView.set(AjxVector.fromArray(pending)); 265 }; 266 267 /** 268 * Displays grants (folders shared by the user) in a list view. Grants show up as shares 269 * in folders owned by the user. 270 * 271 * @private 272 */ 273 ZmSharingView.prototype.showGrants = 274 function() { 275 276 // the grant objects we get in the refresh block don't have grantee names, 277 // so use GetFolder in a BatchRequest to get them 278 var batchCmd = new ZmBatchCommand(true, null, true); 279 var list = appCtxt.getFolderTree().asList(); 280 for (var i = 0; i < list.length; i++) { 281 var folder = list[i]; 282 if (folder.shares && folder.shares.length) { 283 for (var j = 0; j < folder.shares.length; j++) { 284 var share = folder.shares[j]; 285 if (!(share.grantee && share.grantee.name)) { 286 batchCmd.add(new AjxCallback(folder, folder.getFolder, [null, batchCmd])); 287 break; 288 } 289 } 290 } 291 } 292 293 if (batchCmd._cmds.length) { 294 var respCallback = new AjxCallback(this, this._handleResponseGetFolder); 295 batchCmd.run(respCallback); 296 } else { 297 this._handleResponseGetFolder(); 298 } 299 }; 300 301 ZmSharingView.prototype._handleResponseGetFolder = 302 function() { 303 304 var shares = [], invalid = []; 305 var list = appCtxt.getFolderTree().asList(); 306 for (var i = 0; i < list.length; i++) { 307 var folder = list[i]; 308 if (folder.shares && folder.shares.length) { 309 for (var j = 0; j < folder.shares.length; j++) { 310 var share = ZmShare.getShareFromGrant(folder.shares[j]); 311 if (share.invalid) { 312 invalid.push(share); 313 } 314 shares.push(share); 315 } 316 } 317 } 318 319 shares.sort(ZmSharingView.sortCompareGrant); 320 this._grantListView.set(AjxVector.fromArray(shares)); 321 322 // an invalid grant is one whose grantee has been removed from the system 323 // if we have some, ask the user if it's okay to remove them 324 if (invalid.length) { 325 invalid.sort(ZmSharingView.sortCompareGrant); 326 var msgDialog = appCtxt.getOkCancelMsgDialog(); 327 var list = []; 328 for (var i = 0; i < invalid.length; i++) { 329 var share = invalid[i]; 330 var path = share.link && share.link.path; 331 if (path) { 332 list.push(["<li>", AjxStringUtil.htmlEncode(path), "</li>"].join("")); 333 } 334 } 335 list = AjxUtil.uniq(list); 336 var listText = list.join(""); 337 msgDialog.setMessage(AjxMessageFormat.format(ZmMsg.granteeGone, listText)); 338 msgDialog.registerCallback(DwtDialog.OK_BUTTON, this._revokeGrantsOk, this, [msgDialog, invalid]); 339 msgDialog.registerCallback(DwtDialog.CANCEL_BUTTON, this._revokeGrantsCancel, this, msgDialog); 340 msgDialog.associateEnterWithButton(DwtDialog.OK_BUTTON); 341 msgDialog.popup(null, DwtDialog.OK_BUTTON); 342 } 343 }; 344 345 ZmSharingView.prototype._revokeGrantsOk = 346 function(dlg, invalid) { 347 348 var batchCmd = new ZmBatchCommand(true, null, true); 349 var zids = {}; 350 for (var i = 0; i < invalid.length; i++) { 351 var share = invalid[i]; 352 zids[share.grantee.id] = share.grantee.type; 353 } 354 355 for (var zid in zids) { 356 batchCmd.add(new AjxCallback(null, ZmShare.revokeOrphanGrants, [zid, zids[zid], null, batchCmd])); 357 } 358 359 if (batchCmd._cmds.length) { 360 batchCmd.run(); 361 } 362 363 dlg.popdown(); 364 }; 365 366 ZmSharingView.prototype._revokeGrantsCancel = 367 function(dlg) { 368 dlg.popdown(); 369 }; 370 371 ZmSharingView._handleAcceptLink = 372 function(domId) { 373 374 var sharingView = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView().getView("SHARING").view; 375 var share = sharingView._shareByDomId[domId]; 376 if (share) { 377 appCtxt.getAcceptShareDialog().popup(share, share.grantor.email); 378 } 379 return false; 380 }; 381 382 ZmSharingView._handleShareAction = 383 function(domId, handler) { 384 385 var sharingView = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView().getView("SHARING").view; 386 var share = sharingView._shareByDomId[domId]; 387 if (share) { 388 var dlg = appCtxt.getFolderPropsDialog(); 389 return dlg[handler](null, share); 390 } 391 }; 392 393 ZmSharingView.prototype._initialize = 394 function() { 395 396 // form for finding shares 397 var params = {}; 398 params.parent = this; 399 params.template = "prefs.Pages#ShareForm"; 400 params.form = { 401 items: [ 402 { id: ZmSharingView.ID_RADIO, type: "DwtRadioButtonGroup", onclick: this._onClick, items: [ 403 { id: ZmSharingView.ID_GROUP, type: "DwtRadioButton", value: ZmSharingView.ID_GROUP, label: ZmMsg.showGroupShares, checked: true }, 404 { id: ZmSharingView.ID_USER, type: "DwtRadioButton", value: ZmSharingView.ID_USER, label: ZmMsg.showUserShares }]}, 405 { id: ZmSharingView.ID_OWNER, type: "ZmAddressInputField", validator: this._validateOwner, params: { singleBubble: true } }, 406 { id: ZmSharingView.ID_FIND_BUTTON, type: "DwtButton", label: ZmMsg.findShares, onclick: this._onClick } 407 ] 408 }; 409 this._shareForm = new DwtForm(params); 410 var shareFormDiv = document.getElementById(this._pageId + "_shareForm"); 411 shareFormDiv.appendChild(this._shareForm.getHtmlElement()); 412 413 // form for creating a new share 414 var options = []; 415 var orgTypes = [ZmOrganizer.FOLDER, ZmOrganizer.CALENDAR, ZmOrganizer.ADDRBOOK, 416 ZmOrganizer.TASKS, ZmOrganizer.BRIEFCASE]; 417 var orgKey = {}; 418 orgKey[ZmOrganizer.FOLDER] = "mailFolder"; 419 orgKey[ZmOrganizer.TASKS] = "tasksFolder"; 420 orgKey[ZmOrganizer.BRIEFCASE] = "briefcase"; 421 for (var i = 0; i < orgTypes.length; i++) { 422 var orgType = orgTypes[i]; 423 if (orgType) { 424 var key = orgKey[orgType] || ZmOrganizer.MSG_KEY[orgType]; 425 options.push({id: orgType, value: orgType, label: ZmMsg[key]}); 426 } 427 } 428 params.template = "prefs.Pages#GrantForm"; 429 params.form = { 430 items: [ 431 { id: ZmSharingView.ID_FOLDER_TYPE, type: "DwtSelect", items: options}, 432 { id: ZmSharingView.ID_SHARE_BUTTON, type: "DwtButton", label: ZmMsg.share, onclick: this._onClick } 433 ] 434 }; 435 this._grantForm = new DwtForm(params); 436 var grantFormDiv = document.getElementById(this._pageId + "_grantForm"); 437 grantFormDiv.appendChild(this._grantForm.getHtmlElement()); 438 439 var folderTypeSelect = this._grantForm._items.folderType.control; 440 folderTypeSelect.fixedButtonWidth(); 441 442 // list views of shares and grants 443 this._pendingShareListView = new ZmSharingListView({parent:this, type:ZmShare.SHARE, 444 status:ZmSharingView.PENDING, sharingView:this, view:ZmId.VIEW_SHARE_PENDING}); 445 this._addListView(this._pendingShareListView, this._pageId + "_pendingShares"); 446 this._mountedShareListView = new ZmSharingListView({parent:this, type:ZmShare.SHARE, 447 status:ZmSharingView.MOUNTED, sharingView:this, view:ZmId.VIEW_SHARE_MOUNTED}); 448 this._addListView(this._mountedShareListView, this._pageId + "_mountedShares"); 449 this._grantListView = new ZmSharingListView({parent:this, type:ZmShare.GRANT, 450 sharingView:this, view:ZmId.VIEW_SHARE_GRANTS}); 451 this._addListView(this._grantListView, this._pageId + "_sharesBy"); 452 453 // autocomplete 454 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED) || appCtxt.get(ZmSetting.GAL_ENABLED)) { 455 var params = { 456 parent: appCtxt.getShell(), 457 dataClass: appCtxt.getAutocompleter(), 458 matchValue: ZmAutocomplete.AC_VALUE_EMAIL, 459 separator: "", 460 keyUpCallback: this._enterCallback.bind(this), 461 contextId: this.toString() 462 }; 463 this._acAddrSelectList = new ZmAutocompleteListView(params); 464 var inputCtrl = this._shareForm.getControl(ZmSharingView.ID_OWNER); 465 this._acAddrSelectList.handle(inputCtrl.getInputElement(), inputCtrl._htmlElId); 466 inputCtrl.setAutocompleteListView(this._acAddrSelectList); 467 } 468 469 appCtxt.getFolderTree().addChangeListener(new AjxListener(this, this._folderTreeChangeListener)); 470 }; 471 472 ZmSharingView.prototype._addListView = 473 function(listView, listViewDivId) { 474 var listDiv = document.getElementById(listViewDivId); 475 listDiv.appendChild(listView.getHtmlElement()); 476 listView.setUI(null, true); // renders headers and empty list 477 listView._initialized = true; 478 }; 479 480 // make sure user is not looking for folders shared from their account 481 ZmSharingView.prototype._validateOwner = 482 function(value) { 483 if (!value) { return true; } 484 return (appCtxt.isMyAddress(value, true)) ? false: true; 485 }; 486 487 // Note that in the handler call, "this" is set to the form 488 ZmSharingView.prototype._onClick = 489 function(id) { 490 491 if (id == ZmSharingView.ID_FIND_BUTTON) { 492 this.setValue(ZmSharingView.ID_USER, true, true); 493 this.parent.findShares(this.getValue(ZmSharingView.ID_OWNER), true); 494 } else if (id == ZmSharingView.ID_GROUP) { 495 this.parent.findShares(); 496 } else if (id == ZmSharingView.ID_SHARE_BUTTON) { 497 var orgType = this.getValue(ZmSharingView.ID_FOLDER_TYPE); 498 this.parent._showChooser(orgType); 499 } 500 }; 501 502 ZmSharingView.prototype._enterCallback = 503 function(ev) { 504 var key = DwtKeyEvent.getCharCode(ev); 505 if (DwtKeyEvent.IS_RETURN[key]) { 506 this._onClick.call(this._shareForm, ZmSharingView.ID_FIND_BUTTON); 507 return false; 508 } 509 return true; 510 }; 511 512 ZmSharingView.prototype._showChooser = 513 function(orgType) { 514 515 // In multi-account, sharing page gets its own choose-folder dialog since it 516 // only shows the active account's overview. Otherwise, we have to juggle 517 // overviews with between single/multiple overview trees. Ugh. 518 var dialog; 519 if (appCtxt.multiAccounts) { 520 if (!this._chooseFolderDialog) { 521 AjxDispatcher.require("Extras"); 522 this._chooseFolderDialog = new ZmChooseFolderDialog(appCtxt.getShell()); 523 } 524 dialog = this._chooseFolderDialog; 525 } else { 526 dialog = appCtxt.getChooseFolderDialog(); 527 } 528 529 var overviewId = dialog.getOverviewId(ZmOrganizer.APP[orgType]); 530 if (appCtxt.multiAccounts) { 531 overviewId = [overviewId, "-", this.toString(), "-", appCtxt.getActiveAccount().name].join(""); 532 } 533 var omit = {}; 534 omit[ZmFolder.ID_TRASH] = true; 535 var params = { 536 treeIds: [orgType], 537 overviewId: overviewId, 538 title: ZmMsg.chooseFolder, 539 skipReadOnly: true, 540 skipRemote: true, 541 omit: omit, 542 hideNewButton: true, 543 appName: ZmOrganizer.APP[orgType], 544 noRootSelect: true, 545 forceSingle: true 546 }; 547 dialog.reset(); 548 dialog.registerCallback(DwtDialog.OK_BUTTON, this._folderSelectionCallback, this, [dialog]); 549 dialog.popup(params); 550 }; 551 552 ZmSharingView.prototype._folderSelectionCallback = 553 function(chooserDialog, org) { 554 555 chooserDialog.popdown(); 556 var shareDialog = appCtxt.getSharePropsDialog(); 557 shareDialog.popup(ZmSharePropsDialog.NEW, org); 558 }; 559 560 /** 561 * Sorts shares in the following order: 562 * 1. by name of owner 563 * 2. by name of group it was shared with, if any 564 * 3. by path of shared folder 565 * 566 * @private 567 */ 568 ZmSharingView.sortCompareShare = 569 function(a, b) { 570 571 var ownerA = (a.grantor.name && a.grantor.name.toLowerCase()) || (a.grantor.email && a.grantor.email.toLowerCase()) || ""; 572 var ownerB = (b.grantor.name && b.grantor.name.toLowerCase()) || (b.grantor.email && b.grantor.email.toLowerCase()) || ""; 573 if (ownerA != ownerB) { 574 return (ownerA > ownerB) ? 1 : -1; 575 } 576 577 var groupA = (a.grantee.type == ZmShare.TYPE_GROUP) ? (a.grantee.name && a.grantee.name.toLowerCase()) : ""; 578 var groupB = (b.grantee.type == ZmShare.TYPE_GROUP) ? (b.grantee.name && b.grantee.name.toLowerCase()) : ""; 579 if (groupA != groupB) { 580 if (!groupA && groupB) { 581 return 1; 582 } else if (groupA && !groupB) { 583 return -1; 584 } else { 585 return (groupA > groupB) ? 1 : -1; 586 } 587 } 588 589 var pathA = (a.link.name && a.link.name.toLowerCase()) || ""; 590 var pathB = (b.link.name && b.link.name.toLowerCase()) || ""; 591 if (pathA != pathB) { 592 return (pathA > pathB) ? 1 : -1; 593 } 594 595 return 0; 596 }; 597 598 /** 599 * Sorts shares in the following order: 600 * 1. by name of who it was shared with 601 * 2. by path of shared folder 602 * 603 * @private 604 */ 605 ZmSharingView.sortCompareGrant = 606 function(a, b) { 607 608 var granteeA = (a.grantee && a.grantee.name && a.grantee.name.toLowerCase()) || ""; 609 var granteeB = (b.grantee && b.grantee.name && b.grantee.name.toLowerCase()) || ""; 610 if (granteeA != granteeB) { 611 return (granteeA > granteeB) ? 1 : -1; 612 } 613 614 var pathA = (a.link && a.link.name) || ""; 615 var pathB = (b.link && b.link.name) || ""; 616 if (pathA != pathB) { 617 return (pathA > pathB) ? 1 : -1; 618 } 619 620 return 0; 621 }; 622 623 ZmSharingView.prototype._folderTreeChangeListener = 624 function(ev) { 625 626 this._pendingShareListView._changeListener(ev); 627 this._mountedShareListView._changeListener(ev); 628 this._grantListView._changeListener(ev); 629 }; 630 631 /** 632 * Handle modifications to pending shares, which don't have an item to propagate 633 * changes through. The preferences app sends the notifications here. 634 * 635 * @param modifies [hash] notifications 636 * 637 * @private 638 */ 639 ZmSharingView.prototype.notifyModify = 640 function(modifies) { 641 642 for (var name in modifies) { 643 if (name == "folder") { 644 modifies = modifies.folder; 645 for (var i = 0; i < modifies.length; i++) { 646 var mod = modifies[i]; 647 var share = this._shareByKey[mod.id]; 648 var ev = new ZmEvent(); 649 if (share) { 650 var parts = mod.id.split(":"); 651 share.zid = parts[0]; 652 share.rid = parts[1]; 653 ev.ersatz = true; 654 ev.set(ZmEvent.E_MODIFY); 655 var fields = {}; 656 if (mod.perm) { 657 share.setPermissions(mod.perm); 658 fields[ZmOrganizer.F_PERMS] = true; 659 } 660 if (mod.name) { 661 fields[ZmOrganizer.F_RNAME] = true; 662 } 663 if (mod.l) { 664 ev.set(ZmEvent.E_MOVE); 665 } 666 ev.setDetail("share", share); 667 ev.setDetail("fields", fields); 668 this._folderTreeChangeListener(ev); 669 mod._handled = true; 670 } else if (mod.id.indexOf(":") != -1) { 671 ev.set(ZmEvent.E_CREATE); 672 } 673 } 674 } 675 } 676 }; 677 678 /** 679 * If we get a refresh block from the server, redraw all three list views. 680 * 681 * @param refresh [object] the refresh block JSON 682 * 683 * @private 684 */ 685 ZmSharingView.prototype.refresh = 686 function(refresh) { 687 this.findShares(this._curOwner); 688 this.showGrants(); 689 }; 690 691 /** 692 * A list view that displays some form of shares, either with or by the user. The data 693 * is in the form of a list of ZmShare's. 694 * 695 * @param {Hash} params a hash of parameters 696 * @param {constant} params.type the SHARE (shared with user) or GRANT (shared by user) 697 * @param {ZmSharingView} params.view the owning view 698 * @param {constant} params.status the pending or mounted 699 * 700 * @extends DwtListView 701 * 702 * @private 703 */ 704 ZmSharingListView = function(params) { 705 706 this.type = params.type; 707 this.status = params.status; 708 params.headerList = this._getHeaderList(); 709 DwtListView.call(this, params); 710 711 this.sharingView = params.sharingView; 712 this._idMap = {}; 713 }; 714 715 ZmSharingListView.prototype = new DwtListView; 716 ZmSharingListView.prototype.constructor = ZmSharingListView; 717 718 ZmSharingListView.prototype.toString = 719 function() { 720 return "ZmSharingListView"; 721 }; 722 723 ZmSharingListView.prototype._getHeaderList = 724 function() { 725 726 var headerList = []; 727 if (this.type == ZmShare.SHARE) { 728 headerList.push(new DwtListHeaderItem({field:ZmSharingView.F_OWNER, text:ZmMsg.sharingOwner, width:ZmMsg.COLUMN_WIDTH_OWNER_SH})); 729 } else if (this.type == ZmShare.GRANT) { 730 headerList.push(new DwtListHeaderItem({field:ZmSharingView.F_WITH, text:ZmMsg.sharingWith, width:ZmMsg.COLUMN_WIDTH_WITH_SH})); 731 } 732 headerList.push(new DwtListHeaderItem({field:ZmSharingView.F_ITEM, text:ZmMsg.sharingItem})); 733 headerList.push(new DwtListHeaderItem({field:ZmSharingView.F_TYPE, text:ZmMsg.sharingFolderType, width:ZmMsg.COLUMN_WIDTH_TYPE_SH})); 734 headerList.push(new DwtListHeaderItem({field:ZmSharingView.F_ROLE, text:ZmMsg.sharingRole, width:ZmMsg.COLUMN_WIDTH_ROLE_SH})); 735 if (this.type == ZmShare.SHARE) { 736 if (this.status == ZmSharingView.PENDING) { 737 headerList.push(new DwtListHeaderItem({field:ZmSharingView.F_ACTIONS, text:ZmMsg.actions, width:ZmMsg.COLUMN_WIDTH_ACTIONS_SH})); 738 } else { 739 headerList.push(new DwtListHeaderItem({field:ZmSharingView.F_FOLDER, text:ZmMsg.sharingFolder, width:ZmMsg.COLUMN_WIDTH_FOLDER_SH})); 740 } 741 headerList.push(new DwtListHeaderItem({field:ZmSharingView.F_WITH, text:ZmMsg.sharingWith, width:ZmMsg.COLUMN_WIDTH_WITH_SH})); 742 } else { 743 headerList.push(new DwtListHeaderItem({field:ZmSharingView.F_ACTIONS, text:ZmMsg.actions, width:ZmMsg.COLUMN_WIDTH_ACTIONS_SH})); 744 } 745 746 return headerList; 747 }; 748 749 ZmSharingListView.prototype._getItemId = 750 function(item) { 751 752 var account = (item.type == ZmShare.SHARE) ? item.grantor && item.grantor.id : 753 item.grantee && item.grantee.id; 754 var key = [account, item.link.id].join(":"); 755 var id = item.domId; 756 if (!id) { 757 id = Dwt.getNextId(); 758 item.domId = id; 759 this.sharingView._shareByDomId[id] = item; 760 this.sharingView._shareByKey[key] = item; 761 } 762 763 return id; 764 }; 765 766 ZmSharingListView.prototype._getCellId = 767 function(item, field, params) { 768 var rowId = this._getItemId(item); 769 return [rowId, field].join("_"); 770 }; 771 772 ZmSharingListView.prototype._getCellContents = 773 function(html, idx, item, field, colIdx, params) { 774 775 if (field == ZmSharingView.F_OWNER) { 776 html[idx++] = AjxStringUtil.htmlEncode(item.grantor.name) || item.grantor.email; 777 } else if (field == ZmSharingView.F_WITH) { 778 var type = item.grantee.type; 779 if (type == ZmShare.TYPE_PUBLIC) { 780 html[idx++] = ZmMsg.shareWithPublic; 781 } else if (type == ZmShare.TYPE_ALL) { 782 html[idx++] = ZmMsg.shareWithAll; 783 } else if (type == ZmShare.TYPE_GUEST) { 784 html[idx++] = item.grantee.id; 785 } else { 786 html[idx++] = AjxStringUtil.htmlEncode(item.grantee.name); 787 } 788 } else if (field == ZmSharingView.F_ITEM) { 789 html[idx++] = AjxStringUtil.htmlEncode(item.link.path); 790 } else if (field == ZmSharingView.F_TYPE) { 791 html[idx++] = (item.object && item.object.type) ? ZmMsg[ZmOrganizer.FOLDER_KEY[item.object.type]] : 792 ZmShare._getFolderType(item.link.view); 793 } else if (field == ZmSharingView.F_ROLE) { 794 var role = item.link.role || ZmShare._getRoleFromPerm(item.link.perm); 795 html[idx++] = ZmShare.getRoleName(role); 796 } else if (field == ZmSharingView.F_FOLDER) { 797 html[idx++] = (item.mountpoint && item.mountpoint.path) || " "; 798 } else if (field == ZmSharingView.F_ACTIONS) { 799 if (this.type == ZmShare.SHARE) { 800 var id = this._getItemId(item); 801 var linkId = [id, ZmShare.ACCEPT].join("_"); 802 html[idx++] = "<a href='javascript:;' id='" + linkId + "' onclick='ZmSharingView._handleAcceptLink(" + '"' + id + '"' + ");'>" + ZmMsg.accept + "</a>"; 803 } else { 804 idx = this._addActionLinks(item, html, idx); 805 } 806 } 807 808 return (params && params.returnText) ? html.join("") : idx; 809 }; 810 811 ZmSharingListView.prototype._changeListener = 812 function(ev) { 813 814 var organizers = ev.getDetail("organizers") || []; 815 var fields = ev.getDetail("fields") || {}; 816 817 if (this.type == ZmShare.SHARE) { 818 var share = ev.getDetail("share"); 819 if (!share) { 820 var mtpt = organizers[0]; 821 if (!mtpt.link) { return; } 822 var share = this.sharingView._shareByKey[[mtpt.zid, mtpt.rid].join(":")]; 823 share = ZmShare.getShareFromLink(mtpt, share); // update share 824 } 825 if (!share) { return; } 826 if (ev.event == ZmEvent.E_CREATE) { 827 // share accepted, mountpoint created; move from pending to mounted list 828 if (this.status == ZmSharingView.PENDING) { 829 this.removeItem(share); 830 } else if (this.status == ZmSharingView.MOUNTED) { 831 var index = this._list && this._getIndex(share, this._list.getArray(), ZmSharingView.sortCompareShare); 832 this.addItem(share, index, true); 833 } 834 } else if (ev.event == ZmEvent.E_MODIFY) { 835 if ((this.status == ZmSharingView.PENDING && share.mounted) || 836 (this.status == ZmSharingView.MOUNTED && !share.mounted)) { return; } 837 if (fields[ZmOrganizer.F_PERMS]) { 838 var cell = document.getElementById(this._getCellId(share, ZmSharingView.F_ROLE)); 839 if (cell) { 840 cell.innerHTML = this._getCellContents([], 0, share, ZmSharingView.F_ROLE, null, {returnText:true}); 841 } 842 } 843 if ((this.status == ZmSharingView.MOUNTED) && fields[ZmOrganizer.F_NAME]) { 844 var cell = document.getElementById(this._getCellId(share, ZmSharingView.F_FOLDER)); 845 if (cell) { 846 cell.innerHTML = this._getCellContents([], 0, share, ZmSharingView.F_FOLDER, null, {returnText:true}); 847 } 848 } 849 } 850 // if a remote folder has been renamed or moved, rerun the search 851 if (ev.event == ZmEvent.E_MOVE || fields[ZmOrganizer.F_RNAME]) { 852 if (this.sharingView._curOwner) { 853 this.sharingView.findShares(this.sharingView._curOwner); 854 } 855 } 856 } 857 858 // Any change to a grant (including create or revoke) results in a wholesale replacement of 859 // the folder's shares, so it's easiest to just redraw the list. Also check for folder rename. 860 if (this.type == ZmShare.GRANT) { 861 if ((ev.event = ZmEvent.E_MODIFY && fields[ZmOrganizer.F_SHARES]) || 862 (ev.event = ZmEvent.E_MODIFY && fields[ZmOrganizer.F_NAME] && organizers[0].shares)) { 863 864 this.sharingView.showGrants(); 865 } 866 } 867 }; 868 869 /** 870 * Adds links for editing, revoking, or resending a grant. 871 * 872 * @param share [ZmShare] share 873 * @param html [array] HTML content 874 * @param idx [int] index 875 * 876 * @private 877 */ 878 ZmSharingListView.prototype._addActionLinks = 879 function(share, html, idx) { 880 881 var type = share.grantee.type; 882 var actions = ["edit", "revoke", "resend"]; 883 if (type == ZmShare.TYPE_ALL || type == ZmShare.TYPE_DOMAIN || !share.link.role) { 884 html[idx++] = ZmMsg.configureWithAdmin; 885 actions = []; 886 } 887 888 var handlers = ["_handleEditShare", "_handleRevokeShare", "_handleResendShare"]; // handlers in ZmFolderPropsDialog 889 890 for (var i = 0; i < actions.length; i++) { 891 892 var action = actions[i]; 893 var linkId = [share.domId, action].join("_"); 894 // public shares have no editable fields, and sent no mail 895 if (share.isGuest() && action == "edit") { continue; } 896 if ((share.isPublic() || share.invalid) && (action == "edit" || action == "resend")) { continue; } 897 898 html[idx++] = "<a href='javascript:;' id='" + linkId + "' onclick='ZmSharingView._handleShareAction(" + '"' + share.domId + '", "' + handlers[i] + '"' + ");'>" + ZmMsg[action] + "</a> "; 899 } 900 901 return idx; 902 }; 903 904 /** 905 * Returns the position of the share in the given list using the given compare function. 906 * 907 * @param share [ZmShare] a share 908 * @param list [array] list of shares 909 * @param compareFunc [function] compare function 910 * 911 * @private 912 */ 913 ZmSharingListView.prototype._getIndex = 914 function(share, list, compareFunc) { 915 916 for (var i = 0; i < list.length; i++) { 917 var result = compareFunc(share, list[i]); 918 if (result == -1) { 919 return i; 920 } 921 } 922 return null; 923 }; 924