1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2007, 2008, 2009, 2010, 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) 2007, 2008, 2009, 2010, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates the identity collection.
 26  * @class
 27  * This class represents the identity collection
 28  * 
 29  * @extends		ZmModel
 30  */
 31 ZmIdentityCollection = function() {
 32 	ZmModel.call(this, ZmEvent.S_IDENTITY);
 33 	this.defaultIdentity = null;
 34 	this._initialized = false;
 35 	this._idToIdentity = {};
 36 	this._addressToIdentity = {};
 37 	this._folderToIdentity = {};
 38 	this._size = 0;
 39 };
 40 
 41 ZmIdentityCollection.prototype = new ZmModel;
 42 ZmIdentityCollection.prototype.constructor = ZmIdentityCollection;
 43 
 44 ZmIdentityCollection.prototype.toString =
 45 function() {
 46 	return "ZmIdentityCollection";
 47 };
 48 
 49 //
 50 // Public methods
 51 //
 52 
 53 /**
 54  * Gets the count of identities.
 55  * 
 56  * @return	{int}		the size
 57  */
 58 ZmIdentityCollection.prototype.getSize =
 59 function() {
 60 	// bug: 30009
 61 	return this.getIdentities().length;
 62 };
 63 
 64 /**
 65  * Gets the identities.
 66  * 
 67  * @param	{Object}	sort		(not used)
 68  * @return	{Array}		an array of {ZmIdentity} objects
 69  */
 70 ZmIdentityCollection.prototype.getIdentities =
 71 function(sort) {
 72 	var identity, i = 0, result = [], isOffline = appCtxt.isOffline;
 73 	for (var id in this._idToIdentity) {
 74 		identity = this._idToIdentity[id];
 75 		// bug: 30009
 76 		if (isOffline && identity.isFromDataSource) continue;
 77 		result[i++] = identity;
 78 	}
 79 	if (sort) {
 80 		result.sort(ZmIdentityCollection._comparator);
 81 	}
 82 	return result;
 83 };
 84 
 85 /**
 86  * Gets the identity by id.
 87  * 
 88  * @param	{String}	id	the identity id
 89  * @return	{ZmIdentity}	the identity
 90  */
 91 ZmIdentityCollection.prototype.getById =
 92 function(id) {
 93 	return this._idToIdentity[id];
 94 };
 95 
 96 /**
 97  * Gets the identity by name.
 98  * 
 99  * @param	{String}	name		the identity name
100  * @return	{ZmIdentity}	the identity
101  */
102 ZmIdentityCollection.prototype.getByName =
103 function(name) {
104 	name = name.toLowerCase();
105 	for (var id in this._idToIdentity) {
106 		var identity = this._idToIdentity[id];
107 		if (identity.name.toLowerCase() == name) {
108 			return identity;
109 		}
110 	}
111 	return null;
112 };
113 
114 /**
115  * Adds the identity to the collection.
116  * 
117  * @param	{ZmIdentity}	identity		the identity
118  */
119 ZmIdentityCollection.prototype.add =
120 function(identity) {
121 	if (!this._idToIdentity[identity.id]) {
122 		this._idToIdentity[identity.id] = identity;
123 		if (identity.isDefault) {
124 			this.defaultIdentity = identity;
125 		}
126 
127 		this._addToMaps(identity);
128 		this._size++;
129 	}
130 };
131 
132 /**
133  * Removes the identity from the collection.
134  * 
135  * @param	{ZmIdentity}	identity		the identity
136  */
137 ZmIdentityCollection.prototype.remove =
138 function(identity) {
139 	if (this._idToIdentity[identity.id]) {
140 		this._removeFromMaps(identity);
141 		delete this._idToIdentity[identity.id];
142 		this._size--;
143 	}
144 };
145 /**
146  * try to find the persona to use from the rules defined in the accounts settings. Recurse to parent so to apply rules to sub-folders too.
147  * @param folderId
148  * @returns {*}
149  */
150 ZmIdentityCollection.prototype.selectIdentityFromFolder =
151 function(folderId) {
152 	if (!folderId) {
153 		return this.defaultIdentity;
154 	}
155 	var folder = appCtxt.getById(folderId);
156 	var parent = folder.parent;
157 	return this._folderToIdentity[folder.getRemoteId()] || this.selectIdentityFromFolder(parent && parent.id);
158 };
159 
160 ZmIdentityCollection.prototype.selectIdentity =
161 function(mailMsg, type) {
162 	if (!appCtxt.get(ZmSetting.IDENTITIES_ENABLED) || !mailMsg) {
163 		return this.defaultIdentity;
164 	}
165 
166 	// Check if the a identity's address was in the given type field.
167 	if (type) {
168 		return this._selectIdentityFromAddresses(mailMsg, type);
169 	}
170 
171 	// Check if the a identity's address was in the to field.
172 	var identity = this._selectIdentityFromAddresses(mailMsg, AjxEmailAddress.TO);
173 	if (identity) { return identity; }
174 
175 	// Check if the a identity's address was in the cc field.
176 	identity = this._selectIdentityFromAddresses(mailMsg, AjxEmailAddress.CC);
177 	if (identity) { return identity; }
178 
179     //Check if a identity's address was in the attendees list
180     if(mailMsg.isInvite()) {
181         identity = this._selectIdentityFromAttendees(mailMsg);
182         if (identity) { return identity; }
183     }
184 
185 	// Check if a identity's folder is the same as where the message lives.
186 	return this.selectIdentityFromFolder(mailMsg.folderId);
187 };
188 
189 ZmIdentityCollection.prototype.initialize =
190 function(data) {
191 	// This can be called unnecessarily after auth token expires.
192 	if (this._initialized || this.getSize() || !data) { return; }
193 
194 	var identities = data.identity;
195 	for (var i = 0, count = identities ? identities.length : 0; i < count; i++) {
196 		var identity = new ZmIdentity('');
197 		identity._loadFromDom(identities[i]);
198 		this.add(identity);
199 	}
200 	this._initialized = true;
201 };
202 
203 //
204 // Protected methods
205 //
206 
207 ZmIdentityCollection.prototype._addToMaps =
208 function(identity) {
209 	if (identity.useWhenSentTo) {
210 		var addresses = identity.whenSentToAddresses;
211 		for (var i = 0, count = addresses.length; i < count; i++) {
212 			var address = addresses[i].toLowerCase();
213 			// External emails are added after other identities, potentially overwriting a persona which should have
214 			// precedence.  Use the external identity only if the email address has not been assigned an identity.
215 			if (!this._addressToIdentity[address] || !identity.isFromDataSource) {
216 				this._addressToIdentity[address] = identity;
217 			}
218 		}
219 	}
220 
221 	if (identity.useWhenInFolder) {
222 		var folders = identity.whenInFolderIds;
223 		for (var i = 0, count = folders.length; i < count; i++) {
224 			var folder = appCtxt.getById(folders[i]);
225 			if (folder) {
226 				var fid = folder.getRemoteId();
227 				this._folderToIdentity[fid] = identity;
228 			}
229 		}
230 	}
231 };
232 
233 ZmIdentityCollection.prototype._removeFromMaps =
234 function(identity) {
235 	for (var i = 0, count = identity.whenSentToAddresses.length; i < count; i++) {
236 		var address = identity.whenSentToAddresses[i];
237 		delete this._addressToIdentity[address];
238 	}
239 
240 	for (var i = 0, count = identity.whenInFolderIds.length; i < count; i++) {
241 		var folder = appCtxt.getById(identity.whenInFolderIds[i]);
242 		if (folder) {
243 			var fid = folder.getRemoteId();
244 			delete this._folderToIdentity[fid];
245 		}
246 	}
247 };
248 
249 ZmIdentityCollection._comparator =
250 function(a, b) {
251 	if (a.isDefault) {
252 		return -1;
253 	} else if (b.isDefault) {
254 		return 1;
255 	} else {
256 		return a.name == b.name ? 0 : a.name < b.name ? -1 : 1;
257 	}
258 };
259 
260 ZmIdentityCollection.prototype.getSortIndex =
261 function(identity) {
262 
263 	var identities = this.getIdentities(true);
264 	if (!(identities && identities.length)) { return 0; }
265 
266 	if (this.getById(identity.id)) {
267 		// already have the identity, find its current position
268 		for (var i = 0; i < identities.length; i++) {
269 			if (identities[i].id == identity.id) {
270 				return i;
271 			}
272 		}
273 	} else {
274 		// hasn't been added yet, find where it should go
275 		for (var i = 0; i < identities.length; i++) {
276 			var test = ZmIdentityCollection._comparator(identity, identities[i]);
277 			if (test == -1) {
278 				return i;
279 			}
280 		}
281 	}
282 	return identities.length - 1;
283 };
284 
285 ZmIdentityCollection.prototype._selectIdentityFromAddresses =
286 function(mailMsg, type) {
287 	var identity;
288 	var addresses = mailMsg.getAddresses(type).getArray();
289 	for (var i = 0, count = addresses.length; i < count; i++) {
290 		var address = addresses[i].getAddress();
291 		if (address) {
292 			identity = this._addressToIdentity[address.toLowerCase()];
293 			if(identity) {
294 				return identity;
295 			}
296 		}
297 	}
298 	return null;
299 };
300 
301 /**
302  * Gets the identity based on attendees list
303  *
304  * @param	{ZmMailMsg}	    mail msg which is an invitation, passing non-invite mail msg will return null
305  * @return	{ZmIdentity}	the identity
306  */
307 ZmIdentityCollection.prototype._selectIdentityFromAttendees =
308 function(mailMsg) {
309 
310     if(!mailMsg.isInvite()) return null;
311 
312 	var identity;
313     var attendees = mailMsg.invite.getAttendees();
314 
315     if(!attendees) return null;
316     
317 	for (var i = 0, count = attendees.length; i < count; i++) {
318 		var address = attendees[i].url;
319 		if (address) {
320 			identity = this._addressToIdentity[address.toLowerCase()];
321 			if(identity) {
322 				return identity;
323 			}
324 		}
325 	}
326     
327 	return null;
328 };
329 
330 ZmIdentityCollection.prototype.getIdentityBySendAddress =
331 function(address) {
332     for(var id in this._idToIdentity){
333         var identity = this._idToIdentity[id];
334         if(identity.sendFromAddress == address){
335             return identity;
336         }
337     }
338     return null;
339 };
340