1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates a data source. 26 * @class 27 * This class represents a data source. 28 * 29 * @param {constant} type the account type (see <code>ZmAccount.TYPE_</code> constants) 30 * @param {String} id the id 31 * 32 * @extends ZmAccount 33 */ 34 ZmDataSource = function(type, id) { 35 if (arguments.length == 0) { return; } 36 ZmAccount.call(this, type, id); 37 this.reset(); 38 }; 39 40 ZmDataSource.prototype = new ZmAccount; 41 ZmDataSource.prototype.constructor = ZmDataSource; 42 43 ZmDataSource.prototype.toString = 44 function() { 45 return "ZmDataSource"; 46 }; 47 48 // 49 // Constants 50 // 51 /** 52 * Defines the "cleartext" connection type. 53 */ 54 ZmDataSource.CONNECT_CLEAR = "cleartext"; 55 /** 56 * Defines the "ssl" connection type. 57 */ 58 ZmDataSource.CONNECT_SSL = "ssl"; 59 ZmDataSource.CONNECT_DEFAULT = ZmDataSource.CONNECT_CLEAR; 60 61 ZmDataSource.POLL_NEVER = "0"; 62 63 // soap attribute to property maps 64 65 ZmDataSource.DATASOURCE_ATTRS = { 66 // SOAP attr: JS property 67 "id": "id", 68 "name": "name", 69 "isEnabled": "enabled", 70 "emailAddress": "email", 71 "host": "mailServer", 72 "port": "port", 73 "username": "userName", 74 "password": "password", 75 "l": "folderId", 76 "connectionType": "connectionType", 77 "pollingInterval": "pollingInterval", 78 "smtpEnabled": "smtpEnabled", 79 "leaveOnServer": "leaveOnServer" // POP only 80 }; 81 82 ZmDataSource.IDENTITY_ATTRS = { 83 // SOAP attr: JS property 84 "fromDisplay": "sendFromDisplay", 85 "useAddressForForwardReply": "setReplyTo", 86 "replyToAddress": "setReplyToAddress", 87 "replyToDisplay": "setReplyToDisplay", 88 "defaultSignature": "signature", 89 "forwardReplySignature": "replySignature" 90 }; 91 92 // 93 // Data 94 // 95 96 ZmDataSource.prototype.ELEMENT_NAME = "dsrc"; 97 98 // data source settings 99 100 ZmDataSource.prototype.enabled = true; 101 102 // basic settings 103 104 ZmDataSource.prototype.mailServer = ""; 105 ZmDataSource.prototype.userName = ""; 106 ZmDataSource.prototype.password = ""; 107 ZmDataSource.prototype.folderId = ZmOrganizer.ID_INBOX; 108 109 // advanced settings 110 111 ZmDataSource.prototype.leaveOnServer = true; 112 ZmDataSource.prototype.connectionType = ZmDataSource.CONNECT_DEFAULT; 113 114 // 115 // Public methods 116 // 117 118 /** NOTE: Email is same as the identity's from address. */ 119 ZmDataSource.prototype.setEmail = 120 function(email) { 121 this.email = email; 122 }; 123 124 ZmDataSource.prototype.getEmail = 125 function() { 126 var email = this.email != null ? this.email : this.identity.getField(ZmIdentity.SEND_FROM_ADDRESS); // bug: 23042 127 if (!email) { // bug: 38175 128 var provider = ZmDataSource.getProviderForAccount(this); 129 var host = (provider && provider._host) || this.mailServer; 130 email = ""; 131 if (this.userName) { 132 if (this.userName.match(/@/)) email = this.userName; // bug: 48186 133 else if (host) email = [ this.userName, host].join("@"); 134 } 135 } 136 return email; 137 }; 138 139 ZmDataSource.prototype.setFolderId = 140 function(folderId) { 141 // TODO: Is there a better way to do this? 142 // I basically need to have the folder selector on the options 143 // page have a value of -1 but allow other code to see that and 144 // fill in the correct folder id. But I don't want it to 145 // overwrite that value once set. 146 if (folderId == -1 && this.folderId != ZmOrganizer.ID_INBOX) { return; } 147 this.folderId = folderId; 148 }; 149 150 ZmDataSource.prototype.getFolderId = 151 function() { 152 return this.folderId; 153 }; 154 155 ZmDataSource.prototype.getIdentity = 156 function() { 157 return this.identity; 158 }; 159 160 // operations 161 162 ZmDataSource.prototype.create = 163 function(callback, errorCallback, batchCommand) { 164 var soapDoc = AjxSoapDoc.create("CreateDataSourceRequest", "urn:zimbraMail"); 165 var dsrc = soapDoc.set(this.ELEMENT_NAME); 166 for (var aname in ZmDataSource.DATASOURCE_ATTRS) { 167 var pname = ZmDataSource.DATASOURCE_ATTRS[aname]; 168 var pvalue = pname == "folderId" 169 ? ZmOrganizer.normalizeId(this[pname]) 170 : this[pname]; 171 if (pname == "id" || (!pvalue && pname != "enabled" && pname != "leaveOnServer")) continue; 172 173 dsrc.setAttribute(aname, String(pvalue)); 174 } 175 var identity = this.getIdentity(); 176 for (var aname in ZmDataSource.IDENTITY_ATTRS) { 177 var pname = ZmDataSource.IDENTITY_ATTRS[aname]; 178 var pvalue = identity[pname]; 179 if (!pvalue) continue; 180 181 dsrc.setAttribute(aname, String(pvalue)); 182 } 183 184 var respCallback = new AjxCallback(this, this._handleCreateResponse, [callback]); 185 if (batchCommand) { 186 batchCommand.addNewRequestParams(soapDoc, respCallback, errorCallback); 187 batchCommand.setSensitive(Boolean(this.password)); 188 return; 189 } 190 191 var params = { 192 soapDoc: soapDoc, 193 sensitive: Boolean(this.password), 194 asyncMode: Boolean(callback), 195 callback: respCallback, 196 errorCallback: errorCallback 197 }; 198 return appCtxt.getAppController().sendRequest(params); 199 }; 200 201 ZmDataSource.prototype.save = function(callback, errorCallback, batchCommand, isIdentity) { 202 203 var soapDoc = AjxSoapDoc.create("ModifyDataSourceRequest", "urn:zimbraMail"); 204 var dsrc = soapDoc.set(this.ELEMENT_NAME); 205 // NOTE: If this object is a proxy, we guarantee that the 206 // the id attribute is *always* set. 207 dsrc.setAttribute("id", this.id); 208 if (!isIdentity) { 209 for (var aname in ZmDataSource.DATASOURCE_ATTRS) { 210 var pname = ZmDataSource.DATASOURCE_ATTRS[aname]; 211 if (!this.hasOwnProperty(pname)) { 212 continue; 213 } 214 var avalue = this[pname]; 215 if (pname === "folderId") { 216 avalue = ZmOrganizer.normalizeId(avalue); 217 } 218 // server sends us pollingInterval in ms, expects it back in seconds (!) 219 // since it is not a user-visible value, it's safer to not send it back at all 220 else if (pname === "pollingInterval") { 221 continue; 222 } 223 dsrc.setAttribute(aname, String(avalue)); 224 } 225 } 226 var identity = this.getIdentity(); 227 for (var aname in ZmDataSource.IDENTITY_ATTRS) { 228 var pname = ZmDataSource.IDENTITY_ATTRS[aname]; 229 if (!identity.hasOwnProperty(pname)) continue; 230 231 var avalue = identity[pname]; 232 dsrc.setAttribute(aname, String(avalue)); 233 } 234 235 var respCallback = new AjxCallback(this, this._handleSaveResponse, [callback]); 236 if (batchCommand) { 237 batchCommand.addNewRequestParams(soapDoc, respCallback, errorCallback); 238 batchCommand.setSensitive(Boolean(this.password)); 239 return; 240 } 241 242 var params = { 243 soapDoc: soapDoc, 244 sensitive: Boolean(this.password), 245 asyncMode: Boolean(callback), 246 callback: respCallback, 247 errorCallback: errorCallback 248 }; 249 return appCtxt.getAppController().sendRequest(params); 250 }; 251 252 ZmDataSource.prototype.doDelete = 253 function(callback, errorCallback, batchCommand) { 254 var soapDoc = AjxSoapDoc.create("DeleteDataSourceRequest", "urn:zimbraMail"); 255 var dsrc = soapDoc.set(this.ELEMENT_NAME); 256 dsrc.setAttribute("id", this.id); 257 258 var respCallback = new AjxCallback(this, this._handleDeleteResponse, [callback]); 259 if (batchCommand) { 260 batchCommand.addNewRequestParams(soapDoc, respCallback, errorCallback); 261 return; 262 } 263 264 var params = { 265 soapDoc: soapDoc, 266 asyncMode: Boolean(callback), 267 callback: respCallback, 268 errorCallback: errorCallback 269 }; 270 return appCtxt.getAppController().sendRequest(params); 271 }; 272 273 /** 274 * Tests the data source connection. 275 * 276 * @param {AjxCallback} callback the callback 277 * @param {AjxCallback} errorCallback the error callback 278 * @param {ZmBatchCommand} batchCommand the batch command 279 * @param {Boolean} noBusyOverlay if <code>true</code>, do not show busy overlay 280 * @return {Object} the response 281 */ 282 ZmDataSource.prototype.testConnection = 283 function(callback, errorCallback, batchCommand, noBusyOverlay) { 284 var soapDoc = AjxSoapDoc.create("TestDataSourceRequest", "urn:zimbraMail"); 285 var dsrc = soapDoc.set(this.ELEMENT_NAME); 286 287 var attrs = ["host", "port", "username", "password", "connectionType", "leaveOnServer"]; 288 for (var i = 0; i < attrs.length; i++) { 289 var aname = attrs[i]; 290 var pname = ZmDataSource.DATASOURCE_ATTRS[aname]; 291 dsrc.setAttribute(aname, this[pname]); 292 } 293 294 if (batchCommand) { 295 batchCommand.addNewRequestParams(soapDoc, callback, errorCallback); 296 batchCommand.setSensitive(true); 297 return; 298 } 299 300 var params = { 301 soapDoc: soapDoc, 302 sensitive: true, 303 asyncMode: Boolean(callback), 304 noBusyOverlay: noBusyOverlay, 305 callback: callback, 306 errorCallback: errorCallback 307 }; 308 return appCtxt.getAppController().sendRequest(params); 309 }; 310 311 /** 312 * Gets the port. 313 * 314 * @return {int} port 315 */ 316 ZmDataSource.prototype.getPort = 317 function() { 318 return this.port || this.getDefaultPort(); 319 }; 320 321 ZmDataSource.prototype.isStatusOk = function() { 322 return this.enabled && !this.failingSince; 323 }; 324 325 ZmDataSource.prototype.setFromJson = 326 function(obj) { 327 // errors 328 if (obj.failingSince) { 329 this.failingSince = obj.failingSince; 330 this.lastError = (obj.lastError && obj.lastError[0]._content) || ZmMsg.unknownError; 331 } 332 else { 333 delete this.failingSince; 334 delete this.lastError; 335 } 336 // data source fields 337 for (var aname in ZmDataSource.DATASOURCE_ATTRS) { 338 var avalue = obj[aname]; 339 if (avalue == null) continue; 340 if (aname == "isEnabled" || aname == "leaveOnServer") { 341 avalue = avalue == "1" || String(avalue).toLowerCase() == "true"; 342 } 343 // server sends us pollingInterval in ms, expects it back in seconds (!) 344 else if (aname === "pollingInterval") { 345 avalue = avalue / 1000; 346 } 347 var pname = ZmDataSource.DATASOURCE_ATTRS[aname]; 348 this[pname] = avalue; 349 } 350 351 // pseudo-identity fields 352 var identity = this.getIdentity(); 353 for (var aname in ZmDataSource.IDENTITY_ATTRS) { 354 var avalue = obj[aname]; 355 if (avalue == null) continue; 356 if (aname == "useAddressForForwardReply") { 357 avalue = avalue == "1" || String(avalue).toLowerCase() == "true"; 358 } 359 360 var pname = ZmDataSource.IDENTITY_ATTRS[aname]; 361 identity[pname] = avalue; 362 } 363 this._setupIdentity(); 364 }; 365 366 ZmDataSource.prototype.reset = function() { 367 // reset data source properties 368 // NOTE: These have default values on the prototype object 369 delete this.mailServer; 370 delete this.userName; 371 delete this.password; 372 delete this.folderId; 373 delete this.leaveOnServer; 374 delete this.connectionType; 375 delete this.pollingInterval; 376 // other 377 this.email = ""; 378 this.port = this.getDefaultPort(); 379 380 // reset identity 381 var identity = this.identity = new ZmIdentity(); 382 identity.id = this.id; 383 identity.isFromDataSource = true; 384 385 // saving the identity itself won't work; need to save the data source 386 var self = this; 387 identity.save = function(callback, errorCallback, batchCommand) { 388 ZmDataSource.prototype.save.call(self, callback, errorCallback, batchCommand, true); 389 }; 390 }; 391 392 ZmDataSource.prototype.getProvider = function() { 393 return ZmDataSource.getProviderForAccount(this); 394 }; 395 396 // 397 // Public functions 398 // 399 400 // data source providers - provides default values 401 402 /** 403 * Adds a data source provider. The registered providers are objects that 404 * specify default values for data sources. This can be used to show the 405 * user a list of known email providers (e.g. Yahoo! Mail) to pre-fill the 406 * account information. 407 * 408 * @param {Hash} provider a hash of provider information 409 * @param {String} provider.id a unique identifier for this provider 410 * @param {String} provider.name the name of this provider to display to the user 411 * @param {String} [provider.type] the type (see <code>ZmAccount.TYPE_</code> constants) 412 * @param {String} [provider.connectionType] the connection type (see <code>ZmDataSource.CONNECT_</code> constants) 413 * @param {String} [provider.host] the server 414 * @param {String} [provider.pollingInterval] the polling interval 415 * @param {Boolean} [provider.leaveOnServer] if <code>true</code>, leave message on server (POP only) 416 */ 417 ZmDataSource.addProvider = function(provider) { 418 var providers = ZmDataSource.getProviders(); 419 providers[provider.id] = provider; 420 // normalize values -- defensive programming 421 if (provider.type) { 422 provider.type = provider.type.toLowerCase() == "pop" ? ZmAccount.TYPE_POP : ZmAccount.TYPE_IMAP; 423 } 424 else { 425 provider.type = ZmAccount.TYPE_POP; 426 } 427 if (provider.connectionType) { 428 var isSsl = provider.connectionType.toLowerCase() == "ssl"; 429 provider.connectionType = isSsl ? ZmDataSource.CONNECT_SSL : ZmDataSource.CONNECT_CLEAR; 430 } 431 else { 432 provider.connectionType = ZmDataSource.CONNECT_CLEAR; 433 } 434 if (!provider.port) { 435 var isPop = provider.type == ZmAccount.TYPE_POP; 436 if (isSsl) { 437 provider.port = isPop ? ZmPopAccount.PORT_SSL : ZmImapAccount.PORT_SSL; 438 } 439 else { 440 provider.port = isPop ? ZmPopAccount.PORT_CLEAR : ZmImapAccount.PORT_CLEAR; 441 } 442 } 443 }; 444 445 /** 446 * Gets the providers. 447 * 448 * @return {Array} an array of providers 449 */ 450 ZmDataSource.getProviders = 451 function() { 452 if (!ZmDataSource._providers) { 453 ZmDataSource._providers = {}; 454 } 455 return ZmDataSource._providers; 456 }; 457 458 /** 459 * Gets the provider. 460 * 461 * @param {ZmAccount} account the account 462 * @return {Hash} the provider or <code>null</code> for none 463 */ 464 ZmDataSource.getProviderForAccount = 465 function(account) { 466 return ZmDataSource.getProviderForHost(account.mailServer); 467 }; 468 469 /** 470 * Gets the provider. 471 * 472 * @param {String} host the host 473 * @return {Hash} the provider or <code>null</code> for none 474 */ 475 ZmDataSource.getProviderForHost = 476 function(host) { 477 var providers = ZmDataSource.getProviders(); 478 for (var id in providers) { 479 hasProviders = true; 480 var provider = providers[id]; 481 if (provider.host == host) { 482 return provider; 483 } 484 } 485 return null; 486 }; 487 488 /** 489 * Removes all providers. 490 */ 491 ZmDataSource.removeAllProviders = function() { 492 delete ZmDataSource._providers; 493 }; 494 495 // 496 // Protected methods 497 // 498 499 500 ZmDataSource.prototype._setupIdentity = 501 function() { 502 this.identity.useWhenSentTo = true; 503 this.identity.whenSentToAddresses = [ this.getEmail() ]; 504 this.identity.name = this.name; 505 }; 506 507 ZmDataSource.prototype._loadFromDom = 508 function(data) { 509 this.setFromJson(data); 510 }; 511 512 ZmDataSource.prototype._handleCreateResponse = 513 function(callback, result) { 514 var resp = result._data.CreateDataSourceResponse; 515 this.id = resp[this.ELEMENT_NAME][0].id; 516 this.identity.id = this.id; 517 this._setupIdentity(); 518 delete this._new; 519 delete this._dirty; 520 521 appCtxt.getDataSourceCollection().add(this); 522 523 var apps = [ZmApp.MAIL, ZmApp.PORTAL]; 524 for (var i=0; i<apps.length; i++) { 525 var app = appCtxt.getApp(apps[i]); 526 if (app) { 527 var overviewId = app.getOverviewId(); 528 var treeView = appCtxt.getOverviewController().getTreeView(overviewId, ZmOrganizer.FOLDER); 529 var fid = appCtxt.getActiveAccount().isMain ? this.folderId : ZmOrganizer.getSystemId(this.folderId); 530 var treeItem = treeView ? treeView.getTreeItemById(fid) : null; 531 if (treeItem) { 532 // reset the icon in the tree view if POP account since the first time it 533 // was created, we didnt know it was a data source 534 if (this.type == ZmAccount.TYPE_POP && this.folderId != ZmFolder.ID_INBOX) { 535 treeItem.setImage("POPAccount"); 536 } 537 else if (this.type == ZmAccount.TYPE_IMAP) { 538 // change imap folder to a tree header since folder is first created 539 // without knowing its a datasource 540 treeItem.dispose(); 541 var rootId = ZmOrganizer.getSystemId(ZmOrganizer.ID_ROOT); 542 var parentNode = treeView.getTreeItemById(rootId); 543 var organizer = appCtxt.getById(this.folderId); 544 treeView._addNew(parentNode, organizer); 545 } 546 } 547 } 548 } 549 550 if (callback) { 551 callback.run(); 552 } 553 }; 554 555 ZmDataSource.prototype._handleSaveResponse = 556 function(callback, result) { 557 delete this._dirty; 558 559 var collection = appCtxt.getDataSourceCollection(); 560 // NOTE: By removing and adding it again, we make this proxy the 561 // base datasource object in the collection. 562 collection.remove(this); 563 collection.add(this); 564 565 if (callback) { 566 callback.run(); 567 } 568 }; 569 570 ZmDataSource.prototype._handleDeleteResponse = 571 function(callback, result) { 572 appCtxt.getDataSourceCollection().remove(this); 573 574 var overviewId = appCtxt.getApp(ZmApp.MAIL).getOverviewId(); 575 var treeView = appCtxt.getOverviewController().getTreeView(overviewId, ZmOrganizer.FOLDER); 576 var fid = appCtxt.getActiveAccount().isMain ? this.folderId : ZmOrganizer.getSystemId(this.folderId); 577 if(this.folderId == ZmAccountsPage.DOWNLOAD_TO_FOLDER && this._object_ && this._object_.folderId) { 578 fid = this._object_.folderId; 579 } 580 var treeItem = treeView ? treeView.getTreeItemById(fid) : null; 581 if (treeItem) { 582 if (this.type == ZmAccount.TYPE_POP && this.folderId != ZmFolder.ID_INBOX) { 583 // reset icon since POP folder is no longer hooked up to a datasource 584 treeItem.setImage("Folder"); 585 } else if (this.type == ZmAccount.TYPE_IMAP) { 586 // reset the icon in the tree view if POP account since the first time it 587 // was created, we didnt know it was a data source 588 treeItem.dispose(); 589 var parentNode = treeView.getTreeItemById(ZmOrganizer.ID_ROOT); 590 var organizer = appCtxt.getById(fid); 591 if (organizer) { 592 treeView._addNew(parentNode, organizer); 593 } 594 } 595 } 596 597 if (callback) { 598 callback.run(); 599 } 600 }; 601