1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file defines an organizer. 27 */ 28 29 /** 30 * Creates an empty organizer. 31 * @class 32 * This class represents an "organizer", which is something used to classify or contain 33 * items. So far, that's either a tag or a folder. Tags and folders are represented as 34 * a tree structure, though tags are flat and have only one level below the root. Folders 35 * can be nested. 36 * 37 * @author Conrad Damon 38 * 39 * @param {Hash} params a hash of parameters 40 * @param {constant} params.type the organizer type 41 * @param {int} params.id the numeric ID 42 * @param {String} params.name the name 43 * @param {ZmOrganizer} params.parent the parent organizer 44 * @param {ZmTree} params.tree the tree model that contains this organizer 45 * @param {constant} params.color the color for this organizer 46 * @param {String} params.rgb the color for this organizer, as HTML RGB value 47 * @param {Boolean} params.link <code>true</code> if this organizer is shared 48 * @param {Boolean} params.broken <code>true</code> if this link is broken 49 * @param {int} params.numUnread the number of unread items for this organizer 50 * @param {int} params.numTotal the number of items for this organizer 51 * @param {Boolean} params.noTooltip do not show tooltip 52 * @param {int} params.sizeTotal the total size of organizer's items 53 * @param {String} params.url the URL for this organizer's feed 54 * @param {String} params.owner the owner for this organizer 55 * @param {String} params.oname the owner's name for this organizer 56 * @param {String} params.zid the Zimbra ID of owner, if remote folder 57 * @param {String} params.rid the remote ID of organizer, if remote folder 58 * @param {String} params.restUrl the REST URL of this organizer. 59 * @param {String} params.newOp the name of operation run by button in overview header 60 * @param {ZmZimbraAccount} params.account the account this organizer belongs to 61 */ 62 ZmOrganizer = function(params) { 63 64 if (arguments.length == 0) { return; } 65 66 this.type = params.type; 67 var id = this.id = params.id; 68 this.nId = ZmOrganizer.normalizeId(id); 69 this.name = ZmFolder.MSG_KEY[this.nId] ? ZmMsg[ZmFolder.MSG_KEY[this.nId]] : params.name; 70 this._systemName = this.nId < 256 && params.name; 71 this.parent = params.parent; 72 this.tree = params.tree; 73 this.numUnread = params.numUnread || 0; 74 this.numTotal = params.numTotal || 0; 75 this.noTooltip = params.noTooltip; 76 this.sizeTotal = params.sizeTotal || 0; 77 this.url = params.url; 78 this.owner = params.owner; 79 this.oname = params.oname; 80 this.link = params.link || (Boolean(params.zid)) || (this.parent && this.parent.link); 81 this.isMountpoint = params.link; 82 this.zid = params.zid; 83 this.rid = params.rid; 84 this.restUrl = params.restUrl; 85 this.account = params.account; 86 this.perm = params.perm; 87 this.noSuchFolder = params.broken; // Is this a link to some folder that ain't there. 88 this._isAdmin = this._isReadOnly = this._hasPrivateAccess = null; 89 this.retentionPolicy = params.retentionPolicy; 90 this.webOfflineSyncDays = params.webOfflineSyncDays; 91 92 this.color = 93 params.color || 94 (this.parent && this.parent.color) || 95 ZmOrganizer.ORG_COLOR[id] || 96 ZmOrganizer.ORG_COLOR[this.nId] || 97 ZmOrganizer.DEFAULT_COLOR[this.type] || 98 ZmOrganizer.C_NONE 99 ; 100 this.isColorCustom = params.rgb != null; //set so we know if the user chose a custom color (to distiguish from basic color or none 101 this.rgb = 102 params.rgb || 103 ZmOrganizer.COLOR_VALUES[this.color] || 104 ZmOrganizer.COLOR_VALUES[ZmOrganizer.ORG_DEFAULT_COLOR] 105 ; 106 107 if (appCtxt.isOffline && !this.account && this.id == this.nId) { 108 this.account = appCtxt.accountList.mainAccount; 109 } 110 111 if (id && params.tree) { 112 appCtxt.cacheSet(id, this); 113 if (this.link) { 114 // also store under ID that items use for parent folder ("l" attribute in node) 115 appCtxt.cacheSet([this.zid, this.rid].join(":"), this); 116 } 117 } 118 119 this.children = new AjxVector(); 120 }; 121 122 ZmOrganizer.prototype.isZmOrganizer = true; 123 ZmOrganizer.prototype.toString = function() { return "ZmOrganizer"; }; 124 125 // global organizer types 126 ZmOrganizer.TAG = ZmEvent.S_TAG; 127 ZmOrganizer.SEARCH = ZmEvent.S_SEARCH; 128 ZmOrganizer.SHARE = ZmEvent.S_SHARE; 129 ZmOrganizer.MOUNTPOINT = ZmEvent.S_MOUNTPOINT; 130 ZmOrganizer.ZIMLET = ZmEvent.S_ZIMLET; 131 132 // folder IDs defined in com.zimbra.cs.mailbox.Mailbox 133 // Note: since these are defined as Numbers, and IDs come into our system as Strings, 134 // we need to use == for comparisons (instead of ===, which will fail) 135 ZmOrganizer.ID_ROOT = 1; 136 ZmOrganizer.ID_INBOX = 2; 137 ZmOrganizer.ID_TRASH = 3; 138 ZmOrganizer.ID_SPAM = 4; 139 ZmOrganizer.ID_ADDRBOOK = 7; 140 ZmOrganizer.ID_CALENDAR = 10; 141 ZmOrganizer.ID_AUTO_ADDED = 13; 142 ZmOrganizer.ID_CHATS = 14; 143 ZmOrganizer.ID_TASKS = 15; 144 ZmOrganizer.ID_BRIEFCASE = 16; 145 ZmOrganizer.ID_ALL_MAILBOXES = 249; 146 ZmOrganizer.ID_NOTIFICATION_MP = 250; 147 ZmOrganizer.ID_SYNC_FAILURES = 252; // offline only 148 ZmOrganizer.ID_OUTBOX = 254; // offline only 149 ZmOrganizer.ID_ZIMLET = -1000; // zimlets need a range. start from -1000 incrementing up. 150 ZmOrganizer.ID_ATTACHMENTS = -17; // Attachments View 151 ZmOrganizer.ID_DLS = -18; 152 153 // fields that can be part of a displayed organizer 154 ZmOrganizer.F_NAME = "name"; 155 ZmOrganizer.F_UNREAD = "unread"; 156 ZmOrganizer.F_TOTAL = "total"; 157 ZmOrganizer.F_SIZE = "size"; 158 ZmOrganizer.F_COLOR = "color"; 159 ZmOrganizer.F_RGB = "rgb"; 160 ZmOrganizer.F_QUERY = "query"; 161 ZmOrganizer.F_SHARES = "shares"; 162 ZmOrganizer.F_FLAGS = "flags"; 163 ZmOrganizer.F_REST_URL = "rest"; 164 ZmOrganizer.F_PERMS = "perms"; 165 ZmOrganizer.F_RNAME = "rname"; // remote name 166 167 // server representation of org flags 168 ZmOrganizer.FLAG_CHECKED = "#"; 169 ZmOrganizer.FLAG_DISALLOW_SUBFOLDER = "o"; 170 ZmOrganizer.FLAG_EXCLUDE_FREE_BUSY = "b"; 171 ZmOrganizer.FLAG_IMAP_SUBSCRIBED = "*"; 172 ZmOrganizer.FLAG_OFFLINE_GLOBAL = "g"; 173 ZmOrganizer.FLAG_OFFLINE_SYNCABLE = "y"; 174 ZmOrganizer.FLAG_OFFLINE_SYNCING = "~"; 175 ZmOrganizer.ALL_FLAGS = [ 176 ZmOrganizer.FLAG_CHECKED, 177 ZmOrganizer.FLAG_IMAP_SUBSCRIBED, 178 ZmOrganizer.FLAG_EXCLUDE_FREE_BUSY, 179 ZmOrganizer.FLAG_DISALLOW_SUBFOLDER, 180 ZmOrganizer.FLAG_OFFLINE_GLOBAL, 181 ZmOrganizer.FLAG_OFFLINE_SYNCABLE, 182 ZmOrganizer.FLAG_OFFLINE_SYNCING 183 ]; 184 185 // org property for each flag 186 ZmOrganizer.FLAG_PROP = {}; 187 ZmOrganizer.FLAG_PROP[ZmOrganizer.FLAG_CHECKED] = "isChecked"; 188 ZmOrganizer.FLAG_PROP[ZmOrganizer.FLAG_IMAP_SUBSCRIBED] = "imapSubscribed"; 189 ZmOrganizer.FLAG_PROP[ZmOrganizer.FLAG_EXCLUDE_FREE_BUSY] = "excludeFreeBusy"; 190 ZmOrganizer.FLAG_PROP[ZmOrganizer.FLAG_DISALLOW_SUBFOLDER] = "disallowSubFolder"; 191 ZmOrganizer.FLAG_PROP[ZmOrganizer.FLAG_OFFLINE_GLOBAL] = "isOfflineGlobalSearch"; 192 ZmOrganizer.FLAG_PROP[ZmOrganizer.FLAG_OFFLINE_SYNCABLE] = "isOfflineSyncable"; 193 ZmOrganizer.FLAG_PROP[ZmOrganizer.FLAG_OFFLINE_SYNCING] = "isOfflineSyncing"; 194 195 // Following chars invalid in organizer names: " : / [anything less than " "] 196 ZmOrganizer.VALID_NAME_CHARS = "[^\\x00-\\x1F\\x7F:\\/\\\"]"; 197 ZmOrganizer.VALID_PATH_CHARS = "[^\\x00-\\x1F\\x7F:\\\"]"; // forward slash is OK in path 198 ZmOrganizer.VALID_NAME_RE = new RegExp('^' + ZmOrganizer.VALID_NAME_CHARS + '+$'); 199 200 ZmOrganizer.MAX_NAME_LENGTH = 128; // max allowed by server 201 ZmOrganizer.MAX_DISPLAY_NAME_LENGTH = 30; // max we will show 202 203 // color constants (server stores a number) 204 ZmOrganizer.C_NONE = 0; 205 ZmOrganizer.C_BLUE = 1; 206 ZmOrganizer.C_CYAN = 2; 207 ZmOrganizer.C_GREEN = 3; 208 ZmOrganizer.C_PURPLE = 4; 209 ZmOrganizer.C_RED = 5; 210 ZmOrganizer.C_YELLOW = 6; 211 ZmOrganizer.C_PINK = 7; 212 ZmOrganizer.C_GRAY = 8; 213 ZmOrganizer.C_ORANGE = 9; 214 ZmOrganizer.MAX_COLOR = ZmOrganizer.C_ORANGE; 215 ZmOrganizer.ORG_DEFAULT_COLOR = ZmOrganizer.C_GRAY; 216 217 ZmOrganizer.COLOR_VALUES = [ 218 null, 219 ZmMsg.colorBlue, 220 ZmMsg.colorCyan, 221 ZmMsg.colorGreen, 222 ZmMsg.colorPurple, 223 ZmMsg.colorRed, 224 ZmMsg.colorYellow, 225 ZmMsg.colorPink, 226 ZmMsg.colorGray, 227 ZmMsg.colorOrange 228 ]; 229 230 // color names 231 ZmOrganizer.COLOR_TEXT = {}; 232 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_NONE] = ZmMsg.none; 233 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_ORANGE] = ZmMsg.orange; 234 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_BLUE] = ZmMsg.blue; 235 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_CYAN] = ZmMsg.cyan; 236 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_GREEN] = ZmMsg.green; 237 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_PURPLE] = ZmMsg.purple; 238 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_RED] = ZmMsg.red; 239 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_YELLOW] = ZmMsg.yellow; 240 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_PINK] = ZmMsg.pink; 241 ZmOrganizer.COLOR_TEXT[ZmOrganizer.C_GRAY] = ZmMsg.gray; 242 243 // list of colors and text for populating a color select menu 244 ZmOrganizer.COLORS = []; 245 ZmOrganizer.COLOR_CHOICES = []; 246 (function() { 247 for (var i = 0; i <= ZmOrganizer.MAX_COLOR; i++) { 248 var color = ZmOrganizer.COLOR_TEXT[i]; 249 ZmOrganizer.COLORS.push(color); 250 ZmOrganizer.COLOR_CHOICES.push( { value:i, label:color } ); 251 } 252 })(); 253 254 255 ZmOrganizer.MSG_KEY = {}; // keys for org names 256 ZmOrganizer.ROOT_MSG_KEY = {}; // key for name of root (used as tree header) 257 ZmOrganizer.ITEM_ORGANIZER = {}; // primary organizer for item types 258 ZmOrganizer.DEFAULT_FOLDER = {}; // default folder for org type 259 ZmOrganizer.SOAP_CMD = {}; // SOAP command for modifying an org 260 ZmOrganizer.FIRST_USER_ID = {}; // lowest valid user ID for an org type 261 ZmOrganizer.PRECONDITION = {}; // setting that this org type depends on 262 ZmOrganizer.HAS_COLOR = {}; // whether an org uses colors 263 ZmOrganizer.DEFAULT_COLOR = {}; // default color for each org type 264 ZmOrganizer.ORG_COLOR = {}; // color overrides by ID 265 ZmOrganizer.APP = {}; // App responsible for organizer 266 ZmOrganizer.ORG_CLASS = {}; // constructor for organizer 267 ZmOrganizer.ORG_PACKAGE = {}; // package required to construct organizer 268 ZmOrganizer.CREATE_FUNC = {}; // function that creates this organizer 269 ZmOrganizer.LABEL = {}; // msg key for text for tree view header item 270 ZmOrganizer.ITEMS_KEY = {}; // msg key for text describing contents 271 ZmOrganizer.TREE_TYPE = {}; // type of server data tree that contains this type of organizer 272 ZmOrganizer.VIEWS = {}; // views by org type 273 ZmOrganizer.VIEW_HASH = {}; // view hash by org type 274 ZmOrganizer.TYPE = {}; // types by view (reverse map of above) 275 ZmOrganizer.FOLDER_KEY = {}; // keys for label "[org] folder" 276 ZmOrganizer.MOUNT_KEY = {}; // keys for label "mount [org]" 277 ZmOrganizer.DEFERRABLE = {}; // creation can be deferred to app launch 278 ZmOrganizer.PATH_IN_NAME = {}; // if true, provide full path when asked for name 279 ZmOrganizer.OPEN_SETTING = {}; // setting that controls whether the tree view is open 280 ZmOrganizer.NEW_OP = {}; // name of operation for new button in tree header (optional) 281 ZmOrganizer.DISPLAY_ORDER = {}; // sort number to determine order of tree view (optional) 282 ZmOrganizer.HIDE_EMPTY = {}; // if true, hide tree header if tree is empty 283 ZmOrganizer.SHAREABLE = {}; // allow share or not 284 285 ZmOrganizer.APP2ORGANIZER = {}; // organizer types, keyed by app name 286 ZmOrganizer.APP2ORGANIZER_R = {}; // app names, keyed by app organizer type 287 288 // allowed permission bits 289 ZmOrganizer.PERM_READ = "r"; 290 ZmOrganizer.PERM_WRITE = "w"; 291 ZmOrganizer.PERM_INSERT = "i"; 292 ZmOrganizer.PERM_DELETE = "d"; 293 ZmOrganizer.PERM_ADMIN = "a"; 294 ZmOrganizer.PERM_WORKFLOW = "x"; 295 ZmOrganizer.PERM_PRIVATE = "p"; 296 297 // Retention Policy Elements - Keep or Purge 298 ZmOrganizer.RETENTION_KEEP = "keep"; 299 ZmOrganizer.RETENTION_PURGE = "purge"; 300 301 // Abstract methods 302 303 /** 304 * Stores information about the given organizer type. 305 * 306 * @param {constant} org the organizer type 307 * @param {Hash} params a hash of parameters 308 * @param {constant} app the app that handles this org type 309 * @param {String} nameKey the msg key for org name 310 * @param {constant} precondition the setting that this org type depends on 311 * @param {int} defaultFolder the folder ID of default folder for this org 312 * @param {String} soapCmd the SOAP command for acting on this org 313 * @param {int} firstUserId the minimum ID for a user instance of this org 314 * @param {String} orgClass the name of constructor for this org 315 * @param {String} orgPackage the name of smallest package with org class 316 * @param {String} treeController the name of associated tree controller 317 * @param {String} labelKey the msg key for label in overview 318 * @param {String} itemsKey the msg key for text describing contents 319 * @param {Boolean} hasColor <code>true</code> if org has color associated with it 320 * @param {constant} defaultColor the default color for org in overview 321 * @param {Array} orgColor the color override by ID (in pairs) 322 * @param {constant} treeType the type of data tree (from server) that contains this org 323 * @param {String} views the associated folder views (JSON) 324 * @param {String} folderKey the msg key for folder props dialog 325 * @param {String} mountKey the msg key for folder mount dialog 326 * @param {String} createFunc the name of function for creating this org 327 * @param {String} compareFunc the name of function for comparing instances of this org 328 * @param {Boolean} deferrable if <code>true</code>, creation can be deferred to app launch 329 * @param {Boolean} pathInName if <code>true</code>, provide full path when asked for name 330 * @param {constant} openSetting the setting that controls whether the tree view is open 331 * @param {int} displayOrder the number that is used when sorting the display of trees. (Lower number means higher display.) 332 * @param {Boolean} hideEmpty if <code>true</code>, hide tree header if tree is empty 333 */ 334 ZmOrganizer.registerOrg = 335 function(org, params) { 336 if (params.nameKey) { ZmOrganizer.MSG_KEY[org] = params.nameKey; } 337 if (params.app) { 338 ZmOrganizer.APP[org] = params.app; 339 if (!ZmOrganizer.APP2ORGANIZER[params.app]) { 340 ZmOrganizer.APP2ORGANIZER[params.app] = []; 341 } 342 ZmOrganizer.APP2ORGANIZER[params.app].push(org); 343 ZmOrganizer.APP2ORGANIZER_R[org] = params.app; 344 } 345 if (params.defaultFolder) { ZmOrganizer.DEFAULT_FOLDER[org] = params.defaultFolder; } 346 if (params.precondition) { ZmOrganizer.PRECONDITION[org] = params.precondition; } 347 if (params.soapCmd) { ZmOrganizer.SOAP_CMD[org] = params.soapCmd; } 348 if (params.firstUserId) { ZmOrganizer.FIRST_USER_ID[org] = params.firstUserId; } 349 if (params.orgClass) { ZmOrganizer.ORG_CLASS[org] = params.orgClass; } 350 if (params.orgPackage) { ZmOrganizer.ORG_PACKAGE[org] = params.orgPackage; } 351 if (params.labelKey) { ZmOrganizer.LABEL[org] = params.labelKey; } 352 if (params.itemsKey) { ZmOrganizer.ITEMS_KEY[org] = params.itemsKey; } 353 if (params.hasColor) { ZmOrganizer.HAS_COLOR[org] = params.hasColor; } 354 if (params.views) { ZmOrganizer.VIEWS[org] = params.views; } 355 if (params.folderKey) { ZmOrganizer.FOLDER_KEY[org] = params.folderKey; } 356 if (params.mountKey) { ZmOrganizer.MOUNT_KEY[org] = params.mountKey; } 357 if (params.deferrable) { ZmOrganizer.DEFERRABLE[org] = params.deferrable; } 358 if (params.pathInName) { ZmOrganizer.PATH_IN_NAME[org] = params.pathInName; } 359 if (params.openSetting) { ZmOrganizer.OPEN_SETTING[org] = params.openSetting; } 360 if (params.newOp) { ZmOrganizer.NEW_OP[org] = params.newOp; } 361 if (params.displayOrder) { ZmOrganizer.DISPLAY_ORDER[org] = params.displayOrder; } 362 if (params.hideEmpty) { ZmOrganizer.HIDE_EMPTY[org] = params.hideEmpty; } 363 ZmOrganizer.SHAREABLE[org] = !params.disableShare; 364 365 if (!appCtxt.isChildWindow || params.childWindow ) { 366 if (params.compareFunc) { ZmTreeView.COMPARE_FUNC[org] = params.compareFunc; } 367 if (params.treeController) { ZmOverviewController.CONTROLLER[org] = params.treeController; } 368 } 369 370 ZmOrganizer.TREE_TYPE[org] = params.treeType || org; // default to own type 371 ZmOrganizer.CREATE_FUNC[org] = params.createFunc || "ZmOrganizer.create"; 372 373 if (params.views) { 374 ZmOrganizer.VIEW_HASH[org] = AjxUtil.arrayAsHash(ZmOrganizer.VIEWS[org]); 375 } 376 377 if (params.hasColor) { 378 ZmOrganizer.DEFAULT_COLOR[org] = (params.defaultColor != null) 379 ? params.defaultColor 380 : ZmOrganizer.ORG_DEFAULT_COLOR; 381 } 382 383 if (params.orgColor) { 384 for (var id in params.orgColor) { 385 ZmOrganizer.ORG_COLOR[id] = params.orgColor[id]; 386 } 387 } 388 389 if (params.dropTargets) { 390 if (!ZmApp.DROP_TARGETS[params.app]) { 391 ZmApp.DROP_TARGETS[params.app] = {}; 392 } 393 ZmApp.DROP_TARGETS[params.app][org] = params.dropTargets; 394 } 395 }; 396 397 ZmOrganizer.sortCompare = function(organizerA, organizerB) {}; 398 399 /** 400 * nulls value that is the default color for the type. 401 * @param value 402 */ 403 ZmOrganizer.getColorValue = 404 function(value, type) { 405 // no need to save color if missing or default 406 if (value == ZmOrganizer.DEFAULT_COLOR[type]) { 407 return null; 408 } 409 410 return value; 411 }; 412 413 /** 414 * Creates an organizer via <code><CreateFolderRequest></code>. Attribute pairs can 415 * be passed in and will become attributes of the folder node in the request. 416 * 417 * @param {Hash} params a hash of parameters 418 */ 419 ZmOrganizer.create = 420 function(params) { 421 var jsonObj = {CreateFolderRequest:{_jsns:"urn:zimbraMail"}}; 422 var folder = jsonObj.CreateFolderRequest.folder = {}; 423 var errorCallback = params.errorCallback || new AjxCallback(null, ZmOrganizer._handleErrorCreate, params); 424 var type = params.type; 425 426 // set attributes 427 params.view = params.view || ZmOrganizer.VIEWS[type] ? ZmOrganizer.VIEWS[type][0] : null; 428 for (var i in params) { 429 if (i == "type" || i == "errorCallback" || i == "account") { continue; } 430 431 var value = params[i]; 432 if (value) { 433 folder[i] = value; 434 } 435 } 436 //adding support to asyncMode == false didn't eventually help me, but why not keep it? 437 var asyncMode = params.asyncMode === undefined ? true : params.asyncMode; //default is true 438 439 return appCtxt.getAppController().sendRequest({ 440 jsonObj: jsonObj, 441 asyncMode: asyncMode, 442 accountName: (params.account && params.account.name), 443 callback: params.callback, 444 callbackAfterNotifications: params.callbackAfterNotifications, 445 errorCallback: errorCallback 446 }); 447 }; 448 449 /** 450 * @private 451 */ 452 ZmOrganizer._handleErrorCreate = 453 function(params, ex) { 454 if (!params.url && !params.name) { return false; } 455 456 var msg; 457 if (params.name && (ex.code == ZmCsfeException.MAIL_ALREADY_EXISTS)) { 458 var type = appCtxt.getFolderTree(appCtxt.getActiveAccount()).getFolderTypeByName(params.name); 459 msg = AjxMessageFormat.format(ZmMsg.errorAlreadyExists, [params.name, type.toLowerCase()]); 460 } else if (params.url) { 461 var errorMsg = (ex.code == ZmCsfeException.SVC_RESOURCE_UNREACHABLE) ? ZmMsg.feedUnreachable : ZmMsg.feedInvalid; 462 msg = AjxMessageFormat.format(errorMsg, params.url); 463 } 464 465 if (msg) { 466 ZmOrganizer._showErrorMsg(msg); 467 return true; 468 } 469 470 return false; 471 }; 472 473 /** 474 * @private 475 */ 476 ZmOrganizer._showErrorMsg = 477 function(msg) { 478 var msgDialog = appCtxt.getMsgDialog(); 479 msgDialog.reset(); 480 msgDialog.setMessage(AjxStringUtil.htmlEncode(msg), DwtMessageDialog.CRITICAL_STYLE); 481 msgDialog.popup(); 482 }; 483 484 /** 485 * Gets the folder. 486 * 487 * @param {String} id the folder id 488 * @param {AjxCallback} callback the callback 489 * @param {ZmBatchCommand} batchCmd the batch command or <code>null</code> for none 490 */ 491 ZmOrganizer.getFolder = 492 function(id, callback, batchCmd) { 493 var jsonObj = {GetFolderRequest:{_jsns:"urn:zimbraMail"}}; 494 var request = jsonObj.GetFolderRequest; 495 request.folder = {l:id}; 496 var respCallback = new AjxCallback(null, ZmOrganizer._handleResponseGetFolder, [callback]); 497 if (batchCmd) { 498 batchCmd.addRequestParams(jsonObj, respCallback); 499 } else { 500 appCtxt.getRequestMgr().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback}); 501 } 502 }; 503 504 /** 505 * @private 506 */ 507 ZmOrganizer._handleResponseGetFolder = 508 function(callback, result) { 509 var resp = result.getResponse().GetFolderResponse; 510 var folderObj = (resp && resp.folder && resp.folder[0]) || 511 (resp && resp.link && resp.link[0]); 512 var folder; 513 if (folderObj) { 514 folder = appCtxt.getById(folderObj.id); 515 if (folder) { 516 folder.clearShares(); 517 folder._setSharesFromJs(folderObj); 518 } else { 519 var parent = appCtxt.getById(folderObj.l); 520 folder = ZmFolderTree.createFromJs(parent, folderObj, appCtxt.getFolderTree(), "folder"); 521 } 522 } 523 if (callback) { 524 callback.run(folder); 525 } 526 }; 527 528 /** 529 * Gets the folder. 530 * 531 * @param {AjxCallback} callback the callback 532 * @param {ZmBatchCommand} batchCmd the batch command or <code>null</code> for none 533 */ 534 ZmOrganizer.prototype.getFolder = 535 function(callback, batchCmd) { 536 ZmOrganizer.getFolder(this.id, callback, batchCmd); 537 }; 538 539 540 // Static methods 541 542 /** 543 * Gets the view name by organizer type. 544 * 545 * @param {String} organizerType the organizer type 546 * @return {String} the view 547 */ 548 ZmOrganizer.getViewName = 549 function(organizerType) { 550 return ZmOrganizer.VIEWS[organizerType][0]; 551 }; 552 553 /** 554 * Checks an organizer (folder or tag) offlineSyncInterval for validity. 555 * 556 * @param {String} value offlineSyncInterval 557 * @return {String} <code>null</code> if the offlineSyncInterval is valid or an error message if the name is invalid 558 */ 559 ZmOrganizer.checkWebOfflineSyncDays = 560 function(value) { 561 if (isNaN(value)) { return ZmMsg.invalidFolderSyncInterval; } 562 var interval = parseInt(value); 563 if (interval < 0 || interval > 30) { 564 return ZmMsg.invalidFolderSyncInterval; 565 } 566 return null; 567 }; 568 569 /** 570 * Checks an organizer (folder or tag) name for validity. 571 * 572 * @param {String} name an organizer name 573 * @return {String} <code>null</code> if the name is valid or an error message if the name is invalid 574 */ 575 ZmOrganizer.checkName = 576 function(name) { 577 if (name.length == 0) { return ZmMsg.nameEmpty; } 578 579 if (name.length > ZmOrganizer.MAX_NAME_LENGTH) { 580 return AjxMessageFormat.format(ZmMsg.nameTooLong, ZmOrganizer.MAX_NAME_LENGTH); 581 } 582 583 if (!ZmOrganizer.VALID_NAME_RE.test(name)) { 584 return AjxMessageFormat.format(ZmMsg.errorInvalidName, AjxStringUtil.htmlEncode(name)); 585 } 586 587 return null; 588 }; 589 590 /** 591 * Checks a URL (a folder or calendar feed, for example) for validity. 592 * 593 * @param {String} url a URL 594 * @return {String} <code>null</code> if valid or an error message 595 */ 596 ZmOrganizer.checkUrl = 597 function(url) { 598 // TODO: be friendly and prepend "http://" when it's missing 599 if (!url.match(/^[a-zA-Z]+:\/\/.*$/i)) { 600 return ZmMsg.errorUrlMissing; 601 } 602 603 return null; 604 }; 605 606 /** 607 * @private 608 */ 609 ZmOrganizer.checkSortArgs = 610 function(orgA, orgB) { 611 if (!orgA && !orgB) return 0; 612 if (orgA && !orgB) return 1; 613 if (!orgA && orgB) return -1; 614 return null; 615 }; 616 617 /** 618 * @private 619 */ 620 ZmOrganizer.checkColor = 621 function(color) { 622 return ((color != null) && (color >= 0 && color <= ZmOrganizer.MAX_COLOR)) 623 ? color : ZmOrganizer.ORG_DEFAULT_COLOR; 624 }; 625 626 /** 627 * Gets the system ID for the given system ID and account. Unless this 628 * is a child account, the system ID is returned unchanged. For child 629 * accounts, the ID consists of the account ID and the local ID. 630 * 631 * @param {int} id the ID of a system organizer 632 * @param {ZmZimbraAccount} account the account 633 * @param {Boolean} force <code>true</code> to generate the fully qualified ID even if this is the main account 634 * @return {String} the ID 635 */ 636 ZmOrganizer.getSystemId = 637 function(id, account, force) { 638 account = account || appCtxt.getActiveAccount(); 639 if ((account && !account.isMain) || force) { 640 return ((typeof(id) == "string") && (id.indexOf(":") != -1) || !id) 641 ? id : ([account.id, id].join(":")); 642 } 643 return id; 644 }; 645 646 /** 647 * Normalizes the id by stripping the account ID portion from a system ID for a child account, which 648 * can then be used to check against known system IDs. Any non-system ID is 649 * returned unchanged (if type is provided). 650 * 651 * @param {String} id ID of an organizer 652 * @param {constant} type the type of organizer 653 * @return {String} the resulting id 654 */ 655 ZmOrganizer.normalizeId = 656 function(id, type) { 657 if (typeof(id) != "string") { return id; } 658 var idx = id.indexOf(":"); 659 var localId = (idx == -1) ? id : id.substr(idx + 1); 660 return (type && (localId >= ZmOrganizer.FIRST_USER_ID[type])) ? id : localId; 661 }; 662 663 /** 664 * Parses an id into an object with fields for account and normalized id 665 * 666 * @param {String} id the ID of an organizer 667 * @param {Object} result an optional object in which the result is stored 668 * @return {Object} the resulting ID 669 */ 670 ZmOrganizer.parseId = 671 function(id, result) { 672 var ac = window.parentAppCtxt || window.appCtxt; 673 674 result = result || {}; 675 if (id == null) { return result; } 676 var idx = (typeof id == "string") ? id.indexOf(":") : -1; 677 if (idx == -1) { 678 result.account = ac.accountList.mainAccount; 679 result.id = id; 680 } else { 681 result.acctId = id.substring(0, idx); 682 result.account = ac.accountList.getAccount(result.acctId); 683 result.id = id.substr(idx + 1); 684 } 685 return result; 686 }; 687 688 // Public methods 689 690 /** 691 * Gets the name of this organizer. 692 * 693 * @param {Boolean} showUnread <code>true</code> to display the number of unread items (in parens) 694 * @param {int} maxLength the length (in chars) to truncate the name to 695 * @param {Boolean} noMarkup if <code>true</code>, don't return any HTML 696 * @param {Boolean} useSystemName if <code>true</code>, don't use translated version of name 697 * @return {String} the name 698 */ 699 ZmOrganizer.prototype.getName = 700 function(showUnread, maxLength, noMarkup, useSystemName, useOwnerName, defaultRootType) { 701 if (this.nId == ZmFolder.ID_ROOT) { 702 var type = defaultRootType || this.type; 703 return (ZmOrganizer.LABEL[type]) 704 ? ZmMsg[ZmOrganizer.LABEL[type]] : ""; 705 } 706 var name = (useSystemName && this._systemName) || (useOwnerName && this.oname) || this.name || ""; 707 if (ZmOrganizer.PATH_IN_NAME[this.type] && this.path) { 708 name = [this.path, name].join("/"); 709 } 710 name = (maxLength && name.length > maxLength) 711 ? name.substring(0, maxLength - 3) + "..." : name; 712 return this._markupName(name, showUnread, noMarkup); 713 }; 714 715 /** 716 * Gets the full path as a string. 717 * 718 * @param {Boolean} includeRoot <code>true</code> to include root name at the beginning of the path 719 * @param {Boolean} showUnread <code>true</code> to display the number of unread items (in parens) 720 * @param {int} maxLength the length (in chars) to truncate the name to 721 * @param {Boolean} noMarkup if <code>true</code>, do not return any HTML 722 * @param {Boolean} useSystemName if <code>true</code>, use untranslated version of system folder names 723 * @return {String} the path 724 */ 725 ZmOrganizer.prototype.getPath = 726 function(includeRoot, showUnread, maxLength, noMarkup, useSystemName, useOwnerName) { 727 var parent = this.parent; 728 var path = this.getName(showUnread, maxLength, noMarkup, useSystemName, useOwnerName); 729 while (parent && ((parent.nId != ZmOrganizer.ID_ROOT) || includeRoot)) { 730 path = parent.getName(showUnread, maxLength, noMarkup, useSystemName, useOwnerName) + ZmFolder.SEP + path; 731 parent = parent.parent; 732 } 733 734 return path; 735 }; 736 737 /** 738 * Gets the tooltip. The tooltip shows number of unread items, total messages and the total size. 739 * 740 * @param {Boolean} force if <code>true</code>, don't use cached tooltip 741 * @return {String} the tooltip 742 */ 743 ZmOrganizer.prototype.getToolTip = 744 function(force) { 745 if (this.noTooltip) { 746 return null; 747 } 748 if (!this._tooltip || force) { 749 var itemText = this._getItemsText(); 750 var unreadLabel = this._getUnreadLabel(); 751 var subs = {name:this.name, itemText:itemText, numTotal:this.numTotal, sizeTotal:this.sizeTotal, numUnread:this.numUnread, unreadLabel:unreadLabel}; 752 this._tooltip = AjxTemplate.expand("share.App#FolderTooltip", subs); 753 } 754 return this._tooltip; 755 }; 756 757 /** 758 * Gets the full path, suitable for use in search expressions. 759 * 760 * @return {String} the path 761 */ 762 ZmOrganizer.prototype.getSearchPath = 763 function(useOwnerName) { 764 return (this.nId != ZmOrganizer.ID_ROOT) 765 ? this.getPath(null, null, null, true, true, useOwnerName) : "/"; 766 }; 767 768 /** 769 * Gets the URL. 770 * 771 * @return {String} the URL 772 * 773 * @deprecated use {@link getRestUrl} 774 */ 775 ZmOrganizer.prototype.getUrl = 776 function() { 777 return this.getRestUrl(); 778 }; 779 780 /** 781 * Gets the sync URL. 782 * 783 * @return {String} the URL 784 */ 785 ZmOrganizer.prototype.getSyncUrl = 786 function() { 787 return url; 788 }; 789 790 /** 791 * Gets the remote ID. 792 * 793 * @return {String} the ID 794 */ 795 ZmOrganizer.prototype.getRemoteId = 796 function() { 797 if (!this._remoteId) { 798 this._remoteId = (this.isRemote() && this.zid && this.rid) 799 ? (this.zid + ":" + this.rid) 800 : this.id; 801 } 802 return this._remoteId; 803 }; 804 805 806 /** 807 * Gets the REST URL. 808 * 809 * @return {String} the URL 810 */ 811 ZmOrganizer.prototype.getRestUrl = 812 function(noRemote) { 813 814 var restUrl = appCtxt.get(ZmSetting.REST_URL); 815 if (restUrl && (!this.isRemote() || noRemote)) { //for remote - this does not work. either use this.restUrl (if set, which is for shared folder, but not for sub-folders) or call _generateRestUrl which seems to work for subfodlers of shared as well. 816 var path = AjxStringUtil.urlEncode(this.getSearchPath()).replace("#","%23").replace(";", "%3B"); // User may type in a # in a folder name, but that's not ok for our urls 817 // return REST URL as seen by the GetInfoResponse 818 return ([restUrl, "/", path].join("")); 819 } 820 821 // return REST URL as seen by server - this is the remote (isRemote() true) case - shared folder. 822 if (this.restUrl) { 823 return this.restUrl; 824 } 825 826 // if server doesn't tell us what URL to use, do our best to generate 827 var url = this._generateRestUrl(); 828 DBG.println(AjxDebug.DBG3, "NO REST URL FROM SERVER. GENERATED URL: " + url); 829 830 return url; 831 }; 832 833 /** 834 * Gets the OWNER'S REST URL,used to fetch resturl of shared folders. 835 * 836 * @return {String} the URL 837 */ 838 ZmOrganizer.prototype.getOwnerRestUrl = 839 function(){ 840 var restUrl=this.restUrl; 841 var path = AjxStringUtil.urlEncode(this.oname).replace("#","%23"); 842 843 // return REST URL as seen by the GetInfoResponse 844 return ([restUrl, "/", path].join("")); 845 }; 846 847 ZmOrganizer.prototype._generateRestUrl = 848 function() { 849 var loc = document.location; 850 var uname = appCtxt.get(ZmSetting.USERNAME); 851 var host = loc.host; 852 var m = uname.match(/^(.*)@(.*)$/); 853 854 host = (m && m[2]) || host; 855 856 // REVISIT: What about port? For now assume other host uses same port 857 if (loc.port && loc.port != 80) { 858 host = host + ":" + loc.port; 859 } 860 861 var path = AjxStringUtil.urlEncode(this.getSearchPath()).replace("#","%23"); // User may type in a # in a folder name, but that's not ok for our urls 862 863 return [ 864 loc.protocol, "//", host, "/service/user/", uname, "/", path 865 ].join(""); 866 }; 867 868 /** 869 * Gets the account. 870 * 871 * @return {ZmZimbraAccount} the account 872 */ 873 ZmOrganizer.prototype.getAccount = 874 function() { 875 if (appCtxt.multiAccounts) { 876 if (!this.account) { 877 this.account = ZmOrganizer.parseId(this.id).account; 878 } 879 // bug 46364: 880 // still no account?! Must be remote organizer, keep checking parent 881 if (!this.account) { 882 var parent = this.parent; 883 while (parent && !this.account) { 884 this.account = parent.getAccount(); 885 parent = parent.parent; 886 } 887 } 888 return this.account; 889 } 890 891 return (this.account || appCtxt.accountList.mainAccount); 892 }; 893 894 /** 895 * Gets the shares. 896 * 897 * @return {Array} an array of shares 898 */ 899 ZmOrganizer.prototype.getShares = 900 function() { 901 return this.shares; 902 }; 903 904 /** 905 * Adds the share. 906 * 907 * @param {Object} share the share to add 908 */ 909 ZmOrganizer.prototype.addShare = 910 function(share) { 911 this.shares = this.shares || []; 912 this.shares.push(share); 913 914 var curAcct = appCtxt.getActiveAccount(); 915 var curZid = curAcct && curAcct.id; 916 var shareId = share.grantee && share.grantee.id; 917 if (shareId && (shareId == curZid)) { 918 this._mainShare = share; 919 } 920 }; 921 922 /** 923 * Clears all shares. 924 * 925 */ 926 ZmOrganizer.prototype.clearShares = 927 function() { 928 if (this.shares && this.shares.length) { 929 for (var i = 0; i < this.shares.length; i++) { 930 this.shares[i] = null; 931 } 932 } 933 this.shares = null; 934 this._mainShare = null; 935 }; 936 937 /** 938 * Gets the share granted to the current user. 939 * 940 * @return {String} the main share 941 */ 942 ZmOrganizer.prototype.getMainShare = 943 function() { 944 if (!this._mainShare) { 945 var curAcct = appCtxt.getActiveAccount(); 946 var curZid = curAcct && curAcct.id; 947 if (curZid && this.shares && this.shares.length) { 948 for (var i = 0; i < this.shares.length; i++) { 949 var share = this.shares[i]; 950 var id = share && share.grantee && share.grantee.id; 951 if (id && id == curZid) { 952 this._mainShare = share; 953 break; 954 } 955 } 956 } 957 } 958 return this._mainShare; 959 }; 960 961 /** 962 * Checks if the organizer supports sharing. 963 * 964 * @return {Boolean} <code>true</code> if the organizer supports sharing 965 */ 966 ZmOrganizer.prototype.supportsSharing = 967 function() { 968 // overload per organizer type 969 return true; 970 }; 971 972 /** 973 * Checks if the organizer supports pulbic access. 974 * 975 * @return {Boolean} <code>true</code> if the organizer supports public access 976 */ 977 ZmOrganizer.prototype.supportsPublicAccess = 978 function() { 979 // overload per organizer type 980 return true; 981 }; 982 983 /** 984 * Checks if the organizer supports private permission. 985 * 986 * @return {Boolean} <code>true</code> if the organizer supports private permission 987 */ 988 ZmOrganizer.prototype.supportsPrivatePermission = 989 function() { 990 // overload per organizer type 991 return false; 992 }; 993 994 /** 995 * Gets the icon. 996 * 997 * @return {String} the icon 998 */ 999 ZmOrganizer.prototype.getIcon = function() {}; 1000 1001 /** 1002 * Gets the color of the organizer 1003 * 1004 * @return {String} the color 1005 */ 1006 ZmOrganizer.prototype.getColor = 1007 function() { 1008 return this.rgb || ZmOrganizer.COLOR_VALUES[this.color]; 1009 } 1010 1011 1012 /** 1013 * Gets the icon with color 1014 * 1015 * @return {String} the icon 1016 */ 1017 ZmOrganizer.prototype.getIconWithColor = 1018 function() { 1019 var icon = this.getIcon() || ""; 1020 var color = this.getColor(); 1021 return color ? [icon,color].join(",color=") : icon; 1022 }; 1023 1024 // Actions 1025 1026 /** 1027 * Renames the organizer. 1028 * 1029 * @param {String} name the name 1030 * @param {AjxCallback} callback the callback 1031 * @param {AjxCallback} errorCallback the error callback 1032 * @param {ZmBatchCommand} batchCmd the batch command 1033 */ 1034 ZmOrganizer.prototype.rename = 1035 function(name, callback, errorCallback, batchCmd) { 1036 if (name == this.name) { return; } 1037 var params = { 1038 action: "rename", 1039 attrs: {name: name}, 1040 callback: callback, 1041 errorCallback: errorCallback, 1042 batchCmd: batchCmd 1043 }; 1044 this._organizerAction(params); 1045 }; 1046 1047 /** 1048 * Sets the web offline sync interval. 1049 * 1050 * @param {String} interval the web offline sync interval 1051 * @param {AjxCallback} callback the callback 1052 * @param {AjxCallback} errorCallback the error callback 1053 * @param {ZmBatchCommand} batchCmd optional batch command 1054 */ 1055 ZmOrganizer.prototype.setOfflineSyncInterval = 1056 function(interval, callback, errorCallback, batchCmd) { 1057 if (this.webOfflineSyncDays == interval) { return; } 1058 1059 this._organizerAction({action: "webofflinesyncdays", attrs: {numDays: interval}, callback: callback, 1060 errorCallback: errorCallback, batchCmd: batchCmd}); 1061 }; 1062 1063 /** 1064 * Sets the color. 1065 * 1066 * @param {String} color the color 1067 * @param {AjxCallback} callback the callback 1068 * @param {AjxCallback} errorCallback the error callback 1069 * @param {ZmBatchCommand} batchCmd optional batch command 1070 */ 1071 ZmOrganizer.prototype.setColor = 1072 function(color, callback, errorCallback, batchCmd) { 1073 var color = ZmOrganizer.checkColor(color); 1074 if (!this.isColorChanged(color)) { return; } 1075 1076 this._organizerAction({action: "color", attrs: {color: color}, callback: callback, 1077 errorCallback: errorCallback, batchCmd: batchCmd}); 1078 }; 1079 1080 /** 1081 * Sets the RGB color. 1082 * 1083 * @param {Object} rgb the rgb 1084 * @param {AjxCallback} callback the callback 1085 * @param {AjxCallback} errorCallback the error callback 1086 * @param {ZmBatchCommand} batchCmd optional batch command 1087 */ 1088 ZmOrganizer.prototype.setRGB = function(rgb, callback, errorCallback, batchCmd) { 1089 if (!this.isColorChanged(rgb)) { return; } 1090 this._organizerAction({action: "color", attrs: {rgb: rgb}, callback: callback, 1091 errorCallback: errorCallback, batchCmd: batchCmd}); 1092 }; 1093 1094 1095 ZmOrganizer.prototype.getRetentionPolicy = 1096 function(policyElement) { 1097 var policy = null; 1098 if (this.retentionPolicy && this.retentionPolicy[0] && this.retentionPolicy[0][policyElement] && 1099 this.retentionPolicy[0][policyElement][0] && this.retentionPolicy[0][policyElement][0].policy && 1100 this.retentionPolicy[0][policyElement][0].policy[0]) { 1101 policy = this.retentionPolicy[0][policyElement][0].policy[0]; 1102 } 1103 return policy; 1104 } 1105 1106 ZmOrganizer.prototype.getRetentionPolicyLifetimeMsec = 1107 function(policy) { 1108 if (policy) { 1109 // Apply the keep (retention) period 1110 var lifetime = policy.lifetime; 1111 var amount = parseInt(lifetime); 1112 // Intervals taken from DateUtil.java. 1113 var interval = lifetime.slice(lifetime.length-1); 1114 var lifetimeMsec = 0; 1115 switch (interval) { 1116 case "d": lifetimeMsec = amount * AjxDateUtil.MSEC_PER_DAY; break; 1117 case "h": lifetimeMsec = amount * AjxDateUtil.MSEC_PER_HOUR; break; 1118 case "m": lifetimeMsec = amount * AjxDateUtil.MSEC_PER_MINUTE; break; 1119 case "s": lifetimeMsec = amount * 1000; break; 1120 case "ms": lifetimeMsec = amount; break; 1121 default : lifetimeMsec = amount * 1000; break; 1122 } 1123 } 1124 return lifetimeMsec; 1125 } 1126 1127 /** 1128 * Sets the Retention Policy. 1129 * 1130 * @param {Object} retentionPolicy the new retention policy 1131 * @param {AjxCallback} callback the callback 1132 * @param {AjxCallback} errorCallback the error callback 1133 * @param {ZmBatchCommand} batchCmd optional batch command 1134 */ 1135 ZmOrganizer.prototype.setRetentionPolicy = function(newRetentionPolicy, callback, errorCallback, batchCmd) { 1136 var keepPolicy = this.getRetentionPolicy(ZmOrganizer.RETENTION_KEEP); 1137 var purgePolicy = this.getRetentionPolicy(ZmOrganizer.RETENTION_PURGE); 1138 if (!this.policiesDiffer(keepPolicy, newRetentionPolicy.keep) && 1139 !this.policiesDiffer(purgePolicy, newRetentionPolicy.purge)) { 1140 // No updated policy specified or no changes. 1141 return; 1142 } 1143 1144 var cmd = ZmOrganizer.SOAP_CMD[this.type] + "Request"; 1145 var request = { 1146 _jsns: "urn:zimbraMail", 1147 action : { 1148 op: "retentionpolicy", 1149 id: this.id, 1150 retentionPolicy: { 1151 keep: {}, 1152 purge: {} 1153 } 1154 } 1155 }; 1156 var jsonObj = {}; 1157 jsonObj[cmd] = request; 1158 1159 var retentionNode = request.action.retentionPolicy; 1160 1161 if (newRetentionPolicy.keep) { 1162 this._addPolicy(retentionNode.keep, newRetentionPolicy.keep); 1163 } 1164 if (newRetentionPolicy.purge) { 1165 this._addPolicy(retentionNode.purge, newRetentionPolicy.purge); 1166 } 1167 1168 if (batchCmd) { 1169 batchCmd.addRequestParams(jsonObj, callback, errorCallback); 1170 } 1171 else { 1172 var accountName; 1173 if (appCtxt.multiAccounts) { 1174 accountName = (this.account) 1175 ? this.account.name : appCtxt.accountList.mainAccount.name; 1176 } 1177 appCtxt.getAppController().sendRequest({ 1178 jsonObj: jsonObj, 1179 asyncMode: true, 1180 accountName: accountName, 1181 callback: callback, 1182 errorCallback: errorCallback 1183 }); 1184 } 1185 1186 }; 1187 1188 ZmOrganizer.prototype.policiesDiffer = 1189 function(policyA, policyB) { 1190 var differ = false; 1191 if ((policyA && !policyB) || (!policyA && policyB)) { 1192 differ = true; 1193 } else if (policyA) { 1194 // Old and new specified 1195 if (policyA.type != policyB.type) { 1196 differ = true; 1197 } else { 1198 if (policyA.type == "user") { 1199 differ = policyA.lifetime != policyB.lifetime; 1200 } else { 1201 // System policy 1202 differ = policyA.id != policyB.id; 1203 } 1204 } 1205 } 1206 return differ; 1207 } 1208 1209 ZmOrganizer.prototype._addPolicy = 1210 function(node, policy) { 1211 var policyNode = node.policy = {}; 1212 for (var attr in policy) { 1213 if (AjxEnv.isIE) { 1214 policy[attr] += ""; //To string 1215 } 1216 1217 policyNode[attr] = policy[attr]; 1218 } 1219 }; 1220 1221 /** 1222 * Returns color number b/w 0-9 for a given color code 1223 * 1224 * @param {String} color The color (usually in #43eded format 1225 * @return {int} Returns 0-9 for a standard color and returns -1 for custom color 1226 */ 1227 ZmOrganizer.getStandardColorNumber = 1228 function(color) { 1229 if (String(color).match(/^#/)) { 1230 var len = ZmOrganizer.COLOR_VALUES.length; 1231 for(var i =0; i < len; i++) { 1232 var currentVal = ZmOrganizer.COLOR_VALUES[i]; 1233 if(currentVal && currentVal == color) { 1234 return i; 1235 } 1236 } 1237 } else if(color <= 9 && color >= 0) { 1238 return color; 1239 } 1240 return -1; 1241 }; 1242 1243 /** 1244 * Returns true if the color is changed 1245 * 1246 * @param {String/int} color The color (usually in #rgb format or numeric color code 1247 * @return {Boolean} Returns true if the color is changed 1248 */ 1249 ZmOrganizer.prototype.isColorChanged = 1250 function(color) { 1251 var isNewColorCustom = ZmOrganizer.getStandardColorNumber(color) === -1, 1252 isPrevColorCustom = this.isColorCustom; 1253 if ((isNewColorCustom && !isPrevColorCustom) || 1254 (!isNewColorCustom && isPrevColorCustom) ) { 1255 //Custom changed to standard or standard changed to custom 1256 return true; 1257 } 1258 else if (isNewColorCustom && isPrevColorCustom) { 1259 //If both are custom colors check the rgb codes 1260 return color != this.rgb; 1261 } 1262 else if (!isNewColorCustom && !isPrevColorCustom){ 1263 //If both are standard check the numeric color codes 1264 return color != this.color; 1265 } 1266 //default fallback 1267 return false; 1268 }; 1269 1270 /** 1271 * Updates the folder. Although it is possible to use this method to change just about any folder 1272 * attribute, it should only be used to set multiple attributes at once since it 1273 * has extra overhead on the server. 1274 * 1275 * @param {Hash} attrs the attributes 1276 */ 1277 ZmOrganizer.prototype.update = 1278 function(attrs) { 1279 this._organizerAction({action: "update", attrs: attrs}); 1280 }; 1281 1282 /** 1283 * Assigns the organizer a new parent, moving it within its tree. 1284 * 1285 * @param {ZmOrganizer} newParent the new parent of this organizer 1286 * @param {boolean} noUndo if true, action is not undoable 1287 */ 1288 ZmOrganizer.prototype.move = 1289 function(newParent, noUndo, batchCmd) { 1290 1291 var newId = (newParent.nId > 0) 1292 ? newParent.id 1293 : ZmOrganizer.getSystemId(ZmOrganizer.ID_ROOT); 1294 1295 if ((newId == this.id || newId == this.parent.id) || 1296 (this.type == ZmOrganizer.FOLDER && (ZmOrganizer.normalizeId(newId, this.type) == ZmFolder.ID_SPAM)) || 1297 (newParent.isChildOf(this))) 1298 { 1299 return; 1300 } 1301 var params = {}; 1302 params.batchCmd = batchCmd; 1303 params.actionTextKey = 'actionMoveOrganizer'; 1304 params.orgName = this.getName(false, false, true, false, false, this.type); 1305 if (newId == ZmOrganizer.ID_TRASH) { 1306 params.actionArg = ZmMsg.trash; 1307 params.action = "trash"; 1308 params.noUndo = noUndo; 1309 } 1310 else { 1311 if (newParent.account && newParent.account.isLocal()) { 1312 newId = [ZmAccount.LOCAL_ACCOUNT_ID, newId].join(":"); 1313 } 1314 params.actionArg = newParent.getName(false, false, true, false, false, this.type); 1315 params.action = "move"; 1316 params.attrs = {l: newId}; 1317 params.noUndo = noUndo; 1318 } 1319 this._organizerAction(params); 1320 }; 1321 1322 /** 1323 * Deletes an organizer. If it's a folder, the server deletes any contents and/or 1324 * subfolders. If the organizer is "Trash" or "Spam", the server deletes and re-creates the 1325 * folder. In that case, we do not bother to remove it from the UI (and we ignore 1326 * creates on system folders). 1327 * 1328 */ 1329 ZmOrganizer.prototype._delete = 1330 function(batchCmd) { 1331 DBG.println(AjxDebug.DBG1, "deleting: " + AjxStringUtil.htmlEncode(this.name) + ", ID: " + this.id); 1332 var isEmptyOp = ((this.type == ZmOrganizer.FOLDER || this.type == ZmOrganizer.ADDRBOOK || this.type == ZmOrganizer.BRIEFCASE) && 1333 (this.nId == ZmFolder.ID_SPAM || this.nId == ZmFolder.ID_TRASH)); 1334 // make sure we're not deleting a system object (unless we're emptying SPAM or TRASH) 1335 if (this.isSystem() && !isEmptyOp) return; 1336 1337 var action = isEmptyOp ? "empty" : "delete"; 1338 this._organizerAction({action: action, batchCmd: batchCmd}); 1339 }; 1340 1341 /** 1342 * Empties the organizer. 1343 * 1344 * @param {Boolean} doRecursive <code>true</code> to recursively empty the organizer 1345 * @param {ZmBatchCommand} batchCmd the batch command 1346 * @param {Object} callback 1347 * @param {number} timeout the timeout(in seconds) 1348 * @param {AjxCallback} errorCallback the callback to run after timeout 1349 * @param {Boolean} noBusyOverlay if <code>true</code>, do not show busy overlay 1350 */ 1351 ZmOrganizer.prototype.empty = 1352 function(doRecursive, batchCmd, callback, timeout, errorCallback, noBusyOverlay) { 1353 doRecursive = doRecursive || false; 1354 1355 var isEmptyOp = ((this.type == ZmOrganizer.FOLDER || this.type == ZmOrganizer.ADDRBOOK) && 1356 (this.nId == ZmFolder.ID_SPAM || 1357 this.nId == ZmFolder.ID_TRASH || 1358 this.nId == ZmFolder.ID_CHATS || 1359 this.nId == ZmOrganizer.ID_SYNC_FAILURES)); 1360 1361 // make sure we're not emptying a system object (unless it's SPAM/TRASH/SYNCFAILURES) 1362 if (this.isSystem() && !isEmptyOp) { return; } 1363 1364 var params = { 1365 action: "empty", 1366 batchCmd: batchCmd, 1367 callback: callback, 1368 timeout: timeout, 1369 errorCallback: errorCallback, 1370 noBusyOverlay: noBusyOverlay 1371 }; 1372 params.attrs = (this.nId == ZmFolder.ID_TRASH) 1373 ? {recursive:true} 1374 : {recursive:doRecursive}; 1375 1376 if (this.isRemote()) { 1377 params.id = this.getRemoteId(); 1378 } 1379 1380 this._organizerAction(params); 1381 }; 1382 1383 /** 1384 * Marks all items as "read". 1385 * 1386 * @param {ZmBatchCommand} batchCmd the batch command 1387 */ 1388 ZmOrganizer.prototype.markAllRead = 1389 function(batchCmd) { 1390 var id = this.isRemote() ? this.getRemoteId() : null; 1391 this._organizerAction({action: "read", id: id, attrs: {l: this.id}, batchCmd:batchCmd}); 1392 }; 1393 1394 /** 1395 * Synchronizes the organizer. 1396 * 1397 */ 1398 ZmOrganizer.prototype.sync = 1399 function() { 1400 this._organizerAction({action: "sync"}); 1401 }; 1402 1403 // Notification handling 1404 1405 /** 1406 * Handles delete notification. 1407 * 1408 */ 1409 ZmOrganizer.prototype.notifyDelete = 1410 function() { 1411 // select next reasonable organizer if the currently selected organizer is 1412 // the one being deleted or is a descendent of the one being deleted 1413 var tc = appCtxt.getOverviewController().getTreeController(this.type); 1414 var treeView = tc && tc.getTreeView(appCtxt.getCurrentApp().getOverviewId()); 1415 1416 // treeview returns array of organizers for checkbox style trees 1417 var organizers = treeView && treeView.getSelected(); 1418 if (organizers) { 1419 if (!(organizers instanceof Array)) organizers = [organizers]; 1420 for (var i = 0; i < organizers.length; i++) { 1421 var organizer = organizers[i]; 1422 if (organizer && (organizer == this || organizer.isChildOf(this))) { 1423 var folderId = this.parent.id; 1424 if (this.parent.nId == ZmOrganizer.ID_ROOT) { 1425 folderId = ZmOrganizer.getSystemId(this.getDefaultFolderId()); 1426 } 1427 var skipNotify = false; 1428 treeView.setSelected(folderId, skipNotify); 1429 } 1430 } 1431 } 1432 1433 // perform actual delete 1434 this.deleteLocal(); 1435 this._notify(ZmEvent.E_DELETE); 1436 }; 1437 1438 /** 1439 * Handles create notification. 1440 */ 1441 ZmOrganizer.prototype.notifyCreate = function() {}; 1442 1443 /** 1444 * Handles modifications to fields that organizers have in general. Note that 1445 * the notification object may contain multiple notifications. 1446 * 1447 * @param {Object} obj a "modified" notification 1448 * @param {Hash} details the event details 1449 */ 1450 ZmOrganizer.prototype.notifyModify = 1451 function(obj, details) { 1452 var doNotify = false; 1453 var details = details || {}; 1454 var fields = {}; 1455 if (obj.name != null && (this.name != obj.name || this.id != obj.id)) { 1456 if (obj.id == this.id) { 1457 details.oldName = this.name; 1458 this.name = obj.name; 1459 fields[ZmOrganizer.F_NAME] = true; 1460 this.parent.children.sort(eval(ZmTreeView.COMPARE_FUNC[this.type])); 1461 } else { 1462 // rename of a remote folder 1463 details.newName = obj.name; 1464 fields[ZmOrganizer.F_RNAME] = true; 1465 } 1466 doNotify = true; 1467 } 1468 if (obj.u != null && this.numUnread != obj.u) { 1469 this.numUnread = obj.u; 1470 fields[ZmOrganizer.F_UNREAD] = true; 1471 doNotify = true; 1472 } 1473 if (obj.n != null && this.numTotal != obj.n) { 1474 this.numTotal = obj.n; 1475 fields[ZmOrganizer.F_TOTAL] = true; 1476 doNotify = true; 1477 } 1478 if (obj.s != null && this.sizeTotal != obj.s) { 1479 this.sizeTotal = obj.s; 1480 fields[ZmOrganizer.F_SIZE] = true; 1481 doNotify = true; 1482 } 1483 if ((obj.rgb != null || obj.color != null) && !obj._isRemote) { 1484 var color = obj.color || obj.rgb; 1485 if (this.isColorChanged(color)) { 1486 this.isColorCustom = obj.rgb != null; 1487 this.color = obj.color; 1488 this.rgb = obj.rgb || ZmOrganizer.COLOR_VALUES[color]; 1489 fields[ZmOrganizer.F_COLOR] = true; 1490 fields[ZmOrganizer.F_RGB] = true; 1491 } 1492 doNotify = true; 1493 } 1494 if (obj.f != null && !obj._isRemote) { 1495 var oflags = this._setFlags().split("").sort().join(""); 1496 var nflags = obj.f.split("").sort().join(""); 1497 if (oflags != nflags) { 1498 this._parseFlags(obj.f); 1499 fields[ZmOrganizer.F_FLAGS] = true; 1500 doNotify = true; 1501 } 1502 } 1503 if (obj.rest != null && this.restUrl != obj.rest && !obj._isRemote) { 1504 this.restUrl = obj.rest; 1505 fields[ZmOrganizer.F_REST_URL] = true; 1506 doNotify = true; 1507 } 1508 // if shares changed, do wholesale replace 1509 if (obj.acl) { 1510 this.clearShares(); 1511 if (obj.acl.grant && obj.acl.grant.length) { 1512 AjxDispatcher.require("Share"); 1513 for (var i = 0; i < obj.acl.grant.length; i++) { 1514 share = ZmShare.createFromJs(this, obj.acl.grant[i]); 1515 this.addShare(share); 1516 } 1517 } 1518 fields[ZmOrganizer.F_SHARES] = true; 1519 doNotify = true; 1520 } 1521 if (obj.perm && obj._isRemote) { 1522 fields[ZmOrganizer.F_PERMS] = true; 1523 doNotify = true; 1524 1525 // clear acl-related flags so they are recalculated 1526 this._isAdmin = this._isReadOnly = this._hasPrivateAccess = null; 1527 } 1528 if (obj.retentionPolicy) { 1529 // Only displayed in a modal dialog - no need to doNotify 1530 if (obj.retentionPolicy[0].keep || obj.retentionPolicy[0].purge) { 1531 this.retentionPolicy = obj.retentionPolicy; 1532 } else { 1533 this.retentionPolicy = null; 1534 } 1535 } 1536 if (obj.hasOwnProperty("webOfflineSyncDays")) { 1537 this.webOfflineSyncDays = obj.webOfflineSyncDays; 1538 } 1539 1540 // Send out composite MODIFY change event 1541 if (doNotify) { 1542 details.fields = fields; 1543 this._notify(ZmEvent.E_MODIFY, details); 1544 } 1545 1546 if (this.parent && obj.l != null && obj.l != this.parent.id) { 1547 var newParent = this._getNewParent(obj.l); 1548 if (newParent) { 1549 this.reparent(newParent); 1550 this._notify(ZmEvent.E_MOVE); 1551 // could be moving search between Folders and Searches - make sure 1552 // it has the correct tree 1553 this.tree = newParent.tree; 1554 } 1555 } 1556 }; 1557 1558 // Local change handling 1559 1560 /** 1561 * Deletes the organizer (local). Cleans up a deleted organizer: 1562 * 1563 * <ul> 1564 * <li>remove from parent's list of children</li> 1565 * <li>remove from item cache</li> 1566 * <li>perform above two steps for each child</li> 1567 * <li>clear list of children</li> 1568 * </ul> 1569 * 1570 */ 1571 ZmOrganizer.prototype.deleteLocal = 1572 function() { 1573 this.parent.children.remove(this); 1574 var a = this.children.getArray(); 1575 var sz = this.children.size(); 1576 for (var i = 0; i < sz; i++) { 1577 var org = a[i]; 1578 if (org) { org.deleteLocal(); } 1579 } 1580 this.children.removeAll(); 1581 }; 1582 1583 /** 1584 * Checks if the organizer has a child with the given name. 1585 * 1586 * @param {String} name the name of the organizer to look for 1587 * @return {Boolean} <code>true</code> if the organizer has a child 1588 */ 1589 ZmOrganizer.prototype.hasChild = 1590 function(name) { 1591 return (this.getChild(name) != null); 1592 }; 1593 1594 /** 1595 * Gets the child with the given name 1596 * 1597 * @param {String} name the name of the organizer to look for 1598 * @return {String} the name of the child or <code>null</code> if no child has the name 1599 */ 1600 ZmOrganizer.prototype.getChild = 1601 function(name) { 1602 name = name ? name.toLowerCase() : ""; 1603 var a = this.children.getArray(); 1604 var sz = this.children.size(); 1605 for (var i = 0; i < sz; i++) { 1606 if (a[i] && a[i].name && (a[i].name.toLowerCase() == name)) { 1607 return a[i]; 1608 } 1609 } 1610 1611 return null; 1612 }; 1613 1614 /** 1615 * Gets the child with the given path 1616 * 1617 * @param {String} path the path of the organizer to look for 1618 * @return {String} the child or <code>null</code> if no child has the path 1619 */ 1620 ZmOrganizer.prototype.getChildByPath = 1621 function(path) { 1622 // get starting organizer 1623 var organizer = this; 1624 if (path.match(/^\//)) { 1625 while (organizer.nId != ZmOrganizer.ID_ROOT) { 1626 organizer = organizer.parent; 1627 } 1628 path = path.substr(1); 1629 } 1630 1631 // if no path, return current organizer 1632 if (path.length == 0) return organizer; 1633 1634 // walk descendent axis to find organizer specified by path 1635 var parts = path.split('/'); 1636 var i = 0; 1637 while (i < parts.length) { 1638 var part = parts[i++]; 1639 var child = organizer.getChild(part); 1640 if (child == null) { 1641 return null; 1642 } 1643 organizer = child; 1644 } 1645 return organizer; 1646 }; 1647 1648 /** 1649 * Changes the parent of this organizer. Note that the new parent passed 1650 * in may be <code>null</code>, which makes this organizer an orphan. 1651 * 1652 * @param {ZmOrganizer} newParent the new parent 1653 */ 1654 ZmOrganizer.prototype.reparent = 1655 function(newParent) { 1656 if (this.parent) { 1657 this.parent.children.remove(this); 1658 } 1659 if (newParent) { 1660 newParent.children.add(this); 1661 } 1662 this.parent = newParent; 1663 }; 1664 1665 /** 1666 * Gets the organizer with the given ID, searching recursively through 1667 * child organizers. The preferred method for getting an organizer by ID 1668 * is to use <code>appCtxt.getById()</code>. 1669 * 1670 * @param {String} id the ID to search for 1671 * @return {ZmOrganizer} the organizer or <code>null</code> if not found 1672 */ 1673 ZmOrganizer.prototype.getById = 1674 function(id) { 1675 if (this.link && id && typeof(id) == "string") { 1676 var ids = id.split(":"); 1677 if (this.zid == ids[0] && this.rid == ids[1]) 1678 return this; 1679 } 1680 1681 if (this.id == id || this.nId == id) { 1682 return this; 1683 } 1684 1685 var organizer; 1686 var a = this.children.getArray(); 1687 var sz = this.children.size(); 1688 for (var i = 0; i < sz; i++) { 1689 if (organizer = a[i].getById(id)) { 1690 return organizer; 1691 } 1692 } 1693 return null; 1694 }; 1695 1696 /** 1697 * Gets the first organizer found with the given name, starting from the root. 1698 * 1699 * @param {String} name the name to search for 1700 * @return {ZmOrganizer} the organizer 1701 */ 1702 ZmOrganizer.prototype.getByName = 1703 function(name, skipImap) { 1704 return this._getByName(name.toLowerCase(), skipImap); 1705 }; 1706 1707 /** 1708 * Gets a list of organizers with the given type. 1709 * 1710 * @param {constant} type the desired organizer type 1711 * @return {Array} an array of {ZmOrganizer} objects 1712 */ 1713 ZmOrganizer.prototype.getByType = 1714 function(type) { 1715 var list = []; 1716 this._getByType(type, list); 1717 return list; 1718 }; 1719 1720 /** 1721 * @private 1722 */ 1723 ZmOrganizer.prototype._getByType = 1724 function(type, list) { 1725 if (this.type == type) { 1726 list.push(this); 1727 } 1728 var a = this.children.getArray(); 1729 for (var i = 0; i < a.length; i++) { 1730 if (a[i]) { 1731 a[i]._getByType(type, list); 1732 } 1733 } 1734 }; 1735 1736 /** 1737 * Gets the organizer with the given path. 1738 * 1739 * @param {String} path the path to search for 1740 * @param {Boolean} useSystemName if <code>true</code>, use untranslated version of system folder names 1741 * @return {ZmOrganizer} the organizer 1742 */ 1743 ZmOrganizer.prototype.getByPath = 1744 function(path, useSystemName) { 1745 return this._getByPath(path.toLowerCase(), useSystemName); 1746 }; 1747 1748 /** 1749 * Test the path of this folder and then descendants against the given path, case insensitively. 1750 * 1751 * @private 1752 */ 1753 ZmOrganizer.prototype._getByPath = 1754 function(path, useSystemName) { 1755 if (this.nId == ZmFolder.ID_TAGS) { return null; } 1756 1757 if (path == this.getPath(false, false, null, true, useSystemName).toLowerCase()) { 1758 return this; 1759 } 1760 1761 var a = this.children.getArray(); 1762 for (var i = 0; i < a.length; i++) { 1763 var organizer = a[i]._getByPath(path, useSystemName); 1764 if (organizer) { 1765 return organizer; 1766 } 1767 } 1768 return null; 1769 }; 1770 1771 /** 1772 * Gets the number of children of this organizer. 1773 * 1774 * @return {int} the size 1775 */ 1776 ZmOrganizer.prototype.size = 1777 function() { 1778 return this.children.size(); 1779 }; 1780 1781 /** 1782 * Checks if the given organizer is a descendant of this one. 1783 * 1784 * @param {ZmOrganizer} organizer a possible descendant of ours 1785 * @return {Boolean} <code>if the given organizer is a descendant; <code>false</code> otherwise 1786 */ 1787 ZmOrganizer.prototype.isChildOf = 1788 function (organizer) { 1789 var parent = this.parent; 1790 while (parent) { 1791 if (parent == organizer) { 1792 return true; 1793 } 1794 parent = parent.parent; 1795 } 1796 return false; 1797 }; 1798 1799 /** 1800 * Gets the organizer with the given ID (looks in this organizer tree). 1801 * 1802 * @param {int} parentId the ID of the organizer to find 1803 * @return {ZmOrganizer} the organizer 1804 * 1805 * @private 1806 */ 1807 ZmOrganizer.prototype._getNewParent = 1808 function(parentId) { 1809 return appCtxt.getById(parentId); 1810 }; 1811 1812 /** 1813 * Checks if the organizer with the given ID is under this organizer. 1814 * 1815 * @param {String} id the ID 1816 * @return {Boolean} <code>true</code> if the organizer is under this organizer 1817 */ 1818 ZmOrganizer.prototype.isUnder = 1819 function(id) { 1820 id = id.toString(); 1821 if (this.nId == id || (this.isRemote() && this.rid == id)) { return true; } 1822 1823 var parent = this.parent; 1824 while (parent && parent.nId != ZmOrganizer.ID_ROOT) { 1825 if (parent.nId == id) { 1826 return true; 1827 } 1828 parent = parent.parent; 1829 } 1830 return false; 1831 }; 1832 1833 /** 1834 * Checks if this organizer is in "Trash". 1835 * 1836 * @return {Boolean} <code>true</code> if in "Trash" 1837 */ 1838 ZmOrganizer.prototype.isInTrash = 1839 function() { 1840 return this.isUnder(ZmOrganizer.ID_TRASH); 1841 }; 1842 1843 /** 1844 * Checks if permissions are allowed. 1845 * 1846 * @return {Boolean} <code>true</code> if permissions are allowed 1847 */ 1848 ZmOrganizer.prototype.isPermAllowed = 1849 function(perm) { 1850 if (this.perm) { 1851 var positivePerms = this.perm.replace(/-./g, ""); 1852 return (positivePerms.indexOf(perm) != -1); 1853 } 1854 return false; 1855 }; 1856 1857 /** 1858 * Checks if the organizer is read-only. 1859 * 1860 * @return {Boolean} <code>true</code> if read-only 1861 */ 1862 ZmOrganizer.prototype.isReadOnly = 1863 function() { 1864 if (this._isReadOnly == null) { 1865 var share = this.getMainShare(); 1866 this._isReadOnly = (share != null) 1867 ? (this.isRemote() && !share.isWrite()) 1868 : (this.isRemote() && this.isPermAllowed(ZmOrganizer.PERM_READ) && !this.isPermAllowed(ZmOrganizer.PERM_WRITE)); 1869 } 1870 return this._isReadOnly; 1871 }; 1872 1873 /** 1874 * Checks if admin. 1875 * 1876 * @return {Boolean} <code>true</code> if this organizer is admin 1877 */ 1878 ZmOrganizer.prototype.isAdmin = 1879 function() { 1880 if (this._isAdmin == null) { 1881 var share = this.getMainShare(); 1882 this._isAdmin = (share != null) 1883 ? (this.isRemote() && share.isAdmin()) 1884 : (this.isRemote() && this.isPermAllowed(ZmOrganizer.PERM_ADMIN)); 1885 } 1886 return this._isAdmin; 1887 }; 1888 1889 /** 1890 * Checks if the organizer has private access. 1891 * 1892 * @return {Boolean} <code>true</code> if has private access 1893 */ 1894 ZmOrganizer.prototype.hasPrivateAccess = 1895 function() { 1896 if (this._hasPrivateAccess == null) { 1897 var share = this.getMainShare(); 1898 this._hasPrivateAccess = (share != null) 1899 ? (this.isRemote() && share.hasPrivateAccess()) 1900 : (this.isRemote() && this.isPermAllowed(ZmOrganizer.PERM_PRIVATE)); 1901 } 1902 return this._hasPrivateAccess; 1903 }; 1904 1905 /** 1906 * Checks if the organizer is "remote". That applies to mountpoints (links), 1907 * the folders they represent, and any subfolders we know about. 1908 * 1909 * @return {Boolean} <code>true</code> if the organizer is "remote" 1910 */ 1911 ZmOrganizer.prototype.isRemote = 1912 function() { 1913 if (this._isRemote == null) { 1914 if (this.zid != null) { 1915 this._isRemote = true; 1916 } else { 1917 if (appCtxt.multiAccounts) { 1918 var account = this.account; 1919 var parsed = ZmOrganizer.parseId(this.id); 1920 1921 if (!account) { 1922 if (parsed.account && parsed.account.isMain) { 1923 this._isRemote = false; 1924 return this._isRemote; 1925 } else { 1926 account = appCtxt.getActiveAccount(); 1927 } 1928 } 1929 this._isRemote = Boolean(!parsed.account || (parsed.account && (parsed.account != account))); 1930 } else { 1931 var id = String(this.id); 1932 this._isRemote = ((id.indexOf(":") != -1) && (id.indexOf(appCtxt.getActiveAccount().id) != 0)); 1933 } 1934 } 1935 } 1936 return this._isRemote; 1937 }; 1938 1939 ZmOrganizer.prototype.isRemoteRoot = function() { 1940 return this.isRemote() && (this.rid == ZmOrganizer.ID_ROOT); 1941 } 1942 1943 /** 1944 * Checks if the organizer is a system tag or folder. 1945 * 1946 * @return {Boolean} <code>true</code> if system tag or folder 1947 */ 1948 ZmOrganizer.prototype.isSystem = 1949 function () { 1950 return (this.nId < ZmOrganizer.FIRST_USER_ID[this.type]); 1951 }; 1952 1953 ZmOrganizer.prototype.isDefault = 1954 function () { 1955 return this.nId == this.getDefaultFolderId(); 1956 }; 1957 1958 ZmOrganizer.prototype.getDefaultFolderId = 1959 function() { 1960 return ZmOrganizer.DEFAULT_FOLDER[this.type]; 1961 }; 1962 1963 ZmOrganizer.prototype.isTrash = 1964 function () { 1965 return this.nId == ZmFolder.ID_TRASH; 1966 }; 1967 1968 1969 /** 1970 * Checks if the organizer gets its contents from an external feed. 1971 * 1972 * @return {Boolean} <code>true</code> if from external feed 1973 */ 1974 ZmOrganizer.prototype.isFeed = 1975 function () { 1976 return Boolean(this.url); 1977 }; 1978 1979 /** Returns true if organizer has feeds. */ 1980 ZmOrganizer.prototype.hasFeeds = function() { return false; }; 1981 1982 /** 1983 * Checks if this folder maps to a datasource. If type is given, returns 1984 * true if folder maps to a datasource *and* is of the given type. 1985 * 1986 * @param {constant} type the type (see {@link ZmAccount.TYPE_POP} or {@link ZmAccount.TYPE_IMAP}) 1987 * @param {Boolean} checkParent if <code>true</code>, walk-up the parent chain 1988 * @return {Boolean} <code>true</code> if this folder maps to a datasource 1989 */ 1990 ZmOrganizer.prototype.isDataSource = 1991 function(type, checkParent) { 1992 var dss = this.getDataSources(type, checkParent); 1993 return (dss && dss.length > 0); 1994 }; 1995 1996 /** 1997 * Gets the data sources this folder maps to. If type is given, 1998 * returns non-null result only if folder maps to datasource(s) *and* is of the 1999 * given type. 2000 * 2001 * @param {constant} type the type (see {@link ZmAccount.TYPE_POP} or {@link ZmAccount.TYPE_IMAP}) 2002 * @param {Boolean} checkParent if <code>true</code>, walk-up the parent chain 2003 * @return {Array} the data sources this folder maps to or <code>null</code> for none 2004 */ 2005 ZmOrganizer.prototype.getDataSources = 2006 function(type, checkParent) { 2007 if (!appCtxt.get(ZmSetting.MAIL_ENABLED)) { return null; } 2008 2009 var dsc = appCtxt.getDataSourceCollection(); 2010 var dataSources = dsc.getByFolderId(this.nId, type); 2011 2012 if (dataSources.length == 0) { 2013 return (checkParent && this.parent) 2014 ? this.parent.getDataSources(type, checkParent) 2015 : null; 2016 } 2017 2018 return dataSources; 2019 }; 2020 2021 /** 2022 * Gets the owner. 2023 * 2024 * @return {String} the owner 2025 */ 2026 ZmOrganizer.prototype.getOwner = 2027 function() { 2028 return this.owner || (this.parent && this.parent.getOwner()) || appCtxt.get(ZmSetting.USERNAME); 2029 }; 2030 2031 /** 2032 * Gets the sort index. 2033 * 2034 * @return {int} the sort index 2035 */ 2036 ZmOrganizer.getSortIndex = 2037 function(child, sortFunction) { 2038 if (!(child && child.parent && sortFunction)) { return null; } 2039 var children = child.parent.children.getArray(); 2040 for (var i = 0; i < children.length; i++) { 2041 var test = sortFunction(child, children[i]); 2042 if (test == -1) { 2043 return i; 2044 } 2045 } 2046 return i; 2047 }; 2048 2049 /** 2050 * Sends a request to the server. Note that it's done asynchronously, but 2051 * there is no callback given. Hence, an organizer action is the last thing 2052 * done before returning to the event loop. The result of the action is 2053 * handled via notifications. 2054 * 2055 * @param {String} action the operation to perform 2056 * @param {Hash} attrs a hash of additional attributes to set in the request 2057 * @param {ZmBatchCommand} batchCmd the batch command that contains this request 2058 * 2059 * @private 2060 */ 2061 ZmOrganizer.prototype._organizerAction = 2062 function(params) { 2063 2064 var cmd = ZmOrganizer.SOAP_CMD[this.type] + "Request"; 2065 var request = { 2066 _jsns: "urn:zimbraMail", 2067 action : { 2068 op: params.action, 2069 id: params.id || this.id 2070 } 2071 }; 2072 var jsonObj = {}; 2073 jsonObj[cmd] = request; 2074 2075 for (var attr in params.attrs) { 2076 if (AjxEnv.isIE) { 2077 params.attrs[attr] += ""; //To string 2078 } 2079 request.action[attr] = params.attrs[attr]; 2080 } 2081 var actionController = appCtxt.getActionController(); 2082 actionController.dismiss(); 2083 var actionLogItem = (!params.noUndo && actionController && actionController.actionPerformed({op: params.action, id: params.id || this.id, attrs: params.attrs})) || null; 2084 var respCallback = new AjxCallback(this, this._handleResponseOrganizerAction, [params, actionLogItem]); 2085 if (params.batchCmd) { 2086 params.batchCmd.addRequestParams(jsonObj, respCallback, params.errorCallback); 2087 } 2088 else { 2089 var accountName; 2090 if (appCtxt.multiAccounts) { 2091 accountName = (this.account) 2092 ? this.account.name : appCtxt.accountList.mainAccount.name; 2093 } 2094 appCtxt.getAppController().sendRequest({ 2095 jsonObj: jsonObj, 2096 asyncMode: true, 2097 accountName: accountName, 2098 callback: respCallback, 2099 errorCallback: params.errorCallback, 2100 timeout: params.timeout, 2101 noBusyOverlay: params.noBusyOverlay 2102 }); 2103 } 2104 }; 2105 2106 /** 2107 * @private 2108 */ 2109 ZmOrganizer.prototype._handleResponseOrganizerAction = 2110 function(params, actionLogItem, result) { 2111 2112 if (actionLogItem) { 2113 actionLogItem.setComplete(); 2114 } 2115 if (params.callback) { 2116 params.callback.run(result); 2117 } 2118 if (params.actionTextKey) { 2119 var actionController = appCtxt.getActionController(); 2120 var summary = ZmOrganizer.getActionSummary({ 2121 actionTextKey: params.actionTextKey, 2122 numItems: params.numItems || 1, 2123 type: this.type, 2124 orgName: params.orgName, 2125 actionArg: params.actionArg 2126 }); 2127 var undoLink = actionLogItem && actionController && actionController.getUndoLink(actionLogItem); 2128 if (undoLink && actionController) { 2129 actionController.onPopup(); 2130 appCtxt.setStatusMsg({msg: summary+undoLink, transitions: actionController.getStatusTransitions()}); 2131 } else { 2132 appCtxt.setStatusMsg(summary); 2133 } 2134 } 2135 }; 2136 2137 /** 2138 * Returns a string describing an action, intended for display as toast to tell the 2139 * user what they just did. 2140 * 2141 * @param {Object} params hash of params: 2142 * {String} type organizer type (ZmOrganizer.*) 2143 * {String} actionTextKey ZmMsg key for text string describing action 2144 * {String} orgName name of the organizer that was affected 2145 * {String} actionArg (optional) additional argument 2146 * 2147 * @return {String} action summary 2148 */ 2149 ZmOrganizer.getActionSummary = 2150 function(params) { 2151 2152 var type = params.type, 2153 typeKey = ZmOrganizer.FOLDER_KEY[type], 2154 typeText = ZmMsg[typeKey], 2155 capKey = AjxStringUtil.capitalize(typeKey), 2156 alternateKey = params.actionTextKey + capKey, 2157 text = ZmMsg[alternateKey] || ZmMsg[params.actionTextKey], 2158 orgName = AjxStringUtil.htmlEncode(params.orgName), 2159 arg = AjxStringUtil.htmlEncode(params.actionArg); 2160 2161 return AjxMessageFormat.format(text, [ typeText, orgName, arg ]); 2162 }; 2163 2164 /** 2165 * Test the name of this organizer and then descendants against the given name, case insensitively. 2166 * 2167 * @private 2168 */ 2169 ZmOrganizer.prototype._getByName = 2170 function(name, skipImap) { 2171 if (this.name && name == this.name.toLowerCase()) { 2172 return this; 2173 } 2174 2175 var organizer; 2176 var a = this.children.getArray(); 2177 var sz = this.children.size(); 2178 for (var i = 0; i < sz; i++) { 2179 if (organizer = a[i]._getByName(name, skipImap)) { 2180 if (skipImap && organizer.isDataSource(ZmAccount.TYPE_IMAP, true)) { 2181 continue; 2182 } 2183 return organizer; 2184 } 2185 } 2186 return null; 2187 }; 2188 2189 /** 2190 * Takes a string of flag chars and applies them to this organizer. 2191 * 2192 * @private 2193 */ 2194 ZmOrganizer.prototype._parseFlags = 2195 function(str) { 2196 for (var i = 0; i < ZmOrganizer.ALL_FLAGS.length; i++) { 2197 var flag = ZmOrganizer.ALL_FLAGS[i]; 2198 this[ZmOrganizer.FLAG_PROP[flag]] = (Boolean(str && (str.indexOf(flag) != -1))); 2199 } 2200 }; 2201 2202 /** 2203 * Converts this organizer's flag-related props into a string of flag chars. 2204 * 2205 * @private 2206 */ 2207 ZmOrganizer.prototype._setFlags = 2208 function() { 2209 var flags = ""; 2210 for (var i = 0; i < ZmOrganizer.ALL_FLAGS.length; i++) { 2211 var flag = ZmOrganizer.ALL_FLAGS[i]; 2212 var prop = ZmOrganizer.FLAG_PROP[flag]; 2213 if (this[prop]) { 2214 flags = flags + flag; 2215 } 2216 } 2217 return flags; 2218 }; 2219 2220 /** 2221 * Adds a change listener. 2222 * 2223 * @param {AjxListener} the listener 2224 */ 2225 ZmOrganizer.prototype.addChangeListener = 2226 function(listener) { 2227 this.tree.addChangeListener(listener); 2228 }; 2229 2230 /** 2231 * Removes a change listener. 2232 * 2233 * @param {AjxListener} the listener 2234 */ 2235 ZmOrganizer.prototype.removeChangeListener = 2236 function(listener) { 2237 this.tree.removeChangeListener(listener); 2238 }; 2239 2240 /** 2241 * @private 2242 */ 2243 ZmOrganizer.prototype._setSharesFromJs = 2244 function(obj) { 2245 2246 // a folder object will have an acl with grants if this user has 2247 // shared it, or if it has been shared to this user with admin rights 2248 if (obj.acl && obj.acl.grant && obj.acl.grant.length > 0) { 2249 AjxDispatcher.require("Share"); 2250 for (var i = 0; i < obj.acl.grant.length; i++) { 2251 var grant = obj.acl.grant[i]; 2252 this.addShare(ZmShare.createFromJs(this, grant)); 2253 } 2254 } 2255 }; 2256 2257 /** 2258 * Handle notifications through the tree. 2259 * 2260 * @private 2261 */ 2262 ZmOrganizer.prototype._notify = 2263 function(event, details) { 2264 2265 if (details) { 2266 details.organizers = [this]; 2267 } else { 2268 details = {organizers: [this]}; 2269 } 2270 this.tree._evt.type = this.type; // all folder types are in a single tree 2271 this.tree._notify(event, details); 2272 }; 2273 2274 /** 2275 * Gets a marked-up version of the name. 2276 * 2277 * @param {String} name the name to mark up 2278 * @param {Boolean} showUnread if <code>true</code>, display the number of unread items (in parens) 2279 * @param {Boolean} noMarkup if <code>true</code>, do not return any HTML 2280 * 2281 * @private 2282 */ 2283 ZmOrganizer.prototype._markupName = 2284 function(name, showUnread, noMarkup) { 2285 if (!noMarkup) { 2286 name = AjxStringUtil.htmlEncode(name, true); 2287 } 2288 if (showUnread && this.hasUnreadDescendent()) { 2289 if (this.numUnread > 0) { 2290 name = AjxMessageFormat.format(ZmMsg.folderUnread, [name, this.numUnread]); 2291 } 2292 if (!noMarkup) { 2293 name = ["<span style='font-weight:bold'>", name, "</span>"].join(""); 2294 } 2295 } 2296 if (this.noSuchFolder && !noMarkup) { 2297 name = ["<del>", name, "</del>"].join(""); 2298 } 2299 return name; 2300 }; 2301 2302 /** 2303 * @private 2304 */ 2305 ZmOrganizer.prototype._getItemsText = 2306 function() { 2307 var result = ZmMsg[ZmOrganizer.ITEMS_KEY[this.type]]; 2308 if (!result || this.isTrash()) { 2309 result = ZmMsg.items; 2310 } 2311 return result; 2312 }; 2313 2314 ZmOrganizer.prototype._getUnreadLabel = 2315 function() { 2316 return ZmMsg.unread; 2317 }; 2318 2319 /** 2320 * Returns true if any descendent folders have unread messages. 2321 * 2322 * @returns {boolean} true if any descendent folders have unread messages 2323 */ 2324 ZmOrganizer.prototype.hasUnreadDescendent = function() { 2325 2326 if (this.numUnread > 0) { 2327 return true; 2328 } 2329 2330 var a = this.children.getArray(), 2331 sz = this.children.size(); 2332 2333 for (var i = 0; i < sz; i++) { 2334 if (a[i].hasUnreadDescendent()) { 2335 return true; 2336 } 2337 } 2338 2339 return false; 2340 }; 2341