1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 2005, 2006, 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file contains the Zimbra mail controller class. 27 * 28 */ 29 30 /** 31 * Creates a controller to run ZimbraMail. Do not call directly, instead use the run() 32 * factory method. 33 * @constructor 34 * @class 35 * This class is the "ubercontroller", as it manages all the apps as well as bootstrapping 36 * the ZimbraMail application. 37 * 38 * @param {Hash} params a hash of parameters 39 * @param {constant} params.app the starting app 40 * @param {Element} params.userShell the top-level skin container 41 * 42 * @extends ZmController 43 */ 44 ZmZimbraMail = function(params) { 45 46 if (arguments.length == 0) { return; } 47 48 ZmController.call(this, null); 49 50 appCtxt.setZimbraMail(this); 51 appCtxt.setAppController(this); 52 53 // ALWAYS set back reference into our world (also used by unload handler) 54 window._zimbraMail = this; 55 56 // app event handling 57 this._evt = new ZmAppEvent(); 58 this._evtMgr = new AjxEventMgr(); 59 // copy over any statically registered listeners 60 for (var type in ZmZimbraMail._listeners) { 61 var list = ZmZimbraMail._listeners[type]; 62 if (list && list.length) { 63 for (var i = 0; i < list.length; i++) { 64 this._evtMgr.addListener(type, list[i]); 65 } 66 } 67 } 68 69 // all subsequent calls to register static app listeners go to instance 70 ZmZimbraMail.addListener = AjxCallback.simpleClosure(this.addListener, this); 71 ZmZimbraMail.addAppListener = AjxCallback.simpleClosure(this.addAppListener, this); 72 73 // Create generic operations 74 ZmOperation.initialize(); 75 76 // settings 77 this._createSettings(params); 78 this._createEnabledApps(); 79 this._initializeSettings(params); 80 this._postInitializeSettings(); 81 82 //update body class to reflect user selected font 83 document.body.className = "user_font_" + appCtxt.get(ZmSetting.FONT_NAME); 84 //update root html elment class to reflect user selected font size (remove the "normal" size that was set by default). 85 Dwt.delClass(document.documentElement, "user_font_size_normal", "user_font_size_" + appCtxt.get(ZmSetting.FONT_SIZE)); 86 87 // set internal state 88 this._shell = appCtxt.getShell(); 89 this._userShell = params.userShell; 90 91 this._requestMgr = new ZmRequestMgr(this); // NOTE: requires settings to be initialized 92 93 this._appIframeView = {}; 94 this._activeApp = null; 95 this._sessionTimer = new AjxTimedAction(null, ZmZimbraMail.executeSessionTimer); 96 this._sessionTimerId = -1; 97 this._pollActionId = null; // AjaxTimedAction ID of timer counting down to next poll time 98 this._pollRequest = null; // HTTP request of poll we've sent to server 99 this._pollInstantNotifications = false; // if TRUE, we're in "instant notification" mode 100 this.statusView = null; 101 ZmZimbraMail._exitTimer = new AjxTimedAction(null, ZmZimbraMail.exitSession); 102 ZmZimbraMail._exitTimerId = -1; 103 ZmZimbraMail.stayOnPagePrompt = false; 104 ZmZimbraMail.STAYONPAGE_INTERVAL = 2; //in minutes 105 // setup history support 106 if (appCtxt.get(ZmSetting.HISTORY_SUPPORT_ENABLED) && !AjxEnv.isSafari) { 107 window.historyMgr = appCtxt.getHistoryMgr(); 108 } 109 110 // create app view manager 111 this._appViewMgr = new ZmAppViewMgr(this._shell, this, false, true); 112 var hidden = [ ZmAppViewMgr.C_SEARCH_RESULTS_TOOLBAR, ZmAppViewMgr.C_TASKBAR ]; 113 if (!appCtxt.get(ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL)) { 114 hidden.push(ZmAppViewMgr.C_TREE_FOOTER); 115 } 116 this._appViewMgr.setHiddenComponents(ZmAppViewMgr.GLOBAL, hidden, true); 117 118 // register handlers 119 AjxDispatcher.setPackageLoadFunction("Zimlet", new AjxCallback(this, this._postLoadZimlet)); 120 121 AjxDispatcher.setPreLoadFunction(new AjxCallback(this, function() { 122 this._appViewMgr.pushView(ZmId.VIEW_LOADING); 123 })); 124 AjxDispatcher.setPostLoadFunction(new AjxCallback(this, function() { 125 if (!AjxUtil.arrayContains(this._appViewMgr._toRemove, ZmId.VIEW_LOADING)) { 126 this._appViewMgr._toRemove.push(ZmId.VIEW_LOADING); 127 } 128 })); 129 130 for (var i in ZmApp.QS_ARG) { 131 ZmApp.QS_ARG_R[ZmApp.QS_ARG[i]] = i; 132 } 133 134 this._shell.addGlobalSelectionListener(new AjxListener(this, this._globalSelectionListener)); 135 136 // setup webClient offline support 137 appCtxt.initWebOffline(); 138 /// go! 139 this.startup(params); 140 }; 141 142 ZmZimbraMail.prototype = new ZmController; 143 ZmZimbraMail.prototype.constructor = ZmZimbraMail; 144 145 ZmZimbraMail.prototype.isZmZimbraMail = true; 146 ZmZimbraMail.prototype.toString = function() { return "ZmZimbraMail"; }; 147 148 // REVISIT: This is done so that we when we switch from being "beta" 149 // to production, we don't have to ensure that all of the 150 // translations are changed at the same time. We can simply 151 // remove the beta suffix from the app name. 152 ZmMsg.BETA_documents = [ZmMsg.documents, ZmMsg.beta].join(" "); 153 154 // dummy app (needed when defining drop targets in _registerOrganizers) 155 ZmApp.MAIN = "ZmZimbraMail"; 156 ZmApp.DROP_TARGETS[ZmApp.MAIN] = {}; 157 158 // Static listener registration 159 ZmZimbraMail._listeners = {}; 160 161 // Consts 162 ZmZimbraMail.UI_LOAD_BEGIN = "ui_load_begin"; 163 ZmZimbraMail.UI_LOAD_END = "ui_load_end"; 164 ZmZimbraMail.UI_NETWORK_UP = "network_up"; 165 ZmZimbraMail.UI_NETWORK_DOWN = "network_down"; 166 167 168 // Public methods 169 170 171 /** 172 * Sets up ZimbraMail, and then starts it by calling its constructor. It is assumed that the 173 * CSFE is on the same host. 174 * 175 * @param {Hash} params a hash of parameters 176 * @param {constant} params.app te starting app 177 * @param {Boolean} params.offlineMode if <code>true</code>, this is the offline client 178 * @param {Boolean} params.devMode if <code>true</code>, we are in development environment 179 * @param {Hash} params.settings the server prefs/attrs 180 * @param {constant} params.protocolMode the protocal mode (http, https or mixed) 181 * @param {Boolean} params.noSplashScreen if <code>true</code>, do not show splash screen during startup 182 */ 183 ZmZimbraMail.run = 184 function(params) { 185 186 if (params.noSplashScreen) { 187 ZmZimbraMail.killSplash(); 188 } 189 190 // Create the global app context 191 window.appCtxt = new ZmAppCtxt(); 192 appCtxt.rememberMe = false; 193 194 // Handle offline mode 195 if (params.offlineMode) { 196 DBG.println(AjxDebug.DBG1, "OFFLINE MODE"); 197 appCtxt.isOffline = true; 198 } 199 200 // Create the shell 201 var userShell = params.userShell = window.document.getElementById(ZmId.SKIN_SHELL); 202 if (!userShell) { 203 alert("Could not get user shell - skin file did not load properly"); 204 } 205 var shell = new DwtShell({userShell:userShell, docBodyScrollable:false, id:ZmId.SHELL}); 206 appCtxt.setShell(shell); 207 208 // Go! 209 new ZmZimbraMail(params); 210 }; 211 212 /** 213 * Unloads the controller. Allows parent window to walk list of open child windows and either "delete" 214 * or "disable" them. 215 * 216 */ 217 ZmZimbraMail.unload = 218 function() { 219 220 if (!ZmZimbraMail._endSessionDone) { 221 ZmZimbraMail._endSession(); 222 } 223 224 if (ZmZimbraMail._isLogOff) { 225 ZmZimbraMail._isLogOff = false; 226 // stop keeping track of user input (if applicable) 227 if (window._zimbraMail) { 228 window._zimbraMail.setSessionTimer(false); 229 } 230 231 ZmCsfeCommand.noAuth = true; 232 } 233 234 ZmZimbraMail.closeChildWindows(); 235 236 ZmZimbraMail.stayOnPagePrompt = false; 237 ZmZimbraMail.setExitTimer(false); 238 ZmZimbraMail.sessionTimerInvoked = false; 239 window._zimbraMail = window.onload = window.onunload = window.onresize = window.document.onkeypress = null; 240 }; 241 242 ZmZimbraMail.closeChildWindows = 243 function() { 244 245 var childWinList = window._zimbraMail && window._zimbraMail._childWinList; 246 if (childWinList) { 247 // close all child windows 248 for (var i = 0; i < childWinList.size(); i++) { 249 var childWin = childWinList.get(i); 250 if (childWin.win) { 251 childWin.win.onbeforeunload = null; 252 childWin.win.parentController = null; 253 childWin.win.close(); 254 } 255 } 256 } 257 }; 258 259 /** 260 * Returns sort order using a and b as keys into given hash. 261 * 262 * @param {Hash} hash a hash with sort values 263 * @param {String} a a key into hash 264 * @param {String} b a key into hash 265 * @return {int} 0 if the items are the same; 1 if "a" is before "b"; -1 if "b" is before "a" 266 */ 267 ZmZimbraMail.hashSortCompare = 268 function(hash, a, b) { 269 var appA = a ? Number(hash[a]) : 0; 270 var appB = b ? Number(hash[b]) : 0; 271 if (appA > appB) { return 1; } 272 if (appA < appB) { return -1; } 273 return 0; 274 }; 275 276 /** 277 * Hides the splash screen. 278 * 279 */ 280 ZmZimbraMail.killSplash = 281 function() { 282 // Splash screen is now a part of the skin, loaded in statically via the JSP 283 // as a well-known ID. To hide the splash screen, just hide that div. 284 Dwt.hide("skin_container_splash_screen"); 285 }; 286 287 /** 288 * Returns the state of ZCS application if user is logged out in case of browser quit. 289 * The public method is added to take appropriate action in the chat app if user session is ending. 290 * 291 * 292 * @public 293 */ 294 ZmZimbraMail.hasSessionEnded = 295 function() { 296 return ZmZimbraMail._endSessionDone; 297 }; 298 299 /** 300 * Startup the mail controller. 301 * 302 * <p> 303 * The following steps are performed: 304 * <ul> 305 * <li>check for skin, show it</li> 306 * <li>create app view mgr</li> 307 * <li>create components (sash, banner, user info, toolbar above overview, status view)</li> 308 * <li>create apps</li> 309 * <li>load user settings (using a <code><GetInfoRequest></code>)</li> 310 * </ul> 311 * 312 * @param {Hash} params a hash of parameters 313 * @param {constant} app the starting app 314 * @param {Hash} settings a hash of settings overrides 315 */ 316 ZmZimbraMail.prototype.startup = 317 function(params) { 318 319 if (appCtxt.isOffline) { 320 this.sendClientEventNotify(ZmZimbraMail.UI_LOAD_BEGIN); 321 } 322 323 appCtxt.inStartup = true; 324 if (typeof(skin) == "undefined") { 325 DBG.println(AjxDebug.DBG1, "No skin!"); 326 } 327 328 skin.show("skin", true); 329 appCtxt.getShell().relayout(); 330 331 if (!this._components) { 332 this._components = {}; 333 this._components[ZmAppViewMgr.C_SASH] = new DwtSash({parent:this._shell, style:DwtSash.HORIZONTAL_STYLE, 334 className:"console_inset_app_l", threshold:20, id:ZmId.MAIN_SASH}); 335 this._components[ZmAppViewMgr.C_SASH].addListener(DwtEvent.ONMOUSEUP, ZmZimbraMail._folderTreeSashRelease); 336 this._components[ZmAppViewMgr.C_BANNER] = this._createBanner(); 337 this._components[ZmAppViewMgr.C_USER_INFO] = this._userNameField = 338 this._createUserInfo("BannerTextUser", ZmAppViewMgr.C_USER_INFO, ZmId.USER_NAME); 339 this._components[ZmAppViewMgr.C_QUOTA_INFO] = this._usedQuotaField = 340 this._createUserInfo("BannerTextQuota", ZmAppViewMgr.C_QUOTA_INFO, ZmId.USER_QUOTA); 341 342 if (appCtxt.isOffline) { 343 this._initOfflineUserInfo(); 344 } 345 } 346 347 if (!this.statusView) { 348 this.statusView = new ZmStatusView(this._shell, "ZmStatus", Dwt.ABSOLUTE_STYLE, ZmId.STATUS_VIEW); 349 } 350 351 this._registerOrganizers(); 352 353 // set up map of search types to item types 354 for (var i in ZmSearch.TYPE) { 355 ZmSearch.TYPE_MAP[ZmSearch.TYPE[i]] = i; 356 } 357 ZmZimbraMail.registerViewsToTypeMap(); 358 359 this._getStartApp(params); 360 appCtxt.startApp = params.startApp; 361 362 this._postRenderCallbacks = []; 363 this._postRenderLast = 0; 364 if (params.startApp == ZmApp.MAIL) { 365 this._doingPostRenderStartup = true; 366 var callback = new AjxCallback(this, 367 function() { 368 AjxDispatcher.require("Startup2"); 369 var account = appCtxt.multiAccounts && appCtxt.accountList.mainAccount; 370 if (appCtxt.get(ZmSetting.CALENDAR_ENABLED, null, account)) { 371 this.handleCalendarComponents(); 372 } 373 if (appCtxt.get(ZmSetting.TASKS_ENABLED, null, account)) { 374 this.handleTaskComponents(); 375 } 376 this._appViewMgr.loadingView.setVisible(false); 377 }); 378 this.addPostRenderCallback(callback, 0, 0, true); 379 380 // wait half a minute and load TinyMCE 381 var callback = new AjxCallback(this, function() { 382 AjxDispatcher.require("Startup2"); 383 384 var timer = new DwtIdleTimer(30 * 1000, function() { 385 AjxDispatcher.require('TinyMCE', true); 386 timer.kill(); 387 }); 388 }); 389 this.addPostRenderCallback(callback, 0, 0, true); 390 } 391 392 // NOTE: We must go through the request mgr for default handling 393 var getInfoResponse = AjxUtil.get(params, "getInfoResponse"); 394 if (getInfoResponse) { 395 this._requestMgr.sendRequest({response:getInfoResponse}); 396 } 397 398 // fetch meta data for the main account 399 var respCallback = new AjxCallback(this, this._handleResponseGetMetaData, params); 400 appCtxt.accountList.mainAccount.loadMetaData(respCallback); 401 402 //todo - might want to move this call and the methods to ZmMailApp as it's specific to mail app only. 403 this._initDelegatedSenderAddrs(); 404 if(appCtxt.isOffline) { 405 var updatePref = appCtxt.get(ZmSetting.OFFLINE_UPDATE_NOTIFY); 406 this._offlineUpdateChannelPref(updatePref) 407 } 408 }; 409 410 411 ZmZimbraMail.prototype._initDelegatedSenderAddrs = 412 function() { 413 var soapDoc = AjxSoapDoc.create("DiscoverRightsRequest", "urn:zimbraAccount"); 414 soapDoc.set("right","sendAs" ); 415 soapDoc.set("right","sendOnBehalfOf"); 416 soapDoc.set("right","sendAsDistList"); 417 soapDoc.set("right","sendOnBehalfOfDistList"); 418 var batchCmd = new ZmBatchCommand(null, appCtxt.accountList.mainAccount.name); 419 var callback = this._initDelegatedSenderEmails.bind(this); 420 batchCmd.addNewRequestParams(soapDoc, callback, callback); 421 var offlineCallback = this._handleOfflineDelegatedSenderEmails.bind(this, callback); 422 batchCmd.run(null, null, offlineCallback); 423 }; 424 425 ZmZimbraMail.prototype._getDelegatedSenderEmails = 426 function(sendRights, sendRight) { 427 var emails = []; 428 if (!sendRights || !sendRights.length) { 429 return emails; 430 } 431 for (var i = 0; i < sendRights.length; i++) { 432 var targets = sendRights[i].target; 433 var right = sendRights[i].right; 434 var sendRightDistList = sendRight + "DistList"; 435 if (right !== sendRight && right !== sendRightDistList) { 436 continue; 437 } 438 var isDL = right === sendRightDistList; 439 for (var j = 0; j < targets.length; j++) { 440 var target = targets[j]; 441 var emailList = target.email; 442 for (var k = 0; k < emailList.length; k++) { 443 var addr = emailList[k].addr; 444 emails.push({addr: addr, isDL: isDL, displayName: target.d}); 445 } 446 } 447 448 } 449 return emails; 450 }; 451 452 ZmZimbraMail.prototype._initDelegatedSenderEmails = 453 function(result){ 454 var response = result.getResponse(); 455 if (ZmOffline.isOnlineMode()) { 456 localStorage.setItem("DiscoverRightsResponse", JSON.stringify(response)); 457 } 458 var discoverRightsResponse = response && response.DiscoverRightsResponse; 459 var sendRights = discoverRightsResponse && discoverRightsResponse.targets; 460 appCtxt.sendAsEmails = this._getDelegatedSenderEmails(sendRights, 'sendAs'); 461 appCtxt.sendOboEmails = this._getDelegatedSenderEmails(sendRights, 'sendOnBehalfOf'); 462 }; 463 464 ZmZimbraMail.prototype._handleOfflineDelegatedSenderEmails = 465 function(callback) { 466 var result = localStorage.getItem("DiscoverRightsResponse"); 467 if (result) { 468 var csfeResult = new ZmCsfeResult({BatchResponse : JSON.parse(result)}); 469 callback.run(csfeResult); 470 } 471 }; 472 473 ZmZimbraMail.registerViewsToTypeMap = function() { 474 // organizer types based on view 475 for (var i in ZmOrganizer.VIEWS) { 476 var list = ZmOrganizer.VIEWS[i]; 477 for (var j = 0; j < list.length; j++) { 478 ZmOrganizer.TYPE[list[j]] = i; 479 } 480 } 481 }; 482 483 ZmZimbraMail.prototype._createSettings = function(params) { 484 // We've received canned SOAP responses for GetInfoRequest and SearchRequest from the 485 // launch JSP, wrapped in a BatchRequest. Jiggle them so that they look like real 486 // responses, and pass them along. 487 if (params.batchInfoResponse) { 488 var batchResponse = params.batchInfoResponse.Body.BatchResponse; 489 490 // always assume there's a get info response 491 var infoResponse = batchResponse.GetInfoResponse[0]; 492 if(!infoResponse) { 493 infoResponse ={} 494 } 495 //store per-domain settings in infoResponse obj so we can access it like other settings 496 infoResponse.domainSettings = params.settings; 497 params.getInfoResponse = { 498 Header: params.batchInfoResponse.Header, 499 Body: { GetInfoResponse: infoResponse} 500 }; 501 var session = AjxUtil.get(params.getInfoResponse, "Header", "context", "session"); 502 if (session) { 503 ZmCsfeCommand.setSessionId(session); 504 } 505 DBG.println(AjxDebug.DBG1, ["<b>RESPONSE (from JSP tag)</b>"].join(""), "GetInfoResponse"); 506 DBG.dumpObj(AjxDebug.DBG1, params.getInfoResponse, -1); 507 508 // we may have an initial search response 509 if (batchResponse.SearchResponse) { 510 params.searchResponse = { 511 Body: { SearchResponse: batchResponse.SearchResponse[0] } 512 }; 513 DBG.println(AjxDebug.DBG1, ["<b>RESPONSE (from JSP tag)</b>"].join(""), "SearchResponse"); 514 DBG.dumpObj(AjxDebug.DBG1, params.searchResponse, -1); 515 } 516 } 517 518 // create settings 519 var settings = new ZmSettings() 520 appCtxt.setSettings(settings); 521 522 // We have to pre-initialize the settings in order to create 523 // the enabled apps correctly. 524 settings.setUserSettings({info:params.getInfoResponse.Body.GetInfoResponse, preInit:true}); 525 }; 526 527 ZmZimbraMail.prototype._initializeSettings = function(params) { 528 var info = params.getInfoResponse.Body.GetInfoResponse; 529 530 var settings = appCtxt.getSettings(); 531 // NOTE: Skip notify to avoid callbacks which reference objects that aren't set, yet 532 settings.setUserSettings(info, null, true, true, true); 533 settings.userSettingsLoaded = true; 534 535 // settings structure and defaults 536 var branch = appCtxt.get(ZmSetting.BRANCH); 537 if (window.DBG && !DBG.isDisabled()) { 538 DBG.setTitle("Debug (" + branch + ")"); 539 } 540 541 // setting overrides 542 if (params.settings) { 543 for (var name in params.settings) { 544 var id = settings.getSettingByName(name); 545 if (id) { 546 settings.getSetting(id).setValue(params.settings[name]); 547 } 548 } 549 } 550 551 // reset polling interval for offline 552 if (appCtxt.isOffline) { 553 appCtxt.set(ZmSetting.POLLING_INTERVAL, 60, null, null, true); 554 } 555 556 // Handle dev mode 557 if (params.devMode == "1") { 558 DBG.println(AjxDebug.DBG1, "DEV MODE"); 559 appCtxt.set(ZmSetting.DEV, true); 560 } 561 562 // Handle protocol mode - standardize on trailing : 563 if (params.protocolMode) { 564 var proto = (params.protocolMode.indexOf(":") == -1) ? params.protocolMode + ":" : params.protocolMode; 565 appCtxt.set(ZmSetting.PROTOCOL_MODE, proto); 566 } 567 if (params.httpPort) { 568 appCtxt.set(ZmSetting.HTTP_PORT, params.httpPort); 569 } 570 if (params.httpsPort) { 571 appCtxt.set(ZmSetting.HTTPS_PORT, params.httpsPort); 572 } 573 574 // hide spam if not enabled 575 if (!appCtxt.get(ZmSetting.SPAM_ENABLED)) { 576 ZmFolder.HIDE_ID[ZmFolder.ID_SPAM] = true; 577 } 578 579 // Chats hidden by default, check for override 580 if (appCtxt.get(ZmSetting.SHOW_CHATS_FOLDER)) { 581 delete ZmFolder.HIDE_ID[ZmOrganizer.ID_CHATS]; 582 } 583 }; 584 585 /** 586 * Perform any additional operation after initializing settings 587 * @private 588 */ 589 ZmZimbraMail.prototype._postInitializeSettings = 590 function() { 591 this._setCustomInvalidEmailPats(); 592 }; 593 594 /** 595 * Set an array of invalid Email patterns(values of zimbraMailAddressValidationRegex in ldap) to 596 * AjxEmailAddress.customInvalidEmailPats 597 * @private 598 */ 599 ZmZimbraMail.prototype._setCustomInvalidEmailPats = 600 function() { 601 var customPatSetting = appCtxt.getSettings().getSetting(ZmSetting.EMAIL_VALIDATION_REGEX); 602 var cPatList = []; 603 if(customPatSetting) { 604 cPatList = customPatSetting.value; 605 } 606 for(var i = 0; i< cPatList.length; i++) { 607 var pat = cPatList[i]; 608 if(pat && pat != "") { 609 AjxEmailAddress.customInvalidEmailPats.push(new RegExp(pat)) 610 } 611 } 612 }; 613 614 /** 615 * @private 616 */ 617 ZmZimbraMail.prototype._handleResponseGetMetaData = 618 function(params) { 619 620 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) { 621 var method = appCtxt.multiAccounts ? "GetContactsForAllAccounts" : "GetContacts"; 622 AjxDispatcher.run({ 623 method: method, 624 callback: this._handleResponseLoadUserSettings.bind(this, params) 625 }); 626 } 627 else { 628 this._handleResponseLoadUserSettings(params); 629 } 630 }; 631 632 /** 633 * Shows the mini-calendar. 634 * 635 */ 636 ZmZimbraMail.prototype.showMiniCalendar = 637 function() { 638 var calMgr = appCtxt.getCalManager(); 639 calMgr.getMiniCalendar(); 640 appCtxt.getAppViewMgr().displayComponent(ZmAppViewMgr.C_TREE_FOOTER, true); 641 calMgr.highlightMiniCal(); 642 calMgr.startDayRollTimer(); 643 }; 644 645 /** 646 * Shows reminders. 647 */ 648 ZmZimbraMail.prototype.showReminder = 649 function() { 650 var reminderController = appCtxt.getApp(ZmApp.CALENDAR).getReminderController(); 651 reminderController.refresh(); 652 }; 653 654 /** 655 * Shows reminders. 656 */ 657 ZmZimbraMail.prototype.showTaskReminder = 658 function() { 659 var taskMgr = appCtxt.getTaskManager(); 660 var taskReminderController = taskMgr.getReminderController(); 661 taskReminderController.refresh(); 662 }; 663 664 ZmZimbraMail.prototype._isProtocolHandlerAccessed = 665 function() { 666 if (AjxEnv.isFirefox){ 667 if (!localStorage || localStorage['zimbra_mailto_init']) return true; 668 localStorage['zimbra_mailto_init'] = true; 669 } 670 return false; 671 }; 672 673 /** 674 * @private 675 */ 676 ZmZimbraMail.prototype._handleResponseLoadUserSettings = 677 function(params, result) { 678 if (appCtxt.multiAccounts) { 679 var callback = new AjxCallback(this, this._handleResponseStartup, [params, result]); 680 appCtxt.accountList.loadAccounts(callback); 681 } else { 682 this._handleResponseStartup(params, result); 683 } 684 }; 685 686 /** 687 * Startup: part 2 688 * - create app toolbar component 689 * - determine and launch starting app 690 * 691 * @param {Hash} params a hash of parameters 692 * @param {constant} params.app the starting app 693 * @param {Object} params.settingOverrides a hash of overrides of user settings 694 * @param {ZmCsfeResult} result the result object from load of user settings 695 * 696 * @private 697 */ 698 ZmZimbraMail.prototype._handleResponseStartup = 699 function(params, result) { 700 701 params = params || {}; 702 if (params.settingOverrides) { 703 this._needOverviewLayout = true; 704 for (var id in params.settingOverrides) { 705 var setting = appCtxt.getSetting(id); 706 if (setting) { 707 setting.setValue(params.settingOverrides[id]); 708 } 709 } 710 } 711 if (params.preset) { 712 var presets = params.preset.split(","); 713 for (var i = 0; i < presets.length; i++) { 714 var fields = presets[i].split(":"); 715 var setting = appCtxt.getSettings().getSetting(fields[0]); 716 if (setting && setting.canPreset) { 717 setting.setValue(fields[1]); 718 } 719 } 720 } 721 722 if (!appCtxt.isOffline) { 723 if (appCtxt.get(ZmSetting.INSTANT_NOTIFY) && appCtxt.get(ZmSetting.INSTANT_NOTIFY_INTERVAL) == appCtxt.get(ZmSetting.POLLING_INTERVAL)) 724 AjxTimedAction.scheduleAction(new AjxTimedAction(this, this.setInstantNotify, [true]), 4000); 725 else 726 this.setPollInterval(true); 727 } else { 728 if (appCtxt.get(ZmSetting.OFFLINE_SUPPORTS_MAILTO) && window.platform && 729 window.platform.isRegisteredProtocolHandler("mailto")) { 730 // bug fix #34342 - always register the protocol handler for mac and linux on start up 731 this.registerMailtoHandler(!AjxEnv.isWindows, true); 732 } 733 } 734 735 window.onbeforeunload = ZmZimbraMail._confirmExitMethod; 736 737 if (!this._components[ZmAppViewMgr.C_APP_CHOOSER]) { 738 this._components[ZmAppViewMgr.C_APP_CHOOSER] = this._appChooser = this._createAppChooser(); 739 } 740 741 ZmApp.initialize(); 742 743 if(appCtxt.get(ZmSetting.DEFAULT_TIMEZONE)) { 744 AjxTimezone.DEFAULT_RULE = AjxTimezone._guessMachineTimezone(appCtxt.get(ZmSetting.DEFAULT_TIMEZONE)); 745 AjxTimezone.DEFAULT = AjxTimezone.getClientId(AjxTimezone.DEFAULT_RULE.serverId); 746 } 747 748 this.notify(ZmAppEvent.PRE_STARTUP); 749 750 params.result = result; 751 var respCallback = new AjxCallback(this, this._handleResponseStartup1, params); 752 753 // startup and packages have been optimized for quick mail display 754 if (this._doingPostRenderStartup) { 755 this.addAppListener(params.startApp, ZmAppEvent.POST_RENDER, new AjxListener(this, this._postRenderStartup)); 756 //For offline mode offline callback will take care 757 if (!appCtxt.isWebClientOffline()) { 758 this._searchResponse = params.searchResponse; 759 } 760 } else { 761 AjxDispatcher.require("Startup2"); 762 } 763 764 // Set up post-render callbacks 765 766 // run app-related startup functions 767 var callback = new AjxCallback(this, 768 function() { 769 this.runAppFunction("startup", false, params.result); 770 }); 771 this.addPostRenderCallback(callback, 2, 0, true); 772 773 callback = new AjxCallback(this, 774 function() { 775 this._setupTabGroups(); 776 this.focusContentPane(); 777 }); 778 this.addPostRenderCallback(callback, 3, 100); 779 780 // miscellaneous post-startup housekeeping 781 callback = new AjxCallback(this, 782 function() { 783 AjxDispatcher.enableLoadFunctions(true); 784 appCtxt.inStartup = false; 785 this.notify(ZmAppEvent.POST_STARTUP); 786 787 var sc = appCtxt.getSearchController(); 788 sc.getSearchToolbar().initAutocomplete(); 789 790 // bug fix #31996 791 if (appCtxt.isOffline) { 792 sc.resetSearchToolbar(); 793 } 794 795 if (appCtxt.get(ZmSetting.OFFLINE_SUPPORTS_MAILTO) && appCtxt.isOffline) { 796 this.handleOfflineMailTo(location.search); 797 } 798 }); 799 this.addPostRenderCallback(callback, 5, 100); 800 801 if (appCtxt.get(ZmSetting.MAIL_ENABLED) && !appCtxt.isExternalAccount() && navigator.registerProtocolHandler && !this._isProtocolHandlerAccessed()){ 802 callback = new AjxCallback(this, 803 function() { 804 try { 805 navigator.registerProtocolHandler("mailto",AjxUtil.formatUrl({qsArgs:{view:'compose',to:'%s'}, qsReset:true}) ,ZmMsg.zimbraTitle); 806 } catch (err){}; 807 }); 808 this.addPostRenderCallback(callback, 6, 100); 809 } 810 811 this.activateApp(params.startApp, false, respCallback, this._errorCallback, params); 812 813 var account = appCtxt.multiAccounts && appCtxt.accountList.mainAccount; 814 if (appCtxt.get(ZmSetting.CALENDAR_ENABLED, null, account) && 815 !this._doingPostRenderStartup && 816 (params.startApp != ZmApp.CALENDAR)) 817 { 818 this.handleCalendarComponents(); 819 } 820 if (appCtxt.get(ZmSetting.TASKS_ENABLED, null, account) && 821 !this._doingPostRenderStartup && 822 (params.startApp != ZmApp.TASKS)) 823 { 824 this.handleTaskComponents(); 825 } 826 827 if (appCtxt.get(ZmSetting.IMPORT_ON_LOGIN_ENABLED)) { 828 var ds = new ZmDataSourceCollection(); 829 var dsCollection = appCtxt.getDataSourceCollection(); 830 var pop3Accounts = dsCollection && dsCollection.getPopAccounts(); 831 var imapAccounts = dsCollection && dsCollection.getImapAccounts(); 832 var sourceMap = {}; 833 if (pop3Accounts) { 834 for (var i=0; i<pop3Accounts.length; i++) { 835 sourceMap[pop3Accounts[i].id] = pop3Accounts[i]; 836 } 837 } 838 if (imapAccounts) { 839 for (var i=0; i<imapAccounts.length; i++) { 840 sourceMap[imapAccounts[i].id] = imapAccounts[i]; 841 } 842 } 843 844 if (pop3Accounts || imapAccounts) { 845 var action = new AjxTimedAction(ds, ds.checkStatus, [sourceMap, 2000]); 846 AjxTimedAction.scheduleAction(action, 10000); //kick off check in 10 seconds 847 } 848 } 849 }; 850 851 /** 852 * Creates & show Task Reminders on delay 853 * 854 * @private 855 */ 856 ZmZimbraMail.prototype.handleTaskComponents = 857 function() { 858 var reminderAction = new AjxTimedAction(this, this.showTaskReminder); 859 var delay = appCtxt.isOffline ? 0 : ZmTasksApp.REMINDER_START_DELAY; 860 AjxTimedAction.scheduleAction(reminderAction, delay); 861 }; 862 863 /** 864 * Creates mini calendar and shows reminders on delay 865 * 866 * @private 867 */ 868 ZmZimbraMail.prototype.handleCalendarComponents = 869 function() { 870 if (appCtxt.get(ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL)) { 871 var miniCalAction = new AjxTimedAction(this, this.showMiniCalendar); 872 var delay = appCtxt.isOffline ? 0 : ZmCalendarApp.MINICAL_DELAY; 873 AjxTimedAction.scheduleAction(miniCalAction, delay); 874 } 875 876 AjxDispatcher.require(["ContactsCore", "MailCore", "CalendarCore", "Calendar"]); 877 var reminderAction = new AjxTimedAction(this, this.showReminder); 878 var delay = appCtxt.isOffline ? 0 : ZmCalendarApp.REMINDER_START_DELAY; 879 AjxTimedAction.scheduleAction(reminderAction, delay); 880 }; 881 882 /** 883 * Startup: part 3 884 * - populate user info 885 * - create search bar 886 * - set up keyboard handling (shortcuts and tab groups) 887 * - kill splash, show UI 888 * - check license 889 * 890 * @param {Hash} params a hash of parameters 891 * @param {constant} params.app the starting app 892 * @param {Object} params.settingOverrides a hash of overrides of user settings 893 * 894 * @private 895 */ 896 ZmZimbraMail.prototype._handleResponseStartup1 = 897 function(params) { 898 899 this._setExternalLinks(); 900 this.setUserInfo(); 901 this._setRefresh(); 902 903 if (appCtxt.get(ZmSetting.SEARCH_ENABLED)) { 904 this._components[ZmAppViewMgr.C_SEARCH] = appCtxt.getSearchController().getSearchToolbar(); 905 } 906 else { 907 Dwt.hide(ZmId.SKIN_SEARCH); 908 } 909 910 var newButton = this.getNewButton(); 911 var tbParams = { 912 parent: this._shell, 913 context: ZmOperation.NEW_MENU, 914 buttons: ZmOperation.NONE, 915 controller: this, 916 refElementId: ZmId.SKIN_APP_NEW_BUTTON 917 }; 918 var tb = this._newToolbar = new ZmButtonToolBar(tbParams); 919 newButton.reparent(tb); 920 this._components[ZmAppViewMgr.C_NEW_BUTTON] = tb; 921 922 if (params.unitTest) { 923 var utm = window.unitTestManager; 924 appCtxt.addZimletsLoadedListener(utm.runTests.bind(utm), 0); 925 } 926 927 this.getKeyMapMgr(); // make sure keyboard handling is initialized 928 929 this.setSessionTimer(true); 930 ZmZimbraMail.killSplash(); 931 932 // Give apps a chance to add their own UI components. 933 this.runAppFunction("addComponents", false, this._components); 934 935 // make the UI appear 936 this._appViewMgr.setViewComponents(ZmAppViewMgr.GLOBAL, this._components, true); 937 938 this._checkLicense(); 939 940 if (!this._doingPostRenderStartup) { 941 this._postRenderStartup(); 942 } 943 }; 944 945 /** 946 * set the refresh button at the masthead. 947 */ 948 ZmZimbraMail.prototype._setRefresh = 949 function() { 950 var containerEl = document.getElementById(ZmId.SKIN_REFRESH); 951 if (!containerEl) { 952 return; 953 } 954 var button = appCtxt.refreshButton = new DwtToolBarButton({parent:DwtShell.getShell(window), id: ZmId.OP_CHECK_MAIL}); //use ToolbarButton just for the style, for now it looks ok. 955 button.setImage("RefreshAll"); 956 button.setToolTipContent(ZmMsg.checkMailPrefUpdate, true); 957 958 button.reparentHtmlElement(ZmId.SKIN_REFRESH); 959 960 var refreshListener = this._refreshListener.bind(this); 961 button.addSelectionListener(refreshListener); 962 963 }; 964 965 /** 966 * refresh button listener. call runRefresh() of all the enabled apps that have this method defined. 967 */ 968 ZmZimbraMail.prototype._refreshListener = 969 function() { 970 if (!appCtxt.isWebClientOffline()) { 971 this.runAppFunction("runRefresh"); 972 } 973 }; 974 975 // popup a warning dialog if there is a problem with the license 976 ZmZimbraMail.prototype._checkLicense = 977 function(ev) { 978 979 var status = appCtxt.get(ZmSetting.LICENSE_STATUS); 980 var msg = ZmSetting.LICENSE_MSG[status]; 981 if (msg) { 982 AjxDispatcher.require("Startup2"); 983 var dlg = appCtxt.getMsgDialog(); 984 dlg.reset(); 985 dlg.setMessage(msg, DwtMessageDialog.WARNING_STYLE); 986 dlg.popup(); 987 } 988 }; 989 990 /** 991 * The work to render the start app has been done. Now perform all the startup 992 * work that remains - each piece of work is contained in a callback with an 993 * associated order and delay. 994 * 995 * @private 996 */ 997 ZmZimbraMail.prototype._postRenderStartup = 998 function(ev) { 999 this._postRenderCallbacks.sort(function(a, b) { 1000 return a.order - b.order; 1001 }); 1002 this._runNextPostRenderCallback(); 1003 }; 1004 1005 /** 1006 * @private 1007 */ 1008 ZmZimbraMail.prototype._runNextPostRenderCallback = 1009 function() { 1010 DBG.println(AjxDebug.DBG2, "POST-RENDER CALLBACKS: " + this._postRenderCallbacks.length); 1011 if (this._postRenderCallbacks && this._postRenderCallbacks.length) { 1012 var prcb = this._postRenderCallbacks.shift(); 1013 if (!prcb) { return; } 1014 DBG.println(AjxDebug.DBG2, "POST-RENDER CALLBACK: #" + prcb.order + ", delay " + prcb.delay + " in " + prcb.callback.obj.toString()); 1015 AjxTimedAction.scheduleAction(new AjxTimedAction(this, 1016 function() { 1017 prcb.callback.run(); 1018 this._runNextPostRenderCallback(); 1019 }), prcb.delay); 1020 } else { 1021 if (appCtxt.isOffline) { 1022 this.sendClientEventNotify(ZmZimbraMail.UI_LOAD_END); 1023 1024 if (AjxEnv.isPrism) { 1025 this._firstTimeNetworkChange = true; 1026 1027 var nc = new ZimbraNetworkChecker(); 1028 nc.addEventListener("offline", function(e) { window["appCtxt"].getAppController().handleNetworkChange(false); }, false); 1029 nc.addEventListener("online", function(e) { window["appCtxt"].getAppController().handleNetworkChange(true); }, false); 1030 } 1031 } 1032 } 1033 }; 1034 1035 /** 1036 * @private 1037 */ 1038 ZmZimbraMail.prototype.handleNetworkChange = 1039 function(online) { 1040 this._isPrismOnline = online; 1041 1042 if (this._isUserOnline || this._firstTimeNetworkChange) { 1043 this._updateNetworkStatus(online); 1044 } 1045 }; 1046 1047 ZmZimbraMail.prototype._updateNetworkStatus = 1048 function(online) { 1049 // bug 48108 - Prism sometimes triggers network status change mutliple times 1050 // So don't bother if the last change is the same as current status 1051 if ((online && this._currentNetworkStatus == ZmZimbraMail.UI_NETWORK_UP) || 1052 (!online && this._currentNetworkStatus == ZmZimbraMail.UI_NETWORK_DOWN)) 1053 { 1054 return; 1055 } 1056 1057 if (online) { 1058 if (!this._firstTimeNetworkChange) { 1059 this.setStatusMsg(ZmMsg.networkChangeOnline); 1060 } else { 1061 this._firstTimeNetworkChange = false; 1062 this._isUserOnline = online; 1063 } 1064 this._currentNetworkStatus = ZmZimbraMail.UI_NETWORK_UP; 1065 this.sendClientEventNotify(this._currentNetworkStatus, true); 1066 } else { 1067 this.setStatusMsg(ZmMsg.networkChangeOffline, ZmStatusView.LEVEL_WARNING); 1068 this._currentNetworkStatus = ZmZimbraMail.UI_NETWORK_DOWN; 1069 this.sendClientEventNotify(this._currentNetworkStatus); 1070 } 1071 1072 this._networkStatusIcon.setToolTipContent(online ? ZmMsg.networkStatusOffline : ZmMsg.networkStatusOnline, true); 1073 this._networkStatusIcon.getHtmlElement().innerHTML = AjxImg.getImageHtml(online ? "Connect" : "Disconnect"); 1074 var netStatus = online ? ZmMsg.netStatusOnline : ZmMsg.netStatusOffline; 1075 this._networkStatusText.getHtmlElement().innerHTML = netStatus.substr(0, 1).toUpperCase() + netStatus.substr(1); 1076 }; 1077 1078 /** 1079 * Sets up a callback to be run after the starting app has rendered, if we're doing 1080 * post-render callbacks. The callback is registered with an order that determines 1081 * when it will run relative to other callbacks. A delay can also be given, so that 1082 * the UI has a chance to do some work between callbacks. 1083 * 1084 * @param {AjxCallback} callback the callback 1085 * @param {int} order the run order for the callback 1086 * @param {int} delay how long to pause before running the callback 1087 * @param {Boolean} runNow if <code>true</code>, we are not doing post-render callbacks, run the callback now and don't add it to the list 1088 */ 1089 ZmZimbraMail.prototype.addPostRenderCallback = 1090 function(callback, order, delay, runNow) { 1091 if (!this._doingPostRenderStartup && runNow) { 1092 callback.run(); 1093 } else { 1094 order = order || this._postRenderLast++; 1095 this._postRenderCallbacks.push({callback:callback, order:order, delay:delay || 0}); 1096 } 1097 }; 1098 1099 ZmZimbraMail.prototype._isInternalApp = 1100 function(app) { 1101 return !ZmApp.SETTING[app] || (appCtxt.get(ZmApp.SETTING[app], null, appCtxt.multiAccounts && appCtxt.accountList.mainAccount)); 1102 }; 1103 1104 ZmZimbraMail.prototype._isIframeApp = 1105 function(app) { 1106 return !this._isInternalApp(app) && appCtxt.get(ZmApp.UPSELL_SETTING[app]); 1107 }; 1108 1109 /** 1110 * @private 1111 */ 1112 ZmZimbraMail.prototype._getStartApp = 1113 function(params) { 1114 // determine starting app 1115 var startApp; 1116 var account = appCtxt.multiAccounts && appCtxt.accountList.mainAccount; 1117 if (params && params.app) { 1118 startApp = ZmApp.QS_ARG_R[params.app.toLowerCase()]; 1119 // make sure app given in QS is actually enabled 1120 // an app is valid if it's enabled as internal, iframe, or external 1121 if (!this._isInternalApp(startApp) && !this._isIframeApp(startApp)) { 1122 startApp = null; 1123 } 1124 } 1125 if (!startApp) { 1126 for (var app in ZmApp.DEFAULT_SORT) { 1127 ZmApp.DEFAULT_APPS.push(app); 1128 } 1129 ZmApp.DEFAULT_APPS.sort(function(a, b) { 1130 return ZmZimbraMail.hashSortCompare(ZmApp.DEFAULT_SORT, a, b); 1131 }); 1132 var defaultStartApp = null; 1133 for (var i = 0; i < ZmApp.DEFAULT_APPS.length; i++) { 1134 var app = ZmApp.DEFAULT_APPS[i]; 1135 if (this._isInternalApp(app)) { 1136 defaultStartApp = app; 1137 break; 1138 } 1139 } 1140 startApp = this._getDefaultStartAppName(account); 1141 } 1142 1143 // parse query string, in case we are coming in with a deep link 1144 var qsParams = AjxStringUtil.parseQueryString(); 1145 if (qsParams && qsParams.view && !qsParams.app) { 1146 startApp = ZmApp.QS_VIEWS[qsParams.view]; 1147 } 1148 1149 params.startApp = startApp; 1150 params.qsParams = qsParams; 1151 }; 1152 1153 /** 1154 * @private 1155 */ 1156 ZmZimbraMail.prototype._getDefaultStartAppName = 1157 function(account) { 1158 account = account || (appCtxt.multiAccounts && appCtxt.accountList.mainAccount) || null; 1159 1160 for (var app in ZmApp.DEFAULT_SORT) { 1161 ZmApp.DEFAULT_APPS.push(app); 1162 } 1163 ZmApp.DEFAULT_APPS.sort(function(a, b) { 1164 return ZmZimbraMail.hashSortCompare(ZmApp.DEFAULT_SORT, a, b); 1165 }); 1166 var defaultStartApp = null; 1167 for (var i = 0; i < ZmApp.DEFAULT_APPS.length; i++) { 1168 var app = ZmApp.DEFAULT_APPS[i]; 1169 var setting = ZmApp.SETTING[app]; 1170 if (!setting || appCtxt.get(setting, null, account)) { 1171 return app; 1172 } 1173 } 1174 }; 1175 1176 /** 1177 * Cancels the request. 1178 * 1179 * @param {String} reqId the request id 1180 * @param {AjxCallback} errorCallback the callback 1181 * @param {Boolean} noBusyOverlay if <code>true</code>, do not show busy overlay 1182 * @see ZmRequestMgr#cancelRequest 1183 */ 1184 ZmZimbraMail.prototype.cancelRequest = 1185 function(reqId, errorCallback, noBusyOverlay) { 1186 this._requestMgr.cancelRequest(reqId, errorCallback, noBusyOverlay); 1187 }; 1188 1189 /** 1190 * Sends the request. 1191 * 1192 * @param {Hash} params a hash of parameters 1193 * @see ZmRequestMgr#sendRequest 1194 */ 1195 ZmZimbraMail.prototype.sendRequest = 1196 function(params) { 1197 return this._requestMgr.sendRequest(params); 1198 }; 1199 1200 /** 1201 * Runs the given function for all enabled apps, passing args. 1202 * 1203 * @param {String} funcName the function name 1204 * @param {Boolean} force if <code>true</code>, run func for disabled apps as well 1205 */ 1206 ZmZimbraMail.prototype.runAppFunction = 1207 function(funcName, force) { 1208 var args = []; 1209 for (var i = 2; i < arguments.length; i++) { 1210 args.push(arguments[i]); 1211 } 1212 for (var i = 0; i < ZmApp.APPS.length; i++) { 1213 var appName = ZmApp.APPS[i]; 1214 var setting = this._isIframeApp(appName) ? ZmApp.UPSELL_SETTING[appName] : ZmApp.SETTING[appName]; 1215 var account = appCtxt.multiAccounts && appCtxt.accountList.mainAccount; 1216 if (!setting || appCtxt.get(setting, null, account) || force) { 1217 var app = appCtxt.getApp(appName, null, account); 1218 var func = app && app[funcName]; 1219 if (func && (typeof(func) == "function")) { 1220 func.apply(app, args); 1221 } 1222 } 1223 } 1224 appCtxt.notifyZimlets("runAppFunction", [funcName]); 1225 }; 1226 1227 /** 1228 * Instantiates enabled apps. An optional argument may be given limiting the set 1229 * of apps that may be created. 1230 * 1231 * @param {Hash} apps the set of apps to create 1232 * 1233 * @private 1234 */ 1235 ZmZimbraMail.prototype._createEnabledApps = 1236 function(apps) { 1237 this._apps = {}; 1238 1239 for (var app in ZmApp.CLASS) { 1240 if (!apps || apps[app]) { 1241 ZmApp.APPS.push(app); 1242 } 1243 } 1244 ZmApp.APPS.sort(function(a, b) { 1245 return ZmZimbraMail.hashSortCompare(ZmApp.LOAD_SORT, a, b); 1246 }); 1247 1248 // Instantiate enabled apps, which will invoke app registration. 1249 // We also create iframed (external) apps, which will only show the content of a URL in an iframe. 1250 for (var i = 0; i < ZmApp.APPS.length; i++) { 1251 var app = ZmApp.APPS[i]; 1252 var isInternal = this._isInternalApp(app); 1253 var isIframe = this._isIframeApp(app); 1254 if (isInternal || isIframe || app === ZmApp.BRIEFCASE) { 1255 ZmApp.ENABLED_APPS[app] = isInternal || isIframe; 1256 this._createApp(app); 1257 this._apps[app].isIframe = isIframe; 1258 } 1259 } 1260 }; 1261 1262 /** 1263 * Static function to add a listener before this class has been instantiated. 1264 * During construction, listeners are copied to the event manager. This function 1265 * could be used by a skin, for example. 1266 * 1267 * @param {constant} type the event type 1268 * @param {AjxListener} listener a listener 1269 */ 1270 ZmZimbraMail.addListener = 1271 function(type, listener) { 1272 if (!ZmZimbraMail._listeners[type]) { 1273 ZmZimbraMail._listeners[type] = []; 1274 } 1275 ZmZimbraMail._listeners[type].push(listener); 1276 }; 1277 1278 /** 1279 * Static function to add an app listener before this class has been 1280 * instantiated. This is separate from {@link ZmZimbraMail#addListener} 1281 * so that the caller doesn't need to know the specifics of how we 1282 * twiddle the type name for app events. 1283 * 1284 * @param {String} appName the application name 1285 * @param {constant} type the event type 1286 * @param {AjxListener} listener a listener 1287 * 1288 */ 1289 ZmZimbraMail.addAppListener = 1290 function(appName, type, listener) { 1291 type = [appName, type].join("_"); 1292 ZmZimbraMail.addListener(type, listener); 1293 }; 1294 1295 /** 1296 * Adds a listener for the given event type. 1297 * 1298 * @param {constant} type the event type 1299 * @param {AjxListener} listener a listener 1300 * @return {Boolean} <code>true</code> if the listener is added 1301 * 1302 */ 1303 ZmZimbraMail.prototype.addListener = 1304 function(type, listener) { 1305 return this._evtMgr.addListener(type, listener); 1306 }; 1307 1308 /** 1309 * Removes a listener for the given event type. 1310 * 1311 * @param {constant} type the event type 1312 * @param {AjxListener} listener a listener 1313 * @return {Boolean} <code>true</code> if the listener is removed 1314 */ 1315 ZmZimbraMail.prototype.removeListener = 1316 function(type, listener) { 1317 return this._evtMgr.removeListener(type, listener); 1318 }; 1319 1320 /** 1321 * Adds a listener for the given event type and app. 1322 * 1323 * @param {constant} app the app name 1324 * @param {constant} type the event type 1325 * @param {AjxListener} listener a listener 1326 * @return {Boolean} <code>true</code> if the listener is added 1327 */ 1328 ZmZimbraMail.prototype.addAppListener = 1329 function(app, type, listener) { 1330 type = [app, type].join("_"); 1331 return this.addListener(type, listener); 1332 }; 1333 1334 /** 1335 * Removes a listener for the given event type and app. 1336 * 1337 * @param {constant} app the app name 1338 * @param {constant} type the event type 1339 * @param {AjxListener} listener a listener 1340 * @return {Boolean} <code>true</code> if the listener is removed 1341 */ 1342 ZmZimbraMail.prototype.removeAppListener = 1343 function(app, type, listener) { 1344 type = [app, type].join("_"); 1345 return this.removeListener(type, listener); 1346 }; 1347 1348 /** 1349 * Sends a <code><NoOpRequest></code> to the server. Used for '$set:noop' 1350 */ 1351 ZmZimbraMail.prototype.sendNoOp = 1352 function() { 1353 var jsonObj = { NoOpRequest: { _jsns: "urn:zimbraMail" } }; 1354 var accountName = appCtxt.isOffline && appCtxt.accountList.mainAccount.name; 1355 this.sendRequest({jsonObj:jsonObj, asyncMode:true, noBusyOverlay:true, accountName:accountName}); 1356 }; 1357 1358 /** 1359 * Sends a <code><ClientEventNotifyRequest></code> to the server. 1360 * 1361 * @param {Object} event the event 1362 */ 1363 ZmZimbraMail.prototype.sendClientEventNotify = 1364 function(event, isNetworkOn) { 1365 var params = { 1366 jsonObj: { 1367 ClientEventNotifyRequest: { 1368 _jsns:"urn:zimbraOffline", 1369 e: event 1370 } 1371 }, 1372 asyncMode:true 1373 }; 1374 1375 if (isNetworkOn) { 1376 params.callback = new AjxCallback(this, this.handleClientEventNotifyResponse, event); 1377 params.noBusyOverlay = true; 1378 1379 if (this.clientEventNotifyReqId) { 1380 appCtxt.getRequestMgr().cancelRequest(this.clientEventNotifyReqId); 1381 } 1382 this.clientEventNotifyTimerId = 1383 AjxTimedAction.scheduleAction(new AjxTimedAction(this, this.sendClientEventNotify, [event, true]), 3000); 1384 } else { 1385 params.callback = new AjxCallback(this, this.setInstantNotify, true); 1386 } 1387 1388 this.clientEventNotifyReqId = this.sendRequest(params); 1389 }; 1390 1391 ZmZimbraMail.prototype.handleClientEventNotifyResponse = 1392 function(event, res) { 1393 if (!res.isException() && res.getResponse()) { 1394 if (this.clientEventNotifyTimerId) { 1395 AjxTimedAction.cancelAction(this.clientEventNotifyTimerId); 1396 this.clientEventNotityTimerId = null; 1397 } 1398 this.setInstantNotify(true); 1399 } 1400 }; 1401 1402 /** 1403 * Sets the client into "instant notifications" mode. 1404 * 1405 * @param {Boolean} on if <code>true</code>, turn on instant notify 1406 */ 1407 ZmZimbraMail.prototype.setInstantNotify = 1408 function(on) { 1409 if (on) { 1410 this._pollInstantNotifications = true; 1411 // set nonzero poll interval so cant ever get into a full-speed request loop 1412 this._pollInterval = appCtxt.get(ZmSetting.INSTANT_NOTIFY_INTERVAL); 1413 if (this._pollActionId) { 1414 AjxTimedAction.cancelAction(this._pollActionId); 1415 this._pollActionId = null; 1416 } 1417 this._kickPolling(true); 1418 } else { 1419 this._pollInstantNotifications = false; 1420 this._cancelInstantNotify(); 1421 this.setPollInterval(true); 1422 } 1423 }; 1424 1425 /** 1426 * Gets the "instant notification" setting. 1427 * 1428 * @return {Boolean} <code>true</code> if instant notification is "ON" 1429 */ 1430 ZmZimbraMail.prototype.getInstantNotify = 1431 function() { 1432 return this._pollInstantNotifications; 1433 }; 1434 1435 ZmZimbraMail.prototype.registerMailtoHandler = 1436 function(regProto, selected) { 1437 if (appCtxt.get(ZmSetting.OFFLINE_SUPPORTS_MAILTO) && window.platform) { 1438 try { // add try/catch - see bug #33870 1439 if (selected) { // user selected zd as default mail app 1440 // register mailto handler 1441 if (regProto) { 1442 var url = appCtxt.get(ZmSetting.OFFLINE_WEBAPP_URI, null, appCtxt.accountList.mainAccount); 1443 window.platform.registerProtocolHandler("mailto", url + "&mailto=%s"); 1444 1445 // handle "send to mail recipient" on windows (requires mapi@zimbra.com extension) 1446 if (AjxEnv.isWindows) { 1447 var shell = new ZimbraDesktopShell; 1448 shell.defaultClient = true; 1449 } 1450 } 1451 1452 // register mailto callback 1453 var callback = AjxCallback.simpleClosure(this.handleOfflineMailTo, this); 1454 window.platform.registerProtocolCallback("mailto", callback); 1455 } else { // unselected (box unchecked) 1456 window.platform.unregisterProtocolHandler("mailto"); 1457 1458 if (AjxEnv.isWindows) { 1459 var shell = new ZimbraDesktopShell; 1460 shell.defaultClient = false; 1461 } 1462 } 1463 } catch(ex) { 1464 // do nothing 1465 } 1466 } 1467 }; 1468 1469 /** 1470 * @private 1471 */ 1472 ZmZimbraMail.prototype.handleOfflineMailTo = 1473 function(uri, callback) { 1474 if (window.platform && !window.platform.isRegisteredProtocolHandler("mailto")) { return false; } 1475 1476 var mailApp = this.getApp(ZmApp.MAIL); 1477 var idx = (uri.indexOf("mailto")); 1478 if (idx >= 0) { 1479 var query = "to=" + decodeURIComponent(uri.substring(idx+7)); 1480 query = query.replace(/\?/g, "&"); 1481 var controller = mailApp._showComposeView(callback, query); 1482 this._checkOfflineMailToAttachments(controller, query); 1483 return true; 1484 } 1485 return false; 1486 }; 1487 1488 ZmZimbraMail.prototype._checkOfflineMailToAttachments = 1489 function(controller, queryStr) { 1490 var qs = queryStr || location.search; 1491 1492 var match = qs.match(/\bto=([^&]+)/); 1493 var to = match ? AjxStringUtil.urlComponentDecode(match[1]) : null; 1494 1495 match = qs.match(/\battachments=([^&]+)/); 1496 var attachments = match ? (AjxStringUtil.urlComponentDecode(match[1]).replace(/\+/g, " ")) : null; 1497 1498 if (to && to.indexOf('mailto') == 0) { 1499 to = to.replace(/mailto:/,''); 1500 var mailtoQuery = to.split('?'); 1501 if (mailtoQuery.length > 1) { 1502 mailtoQuery = mailtoQuery[1]; 1503 match = mailtoQuery.match(/\battachments=([^&]+)/); 1504 if(!attachments) attachments = match ? (AjxStringUtil.urlComponentDecode(match[1]).replace(/\+/g, " ")) : null; 1505 } 1506 } 1507 1508 if(attachments) { 1509 attachments = attachments.replace(/;$/, ""); 1510 attachments = attachments.split(";"); 1511 this._mailtoAttachmentsLength = attachments.length; 1512 this._attachmentsProcessed = 0; 1513 this.attachment_ids = []; 1514 for(var i=0; i<attachments.length; i++) { 1515 this._handleMailToAttachment(attachments[i], controller); 1516 } 1517 } 1518 }; 1519 1520 ZmZimbraMail.prototype._handleMailToAttachment = 1521 function(attachment, controller) { 1522 1523 var filePath = attachment; 1524 var filename = filePath.replace(/^.*\\/, ''); 1525 1526 DBG.println("Uploading File :" + filename + ",filePath:" + filePath); 1527 1528 //check read file permission; 1529 try { 1530 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); 1531 } catch (e) { 1532 //permission denied to read file 1533 DBG.println("Permission denied to read file"); 1534 return; 1535 } 1536 1537 try { 1538 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile); 1539 file.initWithPath( filePath ); 1540 1541 var contentType = this._getAttachmentContentType(file); 1542 1543 var inputStream = Components.classes[ "@mozilla.org/network/file-input-stream;1" ].createInstance(Components.interfaces.nsIFileInputStream); 1544 inputStream.init(file, -1, -1, false ); 1545 1546 var binary = Components.classes[ "@mozilla.org/binaryinputstream;1" ].createInstance(Components.interfaces.nsIBinaryInputStream); 1547 binary.setInputStream(inputStream); 1548 1549 var req = new XMLHttpRequest(); 1550 req.open("POST", appCtxt.get(ZmSetting.CSFE_UPLOAD_URI)+"&fmt=extended,raw", true); 1551 req.setRequestHeader("Cache-Control", "no-cache"); 1552 req.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 1553 req.setRequestHeader("Content-Type", (contentType || "application/octet-stream") ); 1554 req.setRequestHeader("Content-Disposition", 'attachment; filename="'+ AjxUtil.convertToEntities(filename) + '"'); 1555 1556 var reqObj = req; 1557 req.onreadystatechange = AjxCallback.simpleClosure(this._handleUploadResponse, this, reqObj, controller); 1558 req.sendAsBinary(binary.readBytes(binary.available())); 1559 delete req; 1560 }catch(ex) { 1561 DBG.println("exception in handling attachment: " + attachment); 1562 DBG.println(ex); 1563 this._attachmentsProcessed++; 1564 } 1565 }; 1566 1567 ZmZimbraMail.prototype._getAttachmentContentType = 1568 function(file) { 1569 var contentType; 1570 try { 1571 contentType = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService).getTypeFromFile(file); 1572 }catch(ex) { 1573 DBG.println("exception in reading content type: " + ex); 1574 contentType = "application/octet-stream"; 1575 } 1576 return contentType; 1577 }; 1578 1579 ZmZimbraMail.prototype._handleUploadResponse = function(req, controller) { 1580 if(req) { 1581 if(req.readyState == 4 && req.status == 200) { 1582 var resp = eval("["+req.responseText+"]"); 1583 this._attachmentsProcessed++; 1584 this.popupUploadErrorDialog(ZmItem.MSG, resp[0]); 1585 if(resp.length > 2) { 1586 var respObj = resp[2]; 1587 for (var i = 0; i < respObj.length; i++) { 1588 if(respObj[i].aid != "undefined") { 1589 this.attachment_ids.push(respObj[i].aid); 1590 } 1591 } 1592 1593 if(this.attachment_ids.length > 0 && this._attachmentsProcessed == this._mailtoAttachmentsLength) { 1594 var attachment_list = this.attachment_ids.join(","); 1595 if(!controller) { 1596 var msg = new ZmMailMsg(); 1597 controller = AjxDispatcher.run("GetComposeController"); 1598 controller._setView({action:ZmOperation.NEW_MESSAGE, msg:msg, inNewWindow:false}); 1599 } 1600 var callback = new AjxCallback (controller,controller._handleResponseSaveDraftListener); 1601 controller.sendMsg(attachment_list, ZmComposeController.DRAFT_TYPE_MANUAL,callback); 1602 this.getAppViewMgr().pushView(controller.getCurrentViewId()); 1603 } 1604 } 1605 } 1606 } 1607 1608 }; 1609 1610 /** 1611 * Resets the interval between poll requests, based on what's in the settings, 1612 * only if we are not in instant notify mode. 1613 * 1614 * @param {Boolean} kickMe if <code>true</code>, start the poll timer 1615 * @return {Boolean} <code>true</code> if poll interval started; <code>false</code> if in "instant notification" mode 1616 */ 1617 ZmZimbraMail.prototype.setPollInterval = 1618 function(kickMe) { 1619 if (!this._pollInstantNotifications) { 1620 this._pollInterval = appCtxt.get(ZmSetting.POLLING_INTERVAL) * 1000; 1621 1622 if (this._pollInterval) { 1623 DBG.println(AjxDebug.DBG1, "poll interval = " + this._pollInterval + "ms"); 1624 if (kickMe) 1625 this._kickPolling(true); 1626 } else { 1627 // cancel timer if it is waiting... 1628 if (this._pollActionId) { 1629 AjxTimedAction.cancelAction(this._pollActionId); 1630 this._pollActionId = null; 1631 } 1632 } 1633 return true; 1634 } else { 1635 this._pollInterval = appCtxt.get(ZmSetting.INSTANT_NOTIFY_INTERVAL); 1636 DBG.println(AjxDebug.DBG1, "Ignoring Poll Interval (in instant-notify mode)"); 1637 return false; 1638 } 1639 }; 1640 1641 /** 1642 * @private 1643 */ 1644 ZmZimbraMail.prototype._cancelInstantNotify = 1645 function() { 1646 if (this._pollRequest) { 1647 this._requestMgr.cancelRequest(this._pollRequest); 1648 this._pollRequest = null; 1649 } 1650 1651 if (this._pollActionId) { 1652 AjxTimedAction.cancelAction(this._pollActionId); 1653 this._pollActionId = null; 1654 } 1655 }; 1656 1657 /** 1658 * Make sure the polling loop is running. Basic flow: 1659 * 1660 * 1) kickPolling(): 1661 * - cancel any existing timers 1662 * - set a timer for _pollInterval time 1663 * - call execPoll() when the timer goes off 1664 * 1665 * 2) execPoll(): 1666 * - make the NoOp request, if we're in "instant notifications" 1667 * mode, this request will hang on the server until there is more data, 1668 * otherwise it will return immediately. Call into a handle() func below 1669 * 1670 * 3) handleDoPollXXXX(): 1671 * - call back to kickPolling() above 1672 * 1673 * resetBackoff = TRUE e.g. if we've just received a successful 1674 * response from the server, or if the user just changed our 1675 * polling settings and we want to start in fast mode 1676 * 1677 * @private 1678 */ 1679 ZmZimbraMail.prototype._kickPolling = 1680 function(resetBackoff) { 1681 DBG.println(AjxDebug.DBG2, [ 1682 "ZmZimbraMail._kickPolling ", 1683 this._pollInterval, ", ", 1684 this._pollActionId, ", ", 1685 this._pollRequest ? "request_pending" : "no_request_pending" 1686 ].join("")); 1687 1688 // reset the polling timeout 1689 if (this._pollActionId) { 1690 AjxTimedAction.cancelAction(this._pollActionId); 1691 this._pollActionId = null; 1692 } 1693 1694 if (resetBackoff && this._pollInstantNotifications) { 1695 // we *were* backed off -- reset the delay back to 1s fastness 1696 var interval = appCtxt.get(ZmSetting.INSTANT_NOTIFY_INTERVAL); 1697 if (this._pollInterval > interval) { 1698 this._pollInterval = interval; 1699 } 1700 } 1701 1702 if (this._pollInterval && !this._pollRequest) { 1703 try { 1704 this._pollActionId = AjxTimedAction.scheduleAction(new AjxTimedAction(this, this._execPoll), this._pollInterval); 1705 } catch (ex) { 1706 this._pollActionId = null; 1707 DBG.println(AjxDebug.DBG1, "Caught exception in ZmZimbraMail._kickPolling. Polling chain broken!"); 1708 } 1709 } 1710 }; 1711 1712 /** 1713 * We've finished waiting, do the actual poll itself 1714 * 1715 * @private 1716 */ 1717 ZmZimbraMail.prototype._execPoll = 1718 function() { 1719 1720 this._cancelInstantNotify(); 1721 1722 // It'd be more efficient to make these instance variables, but for some 1723 // reason that breaks polling in IE. 1724 var jsonObj = { NoOpRequest: { _jsns: "urn:zimbraMail" } }, 1725 method = jsonObj.NoOpRequest; 1726 1727 try { 1728 if (this._pollInstantNotifications) { 1729 var sessionId = ZmCsfeCommand.getSessionId(); 1730 if (sessionId) { 1731 method.wait = 1; 1732 method.limitToOneBlocked = 1; 1733 } 1734 } 1735 var params = { 1736 jsonObj: jsonObj, 1737 asyncMode: true, 1738 callback: this._handleResponseDoPoll.bind(this), 1739 errorCallback: this._handleErrorDoPoll.bind(this), 1740 noBusyOverlay: true, 1741 timeout: appCtxt.get(ZmSetting.INSTANT_NOTIFY_TIMEOUT), 1742 accountName: appCtxt.isOffline && appCtxt.accountList.mainAccount.name 1743 }; 1744 this._pollRequest = this.sendRequest(params); 1745 1746 // bug #42664 - handle case where sync-status-changes fall between 2 client requests 1747 var accList = appCtxt.accountList; 1748 if (appCtxt.isOffline && !accList.isInitialSyncing() && accList.isSyncStatus(ZmZimbraAccount.STATUS_RUNNING)) { 1749 this.sendNoOp(); 1750 } 1751 } catch (ex) { 1752 this._handleErrorDoPoll(ex); // oops! 1753 } 1754 }; 1755 1756 /** 1757 * @private 1758 */ 1759 ZmZimbraMail.prototype._handleErrorDoPoll = 1760 function(ex) { 1761 if (this._pollRequest) { 1762 // reset the polling timeout 1763 if (this._pollActionId) { 1764 AjxTimedAction.cancelAction(this._pollActionId); 1765 this._pollActionId = null; 1766 } 1767 this._requestMgr.cancelRequest(this._pollRequest); 1768 this._pollRequest = null; 1769 } 1770 1771 if (this._pollInstantNotifications) { 1772 // very simple-minded exponential backoff 1773 this._pollInterval *= 2; 1774 if (this._pollInterval > (1000 * 60 * 2)) { 1775 this._pollInterval = 1000 * 60 * 2; 1776 } 1777 } 1778 1779 var isAuthEx = (ex.code == ZmCsfeException.SVC_AUTH_EXPIRED || 1780 ex.code == ZmCsfeException.SVC_AUTH_REQUIRED || 1781 ex.code == ZmCsfeException.NO_AUTH_TOKEN); 1782 1783 // restart poll timer if we didn't get an auth exception 1784 if (!isAuthEx) { 1785 this._kickPolling(false); 1786 } 1787 1788 return !isAuthEx; 1789 }; 1790 1791 /** 1792 * @private 1793 */ 1794 ZmZimbraMail.prototype._handleResponseDoPoll = 1795 function(result) { 1796 this._pollRequest = null; 1797 var noopResult = result.getResponse().NoOpResponse; 1798 if (noopResult.waitDisallowed) { 1799 this._waitDisallowed = true; 1800 // revert to polling mode - server doesn't want us to use instant notify. 1801 this.setInstantNotify(false); 1802 } else { 1803 // restart poll timer if we didn't get an exception 1804 this._kickPolling(true); 1805 } 1806 }; 1807 1808 /** 1809 * Gets the key map manager. 1810 * 1811 * @return {DwtKeyMapMgr} the key map manager 1812 */ 1813 ZmZimbraMail.prototype.getKeyMapMgr = 1814 function() { 1815 var kbMgr = appCtxt.getKeyboardMgr(); 1816 if (!kbMgr.__keyMapMgr) { 1817 this._initKeyboardHandling(); 1818 } 1819 return kbMgr.__keyMapMgr; 1820 }; 1821 1822 /** 1823 * @private 1824 */ 1825 ZmZimbraMail.prototype._initKeyboardHandling = 1826 function() { 1827 var kbMgr = appCtxt.getKeyboardMgr(); 1828 if (kbMgr.__keyMapMgr) { return; } 1829 if (appCtxt.get(ZmSetting.USE_KEYBOARD_SHORTCUTS)) { 1830 // Register our keymap and global key action handler with the shell's keyboard manager 1831 kbMgr.enable(true); 1832 kbMgr.registerKeyMap(new ZmKeyMap()); 1833 kbMgr.pushDefaultHandler(this); 1834 } else { 1835 kbMgr.enable(false); 1836 } 1837 }; 1838 1839 /** 1840 * @private 1841 */ 1842 ZmZimbraMail.prototype._setupTabGroups = 1843 function() { 1844 DBG.println(AjxDebug.DBG2, "SETTING SEARCH CONTROLLER TAB GROUP"); 1845 var rootTg = appCtxt.getRootTabGroup(); 1846 if (appCtxt.get(ZmSetting.SEARCH_ENABLED)) { 1847 rootTg.addMember(appCtxt.getSearchController().getSearchToolbar().getTabGroupMember()); 1848 } 1849 1850 rootTg.addMember(this._userNameField); 1851 rootTg.addMember(this._usedQuotaField); 1852 1853 if (this._helpButton) { 1854 rootTg.addMember(this._helpButton); 1855 } 1856 1857 rootTg.addMember(appCtxt.getAppChooser().getTabGroupMember()); 1858 rootTg.addMember(appCtxt.refreshButton); 1859 rootTg.addMember(this._newToolbar); 1860 1861 var curApp = appCtxt.getCurrentApp(); 1862 var overview = curApp && curApp.getOverview(); 1863 if (overview) { 1864 rootTg.addMember(overview.getTabGroupMember()); 1865 ZmController._currentOverview = overview; 1866 } 1867 1868 appCtxt.getKeyboardMgr().setTabGroup(rootTg); 1869 }; 1870 1871 /** 1872 * @private 1873 */ 1874 ZmZimbraMail.prototype._registerOrganizers = 1875 function() { 1876 1877 ZmOrganizer.registerOrg(ZmOrganizer.FOLDER, 1878 {app: ZmApp.MAIL, 1879 nameKey: "folder", 1880 defaultFolder: ZmOrganizer.ID_INBOX, 1881 soapCmd: "FolderAction", 1882 firstUserId: 256, 1883 orgClass: "ZmFolder", 1884 orgPackage: "MailCore", 1885 treeController: "ZmMailFolderTreeController", 1886 labelKey: "mailFolders", 1887 itemsKey: "messages", 1888 hasColor: true, 1889 defaultColor: ZmOrganizer.C_NONE, 1890 treeType: ZmOrganizer.FOLDER, 1891 dropTargets: [ZmOrganizer.FOLDER], 1892 views: ["message", "conversation"], 1893 folderKey: "mailFolder", 1894 mountKey: "mountFolder", 1895 createFunc: "ZmOrganizer.create", 1896 compareFunc: "ZmFolder.sortCompare", 1897 newOp: ZmOperation.NEW_FOLDER, 1898 displayOrder: 100, 1899 childWindow: true, 1900 openSetting: ZmSetting.FOLDER_TREE_OPEN 1901 }); 1902 1903 ZmOrganizer.registerOrg(ZmOrganizer.SEARCH, 1904 {app: ZmApp.MAIN, 1905 nameKey: "savedSearch", 1906 precondition: ZmSetting.SAVED_SEARCHES_ENABLED, 1907 soapCmd: "FolderAction", 1908 firstUserId: 256, 1909 orgClass: "ZmSearchFolder", 1910 treeController: "ZmSearchTreeController", 1911 labelKey: "searches", 1912 hasColor: true, 1913 defaultColor: ZmOrganizer.C_NONE, 1914 treeType: ZmOrganizer.FOLDER, 1915 folderKey: "savedSearch", 1916 disableShare: true, 1917 dropTargets: [ZmOrganizer.FOLDER, ZmOrganizer.SEARCH], 1918 createFunc: "ZmSearchFolder.create", 1919 compareFunc: "ZmFolder.sortCompare", 1920 openSetting: ZmSetting.SEARCH_TREE_OPEN, 1921 displayOrder: 300 1922 }); 1923 1924 ZmOrganizer.registerOrg(ZmOrganizer.SHARE, { 1925 orgClass: "ZmShareProxy", 1926 treeController: "ZmShareTreeController", 1927 labelKey: "sharedFoldersHeader", 1928 compareFunc: "ZmFolder.sortCompare", 1929 displayOrder: 101, // NOTE: Always show shares below primary folder tree 1930 hideEmpty: false 1931 }); 1932 1933 ZmOrganizer.registerOrg(ZmOrganizer.TAG, 1934 {app: ZmApp.MAIN, 1935 nameKey: "tag", 1936 precondition: ZmSetting.TAGGING_ENABLED, 1937 soapCmd: "TagAction", 1938 firstUserId: 64, 1939 orgClass: "ZmTag", 1940 treeController: "ZmTagTreeController", 1941 hasColor: true, 1942 defaultColor: ZmOrganizer.C_ORANGE, 1943 labelKey: "tags", 1944 treeType: ZmOrganizer.TAG, 1945 createFunc: "ZmTag.create", 1946 compareFunc: "ZmTag.sortCompare", 1947 newOp: ZmOperation.NEW_TAG, 1948 openSetting: ZmSetting.TAG_TREE_OPEN, 1949 displayOrder: 400 1950 }); 1951 1952 ZmOrganizer.registerOrg(ZmOrganizer.ZIMLET, 1953 {orgClass: "ZmZimlet", 1954 treeController: "ZmZimletTreeController", 1955 labelKey: "zimlets", 1956 compareFunc: "ZmZimlet.sortCompare", 1957 openSetting: ZmSetting.ZIMLET_TREE_OPEN, 1958 hideEmpty: true 1959 }); 1960 1961 // Technically, we don't need to do this because the drop listeners for dragged organizers typically do their 1962 // own checks on the class of the dragged object. But it's better to do it anyway, in case it ever gets 1963 // validated within the drop target against the valid types. 1964 this._name = ZmApp.MAIN; 1965 ZmApp.prototype._setupDropTargets.call(this); 1966 }; 1967 1968 /** 1969 * Gets a handle to the given app. 1970 * 1971 * @param {String} appName the app name 1972 * @return {ZmApp} the app 1973 */ 1974 ZmZimbraMail.prototype.getApp = 1975 function(appName) { 1976 if (!ZmApp.ENABLED_APPS[appName]) { 1977 return null; 1978 } 1979 if (!this._apps[appName]) { 1980 this._createApp(appName); 1981 } 1982 return this._apps[appName]; 1983 }; 1984 1985 /** 1986 * Gets a handle to the app view manager. 1987 * 1988 * @return {ZmAppViewMgr} the app view manager 1989 */ 1990 ZmZimbraMail.prototype.getAppViewMgr = 1991 function() { 1992 return this._appViewMgr; 1993 }; 1994 1995 /** 1996 * Gets the active app. 1997 * 1998 * @return {ZmApp} the app 1999 */ 2000 ZmZimbraMail.prototype.getActiveApp = 2001 function() { 2002 return this._activeApp; 2003 }; 2004 2005 /** 2006 * Gets the previous application. 2007 * 2008 * @return {ZmApp} the app 2009 */ 2010 ZmZimbraMail.prototype.getPreviousApp = 2011 function() { 2012 return this._previousApp; 2013 }; 2014 2015 /** 2016 * Activates the given application. 2017 * 2018 * @param {constant} appName the application name 2019 * @param {Boolean} force if <code>true</code>, launch the app 2020 * @param {AjxCallback} callback the callback 2021 * @param {AjxCallback} errorCallback the error callback 2022 * @param {Hash} params a hash of parameters (see {@link #startup} for full list) 2023 * @param {Boolean} params.checkQS if <code>true</code>, check query string for launch args 2024 * @param {ZmCsfeResult} params.result the result object from load of user settings 2025 */ 2026 ZmZimbraMail.prototype.activateApp = 2027 function(appName, force, callback, errorCallback, params) { 2028 DBG.println(AjxDebug.DBG1, "activateApp: " + appName + ", current app = " + this._activeApp); 2029 2030 var account = appCtxt.multiAccounts && appCtxt.accountList.mainAccount; 2031 var isIframe = this._isIframeApp(appName); 2032 var view = this._appViewMgr.getAppView(appName); 2033 if (view && !force) { 2034 // if the app has been launched, make its view the current one 2035 DBG.println(AjxDebug.DBG3, "activateApp, current " + appName + " view: " + view); 2036 if (this._appViewMgr.pushView(view)) { 2037 this._appViewMgr.setAppView(appName, view); 2038 if (isIframe) { 2039 var title = [ZmMsg.zimbraTitle, appName].join(": "); 2040 Dwt.setTitle(title); 2041 } 2042 } 2043 if (callback) { 2044 callback.run(); 2045 } 2046 } else { 2047 // launch the app 2048 if (!this._apps[appName]) { 2049 this._createApp(appName); 2050 } 2051 2052 if (isIframe) { 2053 this._createAppIframeView(appName); 2054 if (callback) { 2055 callback.run(); 2056 } 2057 } 2058 else { 2059 DBG.println(AjxDebug.DBG1, "Launching app " + appName); 2060 var respCallback = new AjxCallback(this, this._handleResponseActivateApp, [callback, appName]); 2061 var eventType = [appName, ZmAppEvent.PRE_LAUNCH].join("_"); 2062 this._evt.item = this._apps[appName]; 2063 this.notify(eventType); 2064 params = params || {}; 2065 params.searchResponse = this._searchResponse; 2066 this._apps[appName].launch(params, respCallback); 2067 delete this._searchResponse; 2068 } 2069 } 2070 }; 2071 2072 /** 2073 * @private 2074 */ 2075 ZmZimbraMail.prototype._handleResponseActivateApp = 2076 function(callback, appName) { 2077 if (callback) { 2078 callback.run(); 2079 } 2080 2081 if (ZmApp.DEFAULT_SEARCH[appName]) { 2082 appCtxt.getSearchController().setDefaultSearchType(ZmApp.DEFAULT_SEARCH[appName]); 2083 } 2084 2085 var eventType = [appName, ZmAppEvent.POST_LAUNCH].join("_"); 2086 this._evt.item = this._apps[appName]; 2087 this.notify(eventType); 2088 }; 2089 2090 /** 2091 * Handles a change in which app is current. The change will be reflected in the 2092 * current app toolbar and the overview. The previous and newly current apps are 2093 * notified of the change. This method is called after a new view is pushed. 2094 * 2095 * @param {Object} view 2096 */ 2097 ZmZimbraMail.prototype.setActiveApp = 2098 function(view) { 2099 var appName = view.app; 2100 2101 // update app chooser 2102 if (!view.isTabView) { 2103 this._components[ZmAppViewMgr.C_APP_CHOOSER].setSelected(appName); 2104 } 2105 2106 // app not actually enabled if this is result of iframe view push 2107 var account = appCtxt.multiAccounts && appCtxt.accountList.mainAccount; 2108 var appEnabled = !ZmApp.SETTING[appName] || appCtxt.get(ZmApp.SETTING[appName], null, account); 2109 2110 this._activeTabId = null; // app is active; tab IDs are for non-apps 2111 2112 if (appName === ZmApp.SEARCH) { 2113 //this is a special case - the search tab - set the new button based on type by using the results type app to get the button props. 2114 this._setSearchTabNewButtonProps(view.controller._resultsController); 2115 } 2116 2117 if (this._activeApp != appName) { 2118 // deactivate previous app 2119 if (this._activeApp) { 2120 // some views are not stored in _apps collection, so check if it exists. 2121 var app = this._apps[this._activeApp]; 2122 if (app) { 2123 app.activate(false, view.id); 2124 } 2125 this._previousApp = this._activeApp; 2126 } 2127 2128 // switch app 2129 this._activeApp = appName; 2130 if (appEnabled) { 2131 var app = this._apps[this._activeApp]; 2132 if (appCtxt.get(ZmSetting.SEARCH_ENABLED)) { 2133 var searchType; 2134 var currentSearch; 2135 if (appName === ZmApp.SEARCH) { 2136 currentSearch = view.controller._resultsController._currentSearch; 2137 var types = currentSearch && currentSearch.types; 2138 searchType = types && types.size() > 0 && types.get(0); 2139 } 2140 else { 2141 currentSearch = app.currentSearch; 2142 searchType = app.getInitialSearchType(); 2143 if (!searchType) { 2144 searchType = ZmApp.DEFAULT_SEARCH[appName]; 2145 } 2146 } 2147 if (searchType) { 2148 appCtxt.getSearchController().setDefaultSearchType(searchType); 2149 } 2150 // set search string value to match current app's last search, if applicable 2151 var stb = appCtxt.getSearchController().getSearchToolbar(); 2152 if (appCtxt.get(ZmSetting.SHOW_SEARCH_STRING) && stb) { 2153 var value = currentSearch ? currentSearch.query : app.currentQuery; 2154 value = appName === ZmApp.SEARCH ? "" : value; 2155 stb.setSearchFieldValue(value || ""); 2156 } 2157 } 2158 2159 // activate current app - results in rendering of overview 2160 if (app) { 2161 if (appCtxt.inStartup && this._doingPostRenderStartup) { 2162 var callback = new AjxCallback(this, 2163 function() { 2164 app.activate(true); 2165 }); 2166 this.addPostRenderCallback(callback, 1, 0, true); 2167 } else { 2168 app.activate(true); 2169 } 2170 } 2171 } 2172 this._evt.item = this._apps[appName]; 2173 this.notify(ZmAppEvent.ACTIVATE); 2174 } 2175 else if (this._activeApp && this._apps[this._activeApp]) { 2176 this._apps[this._activeApp].stopAlert(); 2177 } 2178 }; 2179 2180 ZmZimbraMail.prototype._setSearchTabNewButtonProps = 2181 function(resultsController) { 2182 var resultsApp; 2183 if (resultsController.isZmCalViewController) { 2184 //calendar search is different, no _currentSearch unfortunately. 2185 resultsApp = appCtxt.getApp(ZmApp.CALENDAR); 2186 } 2187 else { 2188 var currentSearch = resultsController._currentSearch; 2189 var types = currentSearch && currentSearch.types; 2190 var searchType = types && types.size() > 0 && types.get(0); 2191 resultsApp = searchType && appCtxt.getApp(ZmItem.APP[searchType]); 2192 } 2193 if (resultsApp) { 2194 appCtxt.getAppController().setNewButtonProps(resultsApp.getNewButtonProps()); 2195 } 2196 2197 }; 2198 2199 /** 2200 * Gets the app chooser button. 2201 * 2202 * @param {String} id the id 2203 * @return {ZmAppButton} the button 2204 */ 2205 ZmZimbraMail.prototype.getAppChooserButton = 2206 function(id) { 2207 var chooser = this._components[ZmAppViewMgr.C_APP_CHOOSER]; 2208 return chooser && chooser.getButton(id); 2209 }; 2210 2211 /** 2212 * An app calls this once it has fully rendered, so that we may notify 2213 * any listeners. 2214 * 2215 * @param {String} appName the app name 2216 */ 2217 ZmZimbraMail.prototype.appRendered = 2218 function(appName) { 2219 var eventType = [appName, ZmAppEvent.POST_RENDER].join("_"); 2220 this.notify(eventType); 2221 2222 if (window._facadeCleanup) { 2223 window._facadeCleanup(); 2224 window._facadeCleanup = null; 2225 } 2226 }; 2227 2228 /** 2229 * Adds the application. 2230 * 2231 * @param {ZmApp} app the app 2232 */ 2233 ZmZimbraMail.prototype.addApp = function(app) { 2234 var appName = app.getName(); 2235 this._apps[appName] = app; 2236 ZmApp.ENABLED_APPS[appName] = true; 2237 }; 2238 2239 // Private methods 2240 2241 /** 2242 * Creates an app object, which doesn't necessarily do anything just yet. 2243 * 2244 * @private 2245 */ 2246 ZmZimbraMail.prototype._createApp = 2247 function(appName) { 2248 if (!appName || this._apps[appName]) return; 2249 DBG.println(AjxDebug.DBG1, "Creating app " + appName); 2250 var appClass = eval(ZmApp.CLASS[appName]); 2251 this.addApp(new appClass(this._shell)); 2252 }; 2253 2254 /** 2255 * @private 2256 */ 2257 ZmZimbraMail.prototype._setExternalLinks = 2258 function() { 2259 // bug: 41313 - admin console link 2260 var adminUrl; 2261 if (!appCtxt.isOffline && 2262 (appCtxt.get(ZmSetting.IS_ADMIN) || 2263 appCtxt.get(ZmSetting.IS_DELEGATED_ADMIN))) { 2264 2265 adminUrl = appCtxt.get(ZmSetting.ADMIN_URL); 2266 if (!adminUrl) { 2267 adminUrl = ["https://", location.hostname, ":7071"].join(""); 2268 } 2269 } 2270 var el = document.getElementById("skin_container_links"); 2271 if (el) { 2272 var data = { 2273 showOfflineLink: (!appCtxt.isOffline && appCtxt.get(ZmSetting.SHOW_OFFLINE_LINK)), 2274 helpIcon: (appCtxt.getSkinHint("helpButton", "hideIcon") ? null : "Help"), 2275 logoutIcon: (appCtxt.getSkinHint("logoutButton", "hideIcon") ? null : "Logoff"), 2276 logoutText: (appCtxt.isOffline ? ZmMsg.setup : ZmMsg.logOff), 2277 adminUrl: adminUrl 2278 }; 2279 el.innerHTML = AjxTemplate.expand("share.App#UserInfo", data); 2280 } 2281 2282 el = document.getElementById("skin_container_help_button"); 2283 if (el) { 2284 this._helpButton = this.getHelpButton(DwtShell.getShell(window)); 2285 this._helpButton.reparentHtmlElement("skin_container_help_button"); 2286 } 2287 2288 el = document.getElementById("skin_dropMenu"); 2289 if (el) { 2290 this._helpButton = this.getDropMenuOptions(DwtShell.getShell(window), el, adminUrl); 2291 //this._helpButton.reparentHtmlElement("skin_dropMenu"); 2292 } 2293 }; 2294 2295 2296 ZmZimbraMail.ONLINE_HELP_URL = "https://help.zimbra.com/?"; 2297 ZmZimbraMail.NEW_FEATURES_URL = "https://www.zimbra.com/products/whats_new.html?"; 2298 2299 ZmZimbraMail.DEFAULT_CONTACT_ICON = appContextPath + "/img/large/ImgPerson_48.png?v=" + window.cacheKillerVersion; 2300 ZmZimbraMail.DEFAULT_CONTACT_ICON_SMALL = appContextPath + "/img/large/ImgPerson_32.png?v=" + window.cacheKillerVersion; 2301 2302 /** 2303 * Adds a "help" submenu. 2304 * 2305 * @param {DwtComposite} parent the parent widget 2306 * @return {ZmActionMenu} the menu 2307 */ 2308 ZmZimbraMail.prototype.getDropMenuOptions = 2309 function(parent, parentElement, adminUrl) { 2310 2311 var button = new DwtLinkButton({parent: parent, className: DwtButton.LINK_BUTTON_CLASS, parentElement: parentElement, elementTag: "DIV"}); 2312 button.whatToShow = { }; 2313 button.setSize(Dwt.DEFAULT); 2314 button.setAlign(DwtLabel.ALIGN_LEFT); 2315 button.setText(ZmMsg.help); 2316 button.setAttribute('aria-label', ZmMsg.userActions); 2317 var menu = new ZmPopupMenu(button); 2318 2319 var supportedHelps = appCtxt.get(ZmSetting.SUPPORTED_HELPS); 2320 var helpListener = new AjxListener(this, this._helpListener); 2321 button.addSelectionListener(helpListener); 2322 2323 var mi; 2324 if (adminUrl) { 2325 mi = menu.createMenuItem("adminLink", {text: ZmMsg.adminLinkLabel}); 2326 mi.addSelectionListener(new AjxListener(null, ZmZimbraMail.adminLinkCallback, adminUrl)); 2327 } 2328 2329 mi = menu.createMenuItem("standardHtmlLink", {text: ZmMsg.htmlClient}); 2330 mi.addSelectionListener(ZmZimbraMail.standardHtmlLinkCallback); 2331 2332 menu.createSeparator(); 2333 2334 if (supportedHelps.indexOf("productHelp") !== -1) { 2335 mi = menu.createMenuItem("documentation", {text: ZmMsg.productHelp}); 2336 mi.addSelectionListener(helpListener); 2337 } 2338 2339 if (supportedHelps.indexOf("onlineHelp") !== -1) { 2340 mi = menu.createMenuItem("onlinehelp", {text: ZmMsg.onlineHelp}); 2341 mi.addSelectionListener(new AjxListener(this, this._onlineHelpListener)); 2342 } 2343 2344 2345 if (supportedHelps.indexOf("newFeatures") !== -1) { 2346 mi = menu.createMenuItem("newFeatures", {text: ZmMsg.newFeatures}); 2347 mi.addSelectionListener(new AjxListener(this, this._newFeaturesListener)); 2348 } 2349 2350 mi = menu.createMenuItem("showCurrentShortcuts", {text: ZmMsg.shortcuts}); 2351 mi.addSelectionListener(this._showCurrentShortcuts.bind(this)); 2352 2353 menu.createSeparator(); 2354 2355 mi = menu.createMenuItem(ZmZimbraMail.HELP_MENU_ABOUT, {text: ZmMsg.about}); 2356 mi.addSelectionListener(new AjxListener(this, this._aboutListener)); 2357 2358 menu.createSeparator(); 2359 2360 if (!appCtxt.isExternalAccount() && appCtxt.get(ZmSetting.WEBCLIENT_OFFLINE_ENABLED)) { 2361 mi = menu.createMenuItem("offlineSettings", {text: ZmMsg.offlineSettings}); 2362 mi.addSelectionListener(new AjxListener(this, this._offlineSettingsListener)); 2363 } 2364 2365 if (AjxEnv.isFirefox && (AjxEnv.browserVersion >= 23.0) && !appCtxt.isExternalAccount()) { 2366 mi = menu.createMenuItem("socialfoxSettings", {text: ZmMsg.socialfoxEnableSidebar}); 2367 mi.addSelectionListener(this._socialfoxSettingsListener.bind(this)); 2368 } 2369 2370 if (appCtxt.get(ZmSetting.CHANGE_PASSWORD_ENABLED)) { 2371 mi = menu.createMenuItem("changePassword", {text: ZmMsg.changePassword}); 2372 mi.addSelectionListener(new AjxListener(this, this._changePasswordListener)); 2373 } 2374 2375 mi = menu.createMenuItem(ZmZimbraMail.HELP_MENU_LOGOFF, {text: ZmMsg.logOff}); 2376 mi.addSelectionListener(new AjxListener(null, ZmZimbraMail.logOff)); 2377 2378 button.setMenu(menu); 2379 this.setupHelpMenu(button); 2380 return button; 2381 }; 2382 2383 ZmZimbraMail.HELP_MENU_ABOUT = "about"; 2384 ZmZimbraMail.HELP_MENU_LOGOFF = "logOff"; 2385 2386 2387 ZmZimbraMail.prototype.setupHelpMenu = function(button) { 2388 button = button || this._helpButton; 2389 if (!button) return; 2390 2391 var menu = button.getMenu(); 2392 if (!menu) return; 2393 2394 var isOnline = !appCtxt.isWebClientOffline(); 2395 if (isOnline) { 2396 menu.enableAll(true); 2397 } else { 2398 menu.enableAll(false); 2399 var offlineEnabledIds = [ZmZimbraMail.HELP_MENU_ABOUT]; 2400 menu.enable(offlineEnabledIds, true); 2401 } 2402 }; 2403 2404 ZmZimbraMail.prototype.getNewButton = 2405 function() { 2406 2407 var newButton = this._newButton; 2408 if (!newButton) { 2409 var buttonId = ZmId.getButtonId(null, ZmOperation.NEW_MENU); 2410 var buttonParams = { 2411 parent: appCtxt.getShell(), 2412 id: buttonId, 2413 posStyle: DwtControl.ABSOLUTE_STYLE, 2414 className: "ZToolbarButton ZNewButton" 2415 }; 2416 newButton = this._newButton = new DwtToolBarButton(buttonParams); 2417 newButton.setText(ZmMsg._new); 2418 2419 ZmOperation.addNewMenu(newButton); 2420 2421 var selectionListener = this._newButtonListener.bind(this); 2422 var listener = this._newDropDownListener.bind(this, selectionListener); 2423 this._newDropDownListener = listener; 2424 newButton.addSelectionListener(selectionListener); 2425 newButton.addDropDownSelectionListener(listener); 2426 } 2427 2428 return newButton; 2429 }; 2430 2431 2432 2433 /** 2434 * Creates the New menu's drop down menu the first time the drop down arrow is used, 2435 * then removes itself as a listener. 2436 * 2437 * @private 2438 */ 2439 ZmZimbraMail.prototype._newDropDownListener = 2440 function(selectionListener, event) { 2441 2442 var newButton = this.getNewButton(); 2443 var menu = newButton.getMenu(); 2444 var items = menu.getItems(); 2445 for (var i = 0; i < menu.getItemCount(); i++) { 2446 items[i].addSelectionListener(selectionListener); 2447 } 2448 2449 var listener = this._newDropDownListener; 2450 newButton.removeDropDownSelectionListener(listener); 2451 //Called explicitly as its a selection listener. Refer DwtButton._dropDownCellMouseDownHdlr() 2452 newButton.popup(); 2453 2454 delete this._newDropDownListener; 2455 }; 2456 2457 /** 2458 * Create some new thing, via a dialog. If just the button has been pressed (rather than 2459 * a menu item), the action taken depends on the app. 2460 * 2461 * @param {DwtUiEvent} ev the ui event 2462 * @param {constant} op the operation ID 2463 * @param {Boolean} newWin <code>true</code> if in a separate window 2464 * 2465 * @private 2466 */ 2467 ZmZimbraMail.prototype._newButtonListener = 2468 function(ev, op, params) { 2469 2470 if (!ev && !op) { return; } 2471 2472 op = op || ev.item.getData(ZmOperation.KEY_ID); 2473 if (!op || op == ZmOperation.NEW_MENU) { 2474 op = ZmController._defaultNewId; 2475 } 2476 2477 var app = ZmApp.OPS_R[op]; 2478 if (app) { 2479 params = params || {}; 2480 params.ev = ev; 2481 appCtxt.getApp(app).handleOp(op, params); 2482 } else { 2483 var ctlr = appCtxt.getCurrentController(); 2484 if (ctlr) { 2485 ctlr._newListener(ev, op); 2486 } 2487 } 2488 }; 2489 2490 /** 2491 * Set up the New button based on the current app. 2492 */ 2493 ZmZimbraMail.prototype.setNewButtonProps = 2494 function(params) { 2495 var newButton = this.getNewButton(); 2496 if (newButton) { 2497 newButton.setText(params.text); 2498 newButton.setToolTipContent(params.tooltip); 2499 newButton.setImage(params.icon); 2500 newButton.setEnabled(!params.disabled); 2501 ZmController._defaultNewId = params.defaultId; 2502 params.hidden ? newButton.setVisibility(false) : newButton.setVisibility(true); 2503 } 2504 }; 2505 2506 2507 /** 2508 * Adds a "help" submenu. 2509 * 2510 * @param {DwtComposite} parent the parent widget 2511 * @return {ZmActionMenu} the menu 2512 */ 2513 ZmZimbraMail.prototype.getHelpButton = 2514 function(parent) { 2515 2516 var button = new DwtLinkButton({parent: parent, className: DwtButton.LINK_BUTTON_CLASS}); 2517 button.dontStealFocus(); 2518 button.setSize(Dwt.DEFAULT); 2519 button.setAlign(DwtLabel.ALIGN_LEFT); 2520 button.setText(ZmMsg.help); 2521 var menu = new ZmPopupMenu(button); 2522 2523 var helpListener = new AjxListener(this, this._helpListener); 2524 button.addSelectionListener(helpListener); 2525 2526 var mi = menu.createMenuItem("documentation", {text: ZmMsg.productHelp}); 2527 mi.addSelectionListener(helpListener); 2528 2529 var mi = menu.createMenuItem("onlinehelp", {text: ZmMsg.onlineHelp}); 2530 mi.addSelectionListener(new AjxListener(this, this._onlineHelpListener)); 2531 2532 2533 mi = menu.createMenuItem("newFeatures", {text: ZmMsg.newFeatures}); 2534 mi.addSelectionListener(new AjxListener(this, this._newFeaturesListener)); 2535 2536 mi = menu.createMenuItem("showCurrentShortcuts", {text: ZmMsg.shortcuts}); 2537 mi.addSelectionListener(this._showCurrentShortcuts.bind(this)); 2538 2539 menu.createSeparator(); 2540 2541 mi = menu.createMenuItem("about", {text: ZmMsg.about}); 2542 mi.addSelectionListener(new AjxListener(this, this._aboutListener)); 2543 2544 button.setMenu(menu); 2545 return button; 2546 }; 2547 2548 ZmZimbraMail.prototype._helpListener = 2549 function(ev) { 2550 ZmZimbraMail.helpLinkCallback(); 2551 }; 2552 2553 2554 ZmZimbraMail.prototype._getVersion = 2555 function() { 2556 return appCtxt.get(ZmSetting.CLIENT_VERSION); 2557 }; 2558 2559 2560 ZmZimbraMail.prototype._getQueryParams = 2561 function() { 2562 2563 var appName = appCtxt.getCurrentAppName().toLowerCase(); 2564 var prod = appCtxt.isOffline ? "zd" : "zcs"; 2565 return ["utm_source=", appName, "&utm_medium=", prod, "&utm_content=", this._getVersion(), "&utm_campaign=help"].join(""); 2566 }; 2567 2568 2569 ZmZimbraMail.prototype._onlineHelpListener = 2570 function(ev) { 2571 ZmZimbraMail.unloadHackCallback(); 2572 var url = [ZmZimbraMail.ONLINE_HELP_URL, this._getQueryParams()].join(""); 2573 window.open(url); 2574 }; 2575 2576 ZmZimbraMail.prototype._newFeaturesListener = 2577 function(ev) { 2578 ZmZimbraMail.unloadHackCallback(); 2579 var url = [ZmZimbraMail.NEW_FEATURES_URL, this._getQueryParams()].join(""); 2580 window.open(url); 2581 }; 2582 2583 ZmZimbraMail.prototype._changePasswordListener = 2584 function(ev) { 2585 appCtxt.getChangePasswordWindow(ev); 2586 } 2587 2588 ZmZimbraMail.prototype._aboutListener = 2589 function(ev) { 2590 var dialog = appCtxt.getMsgDialog(); 2591 dialog.reset(); 2592 var version = this._getVersion(); 2593 var release = appCtxt.get(ZmSetting.CLIENT_RELEASE); 2594 var aboutMsg = appCtxt.isOffline ? ZmMsg.aboutMessageZD : ZmMsg.aboutMessage; 2595 dialog.setMessage(AjxMessageFormat.format(aboutMsg, [version, release, AjxDateUtil.getYearStr()]), DwtMessageDialog.INFO_STYLE, ZmMsg.about); 2596 dialog.popup(); 2597 2598 }; 2599 2600 ZmZimbraMail.prototype._offlineSettingsListener = 2601 function(ev) { 2602 var dialog; 2603 if (AjxEnv.isOfflineSupported) { 2604 dialog = appCtxt.getOfflineSettingsDialog(); 2605 } else { 2606 dialog = appCtxt.getMsgDialog(); 2607 dialog.setMessage(ZmMsg.offlineSupportedBrowser, "", ZmMsg.offlineSettings); 2608 } 2609 dialog.popup(); 2610 }; 2611 2612 ZmZimbraMail.prototype._socialfoxSettingsListener = 2613 function(ev) { 2614 var dialog = new ZmSocialfoxActivationDialog(); 2615 dialog.popup(); 2616 }; 2617 2618 2619 ZmZimbraMail.prototype._initOfflineUserInfo = 2620 function() { 2621 var htmlElId = this._userNameField.getHTMLElId(); 2622 this._userNameField.getHtmlElement().innerHTML = AjxTemplate.expand('share.App#NetworkStatus', {id:htmlElId}); 2623 this._userNameField.addClassName("BannerTextUserOffline"); 2624 2625 var params = { 2626 parent: this._userNameField, 2627 parentElement: (htmlElId+"_networkStatusIcon") 2628 }; 2629 this._networkStatusIcon = new DwtComposite(params); 2630 2631 var params1 = { 2632 parent: this._userNameField, 2633 parentElement: (htmlElId+"_networkStatusText") 2634 }; 2635 this._networkStatusText = new DwtComposite(params1); 2636 2637 var topTreeEl = document.getElementById("skin_container_tree_top"); 2638 if (topTreeEl) { 2639 Dwt.setSize(topTreeEl, Dwt.DEFAULT, "20"); 2640 } 2641 }; 2642 2643 ZmZimbraMail.prototype._offlineUpdateChannelPref = 2644 function(val) { 2645 try { 2646 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); 2647 var prefs = 2648 Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch); 2649 if (prefs) { 2650 prefs.setCharPref("app.update.channel", val); 2651 } 2652 } catch (ex) { 2653 DBG.println(AjxDebug.DBG1, "-----------Exception while setting update channel preference " + ex); 2654 } 2655 }; 2656 2657 /** 2658 * Sets the user info. 2659 * 2660 */ 2661 ZmZimbraMail.prototype.setUserInfo = function() { 2662 2663 if (appCtxt.isOffline) { 2664 return; 2665 } 2666 2667 // username 2668 var login = appCtxt.getLoggedInUsername(); 2669 var username = (appCtxt.get(ZmSetting.DISPLAY_NAME)) || login; 2670 if (username) { 2671 var el = this._userNameField.getHtmlElement(); 2672 el.innerHTML = AjxStringUtil.htmlEncode(AjxStringUtil.clipByLength(username, 24)); 2673 el.setAttribute('aria-label', username); 2674 if (AjxEnv.isLinux) { // bug fix #3355 2675 el.style.lineHeight = "13px"; 2676 } 2677 } 2678 2679 this.setQuotaInfo(login, username); 2680 }; 2681 2682 ZmZimbraMail.prototype.setQuotaInfo = function(login, username) { 2683 2684 var quota = appCtxt.get(ZmSetting.QUOTA); 2685 var usedQuota = (appCtxt.get(ZmSetting.QUOTA_USED)) || 0; 2686 var data = { 2687 id: this._usedQuotaField._htmlElId, 2688 login: login, 2689 username: username, 2690 quota: quota, 2691 usedQuota: usedQuota, 2692 size: (AjxUtil.formatSize(usedQuota, false, 1)) 2693 }; 2694 2695 var quotaTemplateId; 2696 if (data.quota) { 2697 quotaTemplateId = 'UsedLimited'; 2698 data.limit = AjxUtil.formatSize(data.quota, false, 1); 2699 data.percent = Math.min(Math.round((data.usedQuota / data.quota) * 100), 100); 2700 data.desc = AjxMessageFormat.format(ZmMsg.usingDescLimited, [data.size, '(' + data.percent + '%)', data.limit]); 2701 } 2702 else { 2703 data.desc = AjxMessageFormat.format(ZmMsg.usingDescUnlimited, [data.size]); 2704 quotaTemplateId = 'UsedUnlimited'; 2705 } 2706 2707 var el = this._usedQuotaField.getHtmlElement(); 2708 el.innerHTML = AjxTemplate.expand('share.Quota#' + quotaTemplateId, data); 2709 el.setAttribute('aria-label', data.desc); 2710 2711 // tooltip for username/quota fields 2712 var html = AjxTemplate.expand('share.Quota#Tooltip', data); 2713 this._components[ZmAppViewMgr.C_USER_INFO].setToolTipContent(html); 2714 this._components[ZmAppViewMgr.C_QUOTA_INFO].setToolTipContent(html); 2715 }; 2716 2717 /** 2718 * If a user has been prompted and elects to stay on page, this timer automatically logs them off after an interval of time. 2719 * @param startTimer {boolean} true to start timer, false to cancel 2720 */ 2721 ZmZimbraMail.setExitTimer = 2722 function(startTimer) { 2723 if (startTimer && ZmZimbraMail.stayOnPagePrompt) { 2724 DBG.println(AjxDebug.DBG1, "user has clicked stay on page. scheduled exit timer at " + new Date().toLocaleString()); 2725 if (ZmZimbraMail._exitTimerId == -1) { 2726 ZmZimbraMail._exitTimerId = AjxTimedAction.scheduleAction(ZmZimbraMail._exitTimer, ZmZimbraMail.STAYONPAGE_INTERVAL * 60 * 1000); //give user 2 minutes 2727 if (AjxEnv.isFirefox) { 2728 var msg = AjxMessageFormat.format(ZmMsg.appExitPrompt, [ZmZimbraMail.STAYONPAGE_INTERVAL]); 2729 var msgDialog = appCtxt.getMsgDialog(); 2730 msgDialog.setMessage(msg, DwtMessageDialog.CRITICAL_STYLE); //Firefox 4+ doesn't allow custom stay on page message. Prompt user they have X minutes 2731 //wait 2 seconds before popping up so FF doesn't show dialog when leave page is clicked 2732 setTimeout(function() { msgDialog.popup()}, 1000 * 2); 2733 } 2734 } 2735 2736 } 2737 else if (!startTimer && ZmZimbraMail._exitTimerId) { 2738 DBG.println(AjxDebug.DBG1, "canceling exit timer at " + new Date().toLocaleString()); 2739 AjxTimedAction.cancelAction(ZmZimbraMail._exitTimerId); 2740 ZmZimbraMail._exitTimerId = -1; 2741 } 2742 2743 }; 2744 2745 // Listeners 2746 2747 /** 2748 * Logs off the application. 2749 * 2750 */ 2751 ZmZimbraMail.logOff = 2752 function(ev, relogin) { 2753 if (appCtxt.isChildWindow) { 2754 window.close(); 2755 return; 2756 } 2757 if (appCtxt.isWebClientOfflineSupported && (ev || relogin)) { 2758 return ZmOffline.handleLogOff(ev, relogin); 2759 } 2760 2761 ZmZimbraMail._isLogOff = true; 2762 2763 // bug fix #36791 - reset the systray icon when returning to Account Setup 2764 if (appCtxt.isOffline && AjxEnv.isWindows && 2765 appCtxt.get(ZmSetting.OFFLINE_SUPPORTS_DOCK_UPDATE)) 2766 { 2767 window.platform.icon().imageSpec = "resource://webapp/icons/default/launcher.ico"; 2768 window.platform.icon().title = null; 2769 } 2770 var urlParams = { 2771 path:appContextPath, 2772 qsArgs: { 2773 loginOp: relogin ? 'relogin' : 'logout' 2774 } 2775 }; 2776 if (relogin) { 2777 urlParams.qsArgs.username = appCtxt.getLoggedInUsername(); 2778 } 2779 if(appCtxt.isExternalAccount()) { 2780 var vAcctDomain = appCtxt.getUserDomain(); 2781 urlParams.qsArgs.virtualacctdomain = vAcctDomain ? vAcctDomain : ""; 2782 } 2783 var url = AjxUtil.formatUrl(urlParams); 2784 ZmZimbraMail.sendRedirect(url); // will trigger onbeforeunload 2785 if (AjxEnv.isFirefox) { 2786 DBG.println(AjxDebug.DBG1, "calling setExitTimer from logoff " + new Date().toLocaleString()); 2787 ZmZimbraMail.setExitTimer(true); 2788 } 2789 2790 2791 }; 2792 2793 /** 2794 * Logs user off when session has expired and user has choosen to stay on page when prompted 2795 */ 2796 ZmZimbraMail.exitSession = 2797 function() { 2798 DBG.println(AjxDebug.DBG1, "exit timer called " + new Date().toLocaleString()); 2799 ZmZimbraMail.logOff(); 2800 }; 2801 2802 ZmZimbraMail.executeSessionTimer = 2803 function() { 2804 ZmZimbraMail.sessionTimerInvoked = true; 2805 DBG.println(AjxDebug.DBG1, "session timer invoked " + new Date().toLocaleString()); 2806 ZmZimbraMail.logOff(); 2807 }; 2808 2809 2810 /** 2811 * Return the confirmExitMethod that can be used for window.onbeforeunload 2812 * 2813 */ 2814 ZmZimbraMail.getConfirmExitMethod = 2815 function(){ 2816 return this._confirmExitMethod; 2817 } 2818 2819 2820 /** 2821 * @private 2822 */ 2823 ZmZimbraMail._onClickLogOff = 2824 function() { 2825 if (AjxEnv.isIE) { 2826 // Don't the the default <a> handler process the event. It can bring up 2827 // an unwanted "Are you sure you want to exit?" dialog. 2828 var ev = DwtUiEvent.getEvent(); 2829 ev.returnValue = false; 2830 } 2831 ZmZimbraMail.logOff(); 2832 }; 2833 2834 /** 2835 * @private 2836 */ 2837 ZmZimbraMail.adminLinkCallback = 2838 function(url) { 2839 ZmZimbraMail.unloadHackCallback(); 2840 var ac = window.parentAppCtxt || window.appCtxt; 2841 window.open(url); 2842 }; 2843 2844 /** 2845 * @private 2846 */ 2847 ZmZimbraMail.standardHtmlLinkCallback = 2848 function() { 2849 var urlParams = { 2850 path: appContextPath, 2851 qsArgs: { 2852 client: "standard" 2853 } 2854 }; 2855 var url = AjxUtil.formatUrl(urlParams); 2856 ZmZimbraMail.sendRedirect(url); // will trigger onbeforeunload 2857 }; 2858 2859 /** 2860 * @private 2861 */ 2862 ZmZimbraMail.helpLinkCallback = 2863 function(helpurl) { 2864 ZmZimbraMail.unloadHackCallback(); 2865 2866 var ac = window.parentAppCtxt || window.appCtxt; 2867 var url; 2868 if (!ac.isOffline) { 2869 try { url = helpurl || skin.hints.helpButton.url; } catch (e) { /* ignore */ } 2870 url = url || ac.get(ZmSetting.HELP_URI); 2871 var sep = url.match(/\?/) ? "&" : "?"; 2872 url = [url, sep, "locid=", AjxEnv.DEFAULT_LOCALE].join(""); 2873 } else { 2874 url = ac.get(ZmSetting.HELP_URI).replace(/\/$/,""); 2875 // bug fix #35098 - offline help is only available in en_US for now 2876 url = [url, "help", "en_US", "Zimbra_Mail_Help.htm"].join("/"); 2877 // url = [url, "help", AjxEnv.DEFAULT_LOCALE, "Zimbra_Mail_Help.htm"].join("/"); 2878 } 2879 window.open(url); 2880 }; 2881 2882 /** 2883 * Sends a redirect. 2884 * 2885 * @param {String} locationStr the redirect location 2886 */ 2887 ZmZimbraMail.sendRedirect = 2888 function(locationStr) { 2889 // not sure why IE doesn't allow this to process immediately, but since 2890 // it does not, we'll set up a timed action. 2891 if (AjxEnv.isIE) { 2892 var act = new AjxTimedAction(null, ZmZimbraMail.redir, [locationStr]); 2893 AjxTimedAction.scheduleAction(act, 1); 2894 } else { 2895 ZmZimbraMail.redir(locationStr); 2896 } 2897 }; 2898 2899 /** 2900 * Redirect. 2901 * 2902 * @param {String} locationStr the redirect location 2903 */ 2904 ZmZimbraMail.redir = 2905 function(locationStr){ 2906 // IE has a tendency to throw a mysterious error when the "are you sure" dialog pops up and the user presses "cancel". 2907 // Pressing cancel, however, equals doing nothing, so we can just catch the exception and ignore it (bug #59853) 2908 try { 2909 window.location = locationStr; 2910 } catch (e) { 2911 } 2912 }; 2913 2914 /** 2915 * Sets the session timer. 2916 * 2917 * @param {Boolean} bStartTimer if <code>true</code>, start the timer 2918 */ 2919 ZmZimbraMail.prototype.setSessionTimer = 2920 function(bStartTimer) { 2921 2922 // if no timeout value, user's client never times out from inactivity 2923 var timeout = appCtxt.get(ZmSetting.IDLE_SESSION_TIMEOUT) * 1000; 2924 if (timeout <= 0) { 2925 return; 2926 } 2927 2928 if (bStartTimer) { 2929 DBG.println(AjxDebug.DBG3, "INACTIVITY TIMER SET (" + (new Date()).toLocaleString() + ")"); 2930 this._sessionTimerId = AjxTimedAction.scheduleAction(this._sessionTimer, timeout); 2931 2932 DwtEventManager.addListener(DwtEvent.ONMOUSEUP, ZmZimbraMail._userEventHdlr); 2933 this._shell.setHandler(DwtEvent.ONMOUSEUP, ZmZimbraMail._userEventHdlr); 2934 if (document.attachEvent) { 2935 document.attachEvent("onkeydown", ZmZimbraMail._userEventHdlr); 2936 } 2937 window.onkeydown = ZmZimbraMail._userEventHdlr; 2938 } 2939 else { 2940 DBG.println(AjxDebug.DBG3, "INACTIVITY TIMER CANCELED (" + (new Date()).toLocaleString() + ")"); 2941 2942 AjxTimedAction.cancelAction(this._sessionTimerId); 2943 this._sessionTimerId = -1; 2944 2945 DwtEventManager.removeListener(DwtEvent.ONMOUSEUP, ZmZimbraMail._userEventHdlr); 2946 this._shell.clearHandler(DwtEvent.ONMOUSEUP); 2947 if (document.detachEvent) { 2948 document.detachEvent("onkeydown", ZmZimbraMail._userEventHdlr); 2949 } 2950 window.onkeydown = null; 2951 } 2952 }; 2953 2954 /** 2955 * Adds a child window. 2956 * 2957 * @private 2958 */ 2959 ZmZimbraMail.prototype.addChildWindow = 2960 function(childWin, childId) { 2961 if (this._childWinList == null) { 2962 this._childWinList = new AjxVector(); 2963 } 2964 2965 // NOTE: we now save childWin w/in Object so other params can be added to it. 2966 // Otherwise, Safari breaks (see http://bugs.webkit.org/show_bug.cgi?id=7162) 2967 var newWinObj = {win:childWin,childId:childId}; 2968 this._childWinList.add(newWinObj); 2969 2970 return newWinObj; 2971 }; 2972 2973 /** 2974 * Gets a child window. 2975 * 2976 * @private 2977 */ 2978 ZmZimbraMail.prototype.getChildWindow = 2979 function(childWin) { 2980 var list = this._childWinList; 2981 if (list && childWin) { 2982 for (var i = 0; i < list.size(); i++) { 2983 var winObj = list.get(i); 2984 if (childWin === winObj.win || childWin.childId === winObj.childId) { 2985 return winObj; 2986 } 2987 } 2988 } 2989 return null; 2990 }; 2991 2992 /** 2993 * Removes a child window. 2994 * 2995 * @private 2996 */ 2997 ZmZimbraMail.prototype.removeChildWindow = 2998 function(childWin) { 2999 var list = this._childWinList; 3000 if (list) { 3001 for (var i = 0; i < list.size(); i++) { 3002 var winObj = list.get(i); 3003 if (childWin == winObj.win) { 3004 // Bug 84426: We don't want our old window metadata to go away; if it's merely a refresh 3005 // we want access to the parameters of our old window, so clear the actual window object, 3006 // and leave the other parameters in winObj intact 3007 winObj.win = null; 3008 break; 3009 } 3010 } 3011 } 3012 }; 3013 3014 /** 3015 * Checks for a certain type of exception, then hands off to standard 3016 * exception handler. 3017 * 3018 * @param {AjxException} ex the exception 3019 * @param {Object} continuation the original request params 3020 * 3021 * @private 3022 */ 3023 ZmZimbraMail.prototype._handleException = 3024 function(ex, continuation) { 3025 var handled = false; 3026 if (ex.code == ZmCsfeException.MAIL_NO_SUCH_FOLDER) { 3027 // check for fault when getting folder perms 3028 var organizerTypes = [ZmOrganizer.CALENDAR, ZmOrganizer.ADDRBOOK]; 3029 if (ex.data.itemId && ex.data.itemId.length) { 3030 var itemId = ex.data.itemId[0]; 3031 var index = itemId.lastIndexOf(':'); 3032 var zid = itemId.substring(0, index); 3033 var rid = itemId.substring(index + 1, itemId.length); 3034 var ft = appCtxt.getFolderTree(); 3035 for (var type = 0; type < organizerTypes.length; type++) { 3036 handled |= ft.handleNoSuchFolderError(organizerTypes[type], zid, rid, true); 3037 } 3038 } 3039 } 3040 else if (appCtxt.isWebClientOffline() && ex.code === ZmCsfeException.EMPTY_RESPONSE) { 3041 handled = true; 3042 } 3043 if (!handled) { 3044 ZmController.prototype._handleException.apply(this, arguments); 3045 } 3046 }; 3047 3048 /** 3049 * This method is called by the window.onbeforeunload handler 3050 * 3051 * @private 3052 */ 3053 ZmZimbraMail._confirmExitMethod = 3054 function() { 3055 3056 if (!ZmCsfeCommand.noAuth) { 3057 appCtxt.accountList.saveImplicitPrefs(); 3058 3059 if (appCtxt.get(ZmSetting.WARN_ON_EXIT) && !ZmZimbraMail._isOkToExit()) { 3060 if (ZmZimbraMail.stayOnPagePrompt) { 3061 DBG.println(AjxDebug.DBG1, "user has already been prompted. Forcing exit " + new Date().toLocaleString()); 3062 return; 3063 } 3064 3065 ZmZimbraMail._isLogOff = false; 3066 DBG.println(AjxDebug.DBG1, "prompting to user to stay on page or leave " + new Date().toLocaleString()); 3067 var msg = (appCtxt.isOffline) ? ZmMsg.appExitWarningZD : ZmMsg.appExitWarning; 3068 3069 if (ZmZimbraMail.sessionTimerInvoked) { 3070 ZmZimbraMail.stayOnPagePrompt = true; 3071 msg = AjxMessageFormat.format(msg + ZmMsg.appExitTimeWarning, [ZmZimbraMail.STAYONPAGE_INTERVAL]); //append time warning 3072 } 3073 if (!AjxEnv.isFirefox) { 3074 DBG.println(AjxDebug.DBG1, "calling setExitTimer " + new Date().toLocaleString()); 3075 ZmZimbraMail.setExitTimer(true); 3076 } 3077 return msg; 3078 3079 } 3080 3081 ZmZimbraMail._endSession(); 3082 } 3083 if (window.ZmDesktopAlert) { 3084 ZmDesktopAlert.closeNotification(); 3085 } 3086 ZmZimbraMail._endSessionDone = true; 3087 }; 3088 3089 /** 3090 * Returns true if there is no unsaved work. If that's the case, it also 3091 * cancels any pending poll. Typically called by onbeforeunload handling. 3092 * 3093 * @private 3094 */ 3095 ZmZimbraMail._isOkToExit = 3096 function() { 3097 var appCtlr = window._zimbraMail; 3098 if (!appCtlr) { return true; } 3099 var okToExit = appCtlr._appViewMgr.isOkToUnload() && ZmZimbraMail._childWindowsOkToUnload(); 3100 if (okToExit && !AjxEnv.isPrism && appCtlr._pollRequest) { 3101 appCtlr._requestMgr.cancelRequest(appCtlr._pollRequest); 3102 } 3103 return okToExit; 3104 }; 3105 3106 // returns true if no child windows are dirty 3107 ZmZimbraMail._childWindowsOkToUnload = 3108 function() { 3109 var childWinList = window._zimbraMail ? window._zimbraMail._childWinList : null; 3110 if (childWinList) { 3111 for (var i = 0; i < childWinList.size(); i++) { 3112 var childWin = childWinList.get(i); 3113 if (childWin.win && childWin.win.ZmNewWindow && childWin.win.ZmNewWindow._confirmExitMethod()) { 3114 return false; 3115 } 3116 } 3117 } 3118 return true; 3119 }; 3120 3121 ZmZimbraMail.handleNetworkStatusClick = 3122 function() { 3123 var ac = window["appCtxt"].getAppController(); 3124 3125 // if already offline, then ignore this click 3126 if (!ac._isPrismOnline) { return; } 3127 3128 ac._isUserOnline = !ac._isUserOnline; 3129 ac._updateNetworkStatus(ac._isUserOnline); 3130 }; 3131 3132 /** 3133 * @private 3134 */ 3135 ZmZimbraMail.unloadHackCallback = 3136 function() { 3137 window.onbeforeunload = null; 3138 var f = function() { window.onbeforeunload = ZmZimbraMail._confirmExitMethod; }; 3139 AjxTimedAction.scheduleAction((new AjxTimedAction(null, f)), 3000); 3140 }; 3141 3142 /** 3143 * @private 3144 */ 3145 ZmZimbraMail._userEventHdlr = 3146 function(ev) { 3147 var zm = window._zimbraMail; 3148 if (zm) { 3149 // cancel old timer and start a new one 3150 AjxTimedAction.cancelAction(zm._sessionTimerId); 3151 var timeout = appCtxt.get(ZmSetting.IDLE_SESSION_TIMEOUT) * 1000; 3152 if (timeout <= 0) { 3153 return; 3154 } 3155 zm._sessionTimerId = AjxTimedAction.scheduleAction(zm._sessionTimer, timeout); 3156 } 3157 DBG.println(AjxDebug.DBG3, "INACTIVITY TIMER RESET (" + (new Date()).toLocaleString() + ")"); 3158 }; 3159 3160 /** 3161 * @private 3162 */ 3163 ZmZimbraMail.prototype._createBanner = 3164 function() { 3165 var banner = new DwtComposite({parent:this._shell, posStyle:Dwt.ABSOLUTE_STYLE, id:ZmId.BANNER}); 3166 var logoUrl = appCtxt.getSkinHint("banner", "url") || appCtxt.get(ZmSetting.LOGO_URI); 3167 var data = {url:logoUrl, isOffline:appCtxt.isOffline}; 3168 banner.getHtmlElement().innerHTML = AjxTemplate.expand('share.App#Banner', data); 3169 banner.getHtmlElement().style.height = '100%'; 3170 return banner; 3171 }; 3172 3173 /** 3174 * @private 3175 */ 3176 ZmZimbraMail.prototype._createUserInfo = 3177 function(className, cid, id) { 3178 3179 var position = appCtxt.getSkinHint(cid, "position"); 3180 var posStyle = position || Dwt.ABSOLUTE_STYLE; 3181 var ui = new DwtComposite({ 3182 parent: this._shell, 3183 className: className, 3184 posStyle: posStyle, 3185 id: id, 3186 isFocusable: true 3187 }); 3188 ui._setMouseEventHdlrs(); 3189 return ui; 3190 }; 3191 3192 /** 3193 * @private 3194 */ 3195 ZmZimbraMail.prototype._createAppChooser = 3196 function() { 3197 3198 var buttons = []; 3199 for (var id in ZmApp.CHOOSER_SORT) { 3200 if (id == ZmAppChooser.SPACER || id == ZmAppChooser.B_HELP || id == ZmAppChooser.B_LOGOUT) { 3201 continue; 3202 } 3203 3204 if (this._isInternalApp(id) || this._isIframeApp(id)) { 3205 buttons.push(id); 3206 } 3207 } 3208 buttons.sort(function(a, b) { 3209 return ZmZimbraMail.hashSortCompare(ZmApp.CHOOSER_SORT, a, b); 3210 }); 3211 3212 var appChooser = new ZmAppChooser({parent:this._shell, buttons:buttons, id:ZmId.APP_CHOOSER, refElementId:ZmId.SKIN_APP_CHOOSER}); 3213 3214 var buttonListener = new AjxListener(this, this._appButtonListener); 3215 appChooser.addSelectionListener(buttonListener); 3216 3217 return appChooser; 3218 }; 3219 3220 /** 3221 * @private 3222 */ 3223 ZmZimbraMail.prototype._appButtonListener = 3224 function(ev) { 3225 try { 3226 var id = ev.item.getData(Dwt.KEY_ID); 3227 DBG.println(AjxDebug.DBG1, "ZmZimbraMail button press: " + id); 3228 if (id == ZmAppChooser.B_HELP) { 3229 window.open(appCtxt.get(ZmSetting.HELP_URI)); 3230 } else if (id == ZmAppChooser.B_LOGOUT) { 3231 ZmZimbraMail.logOff(); 3232 } else if (id && ZmApp.ENABLED_APPS[id] && (id != this._activeTabId)) { 3233 this.activateApp(id); 3234 if (appCtxt.zimletsPresent()) { 3235 appCtxt.getZimletMgr().notifyZimlets("onSelectApp", id); 3236 } 3237 } else { 3238 var isCloseButton = (DwtUiEvent.getTargetWithProp(ev, "id") == ev.item._getIconEl(DwtLabel.RIGHT)); 3239 if (isCloseButton) { 3240 this._appViewMgr.popView(false, id); 3241 } 3242 else if (id != this._activeTabId) { 3243 this._appViewMgr.pushView(id); 3244 } 3245 } 3246 } catch (ex) { 3247 this._handleException(ex); 3248 } 3249 }; 3250 3251 /** 3252 * Gets the application chooser. 3253 * 3254 * @return {ZmAppChooser} the chooser 3255 */ 3256 ZmZimbraMail.prototype.getAppChooser = 3257 function() { 3258 return this._appChooser; 3259 }; 3260 3261 /** 3262 * Sets the active tab. 3263 * 3264 * @param {String} id the tab id 3265 */ 3266 ZmZimbraMail.prototype.setActiveTabId = 3267 function(id) { 3268 this._activeTabId = id; 3269 this._appChooser.setSelected(id); 3270 }; 3271 3272 /** 3273 * Displays a status message. 3274 * 3275 * @param {Hash} params a hash of parameters 3276 * @param {String} params.msg the message 3277 * @param {constant} [params.level] ZmStatusView.LEVEL_INFO, ZmStatusView.LEVEL_WARNING, or ZmStatusView.LEVEL_CRITICAL 3278 * @param {constant} [params.detail] the details 3279 * @param {constant} [params.transitions] the transitions 3280 * @param {constant} [params.toast] the toast control 3281 * @param {boolean} [force] force any displayed toasts out of the way (dismiss them and run their dismissCallback). Enqueued messages that are not yet displayed will not be displayed 3282 * @param {AjxCallback} [dismissCallback] callback to run when the toast is dismissed (by another message using [force], or explicitly calling ZmStatusView.prototype.dismiss()) 3283 * @param {AjxCallback} [finishCallback] callback to run when the toast finishes its transitions by itself (not when dismissed) 3284 */ 3285 ZmZimbraMail.prototype.setStatusMsg = 3286 function(params) { 3287 params = Dwt.getParams(arguments, ZmStatusView.MSG_PARAMS); 3288 this.statusView.setStatusMsg(params); 3289 }; 3290 3291 /** 3292 * Dismisses the displayed status message, if any 3293 */ 3294 3295 ZmZimbraMail.prototype.dismissStatusMsg = 3296 function(all) { 3297 this.statusView.dismissStatusMsg(all); 3298 }; 3299 3300 /** 3301 * Gets the key map name. 3302 * 3303 * @return {String} the key map name 3304 */ 3305 ZmZimbraMail.prototype.getKeyMapName = 3306 function() { 3307 var ctlr = appCtxt.getCurrentController(); 3308 if (ctlr && ctlr.getKeyMapName) { 3309 return ctlr.getKeyMapName(); 3310 } 3311 return ZmKeyMap.MAP_GLOBAL; 3312 }; 3313 3314 /** 3315 * Handles the key action. 3316 * 3317 * @param {constant} actionCode the action code 3318 * @param {Object} ev the event 3319 */ 3320 ZmZimbraMail.prototype.handleKeyAction = function(actionCode, ev) { 3321 3322 DwtMenu.closeActiveMenu(); 3323 3324 var app = ZmApp.GOTO_ACTION_CODE_R[actionCode]; 3325 if (app) { 3326 if (app == this.getActiveApp()) { 3327 return false; 3328 } 3329 if (appCtxt.isWebClientOffline() && !AjxUtil.arrayContains(ZmOffline.SUPPORTED_APPS, app)) { 3330 return false; 3331 } 3332 this.activateApp(app); 3333 return true; 3334 } 3335 3336 switch (actionCode) { 3337 3338 case ZmKeyMap.QUICK_REMINDER: { 3339 var account = appCtxt.multiAccounts && appCtxt.accountList.mainAccount; 3340 // calMgr.showQuickReminder uses an entire alternate search mechanism from ZmApptCache - setting params, 3341 // sending a search, etc. Suppress for offline - lots of work for little gain to adapt this to offline modek 3342 if (appCtxt.get(ZmSetting.CALENDAR_ENABLED, null, account) && !appCtxt.isWebClientOffline()) { 3343 var calMgr = appCtxt.getCalManager(); 3344 calMgr.showQuickReminder(); 3345 } 3346 break; 3347 } 3348 3349 case ZmKeyMap.FOCUS_SEARCH_BOX: { 3350 var stb = appCtxt.getSearchController().getSearchToolbar(); 3351 if (stb) { 3352 var searchBox = stb.getSearchField(); 3353 appCtxt.getKeyboardMgr().grabFocus(searchBox); 3354 if (ZmSearchAutocomplete) { 3355 ZmSearchAutocomplete._ignoreNextKey = true; 3356 } 3357 } 3358 break; 3359 } 3360 3361 case ZmKeyMap.FOCUS_CONTENT_PANE: { 3362 this.focusContentPane(); 3363 break; 3364 } 3365 3366 case ZmKeyMap.FOCUS_TOOLBAR: { 3367 this.focusToolbar(); 3368 break; 3369 } 3370 3371 case ZmKeyMap.SHORTCUTS: { 3372 this._showCurrentShortcuts(); 3373 break; 3374 } 3375 3376 // this action needs to be last 3377 case ZmKeyMap.CANCEL: { 3378 // see if there's a current drag operation we can cancel 3379 var handled = false; 3380 var captureObj = (DwtMouseEventCapture.getId() == "DwtControl") ? DwtMouseEventCapture.getCaptureObj() : null; 3381 var obj = captureObj && captureObj.targetObj; 3382 if (obj && (obj._dragging == DwtControl._DRAGGING)) { 3383 captureObj.release(); 3384 obj.__lastDestDwtObj = null; 3385 obj._setDragProxyState(false); // turn dnd icon red so user knows no drop is happening 3386 DwtControl.__badDrop(obj, DwtShell.mouseEvent); // shell's mouse ev should have latest info 3387 handled = true; 3388 } 3389 if (handled) { break; } 3390 } 3391 3392 default: { 3393 // Hand shortcut to current controller 3394 var ctlr = appCtxt.getCurrentController(); 3395 return ctlr && ctlr.handleKeyAction ? ctlr.handleKeyAction(actionCode, ev) : false; 3396 } 3397 } 3398 3399 return true; 3400 }; 3401 3402 /** 3403 * Focuses on the content pane. 3404 * 3405 */ 3406 ZmZimbraMail.prototype.focusContentPane = 3407 function() { 3408 // Set focus to the list view that's in the content pane. If there is no 3409 // list view in the content pane, nothing happens. The list view will be 3410 // found in the root tab group hierarchy. 3411 var ctlr = appCtxt.getCurrentController(); 3412 var content = ctlr && ctlr._getDefaultFocusItem(); 3413 if (content) { 3414 appCtxt.getKeyboardMgr().grabFocus(content); 3415 } 3416 }; 3417 3418 /** 3419 * Focuses on the toolbar. 3420 * 3421 */ 3422 ZmZimbraMail.prototype.focusToolbar = 3423 function() { 3424 // Set focus to the toolbar that's in the content pane. 3425 var ctlr = appCtxt.getCurrentController(); 3426 var toolbar = ctlr && ctlr.getCurrentToolbar && ctlr.getCurrentToolbar(); 3427 if (toolbar) { 3428 appCtxt.getKeyboardMgr().grabFocus(toolbar); 3429 } 3430 }; 3431 3432 /** 3433 * Creates an "iframe view", which is a placeholder view for an app that's not 3434 * enabled but which has a tab. The app will have 3435 * a URL for its external content, which we put into an IFRAME. 3436 * 3437 * @param {constant} appName the name of app 3438 * 3439 * @private 3440 */ 3441 ZmZimbraMail.prototype._createAppIframeView = 3442 function(appName) { 3443 3444 var viewName = [appName, "iframe"].join("_"), 3445 isSocial = (appName === ZmApp.SOCIAL), 3446 params = { appName: appName }, 3447 appIframeView = this._appIframeView[appName]; 3448 3449 if (!appIframeView) { 3450 appIframeView = this._appIframeView[appName] = isSocial ? new ZmCommunityView(params) : new ZmAppIframeView(params); 3451 3452 var elements = {}, callbacks = {}; 3453 elements[ZmAppViewMgr.C_APP_CONTENT] = appIframeView; 3454 callbacks[ZmAppViewMgr.CB_POST_SHOW] = this._displayAppIframeView.bind(this); 3455 3456 var hide = [ ZmAppViewMgr.C_TREE, ZmAppViewMgr.C_TREE_FOOTER, ZmAppViewMgr.C_TOOLBAR_TOP, 3457 ZmAppViewMgr.C_NEW_BUTTON, ZmAppViewMgr.C_SASH ]; 3458 3459 this._appViewMgr.createView({ viewId: viewName, 3460 appName: appName, 3461 controller: this, 3462 elements: elements, 3463 hide: hide, 3464 isTransient: true, 3465 isFullScreen: true, 3466 callbacks: callbacks}); 3467 } 3468 3469 this._appViewMgr.pushView(viewName); 3470 appIframeView.activate(true); 3471 }; 3472 3473 ZmZimbraMail.prototype._displayAppIframeView = 3474 function(appName) { 3475 appCtxt.getApp(this._getDefaultStartAppName()).setOverviewPanelContent(false); 3476 }; 3477 3478 /** 3479 * Sets up Zimlet organizer type. This is run if we get zimlets in the 3480 * GetInfoResponse. Note that this will run before apps are instantiated, 3481 * which is necessary because they depend on knowing whether there are zimlets. 3482 * 3483 * @private 3484 */ 3485 ZmZimbraMail.prototype._postLoadZimlet = 3486 function() { 3487 appCtxt.setZimletsPresent(true); 3488 }; 3489 3490 /** 3491 * @private 3492 */ 3493 ZmZimbraMail.prototype._globalSelectionListener = 3494 function(ev) { 3495 // bug 47514 3496 if (this._waitDisallowed) { 3497 this._waitDisallowed = false; 3498 this.setInstantNotify(true); 3499 } 3500 3501 if (!appCtxt.areZimletsLoaded()) { return; } 3502 3503 var item = ev.item; 3504 3505 // normalize action 3506 var text = (item && item.getText) ? (item.getText() || item._toggleText) : null; 3507 if (item && !text) { 3508 text = item.getData(ZmOperation.KEY_ID) || item.getData(Dwt.KEY_ID); 3509 } 3510 if (text) { 3511 var type; 3512 if (item instanceof ZmAppButton) { 3513 type = "app"; 3514 } else if (item instanceof DwtMenuItem) { 3515 type = "menuitem"; 3516 } else if (item instanceof DwtButton) { 3517 type = "button"; 3518 } else if (item instanceof DwtTreeItem) { 3519 if (!item.getSelected()) { return; } 3520 type = "treeitem"; 3521 } else { 3522 type = item.toString(); 3523 } 3524 3525 var avm = appCtxt.getAppViewMgr(); 3526 var currentViewId = avm.getCurrentViewId(); 3527 var lastViewId = avm.getLastViewId(); 3528 var action = (AjxStringUtil.split((""+text), " ")).join(""); 3529 appCtxt.notifyZimlets("onAction", [type, action, currentViewId, lastViewId]); 3530 } 3531 }; 3532 3533 ZmZimbraMail._folderTreeSashRelease = 3534 function(sash) { 3535 var currentWidth = skin.getTreeWidth(); 3536 if (currentWidth) { 3537 appCtxt.set(ZmSetting.FOLDER_TREE_SASH_WIDTH, currentWidth); 3538 } 3539 }; 3540 3541 /** 3542 * @private 3543 */ 3544 ZmZimbraMail._endSession = 3545 function() { 3546 if (!AjxEnv.isPrism && navigator.onLine) { 3547 // Let the server know that the session is ending. 3548 var args = { 3549 jsonObj: { EndSessionRequest: { _jsns: "urn:zimbraAccount" } }, 3550 asyncMode: !appCtxt.get("FORCE_CLEAR_COOKIES"), 3551 emptyResponseOkay: true 3552 }; 3553 var controller = appCtxt.getAppController(); 3554 controller && controller.sendRequest(args); 3555 } 3556 }; 3557 3558 ZmZimbraMail.prototype.notify = 3559 function(eventType) { 3560 this._evtMgr.notifyListeners(eventType, this._evt); 3561 }; 3562 3563 ZmZimbraMail.prototype._showCurrentShortcuts = function() { 3564 3565 var panel = appCtxt.getShortcutsPanel(); 3566 var curMap = this.getKeyMapName(); 3567 var km = appCtxt.getAppController().getKeyMapMgr(); 3568 var maps = km.getAncestors(curMap); 3569 var inherits = (maps && maps.length > 0); 3570 maps.unshift(curMap); 3571 var maps2 = []; 3572 if (inherits) { 3573 if (maps.length > 1 && maps[maps.length - 1] == ZmKeyMap.MAP_GLOBAL) { 3574 maps.pop(); 3575 maps2.push(ZmKeyMap.MAP_GLOBAL); 3576 } 3577 } 3578 3579 var col1 = {}, col2 = {}; 3580 col1.type = ZmShortcutList.TYPE_APP; 3581 col1.maps = maps; 3582 var colList = [col1]; 3583 if (maps2.length) { 3584 col2.type = ZmShortcutList.TYPE_APP; 3585 col2.maps = maps2; 3586 colList.push(col2); 3587 } 3588 var col3 = {}; 3589 col3.type = ZmShortcutList.TYPE_SYS; 3590 col3.maps = []; 3591 var ctlr = appCtxt.getCurrentController(); 3592 var testMaps = ["list", "editor", "tabView"]; 3593 for (var i = 0; i < testMaps.length; i++) { 3594 if (ctlr && ctlr.mapSupported(testMaps[i])) { 3595 col3.maps.push(testMaps[i]); 3596 } 3597 } 3598 col3.maps.push("button", "menu", "tree", "dialog", "toolbarHorizontal"); 3599 colList.push(col3); 3600 panel.popup(colList); 3601 } 3602 3603 // YUCK: 3604 ZmOrganizer.ZIMLET = "ZIMLET"; 3605