1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * 27 * This file defines a list of accounts. 28 * 29 */ 30 31 /** 32 * Creates the account list. 33 * @class 34 * This class is used to store and manage a list of accounts for a mailbox. 35 * 36 * @author Parag Shah 37 */ 38 ZmAccountList = function() { 39 this._accounts = {}; 40 this._count = 0; 41 this.visibleAccounts = []; 42 this.mainAccount = null; 43 this.activeAccount = null; 44 this.defaultAccount = null; // the first non-main account. 45 46 this._evtMgr = new AjxEventMgr(); 47 }; 48 49 ZmAccountList.prototype.constructor = ZmAccountList; 50 51 52 // Consts 53 54 ZmAccountList.DEFAULT_ID = "main"; 55 56 57 // Public methods 58 59 /** 60 * Returns a string representation of the object. 61 * 62 * @return {String} a string representation of the object 63 */ 64 ZmAccountList.prototype.toString = 65 function() { 66 return "ZmAccountList"; 67 }; 68 69 /** 70 * Gets the number of visible accounts for this mailbox. 71 * 72 * @param {Boolean} includeInvisible if <code>true</code>, include the number of invisible accounts for this mailbox 73 * @return {int} the number of accounts for this mailbox 74 */ 75 ZmAccountList.prototype.size = 76 function(includeInvisible) { 77 return (includeInvisible) ? this._count : this.visibleAccounts.length; 78 }; 79 80 /** 81 * Adds the account. 82 * 83 * @param {ZmAccount} account the account 84 */ 85 ZmAccountList.prototype.add = 86 function(account) { 87 this._accounts[account.id] = account; 88 this._count++; 89 90 if (account.visible || account.id == ZmAccountList.DEFAULT_ID) { 91 this.visibleAccounts.push(account); 92 } 93 94 if (account.id == ZmAccountList.DEFAULT_ID) { 95 this.mainAccount = account; 96 } 97 }; 98 99 /** 100 * Gets the accounts. 101 * 102 * @return {Array} an array of {ZmAccount} objects 103 */ 104 ZmAccountList.prototype.getAccounts = 105 function() { 106 return this._accounts; 107 }; 108 109 /** 110 * Gets the account by id. 111 * 112 * @param {String} id the id 113 * @return {ZmAccount} the account 114 */ 115 ZmAccountList.prototype.getAccount = 116 function(id) { 117 return id ? this._accounts[id] : this.mainAccount; 118 }; 119 120 /** 121 * Gets the account by name. 122 * 123 * @param {String} name the name 124 * @return {ZmAccount} the account 125 */ 126 ZmAccountList.prototype.getAccountByName = 127 function(name) { 128 for (var i in this._accounts) { 129 if (this._accounts[i].name == name) { 130 return this._accounts[i]; 131 } 132 } 133 return null; 134 }; 135 136 /** 137 * Gets the account by email. 138 * 139 * @param {String} email the email 140 * @return {ZmAccount} the account 141 */ 142 ZmAccountList.prototype.getAccountByEmail = 143 function(email) { 144 for (var i in this._accounts) { 145 if (this._accounts[i].getEmail() == email) { 146 return this._accounts[i]; 147 } 148 } 149 return null; 150 }; 151 152 /** 153 * Gets the cumulative item count of all accounts for the given folder ID. 154 * 155 * @param {String} folderId the folder id 156 * @param {Boolean} checkUnread if <code>true</code>, checks the unread count instead of item count 157 * @return {int} the item count 158 */ 159 ZmAccountList.prototype.getItemCount = 160 function(folderId, checkUnread) { 161 var count = 0; 162 for (var i = 0; i < this.visibleAccounts.length; i++) { 163 var acct = this.visibleAccounts[i]; 164 if (acct.isMain) { continue; } // local account should never have drafts 165 166 var fid = ZmOrganizer.getSystemId(folderId, acct); 167 var folder = appCtxt.getById(fid); 168 if (folder) { 169 count += (checkUnread ? folder.numUnread : folder.numTotal); 170 } 171 } 172 173 return count; 174 }; 175 176 /** 177 * Generates a query. 178 * 179 * @param {String} folderId the folder id 180 * @param {Array} types the types 181 * @return {String} the query 182 */ 183 ZmAccountList.prototype.generateQuery = 184 function(folderId, types) { 185 // XXX: for now, let's just search for *one* type at a time 186 var type = types && types.get(0); 187 var query = []; 188 var list = this.visibleAccounts; 189 var fid = folderId || ZmOrganizer.ID_ROOT; 190 var syntax = folderId ? "inid" : "underid"; 191 for (var i = 0; i < list.length; i++) { 192 var acct = list[i]; 193 194 // dont add any apps not supported by this account 195 if ((type && !acct.isAppEnabled(ZmItem.APP[type])) || acct.isMain) { continue; } 196 197 var part = [syntax, ':"', ZmOrganizer.getSystemId(fid, acct, true), '"']; 198 query.push(part.join("")); 199 } 200 if(fid == ZmOrganizer.ID_ROOT) { 201 query.push([syntax, ':"', appCtxt.accountList.mainAccount.id, ':', fid, '"'].join("")); 202 } 203 DBG.println(AjxDebug.DBG2, "query = " + query.join(" OR ")); 204 return (query.join(" OR ")); 205 }; 206 207 /** 208 * Loads each visible account serially by requesting the following requests from 209 * the server in a batch request: 210 * 211 * <ul> 212 * <li><code><GetInfoRequest></code></li> 213 * <li><code><GetTafReqyuest></code></li> 214 * <li><code><GetFolderRequest></code></li> 215 * </ul> 216 * 217 * @param {AjxCallback} callback the callback to trigger once all accounts have been loaded 218 */ 219 ZmAccountList.prototype.loadAccounts = 220 function(callback) { 221 var list = (new Array()).concat(this.visibleAccounts); 222 this._loadAccount(list, callback); 223 }; 224 225 /** 226 * @private 227 */ 228 ZmAccountList.prototype._loadAccount = 229 function(accounts, callback) { 230 var acct = accounts.shift(); 231 if (acct) { 232 var respCallback = new AjxCallback(this, this._loadAccount, [accounts, callback]); 233 acct.load(respCallback); 234 } else { 235 // do any post account load initialization 236 ZmOrganizer.HIDE_EMPTY[ZmOrganizer.TAG] = true; 237 ZmOrganizer.HIDE_EMPTY[ZmOrganizer.SEARCH] = true; 238 239 // enable compose based on whether at least one account supports smtp 240 for (var i = 0; i < this.visibleAccounts.length; i++) { 241 if (appCtxt.get(ZmSetting.OFFLINE_SMTP_ENABLED, null, this.visibleAccounts[i])) { 242 appCtxt.set(ZmSetting.OFFLINE_COMPOSE_ENABLED, true, null, null, true); 243 break; 244 } 245 } 246 247 if (callback) { 248 callback.run(); 249 } 250 } 251 }; 252 253 /** 254 * Sets the given account as the active one, which will then be used when fetching 255 * any account-specific data such as settings or folder tree. 256 * 257 * @param {ZmZimbraAccount} account the account to make active 258 * @param {Boolean} skipNotify if <code>true</code>, skip notify 259 */ 260 ZmAccountList.prototype.setActiveAccount = 261 function(account, skipNotify) { 262 this.activeAccount = account; 263 264 this._evt = this._evt || new ZmEvent(); 265 this._evt.account = account; 266 267 if (!skipNotify) { 268 this._evtMgr.notifyListeners("ACCOUNT", this._evt); 269 } 270 }; 271 272 /** 273 * Adds an active account listener. 274 * 275 * @param {AjxListener} listener the listener 276 * @param {int} index the index where to insert the listener 277 */ 278 ZmAccountList.prototype.addActiveAcountListener = 279 function(listener, index) { 280 return this._evtMgr.addListener("ACCOUNT", listener, index); 281 }; 282 283 /** 284 * Checks if any of the non-main, visible accounts is currently doing an initial sync. 285 * 286 * @return {Boolean} <code>true</code> if any of the non-main accounts are doing initial sync 287 */ 288 ZmAccountList.prototype.isInitialSyncing = 289 function() { 290 for (var i = 0; i < this.visibleAccounts.length; i++) { 291 var acct = this.visibleAccounts[i]; 292 if (acct.isMain) { continue; } 293 294 if (acct.isOfflineInitialSync()) { 295 return true; 296 } 297 } 298 299 return false; 300 }; 301 302 /** 303 * Returns true if any of the visible accounts have the given status 304 * 305 * @param {String} status Status to check for 306 */ 307 ZmAccountList.prototype.isSyncStatus = 308 function(status) { 309 for (var i = 0; i < this.visibleAccounts.length; i++) { 310 var acct = this.visibleAccounts[i]; 311 if (acct.isMain) { continue; } 312 313 if (acct.status == status) { 314 return true; 315 } 316 } 317 318 return false; 319 }; 320 321 /** 322 * Checks if there is at least one of the given account types in the 323 * account list. Note: if the given account type is ZCS, the local parent 324 * account is NOT included when searching the account list. 325 * 326 * @param {String} type the type of account to check 327 * @return {Boolean} <code>true</code> if the account exists 328 */ 329 ZmAccountList.prototype.accountTypeExists = 330 function(type) { 331 for (var i = 0; i < this.visibleAccounts.length; i++) { 332 var acct = this.visibleAccounts[i]; 333 if (type == ZmAccount.TYPE_ZIMBRA && acct.isMain) { continue; } 334 if (acct.type == type) { return true; } 335 } 336 337 return false; 338 }; 339 340 /** 341 * Syncs all visible accounts. 342 * 343 * @param {AjxCallback} callback the callback 344 */ 345 ZmAccountList.prototype.syncAll = 346 function(callback) { 347 var list = (new Array()).concat(this.visibleAccounts); 348 this._sendSync(list, callback); 349 }; 350 351 /** 352 * @private 353 */ 354 ZmAccountList.prototype._sendSync = 355 function(accounts, callback) { 356 var acct = accounts.shift(); 357 if (acct) { 358 if (!acct.isMain) { // skip the main account 359 acct.sync(); 360 } 361 AjxTimedAction.scheduleAction(new AjxTimedAction(this, this._sendSync, [accounts, callback]), 500); 362 } else { 363 if (callback) { 364 callback.run(); 365 } 366 } 367 }; 368 369 /** 370 * Creates the main account and all its children. In the normal case, the "main" 371 * account is the only account, and represents the user who logged in. If family 372 * mailbox is enabled, that account is a parent account with dominion over child 373 * accounts. If offline, the main account is the "local" account. 374 * 375 * @param {ZmSettings} settings the settings for the main account 376 * @param {Object} obj the JSON obj containing meta info about the main account and its children 377 */ 378 ZmAccountList.prototype.createAccounts = 379 function(settings, obj) { 380 // first, replace the dummy main account with real information 381 var account = appCtxt.accountList.mainAccount; 382 account.id = obj.id; 383 account.name = obj.name; 384 account.isMain = true; 385 account.isZimbraAccount = true; 386 account.loaded = true; 387 account.visible = true; 388 account.settings = settings; 389 account.type = ZmAccount.TYPE_ZIMBRA; 390 account.icon = "AccountZimbra"; 391 account.active = true; // always set active for main/parent account 392 393 this._accounts[account.id] = account; 394 delete this._accounts[ZmAccountList.DEFAULT_ID]; 395 396 this.setActiveAccount(account); 397 398 if (appCtxt.isOffline) { 399 account.displayName = ZmMsg.localFolders; 400 } 401 402 // second, create all child accounts if applicable 403 var childAccounts = obj.childAccounts && obj.childAccounts.childAccount; 404 if (childAccounts) { 405 for (var i = 0; i < childAccounts.length; i++) { 406 this.add(ZmZimbraAccount.createFromDom(childAccounts[i])); 407 } 408 409 // set global vars per number of child accounts 410 appCtxt.multiAccounts = this.size() > 1; 411 appCtxt.isFamilyMbox = appCtxt.multiAccounts && !appCtxt.isOffline; 412 413 this.defaultAccount = appCtxt.isFamilyMbox ? this.mainAccount : this.visibleAccounts[1]; 414 } 415 }; 416 417 /** 418 * Resets the trees. 419 * 420 */ 421 ZmAccountList.prototype.resetTrees = 422 function() { 423 for (var i = 0; i < this.visibleAccounts.length; i++) { 424 for (var type in trees) { 425 var tree = trees[type]; 426 if (tree && tree.reset) { 427 tree.reset(); 428 } 429 } 430 } 431 }; 432 433 /** 434 * Saves the implicit preferences on the visible accounts. 435 * 436 */ 437 ZmAccountList.prototype.saveImplicitPrefs = 438 function() { 439 for (var i = 0; i < this.visibleAccounts.length; i++) { 440 this.visibleAccounts[i].saveImplicitPrefs(); 441 } 442 }; 443 444 /** 445 * Gets the tool tip for the folder. 446 * 447 * @param {String} folderId the folder id 448 * @return {String} the tool tip 449 */ 450 ZmAccountList.prototype.getTooltipForVirtualFolder = 451 function(folderId) { 452 var numTotal = 0; 453 var sizeTotal = 0; 454 455 for (var i = 0; i < this.visibleAccounts.length; i++) { 456 var acct = this.visibleAccounts[i]; 457 var fid = ZmOrganizer.getSystemId(folderId, acct); 458 var folder = appCtxt.getById(fid); 459 if (folder) { 460 numTotal += folder.numTotal; 461 sizeTotal += folder.sizeTotal; 462 } 463 } 464 465 var subs = { 466 itemText: ZmMsg.messages, 467 numTotal: numTotal, 468 sizeTotal: sizeTotal 469 }; 470 471 return AjxTemplate.expand("share.App#FolderTooltip", subs); 472 }; 473