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 * 27 * This file defines a folder. 28 * 29 */ 30 31 /** 32 * Creates a folder. 33 * @class 34 * This class represents a folder, which may contain mail. At some point, folders may be 35 * able to contain contacts and/or appointments. 36 * 37 * @author Conrad Damon 38 * 39 * @param {Hash} params a hash of parameters 40 * @param {int} params.id the numeric ID 41 * @param {String} params.name the name 42 * @param {ZmOrganizer} params.parent the parent folder 43 * @param {ZmTree} params.tree the tree model that contains this folder 44 * @param {int} params.numUnread the number of unread items for this folder 45 * @param {int} params.numTotal the number of items for this folder 46 * @param {int} params.sizeTotal the total size of folder's items 47 * @param {String} params.url the URL for this folder's feed 48 * @param {String} params.owner the Owner for this organizer 49 * @param {String} params.oname the Owner's name for this organizer, if remote folder 50 * @param {String} params.zid the Zimbra ID of owner, if remote folder 51 * @param {String} params.rid the Remote ID of organizer, if remote folder 52 * @param {String} params.restUrl the REST URL of this organizer 53 * 54 * @extends ZmOrganizer 55 */ 56 ZmFolder = function(params) { 57 if (arguments.length == 0) { return; } 58 params.type = params.type || ZmOrganizer.FOLDER; 59 ZmOrganizer.call(this, params); 60 }; 61 62 ZmFolder.prototype = new ZmOrganizer; 63 ZmFolder.prototype.constructor = ZmFolder; 64 65 ZmFolder.prototype.isZmFolder = true; 66 ZmFolder.prototype.toString = function() { return "ZmFolder"; }; 67 68 69 // needed to construct USER_ROOT if mail disabled 70 ZmOrganizer.ORG_CLASS[ZmId.ORG_FOLDER] = "ZmFolder"; 71 72 ZmFolder.SEP = "/"; // path separator 73 74 // system folders (see Mailbox.java in ZimbraServer for positive int consts) 75 // Note: since these are defined as Numbers, and IDs come into our system as Strings, 76 // we need to use == for comparisons (instead of ===, which will fail) 77 ZmFolder.ID_LOAD_FOLDERS = -3; // special "Load remaining folders" placeholder 78 ZmFolder.ID_OTHER = -2; // used for tcon value (see below) 79 ZmFolder.ID_SEP = -1; // separator 80 ZmFolder.ID_ROOT = ZmOrganizer.ID_ROOT; 81 ZmFolder.ID_INBOX = ZmOrganizer.ID_INBOX; 82 ZmFolder.ID_TRASH = ZmOrganizer.ID_TRASH; 83 ZmFolder.ID_SPAM = ZmOrganizer.ID_SPAM; 84 ZmFolder.ID_SENT = 5; 85 ZmFolder.ID_DRAFTS = 6; 86 ZmFolder.ID_CONTACTS = ZmOrganizer.ID_ADDRBOOK; 87 ZmFolder.ID_AUTO_ADDED = ZmOrganizer.ID_AUTO_ADDED; 88 ZmFolder.ID_TAGS = 8; 89 ZmFolder.ID_TASKS = ZmOrganizer.ID_TASKS; 90 ZmFolder.ID_SYNC_FAILURES = ZmOrganizer.ID_SYNC_FAILURES; 91 ZmFolder.ID_OUTBOX = ZmOrganizer.ID_OUTBOX; 92 ZmFolder.ID_CHATS = ZmOrganizer.ID_CHATS; 93 ZmFolder.ID_ATTACHMENTS = ZmOrganizer.ID_ATTACHMENTS; 94 ZmFolder.ID_DLS = ZmOrganizer.ID_DLS; 95 96 // system folder names 97 ZmFolder.MSG_KEY = {}; 98 ZmFolder.MSG_KEY[ZmFolder.ID_INBOX] = "inbox"; 99 ZmFolder.MSG_KEY[ZmFolder.ID_TRASH] = "trash"; 100 ZmFolder.MSG_KEY[ZmFolder.ID_SPAM] = "junk"; 101 ZmFolder.MSG_KEY[ZmFolder.ID_SENT] = "sent"; 102 ZmFolder.MSG_KEY[ZmFolder.ID_DRAFTS] = "drafts"; 103 ZmFolder.MSG_KEY[ZmFolder.ID_CONTACTS] = "contacts"; 104 ZmFolder.MSG_KEY[ZmFolder.ID_AUTO_ADDED] = "emailedContacts"; 105 ZmFolder.MSG_KEY[ZmFolder.ID_TASKS] = "tasks"; 106 ZmFolder.MSG_KEY[ZmFolder.ID_TAGS] = "tags"; 107 ZmFolder.MSG_KEY[ZmOrganizer.ID_CALENDAR] = "calendar"; 108 ZmFolder.MSG_KEY[ZmOrganizer.ID_BRIEFCASE] = "briefcase"; 109 ZmFolder.MSG_KEY[ZmOrganizer.ID_CHATS] = "chats"; 110 ZmFolder.MSG_KEY[ZmOrganizer.ID_ALL_MAILBOXES] = "allMailboxes"; 111 ZmFolder.MSG_KEY[ZmFolder.ID_OUTBOX] = "outbox"; 112 ZmFolder.MSG_KEY[ZmFolder.ID_SYNC_FAILURES] = "errorReports"; 113 ZmFolder.MSG_KEY[ZmFolder.ID_ATTACHMENTS] = "attachments"; 114 115 // system folder icons 116 ZmFolder.ICON = {}; 117 ZmFolder.ICON[ZmFolder.ID_INBOX] = "Inbox"; 118 ZmFolder.ICON[ZmFolder.ID_TRASH] = "Trash"; 119 ZmFolder.ICON[ZmFolder.ID_SPAM] = "SpamFolder"; 120 ZmFolder.ICON[ZmFolder.ID_SENT] = "SentFolder"; 121 ZmFolder.ICON[ZmFolder.ID_SYNC_FAILURES] = "SendReceive"; 122 ZmFolder.ICON[ZmFolder.ID_OUTBOX] = "Outbox"; 123 ZmFolder.ICON[ZmFolder.ID_DRAFTS] = "DraftFolder"; 124 ZmFolder.ICON[ZmFolder.ID_CHATS] = "ChatFolder"; 125 ZmFolder.ICON[ZmFolder.ID_LOAD_FOLDERS] = "Plus"; 126 ZmFolder.ICON[ZmFolder.ID_ATTACHMENTS] = "Attachment"; 127 128 // name to use within the query language 129 ZmFolder.QUERY_NAME = {}; 130 ZmFolder.QUERY_NAME[ZmFolder.ID_INBOX] = "inbox"; 131 ZmFolder.QUERY_NAME[ZmFolder.ID_TRASH] = "trash"; 132 ZmFolder.QUERY_NAME[ZmFolder.ID_SPAM] = "junk"; 133 ZmFolder.QUERY_NAME[ZmFolder.ID_SENT] = "sent"; 134 ZmFolder.QUERY_NAME[ZmFolder.ID_OUTBOX] = "outbox"; 135 ZmFolder.QUERY_NAME[ZmFolder.ID_DRAFTS] = "drafts"; 136 ZmFolder.QUERY_NAME[ZmOrganizer.ID_CALENDAR] = "calendar"; 137 ZmFolder.QUERY_NAME[ZmFolder.ID_CONTACTS] = "contacts"; 138 ZmFolder.QUERY_NAME[ZmFolder.ID_TASKS] = "tasks"; 139 ZmFolder.QUERY_NAME[ZmFolder.ID_AUTO_ADDED] = "Emailed Contacts"; 140 ZmFolder.QUERY_NAME[ZmOrganizer.ID_BRIEFCASE] = "briefcase"; 141 ZmFolder.QUERY_NAME[ZmFolder.ID_CHATS] = "chats"; 142 ZmFolder.QUERY_NAME[ZmFolder.ID_SYNC_FAILURES] = "Error Reports"; 143 144 ZmFolder.QUERY_ID = AjxUtil.valueHash(ZmFolder.QUERY_NAME); 145 146 // order within the overview panel 147 ZmFolder.SORT_ORDER = {}; 148 ZmFolder.SORT_ORDER[ZmFolder.ID_INBOX] = 1; 149 ZmFolder.SORT_ORDER[ZmFolder.ID_CHATS] = 2; 150 ZmFolder.SORT_ORDER[ZmFolder.ID_SENT] = 3; 151 ZmFolder.SORT_ORDER[ZmFolder.ID_DRAFTS] = 4; 152 ZmFolder.SORT_ORDER[ZmFolder.ID_SPAM] = 5; 153 ZmFolder.SORT_ORDER[ZmFolder.ID_OUTBOX] = 6; 154 ZmFolder.SORT_ORDER[ZmFolder.ID_TRASH] = 7; 155 ZmFolder.SORT_ORDER[ZmFolder.ID_SYNC_FAILURES] = 8; 156 ZmFolder.SORT_ORDER[ZmFolder.ID_SEP] = 9; 157 ZmFolder.SORT_ORDER[ZmFolder.ID_ATTACHMENTS] = 99; // Last 158 159 // character codes for "tcon" attribute in conv action request, which controls 160 // which folders are affected 161 ZmFolder.TCON_CODE = {}; 162 ZmFolder.TCON_CODE[ZmFolder.ID_TRASH] = "t"; 163 ZmFolder.TCON_CODE[ZmFolder.ID_SYNC_FAILURES] = "o"; 164 ZmFolder.TCON_CODE[ZmFolder.ID_SPAM] = "j"; 165 ZmFolder.TCON_CODE[ZmFolder.ID_SENT] = "s"; 166 ZmFolder.TCON_CODE[ZmFolder.ID_DRAFTS] = "d"; 167 ZmFolder.TCON_CODE[ZmFolder.ID_OTHER] = "o"; 168 169 // folders that look like mail folders that we don't want to show 170 ZmFolder.HIDE_ID = {}; 171 ZmFolder.HIDE_ID[ZmOrganizer.ID_CHATS] = true; 172 ZmFolder.HIDE_ID[ZmOrganizer.ID_NOTIFICATION_MP] = true; 173 174 // Hide folders migrated from Outlook mailbox 175 ZmFolder.HIDE_NAME = {}; 176 //ZmFolder.HIDE_NAME["Journal"] = true; 177 //ZmFolder.HIDE_NAME["Notes"] = true; 178 //ZmFolder.HIDE_NAME["Outbox"] = true; 179 //ZmFolder.HIDE_NAME["Tasks"] = true; 180 181 // folders that contain mail from me instead of to me 182 ZmFolder.OUTBOUND = [ZmFolder.ID_SENT, ZmFolder.ID_OUTBOX, ZmFolder.ID_DRAFTS]; 183 184 // The extra-special, visible but untouchable outlook folder 185 ZmFolder.SYNC_ISSUES = "Sync Issues"; 186 187 // map name to ID 188 ZmFolder.QUERY_ID = {}; 189 (function() { 190 for (var i in ZmFolder.QUERY_NAME) { 191 ZmFolder.QUERY_ID[ZmFolder.QUERY_NAME[i]] = i; 192 } 193 })(); 194 195 /** 196 * Comparison function for folders. Intended for use on a list of user folders 197 * through a call to <code>Array.sort()</code>. 198 * 199 * @param {ZmFolder} folderA a folder 200 * @param {ZmFolder} folderB a folder 201 * @param {Boolean} nonMail this is sorting non mail tree. 202 * @return {int} 0 if the folders match 203 */ 204 ZmFolder.sortCompare = 205 function(folderA, folderB, nonMail) { 206 var check = ZmOrganizer.checkSortArgs(folderA, folderB); 207 if (check != null) { return check; } 208 209 // offline client wants POP folders above all else *unless* we are POP'ing into Inbox 210 if (appCtxt.isOffline) { 211 if (folderA.isDataSource(ZmAccount.TYPE_POP)) { 212 if (folderA.id == ZmFolder.ID_INBOX) return -1; 213 if (folderB.isDataSource(ZmAccount.TYPE_POP)) { 214 if (folderA.name.toLowerCase() > folderB.name.toLowerCase()) { return 1; } 215 if (folderA.name.toLowerCase() < folderB.name.toLowerCase()) { return -1; } 216 return 0; 217 } 218 return -1; 219 } else if (folderB.isDataSource(ZmAccount.TYPE_POP)) { 220 return 1; 221 } 222 } 223 224 if (ZmFolder.SORT_ORDER[folderA.nId] && ZmFolder.SORT_ORDER[folderB.nId]) { 225 return (ZmFolder.SORT_ORDER[folderA.nId] - ZmFolder.SORT_ORDER[folderB.nId]); 226 } 227 228 // links (shared folders or mailboxes) appear after personal folders 229 if (folderA.link !== folderB.link) { 230 return folderA.link ? 1 : -1; 231 } 232 233 if (nonMail) { 234 //for nonp-mail apps, trash last of all things 235 if (folderA.isTrash()) { 236 return 1; 237 } 238 if (folderB.isTrash()) { 239 return -1; 240 } 241 //system before non-system (except for trash) 242 if (folderA.isSystem() && !folderB.isSystem()) { 243 return -1; 244 } 245 if (!folderA.isSystem() && folderB.isSystem()) { 246 return 1; 247 } 248 if (folderA.isSystem() && folderB.isSystem()) { 249 //for 2 system folders, the default one is first, and the rest ordered alphabetically (again except for trash that appears after the user folders) 250 if (folderA.isDefault()) { 251 return -1; 252 } 253 if (folderB.isDefault()) { 254 return 1; 255 } 256 //the other cases will be sorted by name below. Either 2 system or 2 user folders. 257 } 258 } 259 else { 260 if (!ZmFolder.SORT_ORDER[folderA.nId] && ZmFolder.SORT_ORDER[folderB.nId]) { return 1; } 261 if (ZmFolder.SORT_ORDER[folderA.nId] && !ZmFolder.SORT_ORDER[folderB.nId]) { return -1; } 262 } 263 264 if (folderA.name.toLowerCase() > folderB.name.toLowerCase()) { return 1; } 265 if (folderA.name.toLowerCase() < folderB.name.toLowerCase()) { return -1; } 266 return 0; 267 }; 268 269 270 ZmFolder.sortCompareNonMail = 271 function(folderA, folderB) { 272 return ZmFolder.sortCompare(folderA, folderB, true); 273 }; 274 275 /** 276 * Compares the folders by path. 277 * 278 * @param {ZmFolder} folderA a folder 279 * @param {ZmFolder} folderB a folder 280 * @return {int} 0 if the folders match 281 */ 282 ZmFolder.sortComparePath = 283 function(folderA, folderB) { 284 285 var pathA = folderA && folderA.getPath(false, false, null, true, true); 286 var pathB = folderB && folderB.getPath(false, false, null, true, true); 287 var check = ZmOrganizer.checkSortArgs(pathA, pathB); 288 if (check != null) { return check; } 289 290 if (ZmFolder.SORT_ORDER[folderA.nId] && ZmFolder.SORT_ORDER[folderB.nId]) { 291 return (ZmFolder.SORT_ORDER[folderA.nId] - ZmFolder.SORT_ORDER[folderB.nId]); 292 } 293 if (!ZmFolder.SORT_ORDER[folderA.nId] && ZmFolder.SORT_ORDER[folderB.nId]) { return 1; } 294 if (ZmFolder.SORT_ORDER[folderA.nId] && !ZmFolder.SORT_ORDER[folderB.nId]) { return -1; } 295 if (pathA.toLowerCase() > pathB.toLowerCase()) { return 1; } 296 if (pathA.toLowerCase() < pathB.toLowerCase()) { return -1; } 297 return 0; 298 }; 299 300 /** 301 * Checks a folder name for validity. Note: that a name, rather than a path, is checked. 302 * 303 * @param {String} name the folder name 304 * @param {ZmFolder} parent the parent folder 305 * @return {String} an error message if the name is invalid; <code>null</code>if the name is valid. 306 */ 307 ZmFolder.checkName = 308 function(name, parent) { 309 var error = ZmOrganizer.checkName(name); 310 if (error) { return error; } 311 312 // make sure path isn't same as a system folder 313 parent = parent || appCtxt.getFolderTree().root; 314 if (parent && (parent.id == ZmFolder.ID_ROOT)) { 315 var lname = name.toLowerCase(); 316 for (var id in ZmFolder.MSG_KEY) { 317 var sysname = ZmMsg[ZmFolder.MSG_KEY[id]]; 318 if (sysname && (lname == sysname.toLowerCase())) { 319 return ZmMsg.folderNameReserved; 320 } 321 } 322 /*if (lname == ZmFolder.SYNC_ISSUES.toLowerCase()) { 323 return ZmMsg.folderNameReserved; 324 }*/ 325 } 326 327 return null; 328 }; 329 330 /** 331 * Gets the "well-known" ID for a given folder name. 332 * 333 * @param {String} folderName the folder name 334 * @return {String} the id or <code>null</code> if not found 335 */ 336 ZmFolder.getIdForName = 337 function(folderName) { 338 var name = folderName.toLowerCase(); 339 for (var i in ZmFolder.MSG_KEY) { 340 if (ZmFolder.MSG_KEY[i] == name) { 341 return i; 342 } 343 } 344 return null; 345 }; 346 347 /** 348 * Moves a folder. A user can move a folder to "Trash" even if there is already a folder in "Trash" with the 349 * same name. A new name will be generated for this folder and a rename is performed before the move. 350 * 351 * @param {ZmFolder} newParent the new parent 352 * @param {boolean} noUndo true if the action should not be undoable 353 * @param {String} actionText optional custom action text to display as summary 354 */ 355 ZmFolder.prototype.move = 356 function(newParent, noUndo, actionText, batchCmd) { 357 var origName = this.name; 358 var name = this.name; 359 while (newParent.hasChild(name)) { 360 name = name + "_"; 361 } 362 if (origName != name) { 363 this.rename(name); 364 } 365 ZmOrganizer.prototype.move.call(this, newParent, noUndo, batchCmd); 366 }; 367 368 /** 369 * Sends <code><FolderActionRequest></code> to turn sync'ing on/off for IMAP folders. Currently, 370 * this is only used by Offline/ZDesktop client 371 * 372 * @param {Boolean} syncIt the flag indicating whether to sync this folder 373 * @param {AjxCallback} callback the callback to call once server request is successful 374 * @param {AjxCallback} errorCallback the callback to call if server returns error 375 */ 376 ZmFolder.prototype.toggleSyncOffline = 377 function(callback, errorCallback) { 378 if (!this.isOfflineSyncable) { return; } 379 380 var op = this.isOfflineSyncing ? "!syncon" : "syncon"; 381 var soapDoc = AjxSoapDoc.create("FolderActionRequest", "urn:zimbraMail"); 382 var actionNode = soapDoc.set("action"); 383 actionNode.setAttribute("op", op); 384 actionNode.setAttribute("id", this.id); 385 386 var params = { 387 soapDoc: soapDoc, 388 asyncMode: true, 389 callback: callback, 390 errorCallback: errorCallback 391 }; 392 appCtxt.getAppController().sendRequest(params); 393 }; 394 395 /** 396 * Checks folders recursively for feeds. 397 * 398 * @return {Boolean} <code>true</code> for feeds 399 */ 400 ZmFolder.prototype.hasFeeds = 401 function() { 402 if (this.type != ZmOrganizer.FOLDER) { return false; } 403 404 var a = this.children.getArray(); 405 var sz = this.children.size(); 406 for (var i = 0; i < sz; i++) { 407 if (a[i].isFeed()) { 408 return true; 409 } 410 if (a[i].children && a[i].children.size() > 0) { 411 return (a[i].hasFeeds && a[i].hasFeeds()); 412 } 413 } 414 return false; 415 }; 416 417 /** 418 * Checks if the folder has search. 419 * 420 * @param {String} id not used 421 * @return {Boolean} <code>true</code> if has search 422 */ 423 ZmFolder.prototype.hasSearch = 424 function(id) { 425 if (this.type == ZmOrganizer.SEARCH) { return true; } 426 427 var a = this.children.getArray(); 428 var sz = this.children.size(); 429 for (var i = 0; i < sz; i++) { 430 if (a[i].hasSearch()) { 431 return true; 432 } 433 } 434 435 return false; 436 }; 437 438 /** 439 * Checks if the folder supports public access. Override this method if you dont want a folder to be accessed publicly 440 * 441 * @return {Boolean} always returns <code>true</code> 442 */ 443 ZmFolder.prototype.supportsPublicAccess = 444 function() { 445 return true; 446 }; 447 448 /** 449 * Handles the creation of a folder or search folder. This folder is the parent 450 * of the newly created folder. A folder may hold a folder or search folder, 451 * and a search folder may hold another search folder. 452 * 453 * @param {Object} obj a JS folder object from the notification 454 * @param {String} elementType the type of containing JSON element 455 * @param {Boolean} skipNotify <code>true</code> if notifying client should be ignored 456 */ 457 ZmFolder.prototype.notifyCreate = 458 function(obj, elementType, skipNotify) { 459 // ignore creates of system folders 460 var nId = ZmOrganizer.normalizeId(obj.id); 461 if (this.isSystem() && nId < ZmOrganizer.FIRST_USER_ID[this.type]) { return; } 462 463 var account = ZmOrganizer.parseId(obj.id).account; 464 var folder = ZmFolderTree.createFromJs(this, obj, this.tree, elementType, null, account); 465 if (folder) { 466 var index = ZmOrganizer.getSortIndex(folder, eval(ZmTreeView.COMPARE_FUNC[this.type])); 467 this.children.add(folder, index); 468 469 if (!skipNotify) { 470 folder._notify(ZmEvent.E_CREATE); 471 } 472 } 473 }; 474 475 /** 476 * Provide some extra info in the change event about the former state 477 * of the folder. Note that we null out the field after setting up the 478 * change event, so the notification isn't also sent when the parent 479 * class's method is called. 480 * 481 * @param {Object} obj a "modified" notification 482 */ 483 ZmFolder.prototype.notifyModify = 484 function(obj) { 485 var details = {}; 486 var fields = {}; 487 var doNotify = false; 488 if (obj.name != null && this.name != obj.name && obj.id == this.id) { 489 details.oldPath = this.getPath(); 490 this.name = obj.name; 491 fields[ZmOrganizer.F_NAME] = true; 492 this.parent.children.sort(eval(ZmTreeView.COMPARE_FUNC[this.type])); 493 doNotify = true; 494 obj.name = null; 495 } 496 if (doNotify) { 497 details.fields = fields; 498 this._notify(ZmEvent.E_MODIFY, details); 499 } 500 501 if (obj.l != null && (!this.parent || (obj.l != this.parent.id))) { 502 var newParent = this._getNewParent(obj.l); 503 if (newParent) { 504 details.oldPath = this.getPath(); 505 this.reparent(newParent); 506 this._notify(ZmEvent.E_MOVE, details); 507 obj.l = null; 508 } 509 } 510 511 ZmOrganizer.prototype.notifyModify.apply(this, [obj]); 512 }; 513 514 /** 515 * Creates a query. 516 * 517 * @param {Boolean} pathOnly <code>true</code> if to use the path only 518 * @return {String} the query 519 */ 520 ZmFolder.prototype.createQuery = 521 function(pathOnly) { 522 if (!this.isRemote() && this.isSystem()) { 523 var qName = ZmFolder.QUERY_NAME[this.nId] || this.getName(false, null, true, true) || this.name; 524 // put quotes around folder names that consist of multiple words or have special characters. 525 var quote = /^\w+$/.test(qName) ? "" : "\""; 526 return pathOnly 527 ? qName 528 : ("in:" + quote + qName + quote); 529 } 530 531 var path = this.isSystem() ? ZmFolder.QUERY_NAME[this.nId] : this.name; 532 var f = this.parent; 533 while (f && (f.nId != ZmFolder.ID_ROOT) && f.name.length) { 534 var name = (f.isSystem() && ZmFolder.QUERY_NAME[f.nId]) || f.name; 535 path = name + "/" + path; 536 f = f.parent; 537 } 538 path = '"' + path + '"'; 539 return pathOnly ? path : ("in:" + path); 540 }; 541 542 /** 543 * Gets the name. 544 * 545 * @param {Boolean} showUnread <code>true</code> to show unread 546 * @param {int} maxLength the max length 547 * @param {Boolean} noMarkup <code>true</code> to not include markup 548 * @param {Boolean} useSystemName <code>true</code> to use the system name 549 * 550 * @return {String} the name 551 */ 552 ZmFolder.prototype.getName = 553 function(showUnread, maxLength, noMarkup, useSystemName) { 554 if (this.nId == ZmFolder.ID_DRAFTS || 555 this.nId == ZmFolder.ID_OUTBOX || 556 this.rid == ZmFolder.ID_DRAFTS) 557 { 558 var name = (useSystemName && this._systemName) ? this._systemName : this.name; 559 if (showUnread && this.numTotal > 0) { 560 name = AjxMessageFormat.format(ZmMsg.folderUnread, [name, this.numTotal]); 561 if (!noMarkup) { 562 name = ["<span style='font-weight:bold'>", name, "</span>"].join(""); 563 } 564 } 565 return name; 566 } 567 else { 568 return ZmOrganizer.prototype.getName.apply(this, arguments); 569 } 570 }; 571 572 /** 573 * Gets the icon. 574 * 575 * @return {String} the icon 576 */ 577 ZmFolder.prototype.getIcon = 578 function() { 579 if (this.nId == ZmOrganizer.ID_ROOT) { return null; } 580 if (ZmFolder.ICON[this.nId]) { return ZmFolder.ICON[this.nId]; } 581 if (this.isFeed()) { return "RSS"; } 582 if (this.isRemote()) { return "SharedMailFolder"; } 583 if (this.isDataSource(ZmAccount.TYPE_POP)) { return "POPAccount"; } 584 585 // make a "best-effort" to map imap folders to a well-known icon 586 // (parent will be the root imap folder) 587 var mappedId = this.getSystemEquivalentFolderId(); 588 if (mappedId) { 589 return ZmFolder.ICON[mappedId] || "Folder"; 590 } 591 592 return "Folder"; 593 }; 594 595 ZmFolder.prototype.getSystemEquivalentFolderId = 596 function() { 597 if (this.parent && this.parent.isDataSource(ZmAccount.TYPE_IMAP)) { 598 return ZmFolder.getIdForName(this.name); 599 } 600 return null; 601 }; 602 603 ZmFolder.prototype.isSystemEquivalent = 604 function() { 605 return this.getSystemEquivalentFolderId() != null; 606 }; 607 608 ZmFolder.prototype.mayContainFolderFromAccount = 609 function(otherAccount) { 610 var thisAccount = this.getAccount(); 611 if (thisAccount == otherAccount) { 612 return true; 613 } 614 return thisAccount.isLocal(); // can only move to local 615 616 }; 617 618 /** 619 * Returns true if the given object(s) may be placed in this folder. 620 * 621 * If the object is a folder, check that: 622 * <ul> 623 * <li>We are not the immediate parent of the folder</li> 624 * <li>We are not a child of the folder</li> 625 * <li>We are not Spam or Drafts</li> 626 * <li>We don't already have a child with the folder's name (unless we are in Trash)</li> 627 * <li>We are not moving it into a folder of a different type</li> 628 * <li>We are not moving a folder into itself</li> 629 * </ul> 630 * 631 * If the object is an item or a list or items, check that: 632 * <ul> 633 * <li>We are not the Folders container</li> 634 * <li>We are not a search folder</li> 635 * <li>The items aren't already in this folder</li> 636 * <li>A contact can only be moved to Trash</li> 637 * <li> A draft can be moved to Trash or Drafts</li> 638 * <li>Non-drafts cannot be moved to Drafts</li> 639 * </ul> 640 * 641 * @param {Object} what the object(s) to possibly move into this folder (item or organizer) 642 * @param {constant} folderType the contextual folder type (for tree view root items) 643 * @param {boolean} ignoreExisting Set to true if checks for item presence in the folder should be skipped (e.g. when recovering deleted items) 644 */ 645 ZmFolder.prototype.mayContain = 646 function(what, folderType, ignoreExisting) { 647 648 if (!what) { 649 return true; 650 } 651 if (this.isFeed() /*|| this.isSyncIssuesFolder()*/) { 652 return false; 653 } 654 // placeholder for showing a large number of folders 655 if (this.id == ZmFolder.ID_LOAD_FOLDERS) { 656 return false; 657 } 658 659 var thisType = folderType || this.type; 660 var invalid = false; 661 if (what instanceof ZmFolder) { 662 invalid = ((what.parent === this && !ignoreExisting) || this.isChildOf(what) || this.nId == ZmFolder.ID_DRAFTS || this.nId == ZmFolder.ID_SPAM || 663 (!this.isInTrash() && this.hasChild(what.name) && !ignoreExisting) || 664 (what.type !== thisType && this.nId != ZmFolder.ID_TRASH) || 665 (what.id === this.id) || 666 (this.disallowSubFolder) || 667 (appCtxt.multiAccounts && !this.mayContainFolderFromAccount(what.getAccount())) || // cannot move folders across accounts, unless the target is local 668 (this.isRemote() && !this._remoteMoveOk(what)) || 669 (what.isRemote() && !this._remoteMoveOk(what))); // a remote folder can be DnD but not its children 670 } else { 671 // An item or an array of items is being moved 672 var items = AjxUtil.toArray(what); 673 var item = items[0]; 674 675 // container can only have folders/searches or calendars 676 if ((this.nId == ZmOrganizer.ID_ROOT && (what.type !== ZmOrganizer.CALENDAR)) || 677 // nothing can be moved to outbox/sync failures folders 678 this.nId == ZmOrganizer.ID_OUTBOX || 679 this.nId == ZmOrganizer.ID_SYNC_FAILURES) 680 { 681 invalid = true; 682 } else if (thisType === ZmOrganizer.SEARCH) { 683 invalid = true; // can't drop items into saved searches 684 } else if (item && (item.type === ZmItem.CONTACT) && item.isGal) { 685 invalid = true; 686 } else if (item && (item.type === ZmItem.CONV) && item.list && item.list.search && (item.list.search.folderId === this.id)) { 687 invalid = true; // convs which are a result of a search for this folder 688 } else { // checks that need to be done for each item 689 for (var i = 0; i < items.length; i++) { 690 var childItem = items[i]; 691 if (!childItem) { 692 invalid = true; 693 break; 694 } 695 if (Dwt.instanceOf(childItem, "ZmBriefcaseFolderItem")) { 696 if (childItem.folder && childItem.folder.isRemote() && !childItem.folder.rid) { 697 invalid = true; 698 break; 699 } 700 } else if (item.type === ZmItem.MSG && childItem.isDraft && (this.nId != ZmFolder.ID_TRASH && this.nId != ZmFolder.ID_DRAFTS && this.rid != ZmFolder.ID_DRAFTS)) { 701 // can move drafts only into Trash or Drafts 702 invalid = true; 703 break; 704 } else if ((this.nId == ZmFolder.ID_DRAFTS || this.rid == ZmFolder.ID_DRAFTS) && !childItem.isDraft) { 705 // only drafts can be moved into Drafts 706 invalid = true; 707 break; 708 } 709 } 710 // items in the "Sync Failures" folder cannot be dragged out 711 if (appCtxt.isOffline && !invalid) { 712 // bug: 41531 - don't allow items to be moved into exchange 713 // account when moving across accounts 714 var acct = this.getAccount(); 715 if (acct && item.getAccount() != acct && 716 (acct.type === ZmAccount.TYPE_MSE || 717 acct.type === ZmAccount.TYPE_EXCHANGE)) 718 { 719 invalid = true; 720 } 721 else { 722 var cs = appCtxt.getCurrentSearch(); 723 var folder = cs && appCtxt.getById(cs.folderId); 724 if (folder && folder.nId == ZmOrganizer.ID_SYNC_FAILURES) { 725 invalid = true; 726 } 727 } 728 } 729 730 // bug #42890 - disable moving to shared folders across accounts 731 // until server bug is fixed 732 if (appCtxt.multiAccounts && this.isRemote() && 733 what.getAccount && this.getAccount().id != what.getAccount().id) 734 { 735 invalid = true; 736 } 737 738 // can't move items to folder they're already in; we're okay if we 739 // have one item from another folder 740 if (!invalid && !ignoreExisting) { 741 if (item && item.folderId) { 742 invalid = true; 743 for (var i = 0; i < items.length; i++) { 744 if (items[i].folderId != this.id) { 745 invalid = false; 746 break; 747 } 748 } 749 } 750 } 751 } 752 if (!invalid && this.link) { 753 invalid = this.isReadOnly(); // cannot drop anything onto a read-only item 754 } 755 } 756 return !invalid; 757 }; 758 759 /** 760 * Checks if this is the sync issues folder. 761 * 762 * @return {Boolean} <code>true</code> if the folder is the one dealing with Outlook sync issues 763 */ 764 765 //Bug#68799 Removing special handling of the folder named "Sync Issues" 766 767 /*ZmFolder.prototype.isSyncIssuesFolder = 768 function() { 769 return (this.name == ZmFolder.SYNC_ISSUES); 770 };*/ 771 772 /** 773 * Checks if this folder required hard delete. 774 * 775 * @return {Boolean} <code>true</code> if deleting items w/in this folder should be hard deleted. 776 */ 777 ZmFolder.prototype.isHardDelete = 778 function() { 779 return (this.isInTrash() || this.isInSpam() || (appCtxt.isOffline && this.isUnder(ZmOrganizer.ID_SYNC_FAILURES))); 780 }; 781 782 /** 783 * Checks if this folder is in spam folder. 784 * 785 * @return {Boolean} <code>true</code> if in spam 786 */ 787 ZmFolder.prototype.isInSpam = 788 function(){ 789 return this.isUnder(ZmFolder.ID_SPAM); 790 }; 791 792 /** 793 * 794 * @param {ZmFolder} folder the source folder 795 * 796 * @return {Boolean} <code>true/code> if the given remote folder can be moved into this remote folder. 797 * The source and the target folder must belong to the same account. The source 798 * must have delete permission and the target must have insert permission. 799 * 800 * @private 801 */ 802 ZmFolder.prototype._remoteMoveOk = 803 function(folder) { 804 if (!this.isRemote() && folder.isMountpoint && folder.rid) { return true; } 805 if (!this.link || !folder.link || this.getOwner() !== folder.getOwner()) { return false; } 806 if (!this._folderActionOk(this, "isInsert")) { 807 return false; 808 } 809 return this._folderActionOk(folder, "isDelete"); 810 }; 811 812 ZmFolder.prototype._folderActionOk = 813 function(folder, func) { 814 var share = folder.shares && folder.shares[0]; 815 if (!share) { 816 //if shares is not set, default to readOnly. 817 return !folder.isReadOnly(); 818 } 819 return share[func](); 820 }; 821 /** 822 * Returns true if this folder is for outbound mail. 823 */ 824 ZmFolder.prototype.isOutbound = 825 function() { 826 for (var i = 0; i < ZmFolder.OUTBOUND.length; i++) { 827 if (this.isUnder(ZmFolder.OUTBOUND[i])) { 828 return true; 829 } 830 } 831 return false; 832 }; 833 834 835 /** 836 * Sets the Global Mark Read flag. When the user sets this flag, read flags are global for all 837 * shared instances of the folder. When not set, each user accessing the shared folder will maintain 838 * their own read/unread flag. 839 * 840 * @param {Object} globalMarkRead the globalMarkRead boolean flag 841 * @param {AjxCallback} callback the callback 842 * @param {AjxCallback} errorCallback the error callback 843 * @param {ZmBatchCommand} batchCmd optional batch command 844 */ 845 ZmOrganizer.prototype.setGlobalMarkRead = function(globalMarkRead, callback, errorCallback, batchCmd) { 846 if (this.globalMarkRead == globalMarkRead) { return; } 847 // TODO: Bug 59559, awaiting server side implementation (Bug 24567) 848 // TODO: - For ZmFolderPropsDialog and ZmSharePropsDialog: 849 // TODO: Make sure that the attrName is indeed globalMarkRead - used in the dialogs 850 // TODO: Make the globalMarkRead labels and controls visible. 851 // TODO: - Uncomment this once the server call is ready, make sure action/attrs are correct 852 //this._organizerAction({action: "globalMarkRead", attrs: {globalMarkRead: globalMarkRead}, callback: callback, 853 // errorCallback: errorCallback, batchCmd: batchCmd}); 854 }; 855