1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 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 an invite mail message singleton class.
 27  *
 28  */
 29 
 30 
 31 /**
 32  * Default constructor for invite mail message class.
 33  * @class
 34  * When a user receives an invite, this class is instatiated to help
 35  * ZmMailMsgView deal with invite-specific rendering/logic.
 36  *
 37  * @author Parag Shah
 38  */
 39 ZmInviteMsgView = function(params) {
 40 	if (arguments.length == 0) { return; }
 41 
 42 	this.parent = params.parent; // back reference to ZmMailMsgView
 43 	this.mode = params.mode;
 44 };
 45 
 46 // Consts
 47 ZmInviteMsgView.REPLY_INVITE_EVENT	= "inviteReply";
 48 
 49 
 50 ZmInviteMsgView.prototype.toString =
 51 function() {
 52 	return "ZmInviteMsgView";
 53 };
 54 
 55 ZmInviteMsgView.prototype.reset =
 56 function(cleanupHTML) {
 57 	if (this._inviteToolbar) {
 58 		if (cleanupHTML) {
 59 			this._inviteToolbar.dispose();
 60 			this._inviteToolbar = null;
 61 		} else {
 62 			this._inviteToolbar.setDisplay(Dwt.DISPLAY_NONE);
 63 		}
 64 	}
 65 
 66 	if (this._counterToolbar) {
 67 		if (cleanupHTML) {
 68 			this._counterToolbar.dispose();
 69 			this._counterToolbar = null;
 70 		} else {
 71 			this._counterToolbar.setDisplay(Dwt.DISPLAY_NONE);
 72 		}
 73 	}
 74 
 75 	if (this._dayView) {
 76 		if (cleanupHTML) {
 77 			this._dayView.dispose();
 78 			this._dayView = null;
 79 		} else {
 80 			this._dayView.setDisplay(Dwt.DISPLAY_NONE);
 81 		}
 82 		Dwt.delClass(this.parent.getHtmlElement(), "RightBorderSeparator");
 83 	}
 84 
 85 	this._msg = null;
 86 	this._invite = null;
 87 };
 88 
 89 ZmInviteMsgView.prototype.isActive =
 90 function() {
 91 	return ((this._invite && !this._invite.isEmpty()) ||
 92 			(this._inviteToolbar && this._inviteToolbar.getVisible()) ||
 93 			(this._counterToolbar && this._counterToolbar.getVisible()));
 94 };
 95 
 96 ZmInviteMsgView.prototype.set =
 97 function(msg) {
 98 
 99 	this._msg = msg;
100 	var invite = this._invite = msg.invite;
101 
102 	this.parent._lazyCreateObjectManager();
103 
104     // Can operate the toolbar if user is the invite recipient, or invite is in a
105     // non-trash shared folder with admin/workflow access permissions
106     var folder   =  appCtxt.getById(msg.folderId);
107     var enabled  = !appCtxt.isExternalAccount();
108     if (enabled && folder && folder.isRemote()) {
109         var workflow = folder.isPermAllowed(ZmOrganizer.PERM_WORKFLOW);
110         var admin    = folder.isPermAllowed(ZmOrganizer.PERM_ADMIN);
111         var enabled  = (admin || workflow) &&
112                        (ZmOrganizer.normalizeId(msg.folderId) != ZmFolder.ID_TRASH);
113     }
114 	if (invite && invite.hasAcceptableComponents() && msg.folderId != ZmFolder.ID_SENT)	{
115 		if (msg.isInviteCanceled()) {
116 			//appointment was canceled (not necessarily on this instance, but by now it is canceled. Do not show the toolbar.
117 			return;
118 		}
119 		if (invite.hasCounterMethod()) {
120 			if (!this._counterToolbar) {
121 				this._counterToolbar = this._getCounterToolbar();
122 			}
123 			this._counterToolbar.reparentHtmlElement(this.parent.getHtmlElement(), 0);
124 			this._counterToolbar.setVisible(enabled);
125 		}
126 		else if (!invite.isOrganizer() && invite.hasInviteReplyMethod()) {
127 			var ac = window.parentAppCtxt || window.appCtxt;
128 			if (AjxEnv.isIE && this._inviteToolbar) {
129 				//according to fix to bug 52412 reparenting doestn't work on IE. so I don't reparent for IE but also if the toolbar element exists,
130 				//I remove it from parent since in the case of double-click message view, it appears multiple times without removing it
131 				this._inviteToolbar.dispose();
132 				this._inviteToolbar = null;
133 			}
134 
135 			var inviteToolbar = this.getInviteToolbar();
136 			inviteToolbar.setVisible(enabled);
137 
138 			// show on-behalf-of info?
139 			this._respondOnBehalfLabel.setContent(msg.cif ? AjxMessageFormat.format(ZmMsg.onBehalfOfText, [msg.cif]) : "");
140 			this._respondOnBehalfLabel.setVisible(!!msg.cif);
141 
142 			// logic for showing calendar/folder chooser
143 			var cc = AjxDispatcher.run("GetCalController");
144 			//note that for a msg from a mountpoint, msgAcct returns the main account, so it's not really msgAcct.
145 			var msgAcct = msg.getAccount();
146 			var calendars = ac.get(ZmSetting.CALENDAR_ENABLED, null, msgAcct) && (!msg.cif)
147 				? cc.getCalendars({includeLinks:true, account:msgAcct, onlyWritable:true}) : [];
148 
149 			var msgFolder = ac.getById(msg.getFolderId());
150 			var msgAcctId = msgFolder && msgFolder.isMountpoint ? ZmOrganizer.parseId(msgFolder.id).acctId : msgAcct.id;
151 
152 			if (appCtxt.multiAccounts) {
153 				var accounts = ac.accountList.visibleAccounts;
154 				for (var i = 0; i < accounts.length; i++) {
155 					var acct = accounts[i];
156 					if (acct == msgAcct || !ac.get(ZmSetting.CALENDAR_ENABLED, null, acct)) { continue; }
157 					if (appCtxt.isOffline && acct.isMain) { continue; }
158 
159 					calendars = calendars.concat(cc.getCalendars({includeLinks:true, account:acct, onlyWritable:true}));
160 				}
161 
162 				// always add the local account *last*
163 				if (appCtxt.isOffline) {
164 					calendars.push(appCtxt.getById(ZmOrganizer.ID_CALENDAR));
165 				}
166 			}
167 
168 			var visible = (calendars.length > 1 || appCtxt.multiAccounts);
169 			if (visible) {
170 				this._inviteMoveSelect.clearOptions();
171 				for (var i = 0; i < calendars.length; i++) {
172 					var calendar = calendars[i];
173 					var calAcct = null;
174 					var calAcctId;
175 					if (calendar.isMountpoint) {
176 						//we can't get account object for mountpoint, just get the ID.
177 						calAcctId = ZmOrganizer.parseId(calendar.id).acctId;
178 					}
179 					else {
180 						calAcct = calendar.getAccount();
181 						calAcctId = calAcct.id;
182 					}
183 					var icon = (appCtxt.multiAccounts && calAcct) ? calAcct.getIcon() : (calendar.getIcon() + ",color=" + calendar.color);
184 					var name = (appCtxt.multiAccounts && calAcct)
185 						? ([calendar.name, " (", calAcct.getDisplayName(), ")"].join(""))
186 						: calendar.name;
187 					var isSelected = (calAcctId && msgAcctId)
188 						? (calAcctId == msgAcctId && calendar.nId == ZmOrganizer.ID_CALENDAR)
189 						: calendar.nId == ZmOrganizer.ID_CALENDAR;
190                     //bug: 57538 - this invite is intended for owner of shared calendar which should be selected
191                     if(msg.cif && calendar.owner == msg.cif && calendar.rid == ZmOrganizer.ID_CALENDAR) isSelected = true;
192 					var option = new DwtSelectOptionData(calendar.id, name, isSelected, null, icon);
193 					this._inviteMoveSelect.addOption(option);
194 				}
195 
196 				// for accounts that don't support calendar, always set the
197 				// selected calendar to the Local calendar
198 				if (!ac.get(ZmSetting.CALENDAR_ENABLED, null, msgAcct)) {
199 					this._inviteMoveSelect.setSelectedValue(ZmOrganizer.ID_CALENDAR);
200 				}
201 			}
202 			this._inviteMoveSelect.setVisible(visible);
203 		}
204 	}
205 };
206 
207 /**
208  * This method does two things:
209  * 1) Checks if invite was responded to with accept/decline/tentative, and if so,
210  *    a GetAppointmentRequest is made to get the status for the other attendees.
211  *
212  * 2) Requests the free/busy status for the start date and renders the day view
213  *    with the results returned.
214  */
215 ZmInviteMsgView.prototype.showMoreInfo =
216 function(callback, dayViewCallback) {
217 	var apptId = this._invite && this._invite.hasAttendeeResponse() && this._invite.getAppointmentId();
218 
219     // Fix for bug: 83785. apptId: 0 is default id for an appointment without any parent.
220     // Getting apptId: 0 when external user takes action on appointment and organizer gets reply mail.
221 	if (apptId !== '0' && apptId) {
222 		var jsonObj = {GetAppointmentRequest:{_jsns:"urn:zimbraMail"}};
223 		var request = jsonObj.GetAppointmentRequest;
224 		var msgId = this._invite.msgId;
225 		var inx = msgId.indexOf(":");
226 		if (inx !== -1) {
227 			apptId = [msgId.substr(0, inx), apptId].join(":");
228 		}
229 		request.id = apptId;
230 
231 		appCtxt.getAppController().sendRequest({
232 			jsonObj: jsonObj,
233 			asyncMode: true,
234 			callback: (new AjxCallback(this, this._handleShowMoreInfo, [callback, dayViewCallback]))
235 		});
236 	}
237 	else {
238 		this._showFreeBusy(dayViewCallback);
239 		if (callback) {
240 			callback.run();
241 		}
242 	}
243 };
244 
245 ZmInviteMsgView.prototype._handleShowMoreInfo =
246 function(callback, dayViewCallback, result) {
247 	var appt = result && result.getResponse().GetAppointmentResponse.appt[0];
248 	if (appt) {
249 		var om = this.parent._objectManager;
250 		var html = [];
251 		var idx = 0;
252 		var attendees = appt.inv[0].comp[0].at || [];
253         AjxDispatcher.require(["MailCore", "CalendarCore"]);
254 
255         var options = {};
256 	    options.shortAddress = appCtxt.get(ZmSetting.SHORT_ADDRESS);
257 
258 		for (var i = 0; i < attendees.length; i++) {
259 			var at = attendees[i];
260 			var subs = {
261 				icon: ZmCalItem.getParticipationStatusIcon(at.ptst),
262 				attendee: this.parent._getBubbleHtml(new AjxEmailAddress(at.a), options)
263 			};
264 			html[idx++] = AjxTemplate.expand("mail.Message#InviteHeaderPtst", subs);
265 		}
266 
267 		var ptstEl = document.getElementById(this._ptstId);
268         if(ptstEl)
269             ptstEl.innerHTML = html.join("");
270 	}
271 
272 	if (callback) {
273 		callback.run();
274 	}
275 
276 	this._showFreeBusy(dayViewCallback);
277 };
278 
279 ZmInviteMsgView.prototype._showFreeBusy =
280 function(dayViewCallback) {
281 	var ac = window.parentAppCtxt || window.appCtxt;
282 
283 	if (!appCtxt.isChildWindow &&
284 		(ac.get(ZmSetting.CALENDAR_ENABLED) || ac.multiAccounts) &&
285 		(this._invite && this._invite.type != "task"))
286 	{
287         var inviteDate = this._getInviteDate();
288         if (inviteDate == null) {
289             return;
290         }
291 
292 		AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
293 		var cc = AjxDispatcher.run("GetCalController");
294 
295 		if (!this._dayView) {
296 			// create a new ZmCalDayView under msgview's parent otherwise, we
297 			// cannot position the day view correctly.
298 			var dayViewParent = (this.mode && (this.mode == ZmId.VIEW_CONV2)) ?
299 			    this.parent : this.parent.parent;
300 			this._dayView = new ZmCalDayView(dayViewParent, DwtControl.ABSOLUTE_STYLE, cc, null,
301                 this.parent._viewId, null, true, true, this.isRight());
302 			this._dayView.addSelectionListener(new AjxListener(this, this._apptSelectionListener));
303 			this._dayView.setZIndex(Dwt.Z_VIEW); // needed by ZmMsgController's msgview
304 		}
305 
306 		this._dayView.setDisplay(Dwt.DISPLAY_BLOCK);
307 		this._dayView.setDate(inviteDate, 0, false);
308         this.resize();
309 
310         var acctFolderIds = [].concat(cc.getCheckedCalendarFolderIds()); // create a *copy*
311         if(this._msg.cif) {
312             acctFolderIds = acctFolderIds.concat(cc.getUncheckedCalendarIdsByOwner(this._msg.cif));
313         }
314 		var rt = this._dayView.getTimeRange();
315 		var params = {
316 			start: rt.start,
317 			end: rt.end,
318 			fanoutAllDay: this._dayView._fanoutAllDay(),
319 			callback: (new AjxCallback(this, this._dayResultsCallback, [dayViewCallback, inviteDate.getHours()])),
320 			accountFolderIds: [acctFolderIds] // pass in array of array
321 		};
322 		cc.apptCache.batchRequest(params);
323 	}
324 };
325 
326 ZmInviteMsgView.prototype._getInviteDate =
327 function() {
328 	if (!this._invite) { return null; }
329     var inviteDate = this._invite.getServerStartDate(null, true);
330     // Not sure when null inviteDate happens (probably a bug) but this is defensive
331     // check for bug 51754
332     if (inviteDate != null) {
333         var inviteTz = this._invite.getServerStartTimeTz();
334         inviteDate = AjxTimezone.convertTimezone(inviteDate,
335             AjxTimezone.getClientId(inviteTz), AjxTimezone.DEFAULT);
336     }
337     return inviteDate;
338 }
339 
340 ZmInviteMsgView.prototype.isRight =
341 function() {
342 	return this.parent._controller.isReadingPaneOnRight();
343 };
344 
345 ZmInviteMsgView.prototype.convResize =
346 function() {
347 	var parentSize = this.parent.getSize();
348 	if (this._dayView) {
349 		this._dayView.setSize(parentSize.x - 5, 218);
350 		var el = this._dayView.getHtmlElement();
351 		el.style.left = el.style.top = "auto";
352 		this._dayView.layout();
353 	}
354 }
355 
356 /**
357  * Resizes the view depending on whether f/b is being shown or not.
358  *
359  * @param reset		Boolean		If true, day view is not shown and msgview's bounds need to be "reset"
360  */
361 ZmInviteMsgView.prototype.resize =
362 function(reset) {
363 	if (appCtxt.isChildWindow) { return; }
364 	if (this.parent.isZmMailMsgCapsuleView) { return; }
365 
366 	var isRight = this.isRight();
367 	var grandParentSize = this.parent.parent.getSize();
368 
369 	if (reset) {
370 		if (isRight) {
371 			this.parent.setSize(Dwt.DEFAULT, grandParentSize.y);
372 		}
373 		else {
374 			this.parent.setSize(grandParentSize.x, Dwt.DEFAULT);
375 		}
376 	} else if (this._dayView) {
377 		// bug: 50412 - fix day view for stand-alone message view which is a parent
378 		// of DwtShell and needs to be resized manually.
379 		var padding = 0;
380 		if (this.parent.getController() instanceof ZmMsgController) {
381 			// get the bounds for the app content area so we can position the day view
382 			var appContentBounds = appCtxt.getAppViewMgr()._getContainerBounds(ZmAppViewMgr.C_APP_CONTENT);
383             if (!isRight)
384 			    grandParentSize = {x: appContentBounds.width, y: appContentBounds.height};
385 
386 			// set padding so we can add it to the day view's x-location since it is a child of the shell
387 			padding = appContentBounds.x;
388 		}
389 
390 		var mvBounds = this.parent.getBounds();
391 
392 		/* on IE sometimes the value of top and left is "auto", in which case we get a NaN value here due to parseInt in getLocation. */
393 		/* not sure if 0 is the right value we should use in this case, but it seems to work */
394 		if (isNaN(mvBounds.x)) {
395 			mvBounds.x = 0;
396 		}
397 		if (isNaN(mvBounds.y)) {
398 			mvBounds.y = 0;
399 		}
400 
401 		if (isRight) {
402 			var parentHeight = grandParentSize.y;
403 			var dvHeight = Math.floor(parentHeight / 3);
404 			var mvHeight = parentHeight - dvHeight;
405 
406 			this._dayView.setBounds(mvBounds.x, mvHeight, mvBounds.width, dvHeight);
407             if (this.parent && this.parent instanceof ZmMailMsgView){
408                 var el = this.parent.getHtmlElement();
409                 if (this.mode && this.mode != ZmId.VIEW_MSG) {
410                     if (el){
411                         el.style.height = mvHeight + "px";
412                         Dwt.setScrollStyle(el, Dwt.SCROLL);
413                     }
414                 }
415                 else {
416                     var bodyDiv = this.parent.getMsgBodyElement();
417                     if (bodyDiv) Dwt.setScrollStyle(bodyDiv, Dwt.CLIP);
418                     if (el) {
419                         Dwt.setScrollStyle(el, Dwt.SCROLL);
420                         var yOffset = this.parent.getBounds().y || 0;
421                         el.style.height = (mvHeight - yOffset) + "px";
422                     }
423                 }
424             }
425 
426 			// don't call DwtControl's setSize() since it triggers control
427 			// listener and leads to infinite loop
428 
429 			Dwt.delClass(this.parent.getHtmlElement(), "RightBorderSeparator");
430 		} else {
431 			var parentWidth = grandParentSize.x;
432 			var dvWidth = Math.floor(parentWidth / 3);
433 			var separatorWidth = 5;
434 			var mvWidth = parentWidth - dvWidth - separatorWidth; 
435 
436 			this._dayView.setBounds(mvWidth + padding + separatorWidth, mvBounds.y, dvWidth, mvBounds.height);
437 			// don't call DwtControl's setSize() since it triggers control
438 			// listener and leads to infinite loop
439 			Dwt.setSize(this.parent.getHtmlElement(), mvWidth, Dwt.DEFAULT);
440 			Dwt.addClass(this.parent.getHtmlElement(), "RightBorderSeparator");
441 		}
442 	}
443 };
444 
445 /**
446  * enables all invite toolbar buttons, except one that matches the current ptst
447  * @param ptst participant status
448  */
449 ZmInviteMsgView.prototype.enableToolbarButtons =
450 function(ptst) {
451 	var disableButtonIds = {};
452 	switch (ptst) {
453 		case ZmCalBaseItem.PSTATUS_ACCEPT:
454 			disableButtonIds[ZmOperation.REPLY_ACCEPT] = true;
455 			break;
456 		case ZmCalBaseItem.PSTATUS_DECLINED:
457 			disableButtonIds[ZmOperation.REPLY_DECLINE] = true;
458 			break;
459 		case ZmCalBaseItem.PSTATUS_TENTATIVE:
460 			disableButtonIds[ZmOperation.REPLY_TENTATIVE] = true;
461 			break;
462 	}
463 	if (appCtxt.isWebClientOffline()) {
464 		 disableButtonIds[ ZmOperation.PROPOSE_NEW_TIME] = true;
465 	}
466 	var inviteToolbar = this.getInviteToolbar();
467 
468 	var buttonIds = [ZmOperation.REPLY_ACCEPT, ZmOperation.REPLY_DECLINE, ZmOperation.REPLY_TENTATIVE, ZmOperation.PROPOSE_NEW_TIME];
469 	for (var i = 0; i < buttonIds.length; i++) {
470 		var buttonId = buttonIds[i];
471 		inviteToolbar.getButton(buttonId).setEnabled(appCtxt.isExternalAccount() ? false : !disableButtonIds[buttonId]);
472 	}
473 };
474 
475 /**
476  * hide the participant status message (no longer relevant)
477  */
478 ZmInviteMsgView.prototype.updatePtstMsg =
479 function(ptst) {
480 	var ptstMsgBannerDiv = document.getElementById(this._ptstMsgBannerId);
481 	if (!ptstMsgBannerDiv) {
482 		return;
483 	}
484 	ptstMsgBannerDiv.className = ZmInviteMsgView.PTST_MSG[ptst].className;
485 	ptstMsgBannerDiv.style.display = "block"; // since it might be display none if there's no message to begin with (this is the first time ptst is set by buttons)
486 
487 	var ptstMsgElement = document.getElementById(this._ptstMsgId);
488 	ptstMsgElement.innerHTML = ZmInviteMsgView.PTST_MSG[ptst].msg;
489 
490 	var ptstIconImg = document.getElementById(this._ptstMsgIconId);
491 	var icon = ZmCalItem.getParticipationStatusIcon(ptst);
492 	ptstIconImg.innerHTML = AjxImg.getImageHtml(icon)
493 
494 
495 };
496 
497 
498 ZmInviteMsgView.PTST_MSG = [];
499 ZmInviteMsgView.PTST_MSG[ZmCalBaseItem.PSTATUS_ACCEPT] = {msg: AjxMessageFormat.format(ZmMsg.inviteAccepted), className: "InviteStatusAccept"};
500 ZmInviteMsgView.PTST_MSG[ZmCalBaseItem.PSTATUS_DECLINED] = {msg: AjxMessageFormat.format(ZmMsg.inviteDeclined), className: "InviteStatusDecline"};
501 ZmInviteMsgView.PTST_MSG[ZmCalBaseItem.PSTATUS_TENTATIVE] = {msg: AjxMessageFormat.format(ZmMsg.inviteAcceptedTentatively), className: "InviteStatusTentative"};
502 
503 ZmInviteMsgView.prototype.addSubs =
504 function(subs, sentBy, sentByAddr, obo) {
505 
506     AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
507 	subs.invite = this._invite;
508 
509 	if (!this._msg.isInviteCanceled() && !subs.invite.isOrganizer() && subs.invite.hasInviteReplyMethod()) {
510 		var yourPtst = this._msg.getPtst();
511 		this.enableToolbarButtons(yourPtst);
512 		if (yourPtst) {
513 			subs.ptstMsg = ZmInviteMsgView.PTST_MSG[yourPtst].msg;
514 			subs.ptstClassName = ZmInviteMsgView.PTST_MSG[yourPtst].className;
515 			subs.ptstIcon = ZmCalItem.getParticipationStatusIcon(yourPtst);
516 		}
517 	}
518 	//ids for updating later
519 	subs.ptstMsgBannerId = this._ptstMsgBannerId = (this.parent._htmlElId + "_ptstMsgBanner");
520 	subs.ptstMsgId = this._ptstMsgId = (this.parent._htmlElId + "_ptstMsg");
521 	subs.ptstMsgIconId = this._ptstMsgIconId = (this.parent._htmlElId + "_ptstMsgIcon");
522 
523 	var isOrganizer = this._invite && this._invite.isOrganizer();
524     var isInviteCancelled = this._invite.components && this._invite.components[0].method === ZmId.OP_CANCEL;
525 	// counter proposal
526 	if (this._invite.hasCounterMethod() &&
527 		this._msg.folderId != ZmFolder.ID_SENT)
528 	{
529         var from = this._msg.getAddress(AjxEmailAddress.FROM) && this._msg.getAddress(AjxEmailAddress.FROM).getAddress();
530         subs.counterInvMsg =  (!sentByAddr || sentByAddr == from) ?
531             AjxMessageFormat.format(ZmMsg.counterInviteMsg, [from]):AjxMessageFormat.format(ZmMsg.counterInviteMsgOnBehalfOf, [sentByAddr, from]);
532 	}
533 	// Fix for bug: 88052 and 77237. Display cancellation banner to organizer or attendee
534 	else if (isInviteCancelled) {
535 		var organizer = this._invite.getOrganizerName() || this._invite.getOrganizerEmail();
536 		subs.ptstMsg = AjxMessageFormat.format(ZmMsg.inviteMsgCancelled, organizer.split());
537 		subs.ptstIcon = ZmCalItem.getParticipationStatusIcon(ZmCalBaseItem.PSTATUS_DECLINED);
538 		subs.ptstClassName = "InviteStatusDecline";
539 	}
540 	// if this an action'ed invite, show the status banner
541 	else if (isOrganizer && this._invite.hasAttendeeResponse()) {
542 		var attendee = this._invite.getAttendees()[0];
543 		var ptst = attendee && attendee.ptst;
544 		if (ptst) {
545             var names = [];
546 			var dispName = attendee.d || attendee.a;
547             var sentBy = attendee.sentBy;
548             var ptstStr = null;
549             if (sentBy) names.push(attendee.sentBy);
550             names.push(dispName);
551 			subs.ptstIcon = ZmCalItem.getParticipationStatusIcon(ptst);
552 			switch (ptst) {
553 				case ZmCalBaseItem.PSTATUS_ACCEPT:
554 					ptstStr = (!sentBy) ? ZmMsg.inviteMsgAccepted : ZmMsg.inviteMsgOnBehalfOfAccepted;
555 					subs.ptstClassName = "InviteStatusAccept";
556 					break;
557 				case ZmCalBaseItem.PSTATUS_DECLINED:
558 					ptstStr = (!sentBy) ? ZmMsg.inviteMsgDeclined : ZmMsg.inviteMsgOnBehalfOfDeclined;
559 					subs.ptstClassName = "InviteStatusDecline";
560 					break;
561 				case ZmCalBaseItem.PSTATUS_TENTATIVE:
562 					ptstStr = (!sentBy) ? ZmMsg.inviteMsgTentative:ZmMsg.inviteMsgOnBehalfOfTentative;
563 					subs.ptstClassName = "InviteStatusTentative";
564 					break;
565 			}
566             if (ptstStr){
567                 subs.ptstMsg = AjxMessageFormat.format(ptstStr, names);
568             }
569 		}
570 	}
571 
572     if (isOrganizer && this._invite && this._invite.hasAttendeeResponse() && this._invite.getAppointmentId()){
573         // set an Id for adding more detailed info later
574         subs.ptstId = this._ptstId = (this.parent._htmlElId + "_ptst");
575     }
576 
577     var options = {};
578 	options.shortAddress = appCtxt.get(ZmSetting.SHORT_ADDRESS);
579 
580 	var om = this.parent._objectManager;
581 	// organizer
582 	var org = new AjxEmailAddress(this._invite.getOrganizerEmail(), null, this._invite.getOrganizerName());
583 	subs.invOrganizer = this.parent._getBubbleHtml(org, options);
584 
585     if (obo) {
586 	    subs.obo = this.parent._getBubbleHtml(obo, options);
587     }
588 
589 	// sent-by
590 	var sentBy = this._invite.getSentBy();
591 	if (sentBy) {
592 		subs.invSentBy = this.parent._getBubbleHtml(sentBy, options);
593 	}
594 
595     if(this._msg.cif) {
596         subs.intendedForMsg = AjxMessageFormat.format(ZmMsg.intendedForInfo, [this._msg.cif]);
597         subs.intendedForClassName = "InviteIntendedFor";
598     }
599 
600 	// inviteees
601 	var invitees = [];
602     var optInvitees = [];
603 
604 	var list = this._invite.getAttendees();
605 	for (var i = 0; i < list.length; i++) {
606 		var at = list[i];
607 		var attendee = new AjxEmailAddress(at.a, null, at.d);
608         if (at.role == ZmCalItem.ROLE_OPTIONAL) {
609             optInvitees.push(attendee);
610         }
611         else {
612             invitees.push(attendee);
613         }
614 	}
615     var addressInfo = this.parent.getAddressesFieldInfo(invitees, options, "inv");
616     subs.invitees = addressInfo.html;
617     addressInfo = this.parent.getAddressesFieldInfo(optInvitees, options, "opt");
618     subs.optInvitees = addressInfo.html;
619 
620 	// convert to local timezone if necessary
621 	var inviteTz = this._invite.getServerStartTimeTz();
622 	var defaultTz = AjxTimezone.getServerId(AjxTimezone.DEFAULT);
623 
624     if (inviteTz) {
625         var sd = AjxTimezone.convertTimezone(this._invite.getServerStartDate(null, true), AjxTimezone.getClientId(inviteTz), AjxTimezone.DEFAULT);
626         var ed = AjxTimezone.convertTimezone(this._invite.getServerEndDate(null, true), AjxTimezone.getClientId(inviteTz), AjxTimezone.DEFAULT);
627 
628         subs.timezone = AjxTimezone.getMediumName(defaultTz);
629     }
630 
631 	// duration text
632 	var durText = this._invite.getDurationText(null, null, null, true, sd, ed);
633 	subs.invDate = durText;
634 
635 	// recurrence
636 	if (this._invite.isRecurring()) {
637 		var recur = new ZmRecurrence();
638 		recur.setRecurrenceRules(this._invite.getRecurrenceRules(), this._invite.getServerStartDate());
639 		subs.recur = recur.getBlurb();
640 	}
641 
642 	// set changes to the invite
643 	var changes = this._invite.getChanges();
644 	if (changes && changes[ZmInvite.CHANGES_LOCATION]) {
645 		subs.locChangeClass = "InvChanged";
646 	}
647 	if (changes && changes[ZmInvite.CHANGES_SUBJECT]) {
648 		subs.subjChangeClass = "InvChanged";
649 	}
650 	if (changes && changes[ZmInvite.CHANGES_TIME]) {
651 		subs.timeChangeClass = "InvChanged";
652 	}
653 };
654 
655 ZmInviteMsgView.truncateBodyContent =
656 function(content, isHtml) {
657     if (!content) return content;
658 	var sepIdx = content.indexOf(ZmItem.NOTES_SEPARATOR);
659 	if (sepIdx == -1) {
660 		return content;
661 	}
662 	if (isHtml) {
663 		//if it is a html content then just remove the content and preserve the html tags
664 		//surrounding the content.
665         content = content.replace("<div>"+ ZmItem.NOTES_SEPARATOR +"</div>", ZmItem.NOTES_SEPARATOR); // Striping div if ZmItem.NOTES_SEPARATOR is part of div.
666         content = content.replace(ZmItem.NOTES_SEPARATOR, "<div id='separatorId'>" + ZmItem.NOTES_SEPARATOR + "</div>");
667         var divEle = document.createElement("div");
668         divEle.innerHTML = content;
669         var node = Dwt.byId("separatorId",divEle) ;
670         if (node){
671             var parent = node.parentNode
672             // Removing all previousSiblings of node that contains ZmItem.NOTES_SEPARATOR
673             while(node.previousSibling){
674                 parent.removeChild(node.previousSibling);
675             }
676             parent.removeChild(node);
677         }
678         return divEle.innerHTML;
679 	}
680 	return content.substring(sepIdx+ZmItem.NOTES_SEPARATOR.length);
681 };
682 
683 ZmInviteMsgView.prototype._getCounterToolbar =
684 function() {
685 	var params = {
686 		parent: this.parent,
687 		buttons: [ZmOperation.ACCEPT_PROPOSAL, ZmOperation.DECLINE_PROPOSAL],
688 		posStyle: DwtControl.STATIC_STYLE,
689 		className: "ZmCounterToolBar",
690 		buttonClassName: "DwtToolbarButton",
691 		context: this.mode,
692 		toolbarType: ZmId.TB_COUNTER
693 	};
694 	var tb = new ZmButtonToolBar(params);
695 
696 	var listener = new AjxListener(this, this._inviteToolBarListener);
697 	for (var i = 0; i < tb.opList.length; i++) {
698 		tb.addSelectionListener(tb.opList[i], listener);
699 	}
700 
701 	return tb;
702 };
703 
704 /**
705  * returns the toolbar. Creates a new one only if it's not already set to the internal field
706  */
707 ZmInviteMsgView.prototype.getInviteToolbar =
708 function() {
709 	if (!this._inviteToolbar) {
710 		this._inviteToolbar = this._createInviteToolbar();
711 		//hide it till needed. Just in case after the fix I submit with this, some future change will call it before needs to be displayed.
712 		this._inviteToolbar.setDisplay(Dwt.DISPLAY_NONE);
713 		
714 	}
715 	return this._inviteToolbar;
716 };
717 
718 
719 ZmInviteMsgView.prototype._createInviteToolbar =
720 function() {
721 	var replyButtonIds = [
722 		ZmOperation.INVITE_REPLY_ACCEPT,
723 		ZmOperation.INVITE_REPLY_TENTATIVE,
724 		ZmOperation.INVITE_REPLY_DECLINE
725 	];
726 	var notifyOperationButtonIds = [
727 		ZmOperation.REPLY_ACCEPT_NOTIFY,
728 		ZmOperation.REPLY_TENTATIVE_NOTIFY,
729 		ZmOperation.REPLY_DECLINE_NOTIFY
730 	];
731 	var ignoreOperationButtonIds = [
732 		ZmOperation.REPLY_ACCEPT_IGNORE,
733 		ZmOperation.REPLY_TENTATIVE_IGNORE,
734 		ZmOperation.REPLY_DECLINE_IGNORE
735 	];
736 	var inviteOps = [
737 		ZmOperation.REPLY_ACCEPT,
738 		ZmOperation.REPLY_TENTATIVE,
739 		ZmOperation.REPLY_DECLINE,
740 		ZmOperation.PROPOSE_NEW_TIME
741 	];
742 
743 	var params = {
744 		parent: this.parent,
745 		buttons: inviteOps,
746 		posStyle: DwtControl.STATIC_STYLE,
747 		className: "ZmInviteToolBar",
748 		buttonClassName: "DwtToolbarButton",
749 		context: this.parent.getHTMLElId(),
750 		toolbarType: ZmId.TB_INVITE
751 	};
752 	var tb = new ZmButtonToolBar(params);
753 
754 	var listener = new AjxListener(this, this._inviteToolBarListener);
755 	for (var i = 0; i < tb.opList.length; i++) {
756 		var id = tb.opList[i];
757 
758 		tb.addSelectionListener(id, listener);
759 
760 		if (id == ZmOperation.PROPOSE_NEW_TIME) { continue; }
761 
762 		var button = tb.getButton(id);
763 		var standardItems = [notifyOperationButtonIds[i], replyButtonIds[i], ignoreOperationButtonIds[i]];
764 		var menu = new ZmActionMenu({parent:button, menuItems:standardItems});
765 		standardItems = menu.opList;
766 		for (var j = 0; j < standardItems.length; j++) {
767 			var menuItem = menu.getItem(j);
768 			menuItem.addSelectionListener(listener);
769 		}
770 		button.setMenu(menu);
771 	}
772 
773 	this._respondOnBehalfLabel = tb.addFiller();
774 	tb.addFiller();
775 
776 	// folder picker
777 	this._inviteMoveSelect = new DwtSelect({parent:tb});
778 	this._inviteMoveSelect.setVisible(false); //by default hide it. bug 74254
779 
780 	return tb;
781 };
782 
783 ZmInviteMsgView.prototype._inviteToolBarListener =
784 function(ev) {
785 	ev._inviteReplyType = ev.item.getData(ZmOperation.KEY_ID);
786 	ev._inviteReplyFolderId = ((this._inviteMoveSelect && this._inviteMoveSelect.getValue()) || ZmOrganizer.ID_CALENDAR);
787 	ev._inviteComponentId = null;
788 	ev._msg = this._msg;
789 	this.parent.notifyListeners(ZmInviteMsgView.REPLY_INVITE_EVENT, ev);
790 };
791 
792 ZmInviteMsgView.prototype._dayResultsCallback =
793 function(dayViewCallback, invitedHour, list, skipMiniCalUpdate, query) {
794 	if (this._dayView) {
795 	    this._dayView.set(list, true);
796 	    this._dayView._scrollToTime(invitedHour);
797 	}
798     if (dayViewCallback) {
799         dayViewCallback.run();
800     }
801 };
802 
803 ZmInviteMsgView.prototype.getDayView =
804 function() {
805     return this._dayView;
806 };
807 
808 ZmInviteMsgView.prototype._apptSelectionListener =
809 function(ev) {
810 	if (ev.detail == DwtListView.ITEM_DBL_CLICKED) {
811 		var appt = ev.item;
812 		if (appt.isPrivate() && appt.getFolder().isRemote() && !appt.getFolder().hasPrivateAccess()) {
813 			var msgDialog = appCtxt.getMsgDialog();
814 			msgDialog.setMessage(ZmMsg.apptIsPrivate, DwtMessageDialog.INFO_STYLE);
815 			msgDialog.popup();
816 		} else {
817 			// open a appointment view
818 			var cc = AjxDispatcher.run("GetCalController");
819 			cc._showAppointmentDetails(appt);
820 		}
821 	}
822 };
823 
824 ZmInviteMsgView.prototype.scrollToInvite =
825 function() {
826     var inviteDate = this._getInviteDate();
827     if ((inviteDate != null) && this._dayView) {
828         this._dayView._scrollToTime(inviteDate.getHours());
829     }
830 }
831 
832 ZmInviteMsgView.prototype.repositionCounterToolbar =
833 function(hdrTableId) {
834     if (this._invite && this._invite.hasCounterMethod() && hdrTableId && this._counterToolbar) {
835         this._counterToolbar.reparentHtmlElement(hdrTableId + '_counterToolbar', 0);
836     }
837 }
838