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