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