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  * Creates an empty message controller.
 26  * @constructor
 27  * @class
 28  * This class controls the display and management of a single message in the content area. Since it
 29  * needs to handle pretty much the same operations as a list, it extends ZmMailListController.
 30  *
 31  * @author Parag Shah
 32  * @author Conrad Damon
 33  * 
 34  * @param {DwtControl}	container		the containing shell
 35  * @param {constant}	type			type of controller
 36  * @param {ZmApp}		mailApp			the containing application
 37  * @param {string}		sessionId		the session id
 38  * 
 39  * @extends		ZmMailListController
 40  */
 41 ZmMsgController = function(container, mailApp, type, sessionId) {
 42 
 43     if (arguments.length == 0) { return; }
 44 	ZmMailListController.apply(this, arguments);
 45 	this._elementsToHide = ZmAppViewMgr.LEFT_NAV;
 46 };
 47 
 48 ZmMsgController.prototype = new ZmMailListController;
 49 ZmMsgController.prototype.constructor = ZmMsgController;
 50 
 51 ZmMsgController.MODE_TO_CONTROLLER = {};
 52 ZmMsgController.MODE_TO_CONTROLLER[ZmId.VIEW_TRAD]		= "GetTradController";
 53 ZmMsgController.MODE_TO_CONTROLLER[ZmId.VIEW_CONV]		= "GetConvController";
 54 ZmMsgController.MODE_TO_CONTROLLER[ZmId.VIEW_CONVLIST]	= "GetConvListController";
 55 
 56 ZmMsgController.DEFAULT_TAB_TEXT = ZmMsg.message;
 57 
 58 ZmMsgController.viewToTab = {};
 59 
 60 ZmMsgController.prototype.isZmMsgController = true;
 61 ZmMsgController.prototype.toString = function() { return "ZmMsgController"; };
 62 
 63 // Public methods
 64 
 65 ZmMsgController.getDefaultViewType =
 66 function() {
 67 	return ZmId.VIEW_MSG;
 68 };
 69 ZmMsgController.prototype.getDefaultViewType = ZmMsgController.getDefaultViewType;
 70 
 71 /**
 72  * Displays a message in the single-pane view.
 73  *
 74  * @param {ZmMailMsg}			msg					the message to display
 75  * @param {ZmListController}	parentController	the controller that called this method
 76  * @param {AjxCallback}			callback			the client callback
 77  * @param {Boolean}				markRead			if <code>true</code>, mark msg read
 78  * @param {Boolean}				hidePagination		if <code>true</code>, hide the pagination buttons
 79  */
 80 ZmMsgController.prototype.show = 
 81 function(msg, parentController, callback, markRead, hidePagination, forceLoad, noTruncate) {
 82 	this.setMsg(msg);
 83 	this._parentController = parentController;
 84 	//if(msg.list) {
 85         this.setList(msg.list);
 86     //}
 87 	if (!msg._loaded || forceLoad) {
 88 		var respCallback = new AjxCallback(this, this._handleResponseShow, [callback, hidePagination]);
 89 		if (msg._loadPending) {
 90 			// override any local callback if we're being launched by double-pane view,
 91 			// so that multiple GetMsgRequest's aren't made
 92 			msg._loadCallback = respCallback;
 93 		} else {
 94 			markRead = markRead || (appCtxt.get(ZmSetting.MARK_MSG_READ) == ZmSetting.MARK_READ_NOW);
 95 			msg.load({callback:respCallback, markRead:markRead, forceLoad:forceLoad, noTruncate:noTruncate});
 96 		}
 97 	} else {
 98 		// May have been explicitly marked as unread
 99 		var marked = false;
100 		if (!msg.isReadOnly() && msg.isUnread && (appCtxt.get(ZmSetting.MARK_MSG_READ) != ZmSetting.MARK_READ_NONE)) {
101 			if (msg.list) {
102 				// Need to mark it on the server
103 				marked = true;
104 				var markCallback =  this._handleResponseShow.bind(this, callback, hidePagination);
105 				msg.list.markRead({items: msg, value: true, callback: markCallback, noBusyOverlay: true});
106 			}  else {
107 				msg.markRead();
108 			}
109 		}
110 		if (!marked) {
111 			this._handleResponseShow(callback, hidePagination);
112 		}
113 	}
114 };
115 
116 ZmMsgController.prototype._handleResponseShow = 
117 function(callback, hidePagination, result) {
118 	this._showMsg();
119 	this._showNavToolBarButtons(this._currentViewId, !hidePagination);
120 	if (callback && callback.run) {
121 		callback.run(this, this._view[this._currentViewId]);
122 	}
123 };
124 
125 
126 /**
127  * can't repro bug 77538 - but since the exception happens in ZmListController.prototype._setupContinuation if lastItem is not set, let's set it here to be on the safe side.
128  */
129 ZmMsgController.prototype._setupContinuation =
130 function() {
131 	this._continuation.lastItem = true; //just a dummy value.  I could use this._msg but afraid that in the case of the bug (77538) - that I can't repro - this._msg might be empty.
132 	this._continuation.totalItems = 1;
133 	ZmListController.prototype._setupContinuation.apply(this, arguments);
134 };
135 
136 
137 /**
138  * Called by ZmNewWindow.unload to remove tag list listener (which resides in 
139  * the parent window). Otherwise, after the child window is closed, the parent 
140  * window is still referencing the child window's msg controller, which has
141  * been unloaded!!
142  * 
143  * @private
144  */
145 ZmMsgController.prototype.dispose = 
146 function() {
147 	this._tagList.removeChangeListener(this._tagChangeListener);
148 };
149 
150 ZmMsgController.prototype._showMsg = 
151 function() {
152 	this._showMailItem();
153 };
154 
155 ZmMsgController.prototype._getTabParams =
156 function(tabId, tabCallback) {
157 	return {
158 		id:				tabId,
159 		textPrecedence:	85,
160         image:          "CloseGray",
161         hoverImage:     "Close",
162         style:          DwtLabel.IMAGE_RIGHT,
163 		tooltip:		ZmMsgController.DEFAULT_TAB_TEXT,
164 		tabCallback:	tabCallback
165 	};
166 };
167 
168 ZmMsgController.prototype.getKeyMapName =
169 function() {
170 	return ZmKeyMap.MAP_MESSAGE;
171 };
172 
173 ZmMsgController.prototype.handleKeyAction =
174 function(actionCode) {
175 	DBG.println(AjxDebug.DBG3, "ZmMsgController.handleKeyAction");
176 	
177 	switch (actionCode) {
178 		case ZmKeyMap.CANCEL:
179 			this._backListener();
180 			break;
181 
182 		case ZmKeyMap.NEXT_PAGE:
183 			this._goToMsg(this._currentViewId, true);
184 			break;
185 
186 		case ZmKeyMap.PREV_PAGE:
187 			this._goToMsg(this._currentViewId, false);
188 			break;
189 
190 		// switching view not supported here
191 		case ZmKeyMap.VIEW_BY_CONV:
192 		case ZmKeyMap.VIEW_BY_MSG:
193 			break;
194 		
195 		default:
196 			if (ZmMsgController.ALLOWED_SHORTCUT[actionCode]) {
197 				return ZmMailListController.prototype.handleKeyAction.call(this, actionCode);
198 			}
199 	}
200 	return true;
201 };
202 
203 ZmMsgController.prototype.mapSupported =
204 function(map) {
205 	return false;
206 };
207 
208 // Private methods (mostly overrides of ZmListController protected methods)
209 
210 ZmMsgController.prototype._getToolBarOps = 
211 function() {
212 	var list = [ZmOperation.CLOSE, ZmOperation.SEP];
213 	list = list.concat(ZmMailListController.prototype._getToolBarOps.call(this));
214 	return list;
215 };
216 
217 ZmMsgController.prototype._getRightSideToolBarOps =
218 function() {
219 	if (appCtxt.isChildWindow || !appCtxt.get(ZmSetting.DETACH_MAILVIEW_ENABLED) || appCtxt.isExternalAccount()) {
220 		return [];
221 	}
222 	return [ZmOperation.DETACH];
223 };
224 
225 
226 ZmMsgController.prototype._showDetachInSecondary =
227 function() {
228 	return false;
229 };
230 
231 ZmMsgController.prototype._initializeToolBar =
232 function(view) {
233 	var className = appCtxt.isChildWindow ? "ZmMsgViewToolBar_cw" : null;
234 
235 	ZmMailListController.prototype._initializeToolBar.call(this, view, className);
236 };
237 
238 ZmMsgController.prototype._navBarListener =
239 function(ev) {
240 	var op = ev.item.getData(ZmOperation.KEY_ID);
241 	if (op == ZmOperation.PAGE_BACK || op == ZmOperation.PAGE_FORWARD) {
242 		this._goToMsg(this._currentViewId, (op == ZmOperation.PAGE_FORWARD));
243 	}
244 };
245 
246 // message view has no view menu button
247 ZmMsgController.prototype._setupViewMenu = function(view, firstTime) {};
248 
249 ZmMsgController.prototype._getActionMenuOps =
250 function() {
251 	return null;
252 };
253 
254 ZmMsgController.prototype._initializeView =
255 function(view) {
256 	if (!this._view[view]) {
257 		var params = {
258 			parent:		this._container,
259 			id:			ZmId.getViewId(ZmId.VIEW_MSG, null, view),
260 			posStyle:	Dwt.ABSOLUTE_STYLE,
261 			mode:		ZmId.VIEW_MSG,
262 			controller:	this
263 		};
264 		this._view[view] = new ZmMailMsgView(params);
265 		this._view[view].addInviteReplyListener(this._inviteReplyListener);
266 		this._view[view].addShareListener(this._shareListener);
267 		this._view[view].addSubscribeListener(this._subscribeListener);
268 	}
269 };
270 
271 ZmMsgController.prototype._initializeTabGroup =
272 function(view) {
273 	if (this._tabGroups[view]) { return; }
274 
275 	ZmMailListController.prototype._initializeTabGroup.apply(this, arguments);
276 
277 	this._tabGroups[view].removeMember(this._view[view]);
278 };
279 
280 ZmMsgController.prototype._getSearchFolderId =
281 function() {
282 	return this._msg.folderId ? this._msg.folderId : (this._msg.list && this._msg.list.search) ?
283 		this._msg.list.search.folderId : null;
284 };
285 
286 ZmMsgController.prototype._getTagMenuMsg =
287 function() {
288 	return ZmMsg.tagMessage;
289 };
290 
291 ZmMsgController.prototype._getMoveDialogTitle =
292 function() {
293 	return ZmMsg.moveMessage;
294 };
295 
296 ZmMsgController.prototype._setViewContents =
297 function(view) {
298 	this._view[view].set(this._msg);
299 };
300 
301 ZmMsgController.prototype._resetNavToolBarButtons =
302 function(view) {
303 	view = view || this.getCurrentViewId();
304 	if (!this._navToolBar[view]) { return; }
305 	// NOTE: we purposely do not call base class here!
306 	if (!appCtxt.isChildWindow) {
307 		var list = this._msg.list && this._msg.list.getVector();
308 
309 		this._navToolBar[view].enable(ZmOperation.PAGE_BACK, (list && (list.get(0) != this._msg)));
310 
311 		var bEnableForw = list && (this._msg.list.hasMore() || (list.getLast() != this._msg));
312 		this._navToolBar[view].enable(ZmOperation.PAGE_FORWARD, bEnableForw);
313 
314 		this._navToolBar[view].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousMessage);
315 		this._navToolBar[view].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextMessage);
316 	}
317 };
318 
319 ZmMsgController.prototype._showNavToolBarButtons =
320 function(view, show) {
321 	var toolbar = this._navToolBar[view];
322 	if (!toolbar) { return; }
323 	if (!appCtxt.isChildWindow) {
324 		toolbar.getButton(ZmOperation.PAGE_BACK).setVisible(show);
325 		toolbar.getButton(ZmOperation.PAGE_FORWARD).setVisible(show);
326 	}
327 };
328 
329 ZmMsgController.prototype._goToMsg =
330 function(view, next) {
331 	var controller = this._parentController;
332 	if (controller && controller.pageItemSilently) {
333 		controller.pageItemSilently(this._msg, next, this);
334 	}
335 };
336 
337 ZmMsgController.prototype._selectNextItemInParentListView =
338 function() {
339 	var controller = this._parentController;
340 	if (controller && controller._getNextItemToSelect) {
341 		controller._view[controller._currentViewId]._itemToSelect = controller._getNextItemToSelect();
342 	}
343 };
344 
345 ZmMsgController.prototype._doDelete =
346 function() {
347 	this._selectNextItemInParentListView();
348 	ZmMailListController.prototype._doDelete.apply(this, arguments);
349 };
350 
351 ZmMsgController.prototype._doMove =
352 function() {
353 	this._selectNextItemInParentListView();
354 	ZmMailListController.prototype._doMove.apply(this, arguments);
355 };
356 
357 ZmMsgController.prototype._doSpam =
358 function() {
359 	this._selectNextItemInParentListView();
360 	ZmMailListController.prototype._doSpam.apply(this, arguments);
361 };
362 
363 ZmMsgController.prototype._menuPopdownActionListener =
364 function(ev) {
365 	// dont do anything since msg view has no action menus
366 };
367 
368 // Miscellaneous
369 
370 ZmMsgController.prototype.getMsg =
371 function(params) {
372 	return this._msg;
373 };
374 
375 ZmMsgController.prototype.getItems =
376 function() {
377 	return [this._msg];
378 };
379 
380 ZmMsgController.prototype._getLoadedMsg =
381 function(params, callback) {
382 	callback.run(this._msg);
383 };
384 
385 ZmMsgController.prototype._getSelectedMsg =
386 function() {
387 	return this._msg;
388 };
389 
390 ZmMsgController.prototype.setMsg = function (msg) {
391 	this._msg = msg;
392     msg.refCount++
393 };
394 
395 ZmMsgController.prototype.getItemView = function() {
396 	return this._view[this._currentViewId];
397 };
398 
399 // No-op replenishment
400 ZmMsgController.prototype._checkReplenish =
401 function(params) {
402 	// XXX: remove this when replenishment is fixed for msg controller!
403 	DBG.println(AjxDebug.DBG1, "SORRY. NO REPLENISHMENT FOR YOU.");
404 };
405 
406 ZmMsgController.prototype._checkItemCount =
407 function() {
408 	if (!appCtxt.isChildWindow) {
409 		this._backListener();
410 	}
411 };
412 
413 ZmMsgController.prototype._getDefaultFocusItem = 
414 function() {
415 	return this._toolbar[this._currentViewId];
416 };
417 
418 ZmMsgController.prototype._backListener =
419 function(ev) {
420 	// bug fix #30835 - prism triggers this listener twice for some reason :/
421 	if (appCtxt.isOffline && (this._currentViewId != appCtxt.getCurrentViewId())) {
422 		return;
423 	}
424 	var isChildWindow = appCtxt.isChildWindow;
425 	if (!this._app.popView() && !isChildWindow) {
426 		this._app.mailSearch();
427 	}
428 };
429 
430 ZmMsgController.prototype.isTransient =
431 function(oldView, newView) {
432 	return (appCtxt.getViewTypeFromId(newView) != ZmId.VIEW_COMPOSE);
433 };
434 
435 ZmMsgController.prototype._tabCallback =
436 function(oldView, newView) {
437 	return (appCtxt.getViewTypeFromId(oldView) == ZmId.VIEW_MSG);
438 };
439 
440 ZmMsgController.prototype._printListener =
441 function(ev) {
442     var ids = [];
443     var item = this._msg;
444     var id;
445     var showImages;
446     // always extract out the msg ids from the conv
447     if (item.toString() == "ZmConv") {
448         // get msg ID in case of virtual conv.
449         // item.msgIds.length is inconsistent, so checking if conv id is negative.
450         if (appCtxt.isOffline && item.id.split(":")[1]<0) {
451             id = item.msgIds[0];
452         } else {
453             id = "C:" + item.id;
454         }
455         var msgList = item.getMsgList();
456         for(var j=0; j<msgList.length; j++) {
457             if(msgList[j].showImages) {
458                 showImages = true;
459                 break;
460             }
461         }
462     } else {
463         id = item.id;
464         // Fix for bug: 84261, bug: 85363. partId is present if original message is present as an attachment.
465         var part = item.partId;
466         if (part) {
467             id += "&part=" + part;
468         }
469 
470         if (item.showImages) {
471             showImages = true;
472         }
473     }
474     var url = "/h/printmessage?id=" + id + "&tz=" + AjxTimezone.getServerId(AjxTimezone.DEFAULT);
475     if (appCtxt.get(ZmSetting.DISPLAY_EXTERNAL_IMAGES) || showImages) {
476         url += "&xim=1";
477     }
478     if (appCtxt.isOffline) {
479         var acctName = item.getAccount().name;
480         url+="&acct=" + acctName ;
481     }
482     window.open(appContextPath+url, "_blank");
483 };
484 
485 ZmMsgController.prototype._subscribeResponseHandler =
486 function(statusMsg, ev) {
487     ZmMailListController.prototype._subscribeResponseHandler.call(this, statusMsg, ev);
488     //Close View
489     appCtxt.getAppViewMgr().popView();
490 };
491 
492 ZmMsgController.prototype._acceptShareHandler =
493 function(ev) {
494     ZmMailListController.prototype._acceptShareHandler.call(this, ev);
495     //Close View
496     appCtxt.getAppViewMgr().popView();
497 };
498 
499 ZmMsgController.prototype._setStatics = function() {
500 
501 	if (!ZmMsgController.ALLOWED_SHORTCUT) {
502 		ZmMsgController.ALLOWED_SHORTCUT = AjxUtil.arrayAsHash([
503 			ZmKeyMap.FORWARD,
504 			ZmKeyMap.MOVE,
505 			ZmKeyMap.PRINT,
506 			ZmKeyMap.TAG,
507 			ZmKeyMap.UNTAG,
508 			ZmKeyMap.REPLY,
509 			ZmKeyMap.REPLY_ALL,
510 			ZmKeyMap.SPAM,
511 			ZmKeyMap.MARK_READ,
512 			ZmKeyMap.MARK_UNREAD,
513 			ZmKeyMap.FLAG
514 		]);
515 	}
516 
517 	ZmMailListController.prototype._setStatics();
518 };
519 
520 ZmMsgController.prototype._postRemoveCallback = function() {
521     this._msg.refCount--;
522 };
523