1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 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 a new appointment controller to manage appointment creation/editing.
 26  * @constructor
 27  * @class
 28  * This class manages appointment creation/editing.
 29  *
 30  * @author Parag Shah
 31  *
 32  * @param {DwtShell}	container	the containing shell
 33  * @param {ZmApp}		app			the containing app
 34  * @param {constant}	type		controller type
 35  * @param {string}		sessionId	the session id
 36  * 
 37  * @extends		ZmCalItemComposeController
 38  */
 39 ZmApptComposeController = function(container, app, type, sessionId) {
 40     if (arguments.length == 0) { return; }
 41 
 42 	ZmCalItemComposeController.apply(this, arguments);
 43 
 44 	this._addedAttendees = [];
 45 	this._removedAttendees = [];
 46 	this._kbMgr = appCtxt.getKeyboardMgr();
 47 };
 48 
 49 ZmApptComposeController.prototype = new ZmCalItemComposeController;
 50 ZmApptComposeController.prototype.constructor = ZmApptComposeController;
 51 
 52 ZmApptComposeController.prototype.isZmApptComposeController = true;
 53 ZmApptComposeController.prototype.toString = function() { return "ZmApptComposeController"; };
 54 
 55 ZmApptComposeController._VALUE = "value";
 56 
 57 ZmApptComposeController._DIALOG_OPTIONS = {
 58 	SEND: 'SEND',
 59 	CANCEL: 'CANCEL',
 60 	DISCARD: 'DISCARD'
 61 };
 62 
 63 // Public methods
 64 
 65 ZmApptComposeController.getDefaultViewType =
 66 function() {
 67 	return ZmId.VIEW_APPOINTMENT;
 68 };
 69 ZmApptComposeController.prototype.getDefaultViewType = ZmApptComposeController.getDefaultViewType;
 70 
 71 ZmApptComposeController.prototype.show =
 72 function(calItem, mode, isDirty) {
 73 	ZmCalItemComposeController.prototype.show.call(this, calItem, mode, isDirty);
 74 
 75 	this._addedAttendees.length = this._removedAttendees.length = 0;
 76 	this._setComposeTabGroup();
 77 };
 78 
 79 /**
 80  * Forwards the calendar item.
 81  * 
 82  * @param	{ZmAppt}	appt		the appointment
 83  * @return	{Boolean}	<code>true</code> indicates the forward is executed
 84  */
 85 ZmApptComposeController.prototype.forwardCalItem =
 86 function(appt, forwardCallback) {
 87 	// todo: to address input validation
 88 	var callback = new AjxCallback(this, this._handleForwardInvite, forwardCallback);
 89 	appt.forward(callback);
 90 	return true;
 91 };
 92 
 93 /**
 94  * Propose new time for an appointment
 95  *
 96  * @param	{ZmAppt}	    appt		            the appointment
 97  * @param	{AjxCallback}	proposeTimeCallback		callback executed  after proposing time
 98  * @return	{Boolean}	    <code>true</code>       indicates that propose time is executed
 99  */
100 ZmApptComposeController.prototype.sendCounterAppointmentRequest =
101 function(appt, proposeTimeCallback) {
102 	var callback = new AjxCallback(this, this._handleCounterAppointmentRequest, proposeTimeCallback);
103     var apptEditView = this._composeView ? this._composeView.getApptEditView() : null;
104     var viewMode = apptEditView ? apptEditView.getMode() : null;
105 	appt.sendCounterAppointmentRequest(callback, null, viewMode);
106 	return true;
107 };
108 
109 ZmApptComposeController.prototype._handleCounterAppointmentRequest =
110 function(proposeTimeCallback) {
111 	appCtxt.setStatusMsg(ZmMsg.newTimeProposed);
112 	if (proposeTimeCallback instanceof AjxCallback) {
113 		proposeTimeCallback.run();
114 	}
115 };
116 
117 ZmApptComposeController.prototype._handleForwardInvite =
118 function(forwardCallback) {
119 	appCtxt.setStatusMsg(ZmMsg.forwardInviteSent);
120 	if (forwardCallback instanceof AjxCallback) {
121 		forwardCallback.run();
122 	}
123 };
124 
125 ZmApptComposeController.prototype._badAddrsOkCallback =
126 function(dialog, appt) {
127 	dialog.popdown();
128 	this.forwardCalItem(appt, new AjxCallback(this, this._apptForwardCallback));
129 };
130 
131 ZmApptComposeController.prototype._apptForwardCallback =
132 function() {
133 	this.closeView();
134 };
135 
136 ZmApptComposeController.prototype._checkIsDirty =
137 function(type, attribs){
138     return this._composeView.checkIsDirty(type, attribs)
139 };
140 
141 ZmApptComposeController.prototype._getChangesDialog =
142 function(){
143     var id,
144         dlg,
145         isOrganizer = this._composeView.isOrganizer();
146     if(isOrganizer) {
147         dlg = this._changesDialog;
148         if (!dlg) {
149 			dlg = this._changesDialog = new DwtOptionDialog({
150 				parent: appCtxt.getShell(),
151 				id: Dwt.getNextId("CHNG_DLG_ORG_"),
152 				title: ZmMsg.apptSave,
153 				message: ZmMsg.apptSignificantChanges,
154 				options: [
155 					{
156 						name: ZmApptComposeController._DIALOG_OPTIONS.SEND,
157 						text: ZmMsg.apptSaveChanges
158 					},
159 					{
160 						name: ZmApptComposeController._DIALOG_OPTIONS.CANCEL,
161 						text: ZmMsg.apptSaveCancel
162 					},
163 					{
164 						name: ZmApptComposeController._DIALOG_OPTIONS.DISCARD,
165 						text: ZmMsg.apptSaveDiscard
166 					}
167 				]
168 			});
169 			dlg.registerCallback(DwtDialog.OK_BUTTON,
170 			                     this._changesDialogListener.bind(this));
171         }
172     }
173     else {
174         dlg = this._attendeeChangesDialog;
175         if (!dlg) {
176             dlg = this._attendeeChangesDialog = new DwtDialog({parent:appCtxt.getShell(), id:Dwt.getNextId("CHNG_DLG_ATTNDE_")});
177             id = this._attendeeChangesDialogId = Dwt.getNextId();
178             dlg.setContent(AjxTemplate.expand("calendar.Appointment#ChangesDialogAttendee", {id: id}));
179             dlg.setTitle(ZmMsg.apptSave);
180             dlg.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._attendeeChangesDialogListener, id));
181         }
182     }
183     return dlg;
184 };
185 
186 ZmApptComposeController.prototype._changesDialogListener =
187 function(){
188 
189     this.clearInvalidAttendees();
190     delete this._invalidAttendees;
191 
192 	switch (this._changesDialog.getSelection()) {
193 	case ZmApptComposeController._DIALOG_OPTIONS.SEND:
194         this._sendListener();
195 		break;
196 
197 	case ZmApptComposeController._DIALOG_OPTIONS.CANCEL:
198 		break;
199 
200 	case ZmApptComposeController._DIALOG_OPTIONS.DISCARD:
201         this.closeView();
202 		break;
203 	}
204 
205 	this._changesDialog.popdown();
206 };
207 
208 ZmApptComposeController.prototype._attendeeChangesDialogListener =
209 function(id){
210     this.clearInvalidAttendees();
211     delete this._invalidAttendees;
212     this.closeView();
213     this._attendeeChangesDialog.popdown();
214 };
215 
216 ZmApptComposeController.prototype.saveCalItem =
217 function(attId) {
218 	this._composeView.cancelLocationRequest();
219 	var appt = this._composeView.getAppt(attId);
220     var numRecurrence = this._composeView.getNumLocationConflictRecurrence ?
221         this._composeView.getNumLocationConflictRecurrence() :
222         ZmTimeSuggestionPrefDialog.DEFAULT_NUM_RECURRENCE;
223 
224 	if (appt) {
225 
226 		if (!appt.isValidDuration()) {
227 			this._composeView.showInvalidDurationMsg();
228 			this.enableToolbar(true);
229 			return false;
230 		}
231 		if (!appt.isValidDurationRecurrence()) {
232 			this._composeView.showInvalidDurationRecurrenceMsg();
233 			this.enableToolbar(true);
234 			return false;
235 		}
236 
237         if (appCtxt.get(ZmSetting.GROUP_CALENDAR_ENABLED)) {
238             if (this._requestResponses)
239             	appt.setRsvp(this._requestResponses.getChecked());
240             appt.setMailNotificationOption(true);
241         }
242 
243         if(appt.isProposeTime && !appt.isOrganizer()) {
244             return this.sendCounterAppointmentRequest(appt);
245         }
246 
247 		if (appt.isForward) {
248 			var addrs = this._composeView.getForwardAddress();
249 
250 			// validate empty forward address
251 			if (!addrs.gotAddress) {
252 				var msgDialog = appCtxt.getMsgDialog();
253 				msgDialog.setMessage(ZmMsg.noForwardAddresses, DwtMessageDialog.CRITICAL_STYLE);
254 				msgDialog.popup();
255                 this.enableToolbar(true);
256 				return false;
257 			}
258 
259 			if (addrs[ZmApptEditView.BAD] && addrs[ZmApptEditView.BAD].size()) {
260 				var cd = appCtxt.getOkCancelMsgDialog();
261 				cd.reset();
262 				var bad = AjxStringUtil.htmlEncode(addrs[ZmApptEditView.BAD].toString(AjxEmailAddress.SEPARATOR));
263 				var msg = AjxMessageFormat.format(ZmMsg.compBadAddresses, bad);
264 				cd.setMessage(msg, DwtMessageDialog.WARNING_STYLE);
265 				cd.registerCallback(DwtDialog.OK_BUTTON, this._badAddrsOkCallback, this, [cd,appt]);
266 				cd.setVisible(true); // per fix for bug 3209
267 				cd.popup();
268                 this.enableToolbar(true);
269 				return false;
270 			}
271 
272             //attendee forwarding an appt
273             /* if(!appt.isOrganizer()) */ return this.forwardCalItem(appt);
274 		}
275 
276 		if (!this._attendeeValidated && this._invalidAttendees && this._invalidAttendees.length > 0) {
277 			var dlg = appCtxt.getYesNoMsgDialog();
278 			dlg.registerCallback(DwtDialog.YES_BUTTON, this._clearInvalidAttendeesCallback, this, [appt, attId, dlg]);
279 			var msg = "";
280             if(this._action == ZmCalItemComposeController.SAVE){
281                msg = AjxMessageFormat.format(ZmMsg.compSaveBadAttendees, AjxStringUtil.htmlEncode(this._invalidAttendees.join(",")));
282             }
283             else{
284                 msg = AjxMessageFormat.format(ZmMsg.compBadAttendees, AjxStringUtil.htmlEncode(this._invalidAttendees.join(",")));
285             }
286 			dlg.setMessage(msg, DwtMessageDialog.WARNING_STYLE);
287 			dlg.popup();
288             this.enableToolbar(true);
289             this._attendeeValidated = true;
290 			return false;
291 		}
292 
293         //Validation Check for Significant / Insignificant / Local changes
294         if(this._action == ZmCalItemComposeController.SAVE && !appt.inviteNeverSent){
295             //Check for Significant Changes
296             if(this._checkIsDirty(ZmApptEditView.CHANGES_SIGNIFICANT)){
297                 this._getChangesDialog().popup();
298                 this.enableToolbar(true);
299                 return false;
300             }
301         }
302 
303 		var origAttendees = appt.origAttendees;						// bug fix #4160
304 		if (origAttendees && origAttendees.length > 0 && 			// make sure we're not u/l'ing a file
305 			attId == null) 											// make sure we are editing an existing appt w/ attendees
306 		{
307 			if (!appt.inviteNeverSent && !this._composeView.getApptEditView().isDirty(true)) {	// make sure other fields (besides attendees field) have not changed
308 				var attendees = appt.getAttendees(ZmCalBaseItem.PERSON);
309 				if (attendees.length > 0) {
310 					// check whether organizer has added/removed any attendees
311 					if (this._action == ZmCalItemComposeController.SEND && this._attendeesUpdated(appt, attId, attendees, origAttendees))
312 						return false;
313 				}
314 			}
315 
316 			// check whether moving appt from local to remote folder with attendees
317 			var cc = AjxDispatcher.run("GetCalController");
318 			if (cc.isMovingBetwAccounts(appt, appt.__newFolderId)) {
319 				var dlg = appCtxt.getMsgDialog();
320                 dlg.setMessage(ZmMsg.orgChange, DwtMessageDialog.WARNING_STYLE);
321                 dlg.popup();
322                 this.enableToolbar(true);
323                 return false;
324 			}
325 		}
326 
327         var ret = this._initiateSaveWithChecks(appt, attId, numRecurrence);
328 		return ret;
329 	}
330 
331 	return false;
332 };
333 
334 ZmApptComposeController.prototype._initiateSaveWithChecks =
335 function(appt, attId, numRecurrence) {
336     var resources = appt.getAttendees(ZmCalBaseItem.EQUIPMENT);
337     var locations = appt.getAttendees(ZmCalBaseItem.LOCATION);
338     var attendees = appt.getAttendees(ZmCalBaseItem.PERSON);
339 
340     var notifyList;
341 
342     var needsPermissionCheck = (attendees && attendees.length > 0) ||
343                                (resources && resources.length > 0) ||
344                                (locations && locations.length > 0);
345 
346     var needsConflictCheck = !appt.isForward &&
347          ((resources && resources.length > 0) ||
348          // If alteredLocations specified, it implies the user
349          // has already examined and modified the location conflicts
350          // that they want - so issue no further warnings.
351 
352          // NOTE: appt.alteredLocations is disabled (and hence undefined)
353          //       for now.  It will be set once CreateAppt/ModifyAppt
354          //       SOAP API changes are completed (Bug 56464)
355           (!appt.alteredLocations && locations && locations.length > 0));
356 
357     if (needsConflictCheck) {
358         this.checkConflicts(appt, numRecurrence, attId, notifyList);
359         return false;
360     } else if (needsPermissionCheck) {
361         this.checkAttendeePermissions(appt, attId, notifyList);
362         return false;
363     } else {
364         this._saveCalItemFoRealz(appt, attId, notifyList);
365     }
366     return true;
367 };
368 
369 ZmApptComposeController.prototype.updateToolbarOps =
370 function(mode, appt) {
371 
372     var saveButton = this._toolbar.getButton(ZmOperation.SAVE);
373     var sendButton = this._toolbar.getButton(ZmOperation.SEND_INVITE);
374 
375     if (mode == ZmCalItemComposeController.APPT_MODE) {
376         saveButton.setText(ZmMsg.saveClose);
377         saveButton.setVisible(true);
378         sendButton.setVisible(false);
379     } else {
380         sendButton.setVisible(true);
381         saveButton.setVisible(true);
382         saveButton.setText(ZmMsg.save);
383 
384         //change cancel button's text/icon to close
385         var cancelButton = this._toolbar.getButton(ZmOperation.CANCEL);
386         cancelButton.setText(ZmMsg.close);
387     }
388 	if (this._requestResponses) {
389 		this._requestResponses.setEnabled(mode !== ZmCalItemComposeController.APPT_MODE);
390 	}
391 
392     if ((this._mode == ZmCalItem.MODE_PROPOSE_TIME) || ZmCalItem.FORWARD_MAPPING[this._mode]) {
393         sendButton.setVisible(true);
394         saveButton.setVisible(false);
395         // Enable the RequestResponse when Forwarding
396 		if (this._requestResponses) {
397 			this._requestResponses.setEnabled(this._mode !== ZmCalItem.MODE_PROPOSE_TIME);
398 		}
399     }
400 
401 };
402 
403 ZmApptComposeController.prototype._initToolbar =
404 function(mode) {
405 
406     ZmCalItemComposeController.prototype._initToolbar.call(this, mode);
407 
408     //use send button for forward appt view
409     //Switch Save Btn label n listeners 
410     var saveButton = this._toolbar.getButton(ZmOperation.SAVE);
411     saveButton.removeSelectionListeners();
412     if(ZmCalItem.FORWARD_MAPPING[mode]) {
413         saveButton.addSelectionListener(new AjxListener(this, this._sendBtnListener));
414     }else {
415         saveButton.addSelectionListener(new AjxListener(this, this._saveBtnListener));
416     }
417 
418     var sendButton = this._toolbar.getButton(ZmOperation.SEND_INVITE);
419     sendButton.removeSelectionListeners();
420     sendButton.addSelectionListener(new AjxListener(this, this._sendBtnListener));
421 
422 	var saveButton = this._toolbar.getButton(ZmOperation.SAVE);
423 	saveButton.setToolTipContent(ZmMsg.saveToCalendar);
424 	
425     var btn = this._toolbar.getButton(ZmOperation.ATTACHMENT);
426     if(btn)
427         btn.setEnabled(!(this._mode == ZmCalItem.MODE_PROPOSE_TIME || ZmCalItem.FORWARD_MAPPING[mode]));
428 };
429 
430 ZmApptComposeController.prototype._sendListener =
431 function(ev){
432 
433      var appt = this._composeView.getApptEditView()._calItem;
434 
435      if(!appt.inviteNeverSent){
436         this._sendAfterExceptionCheck();
437      }
438      else{this._sendContinue();}
439 
440      return true;
441 };
442 
443 ZmApptComposeController.prototype._sendAfterExceptionCheck =
444 function(){
445      var appt = this._composeView.getApptEditView()._calItem;
446      var isExceptionAllowed = appCtxt.get(ZmSetting.CAL_EXCEPTION_ON_SERIES_TIME_CHANGE);
447      var isEditingSeries = (this._mode == ZmCalItem.MODE_EDIT_SERIES);
448      var showWarning = appt.isRecurring() && appt.hasEx && isEditingSeries && appt.getAttendees(ZmCalBaseItem.PERSON) && !isExceptionAllowed && this._checkIsDirty(ZmApptEditView.CHANGES_TIME_RECURRENCE);
449      if(showWarning){
450           var dialog = appCtxt.getYesNoCancelMsgDialog();
451 		  dialog.setMessage(ZmMsg.recurrenceUpdateWarning, DwtMessageDialog.WARNING_STYLE);
452           dialog.registerCallback(DwtDialog.YES_BUTTON, this._sendContinue, this,[dialog]);
453           dialog.registerCallback(DwtDialog.NO_BUTTON, this._dontSend,this,[dialog]);
454           dialog.getButton(DwtDialog.CANCEL_BUTTON).setText(ZmMsg.discard);
455 		  dialog.registerCallback(DwtDialog.CANCEL_BUTTON, this._dontSendAndClose,this,[dialog]);
456 		  dialog.popup();
457     }
458     else{
459         this._sendContinue();
460     }
461 }
462 
463 ZmApptComposeController.prototype._dontSend =
464 function(dialog){
465     this._revertWarningDialog(dialog);
466 }
467 
468 ZmApptComposeController.prototype._dontSendAndClose =
469 function(dialog){
470 this._revertWarningDialog(dialog);
471 this.closeView();
472 }
473 
474 ZmApptComposeController.prototype._revertWarningDialog =
475 function(dialog){
476     if(dialog){
477         dialog.popdown();
478         dialog.getButton(DwtDialog.CANCEL_BUTTON).setText(ZmMsg.cancel);
479     }
480 }
481 
482 ZmApptComposeController.prototype._sendContinue =
483 function(dialog){
484     this._revertWarningDialog(dialog);
485     this._action = ZmCalItemComposeController.SEND;
486     this.enableToolbar(false);
487 	if (this._doSave() === false) {
488 		return;
489     }
490 	this.closeView();
491 }
492 
493 ZmApptComposeController.prototype.isSave =
494 function(){
495     return (this._action == ZmCalItemComposeController.SAVE); 
496 };
497 
498 ZmApptComposeController.prototype._saveBtnListener =
499 function(ev) {
500     delete this._attendeeValidated;
501     return this._saveListener(ev, true);
502 };
503 
504 ZmApptComposeController.prototype._sendBtnListener =
505 function(ev) {
506     delete this._attendeeValidated;
507     return this._sendListener(ev);
508 };
509 
510 ZmApptComposeController.prototype._saveListener =
511 function(ev, force) {
512     var isMeeting = !this._composeView.isAttendeesEmpty();
513 
514     this._action = isMeeting ? ZmCalItemComposeController.SAVE : ZmCalItemComposeController.SAVE_CLOSE;
515 
516     //attendee should not have send/save option
517     if(!this._composeView.isOrganizer()) {
518         this._action = ZmCalItemComposeController.SAVE_CLOSE;
519     }
520     this.enableToolbar(false);
521 
522     var dlg = appCtxt.getOkCancelMsgDialog();
523     if(dlg.isPoppedUp()){
524         dlg.popdown();
525     }
526 
527     if(!force && this._action == ZmCalItemComposeController.SAVE){
528         var appt = this._composeView.getApptEditView()._calItem;
529         var inviteNeverSent = (appt && appt.inviteNeverSent);
530         var showDlg = true;
531         if(appt.isDraft){
532             showDlg = false;
533         }
534         if(showDlg && !inviteNeverSent && (this._checkIsDirty(ZmApptEditView.CHANGES_SIGNIFICANT)
535                 ||  this._checkIsDirty(ZmApptEditView.CHANGES_LOCAL))){
536             showDlg = false;
537         }
538         if(showDlg){
539             dlg.setMessage(ZmMsg.saveApptInfoMsg);
540             dlg.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._saveListener, [ev, true]));
541             dlg.popup();
542             this.enableToolbar(true);
543             return;
544         }
545     }
546 
547 	if (this._doSave() === false) {
548 		return;
549     }
550 };
551 
552 ZmApptComposeController.prototype._createToolBar =
553 function() {
554 
555     ZmCalItemComposeController.prototype._createToolBar.call(this);
556 
557 	var optionsButton = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
558 	if (optionsButton){
559         optionsButton.setVisible(true); //might be invisible if not ZmSetting.HTML_COMPOSE_ENABLED (see ZmCalItemComposeController._createToolBar)
560 
561         var m = optionsButton.getMenu();
562         if (m) {
563             var sepMi = new DwtMenuItem({parent:m, style:DwtMenuItem.SEPARATOR_STYLE});
564         }
565         else {
566             m = new DwtMenu({parent:optionsButton});
567             optionsButton.setMenu(m);
568         }
569 
570         var mi = this._requestResponses = new DwtMenuItem({parent:m, style:DwtMenuItem.CHECK_STYLE});
571         mi.setText(ZmMsg.requestResponses);
572         mi.setChecked(true, true);
573 
574         sepMi = new DwtMenuItem({parent:m, style:DwtMenuItem.SEPARATOR_STYLE});
575         mi = new DwtMenuItem({parent:m, style:DwtMenuItem.NO_STYLE});
576         mi.setText(ZmMsg.suggestionPreferences);
577         mi.addSelectionListener(this._prefListener.bind(this));
578     }
579 
580 	this._toolbar.addSelectionListener(ZmOperation.SPELL_CHECK, new AjxListener(this, this._spellCheckListener));
581 };
582 
583 ZmApptComposeController.prototype._prefListener =
584 function(ev) {
585     this._prefDialog = appCtxt.getSuggestionPreferenceDialog();
586     this._prefDialog.popup(this.getCalendarAccount());
587 };
588 
589 ZmApptComposeController.prototype.setRequestResponsesEnabled =
590 function(enabled) {
591    if (this._requestResponses)
592    this._requestResponses.setEnabled(enabled);
593 };
594 
595 ZmApptComposeController.prototype.setRequestResponses =
596 function(requestResponses) {
597    if (this._requestResponses)
598    this._requestResponses.setChecked(requestResponses);
599 };
600 
601 ZmApptComposeController.prototype.getRequestResponses =
602 function() {
603     if (this._requestResponses)
604     return this._requestResponses.getEnabled() ? this._requestResponses.getChecked() : true;
605 };
606 
607 ZmApptComposeController.prototype.getNotifyList =
608 function(addrs) {
609     var notifyList = [];
610     for(var i = 0; i < addrs.length; i++) {
611         notifyList.push(addrs[i]._inviteAddress || addrs[i].address || addrs[i].getEmail());
612     }
613 
614     return notifyList;
615 }; 
616 
617 ZmApptComposeController.prototype.isAttendeesEmpty =
618 function(appt) {
619     var resources = appt.getAttendees(ZmCalBaseItem.EQUIPMENT);
620 	var locations = appt.getAttendees(ZmCalBaseItem.LOCATION);
621 	var attendees = appt.getAttendees(ZmCalBaseItem.PERSON);
622 
623 	var isAttendeesNotEmpty = (attendees && attendees.length > 0) ||
624 							   (resources && resources.length > 0) ||
625 							   (locations && locations.length > 0);
626     return !isAttendeesNotEmpty
627 };
628 
629 ZmApptComposeController.prototype.checkConflicts =
630 function(appt, numRecurrence, attId, notifyList) {
631 	var resources = appt.getAttendees(ZmCalBaseItem.EQUIPMENT);
632 	var locations = appt.getAttendees(ZmCalBaseItem.LOCATION);
633 	var attendees = appt.getAttendees(ZmCalBaseItem.PERSON);
634 
635 	var needsPermissionCheck = (attendees && attendees.length > 0) ||
636 							   (resources && resources.length > 0) ||
637 							   (locations && locations.length > 0);
638 
639 	var callback = needsPermissionCheck
640 		? (new AjxCallback(this, this.checkAttendeePermissions, [appt, attId, notifyList]))
641 		: (new AjxCallback(this, this.saveCalItemContinue, [appt, attId, notifyList]));
642 
643 	this._checkResourceConflicts(appt, numRecurrence, callback, false, true, false);
644 };
645 
646 ZmApptComposeController.prototype.checkAttendeePermissions =
647 function(appt, attId, notifyList) {
648 	var newEmails = [];
649 
650 	var attendees = appt.getAttendees(ZmCalBaseItem.PERSON);
651 	if (attendees && attendees.length > 0) {
652 		for (var i = 0; i < attendees.length; i++) {
653 			newEmails.push(attendees[i].getEmail());
654 		}
655 	}
656 
657 	var locations = appt.getAttendees(ZmCalBaseItem.LOCATION);
658 	if (locations && locations.length > 0) {
659 		for (var i = 0; i < locations.length; i++) {
660 			newEmails.push(locations[i].getEmail());
661 		}
662 	}
663 
664 	var resources = appt.getAttendees(ZmCalBaseItem.EQUIPMENT);
665 	if (resources && resources.length > 0) {
666 		for (var i = 0; i < resources.length; i++) {
667 			newEmails.push(resources[i].getEmail());
668 		}
669 	}
670 
671 	if (newEmails.length) {
672 		this.checkPermissionRequest(newEmails, appt, attId, notifyList);
673 		return false;
674 	}
675 
676 	// otherwise, just save the appointment
677 	this._saveCalItemFoRealz(appt, attId, notifyList);
678 };
679 
680 // Expose the resource conflict check call to allow the ApptEditView to
681 // trigger a location conflict check
682 ZmApptComposeController.prototype.getCheckResourceConflicts =
683 function(appt, numRecurrence, callback, displayConflictDialog) {
684     return this.checkResourceConflicts.bind(this, appt, numRecurrence, callback, displayConflictDialog);
685 }
686 
687 ZmApptComposeController.prototype.checkResourceConflicts =
688 function(appt, numRecurrence, callback, displayConflictDialog) {
689 	return this._checkResourceConflicts(appt, numRecurrence, callback,
690         true, displayConflictDialog, true);
691 };
692 
693 ZmApptComposeController.prototype._checkResourceConflicts =
694 function(appt, numRecurrence, callback, showAll, displayConflictDialog, conflictCallbackOverride) {
695 	var mode = appt.viewMode;
696 	var reqId;
697 	if (mode!=ZmCalItem.MODE_NEW_FROM_QUICKADD && mode!= ZmCalItem.MODE_NEW) {
698 		if(appt.isRecurring() && mode != ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) {
699 			// for recurring appt - user GetRecurRequest to get full recurrence
700 			// information and use the component in CheckRecurConflictRequest
701 			var recurInfoCallback = this._checkResourceConflicts.bind(this,
702                 appt, numRecurrence, callback, showAll, displayConflictDialog, conflictCallbackOverride);
703 			reqId = this.getRecurInfo(appt, recurInfoCallback);
704 		}
705         else {
706 			reqId = this._checkResourceConflicts(appt, numRecurrence, callback,
707                 showAll, displayConflictDialog, conflictCallbackOverride);
708 		}
709 	}
710     else {
711 		reqId = this._checkResourceConflicts(appt, numRecurrence, callback,
712             showAll, displayConflictDialog, conflictCallbackOverride);
713 	}
714 	return reqId;
715 };
716 
717 /**
718  * JSON request is used to make easy re-use of "comp" elements from GetRecurResponse.
719  * 
720  * @private
721  */
722 ZmApptComposeController.prototype._checkResourceConflicts =
723 function(appt, numRecurrence, callback, showAll, displayConflictDialog,
724          conflictCallbackOverride, recurInfo) {
725 	var mode = appt.viewMode,
726 	    jsonObj = {
727             CheckRecurConflictsRequest: {
728                 _jsns:"urn:zimbraMail"
729             }
730         },
731 	    request = jsonObj.CheckRecurConflictsRequest,
732         startDate = new Date(appt.startDate),
733         comps = request.comp = [],
734         comp = request.comp[0] = {},
735         recurrence,
736         recur;
737 
738     startDate.setHours(0,0,0,0);
739 	request.s = startDate.getTime();
740 	request.e = ZmApptComposeController.getCheckResourceConflictEndTime(
741 	        appt, startDate, numRecurrence);
742 
743     if (showAll) {
744         request.all = "1";
745     }
746 
747 	if (mode!=ZmCalItem.MODE_NEW_FROM_QUICKADD && mode!= ZmCalItem.MODE_NEW) {
748 		request.excludeUid = appt.uid;
749 	}
750 
751 
752     appt._addDateTimeToRequest(request, comp);
753 
754     //preserve the EXDATE (exclude recur) information
755     if(recurInfo) {
756         recurrence = appt.getRecurrence();
757         recur = (recurInfo && recurInfo.comp) ? recurInfo.comp[0].recur : null;
758         recurrence.parseExcludeInfo(recur);
759     }
760 
761     if(mode != ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) {
762         appt._recurrence.setJson(comp);
763     }
764 
765     this.setExceptFromRecurInfo(request, recurInfo);
766 
767     appt.addAttendeesToChckConflictsRequest(request);
768 
769     return appCtxt.getAppController().sendRequest({
770         jsonObj: jsonObj,
771         asyncMode: true,
772         callback: (new AjxCallback(this, this._handleResourceConflict, [appt, callback,
773             displayConflictDialog, conflictCallbackOverride])),
774         errorCallback: (new AjxCallback(this, this._handleResourceConflictError, [appt, callback])),
775         noBusyOverlay: true
776     });
777 };
778 
779 ZmApptComposeController.prototype.setExceptFromRecurInfo =
780 function(request, recurInfo) {
781 	var exceptInfo = recurInfo && recurInfo.except,
782         i,
783         s,
784         e,
785         exceptId,
786         except,
787         sNode,
788         eNode,
789         exceptIdNode;
790 	if (!exceptInfo) { return; }
791 
792 	for (i in exceptInfo) {
793 		s = exceptInfo[i].s ? exceptInfo[i].s[0] : null;
794 		e = exceptInfo[i].e ? exceptInfo[i].e[0] : null;
795 		exceptId = exceptInfo[i].exceptId ? exceptInfo[i].exceptId[0] : null;
796 
797 		except = request.except = {};
798 		if (s) {
799 			sNode = except.s = {};
800 			sNode.d = s.d;
801 			if (s.tz) {
802 				sNode.tz = s.tz;
803 			}
804 		}
805 
806 		if (e) {
807 			eNode = except.e = {};
808 			eNode.d = e.d;
809 			if (e.tz) {
810 				eNode.tz = e.tz;
811 			}
812 		}
813 
814 		if (exceptId) {
815 			exceptIdNode = except.exceptId = {};
816 			exceptIdNode.d = exceptId.d;
817 			if (exceptId.tz) {
818 				exceptIdNode.tz = exceptId.tz;
819 			}
820 		}
821 	}
822 };
823 
824 // Use the (numRecurrences * the recurrence period * repeat.customCount)
825 // time interval to determine the endDate of the resourceConflict check
826 ZmApptComposeController.getCheckResourceConflictEndTime =
827 function(appt, originalStartDate, numRecurrence) {
828     var startDate = new Date(originalStartDate.getTime());
829     var recurrence = appt.getRecurrence();
830     var endDate;
831     var range = recurrence.repeatCustomCount * numRecurrence;
832     if (recurrence.repeatType == ZmRecurrence.NONE) {
833         endDate = appt.endDate;
834     } else if (recurrence.repeatType == ZmRecurrence.DAILY) {
835         endDate = AjxDateUtil.roll(startDate, AjxDateUtil.DAY, range);
836     } else if (recurrence.repeatType == ZmRecurrence.WEEKLY) {
837         endDate = AjxDateUtil.roll(startDate, AjxDateUtil.WEEK, range);
838     } else if (recurrence.repeatType == ZmRecurrence.MONTHLY) {
839         endDate = AjxDateUtil.roll(startDate, AjxDateUtil.MONTH, range);
840     } else if (recurrence.repeatType == ZmRecurrence.YEARLY) {
841         endDate = AjxDateUtil.roll(startDate, AjxDateUtil.YEAR, range);
842     }
843     var endTime = endDate.getTime();
844     if (recurrence.repeatEndDate) {
845         var repeatEndTime = recurrence.repeatEndDate.getTime();
846         if (endTime > repeatEndTime) {
847             endTime = repeatEndTime;
848         }
849     }
850     return endTime;
851 }
852 
853 /**
854  * Gets the recurrence definition of an appointment.
855  * 
856  * @param {ZmAppt}	appt 	the appointment
857  * @param {AjxCallback}	recurInfoCallback 		the callback module after getting recurrence info
858  */
859 ZmApptComposeController.prototype.getRecurInfo =
860 function(appt, recurInfoCallback) {
861 	var soapDoc = AjxSoapDoc.create("GetRecurRequest", "urn:zimbraMail");
862 	soapDoc.setMethodAttribute("id", appt.id);
863 
864 	return appCtxt.getAppController().sendRequest({
865 		soapDoc: soapDoc,
866 		asyncMode: true,
867 		callback: (new AjxCallback(this, this._handleRecurInfo, [appt, recurInfoCallback])),
868 		errorCallback: (new AjxCallback(this, this._handleRecurInfoError, [appt, recurInfoCallback])),
869 		noBusyOverlay: true
870 	});
871 };
872 
873 /**
874  * Handle Response for GetRecurRequest call
875  * 
876  * @private
877  */
878 ZmApptComposeController.prototype._handleRecurInfo =
879 function(appt, callback, result) {
880 	var recurResponse = result.getResponse().GetRecurResponse;
881 	if (callback) {
882 		callback.run(recurResponse);
883 	}
884 };
885 
886 ZmApptComposeController.prototype._handleRecurInfoError =
887 function(appt, callback, result) {
888 	if (callback) {
889 		callback.run();
890 	}
891 };
892 
893 ZmApptComposeController.prototype.checkPermissionRequest =
894 function(names, appt, attId, notifyList) {
895     // CheckPermissions to be retired after IronMaiden.  Replaced with CheckRights
896     var jsonObj = {CheckRightsRequest:{_jsns:"urn:zimbraAccount"}};
897     var request = jsonObj.CheckRightsRequest;
898 
899     request.target = [];
900     for (var i = 0; i < names.length; i++) {
901         var targetInstance = {
902             type: "account",
903             by:   "name",
904             key:   names[i]
905         };
906         targetInstance.right = [{_content: "invite"}];
907         request.target.push(targetInstance);
908     }
909 
910     var respCallback  = new AjxCallback(this, this.handleCheckRightsResponse, [appt, attId, names, notifyList]);
911     var errorCallback = new AjxCallback(this, this.handleCheckRightsResponse, [appt, attId, names, notifyList]);
912     appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback, errorCallback: errorCallback, noBusyOverlay:true});
913 };
914 
915 ZmApptComposeController.prototype.handleCheckRightsResponse =
916 function(appt, attId, names, notifyList, response) {
917 	var checkRightsResponse = response && response._data && response._data.CheckRightsResponse;
918 	if (checkRightsResponse && checkRightsResponse.target) {
919 		var deniedAttendees = [];
920 		for (var i in checkRightsResponse.target) {
921 			if (!checkRightsResponse.target[i].allow) {
922 				deniedAttendees.push(names[i]);
923 			}
924 		}
925 		if (deniedAttendees.length > 0) {
926 			var msg =  AjxMessageFormat.format(ZmMsg.invitePermissionDenied, [deniedAttendees.join(",")]);
927 			var msgDialog = appCtxt.getMsgDialog();
928 			msgDialog.reset();
929 			msgDialog.setMessage(msg, DwtMessageDialog.INFO_STYLE);
930 			msgDialog.popup();
931             this.enableToolbar(true);
932 			return;
933 		}
934 	}
935 	this.saveCalItemContinue(appt, attId, notifyList);
936 };
937 
938 ZmApptComposeController.prototype._saveAfterPermissionCheck =
939 function(appt, attId, notifyList, msgDialog) {
940 	msgDialog.popdown();
941 	this.saveCalItemContinue(appt, attId, notifyList);
942 };
943 
944 ZmApptComposeController.prototype.saveCalItemContinue =
945 function(appt, attId, notifyList) {
946 	this._saveCalItemFoRealz(appt, attId, notifyList);
947 };
948 
949 ZmApptComposeController.prototype.handleCheckPermissionResponseError =
950 function(appt, attId, names, notifyList, response) {
951 	var resp = response && response._data && response._data.BatchResponse;
952 	this.saveCalItemContinue(appt, attId, notifyList);
953 };
954 
955 ZmApptComposeController.prototype._handleResourceConflict =
956 function(appt, callback, displayConflictDialog, conflictCallbackOverride, result) {
957 	var conflictExist = false;
958     var inst = null;
959 	if (result) {
960 		var conflictResponse = result.getResponse().CheckRecurConflictsResponse;
961 		inst = this._conflictingInstances = conflictResponse.inst;
962 		if (inst && inst.length > 0) {
963 			if (displayConflictDialog) {
964 				this.showConflictDialog(appt, callback, inst);
965 			}
966 			conflictExist = true;
967 			this.enableToolbar(true);
968 		}
969 	}
970 
971 	if ((conflictCallbackOverride || !conflictExist) && callback) {
972 		callback.run(inst);
973 	}
974 };
975 
976 ZmApptComposeController.prototype.showConflictDialog =
977 function(appt, callback, inst) {
978 	DBG.println("conflict instances :" + inst.length);
979 
980 	var conflictDialog = this.getConflictDialog();
981 	conflictDialog.initialize(inst, appt, callback);
982 	conflictDialog.popup();
983 };
984 
985 ZmApptComposeController.prototype.getConflictDialog =
986 function() {
987 	if (!this._resConflictDialog) {
988 		this._resConflictDialog = new ZmResourceConflictDialog(this._shell);
989 	}
990 	return this._resConflictDialog;
991 };
992 
993 ZmApptComposeController.prototype._handleResourceConflictError =
994 function(appt, callback) {
995 	// continue with normal saving process via callback
996 	if (callback) {
997 		callback.run();
998 	}
999 };
1000 
1001 ZmApptComposeController.prototype.getFreeBusyInfo =
1002 function(startTime, endTime, emailList, callback, errorCallback, noBusyOverlay) {
1003 	var soapDoc = AjxSoapDoc.create("GetFreeBusyRequest", "urn:zimbraMail");
1004 	soapDoc.setMethodAttribute("s", startTime);
1005 	soapDoc.setMethodAttribute("e", endTime);
1006 	soapDoc.setMethodAttribute("uid", emailList);
1007 
1008 	var acct = (appCtxt.multiAccounts)
1009 		? this._composeView.getApptEditView().getCalendarAccount() : null;
1010 
1011 	return appCtxt.getAppController().sendRequest({
1012 		soapDoc: soapDoc,
1013 		asyncMode: true,
1014 		callback: callback,
1015 		errorCallback: errorCallback,
1016 		noBusyOverlay: noBusyOverlay,
1017 		accountName: (acct ? acct.name : null)
1018 	});
1019 };
1020 
1021 ZmApptComposeController.prototype._createComposeView =
1022 function() {
1023 	return (new ZmApptComposeView(this._container, null, this._app, this));
1024 };
1025 
1026 ZmApptComposeController.prototype._setComposeTabGroup =
1027 function(setFocus) {
1028 	DBG.println(AjxDebug.DBG2, "_setComposeTabGroup");
1029 	var tg = this._createTabGroup();
1030 	var rootTg = appCtxt.getRootTabGroup();
1031 	tg.newParent(rootTg);
1032 	tg.addMember(this._toolbar);
1033 	var editView = this._composeView.getApptEditView();
1034 	editView._addTabGroupMembers(tg);
1035 
1036 	var focusItem = editView._savedFocusMember || editView._getDefaultFocusItem() || tg.getFirstMember(true);
1037 	var ta = new AjxTimedAction(this, this._setFocus, [focusItem, !setFocus]);
1038 	AjxTimedAction.scheduleAction(ta, 10);
1039 };
1040 
1041 ZmApptComposeController.prototype._getDefaultFocusItem =
1042 function() {
1043     return this._composeView.getApptEditView()._getDefaultFocusItem();	
1044 };
1045 
1046 ZmApptComposeController.prototype.getKeyMapName =
1047 function() {
1048 	return ZmKeyMap.MAP_EDIT_APPOINTMENT;
1049 };
1050 
1051 
1052 // Private / Protected methods
1053 
1054 ZmApptComposeController.prototype._attendeesUpdated =
1055 function(appt, attId, attendees, origAttendees) {
1056 	// create hashes of emails for comparison
1057 	var origEmails = {};
1058 	for (var i = 0; i < origAttendees.length; i++) {
1059 		var email = origAttendees[i].getEmail();
1060 		origEmails[email] = true;
1061 	}
1062 	var fwdEmails = {};
1063 	var fwdAddrs = appt.getForwardAddress();
1064 	for(var i=0;i<fwdAddrs.length;i++) {
1065 		var email = fwdAddrs[i].getAddress();
1066 		fwdEmails[email] = true;
1067 	}
1068 	var curEmails = {};
1069 	for (var i = 0; i < attendees.length; i++) {
1070 		var email = attendees[i].getEmail();
1071 		curEmails[email] = true;
1072 	}
1073 
1074 	// walk the current list of attendees and check if there any new ones
1075 	for (var i = 0 ; i < attendees.length; i++) {
1076 		var email = attendees[i].getEmail();
1077 		if (!origEmails[email] && !fwdEmails[email]) {
1078 			this._addedAttendees.push(email);
1079 		}
1080 	}
1081     
1082 	for (var i = 0 ; i < origAttendees.length; i++) {
1083 		var email = origAttendees[i].getEmail();
1084 		if (!curEmails[email]) {
1085 			this._removedAttendees.push(email);
1086 		}
1087 	}
1088 
1089 	if (this._addedAttendees.length > 0 || this._removedAttendees.length > 0) {
1090 		if (!this._notifyDialog) {
1091 			this._notifyDialog = new ZmApptNotifyDialog(this._shell);
1092 			this._notifyDialog.addSelectionListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._notifyDlgOkListener));
1093 			this._notifyDialog.addSelectionListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._notifyDlgCancelListener));
1094 		}
1095 		appt.setMailNotificationOption(true);
1096 		this._notifyDialog.initialize(appt, attId, this._addedAttendees, this._removedAttendees);
1097 		this._notifyDialog.popup();
1098         this.enableToolbar(true);
1099 		return true;
1100 	}
1101 
1102 	return false;
1103 };
1104 
1105 
1106 // Listeners
1107 
1108 // Cancel button was pressed
1109 ZmApptComposeController.prototype._cancelListener =
1110 function(ev) {
1111 
1112     var isDirty = false;
1113 
1114     if(this._composeView.gotNewAttachments()) {
1115         isDirty = true;
1116     }else {
1117         var appt = this._composeView.getAppt(this._attId);
1118         if (appt && !appt.inviteNeverSent){
1119            //Check for Significant Changes
1120             isDirty = this._checkIsDirty(ZmApptEditView.CHANGES_SIGNIFICANT)
1121         }
1122     }
1123 
1124     if(isDirty){
1125         this._getChangesDialog().popup();
1126         this.enableToolbar(true);
1127         return;
1128     }
1129 
1130 	this._app.getCalController().setNeedsRefresh(true);
1131 
1132 	ZmCalItemComposeController.prototype._cancelListener.call(this, ev);
1133 };
1134 
1135 ZmApptComposeController.prototype._printListener =
1136 function() {
1137 	var calItem = this._composeView._apptEditView._calItem;
1138 	var url = ["/h/printappointments?id=", calItem.invId, "&tz=", AjxTimezone.getServerId(AjxTimezone.DEFAULT)]; //bug:53493
1139     if (appCtxt.isOffline) {
1140         url.push("&zd=true", "&acct=", this._composeView.getApptEditView().getCalendarAccount().name);
1141     }
1142 	window.open(appContextPath + url.join(""), "_blank");
1143 };
1144 
1145 
1146 // Callbacks
1147 
1148 ZmApptComposeController.prototype._notifyDlgOkListener =
1149 function(ev) {
1150 	var notifyList = this._notifyDialog.notifyNew() ? this._addedAttendees : null;
1151 	this._saveCalItemFoRealz(this._notifyDialog.getAppt(), this._notifyDialog.getAttId(), notifyList);
1152 };
1153 
1154 ZmApptComposeController.prototype._notifyDlgCancelListener =
1155 function(ev) {
1156 	this._addedAttendees.length = this._removedAttendees.length = 0;
1157 };
1158 
1159 ZmApptComposeController.prototype._changeOrgCallback =
1160 function(appt, attId, dlg) {
1161 	dlg.popdown();
1162 	this._saveCalItemFoRealz(appt, attId);
1163 };
1164 
1165 ZmApptComposeController.prototype._saveCalItemFoRealz =
1166 function(calItem, attId, notifyList, force){
1167     force = force || ( this._action == ZmCalItemComposeController.SEND );
1168 
1169     //organizer forwarding an appt is same as organizer editing appt while adding new attendees
1170     if(calItem.isForward) {
1171         notifyList = this.getForwardNotifyList(calItem);
1172     }
1173 
1174     this._composeView.getApptEditView().resetParticipantStatus();
1175 
1176     // NOTE: Once CreateAppt/ModifyAppt SOAP API changes are completed (Bug 56464), pass to
1177     // the base _saveCalItemFoRealz appt.alteredLocations, to create a set of location
1178     // exceptions along with creation/modification of the underlying appt
1179     // *** NOT DONE ***
1180     ZmCalItemComposeController.prototype._saveCalItemFoRealz.call(this, calItem, attId, notifyList, force);
1181 };
1182 
1183 /**
1184  * To get the array of forward email addresses
1185  *
1186  * @param	{ZmAppt}	appt		the appointment
1187  * @return	{Array}	an array of email addresses
1188  */
1189 ZmApptComposeController.prototype.getForwardNotifyList =
1190 function(calItem){
1191     var fwdAddrs = calItem.getForwardAddress();
1192     var notifyList = [];
1193     for(var i=0;i<fwdAddrs.length;i++) {
1194         var email = fwdAddrs[i].getAddress();
1195         notifyList.push(email);
1196     }
1197     return notifyList;
1198 };
1199 
1200 ZmApptComposeController.prototype._doSaveCalItem =
1201 function(appt, attId, callback, errorCallback, notifyList){
1202     delete this._attendeeValidated;
1203     if(this._action == ZmCalItemComposeController.SEND){
1204         appt.send(attId, callback, errorCallback, notifyList);
1205     }else{
1206         var isMeeting = appt.hasAttendees();
1207         if(isMeeting){
1208             this._draftFlag = appt.isDraft || appt.inviteNeverSent || this._checkIsDirty(ZmApptEditView.CHANGES_INSIGNIFICANT);
1209         }else{
1210             this._draftFlag = false;
1211         }
1212         appt.save(attId, callback, errorCallback, notifyList, this._draftFlag);
1213     }
1214 };
1215 
1216 ZmApptComposeController.prototype._handleResponseSave =
1217 function(calItem, result) {
1218 	if (calItem.__newFolderId) {
1219 		var folder = appCtxt.getById(calItem.__newFolderId);
1220 		calItem.__newFolderId = null;
1221 		this._app.getListController()._doMove(calItem, folder, null, false);
1222 	}
1223 
1224     var isNewAppt;
1225     var viewMode = calItem.getViewMode();
1226     if(viewMode == ZmCalItem.MODE_NEW || viewMode == ZmCalItem.MODE_NEW_FROM_QUICKADD || viewMode == ZmAppt.MODE_DRAG_OR_SASH) {
1227         isNewAppt = true;
1228     }
1229 
1230     if(this.isCloseAction()) {
1231         calItem.handlePostSaveCallbacks();
1232         this.closeView();	    
1233     }else {
1234         this.enableToolbar(true);
1235         if(isNewAppt) {
1236             viewMode = calItem.isRecurring() ? ZmCalItem.MODE_EDIT_SERIES : ZmCalItem.MODE_EDIT;
1237         }
1238         calItem.setFromSavedResponse(result);
1239         if(this._action == ZmCalItemComposeController.SAVE){
1240             calItem.isDraft = this._draftFlag;
1241             calItem.draftUpdated = true;
1242         }
1243         this._composeView.set(calItem, viewMode);        
1244     }
1245 
1246     var msg = isNewAppt ? ZmMsg.apptCreated : ZmMsg.apptSaved;
1247     if(calItem.hasAttendees()){
1248         if(this._action == ZmCalItemComposeController.SAVE || this._action == ZmCalItemComposeController.SAVE_CLOSE){
1249             msg = ZmMsg.apptSaved;
1250         }else{
1251             if(viewMode != ZmCalItem.MODE_NEW){
1252                 msg = ZmMsg.apptSent;
1253             }
1254         }              
1255     }
1256     appCtxt.setStatusMsg(msg);
1257     
1258     appCtxt.notifyZimlets("onSaveApptSuccess", [this, calItem, result]);//notify Zimlets on success
1259 };
1260 
1261 ZmApptComposeController.prototype._resetNavToolBarButtons =
1262 function(view) {
1263 	//do nothing
1264 };
1265 
1266 ZmApptComposeController.prototype._clearInvalidAttendeesCallback =
1267 function(appt, attId, dlg) {
1268 	dlg.popdown();
1269     this.clearInvalidAttendees();
1270 	delete this._invalidAttendees;
1271     if(this._action == ZmCalItemComposeController.SAVE){
1272 	    this._saveListener();
1273     }else{
1274         this._sendListener();
1275     }
1276 };
1277 
1278 ZmApptComposeController.prototype.clearInvalidAttendees =
1279 function() {
1280 	this._invalidAttendees = [];
1281 };
1282 
1283 ZmApptComposeController.prototype.addInvalidAttendee =
1284 function(item) {
1285 	if (AjxUtil.indexOf(this._invalidAttendees, item)==-1) {
1286 		this._invalidAttendees.push(item);
1287 	}
1288 };
1289 
1290 ZmApptComposeController.prototype.closeView =
1291 function() {
1292 	this._closeView();
1293 };
1294 
1295 ZmApptComposeController.prototype.forwardInvite =
1296 function(newAppt) {
1297 	this.show(newAppt, ZmCalItem.MODE_FORWARD_INVITE);
1298 };
1299 
1300 ZmApptComposeController.prototype.proposeNewTime =
1301 function(newAppt) {
1302 	this.show(newAppt, ZmCalItem.MODE_PROPOSE_TIME);
1303 };
1304 
1305 ZmApptComposeController.prototype.initComposeView =
1306 function(initHide) {
1307     
1308 	if (!this._composeView) {
1309 		this._composeView = this._createComposeView();
1310         var appEditView = this._composeView.getApptEditView();
1311         this._savedFocusMember = appEditView._getDefaultFocusItem();
1312 
1313 		var callbacks = {};
1314 		callbacks[ZmAppViewMgr.CB_PRE_HIDE] = new AjxCallback(this, this._preHideCallback);
1315 		callbacks[ZmAppViewMgr.CB_PRE_UNLOAD] = new AjxCallback(this, this._preUnloadCallback);
1316 		callbacks[ZmAppViewMgr.CB_POST_SHOW] = new AjxCallback(this, this._postShowCallback);
1317 		callbacks[ZmAppViewMgr.CB_PRE_SHOW] = new AjxCallback(this, this._preShowCallback);
1318 		callbacks[ZmAppViewMgr.CB_POST_HIDE] = new AjxCallback(this, this._postHideCallback);
1319 		if (!this._toolbar)
1320 			this._createToolBar();
1321 
1322 		var elements = this.getViewElements(null, this._composeView, this._toolbar);
1323 
1324 		this._app.createView({	viewId:		this._currentViewId,
1325 								viewType:	this._currentViewType,
1326 								elements:	elements,
1327 								hide:		this._elementsToHide,
1328 								controller:	this,
1329 								callbacks:	callbacks,
1330 								tabParams:	this._getTabParams()});
1331 		if (initHide) {
1332 			this._composeView.preload();
1333 		}
1334 		return true;
1335 	}
1336     else{
1337         this._savedFocusMember = this._composeView.getApptEditView()._getDefaultFocusItem();
1338     }
1339 	return false;
1340 };
1341 
1342 ZmApptComposeController.prototype.getCalendarAccount =
1343 function() {
1344     return (appCtxt.multiAccounts)
1345         ? this._composeView.getApptEditView().getCalendarAccount() : null;
1346 
1347 };
1348 
1349 ZmApptComposeController.prototype.getAttendees =
1350 function(type) {
1351     return this._composeView.getAttendees(type);
1352 };
1353 
1354 ZmApptComposeController.prototype._postHideCallback =
1355 function() {
1356 
1357 	ZmCalItemComposeController.prototype._postHideCallback(); 
1358 
1359     if (appCtxt.getCurrentAppName() == ZmApp.CALENDAR || appCtxt.get(ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL)) {
1360 		appCtxt.getAppViewMgr().displayComponent(ZmAppViewMgr.C_TREE_FOOTER, true);
1361     }
1362 };
1363 
1364 ZmApptComposeController.prototype._postShowCallback =
1365 function(view, force) {
1366 	var ta = new AjxTimedAction(this, this._setFocus);
1367 	AjxTimedAction.scheduleAction(ta, 10);
1368 };
1369 
1370 ZmApptComposeController.prototype.getWorkingInfo =
1371 function(startTime, endTime, emailList, callback, errorCallback, noBusyOverlay) {
1372    var soapDoc = AjxSoapDoc.create("GetWorkingHoursRequest", "urn:zimbraMail");
1373    soapDoc.setMethodAttribute("s", startTime);
1374    soapDoc.setMethodAttribute("e", endTime);
1375    soapDoc.setMethodAttribute("name", emailList);
1376 
1377    var acct = (appCtxt.multiAccounts)
1378        ? this._composeView.getApptEditView().getCalendarAccount() : null;
1379 
1380    return appCtxt.getAppController().sendRequest({
1381        soapDoc: soapDoc,
1382        asyncMode: true,
1383        callback: callback,
1384        errorCallback: errorCallback,
1385        noBusyOverlay: noBusyOverlay,
1386        accountName: (acct ? acct.name : null)
1387    });
1388 };
1389 
1390 ZmApptComposeController.prototype._resetToolbarOperations =
1391 function() {
1392     //do nothing - this  gets called when this controller handles a list view
1393 };
1394 
1395 // --- Subclass the ApptComposeController for saving Quick Add dialog appointments, and doing a
1396 //     save when the CalColView drag and drop is used
1397 ZmSimpleApptComposeController = function(container, app, type, sessionId) {
1398     ZmApptComposeController.apply(this, arguments);
1399     this._closeCallback = null;
1400     // Initialize a static/dummy compose view.  It is never actually used
1401     // for display (only for the function calls made to it during the save),
1402     // so it can be setup here.
1403     this.initComposeView();
1404 };
1405 
1406 ZmSimpleApptComposeController.prototype = new ZmApptComposeController;
1407 ZmSimpleApptComposeController.prototype.constructor = ZmSimpleApptComposeController;
1408 
1409 ZmSimpleApptComposeController.prototype.toString = function() { return "ZmSimpleApptComposeController"; };
1410 
1411 ZmSimpleApptComposeController.getDefaultViewType =
1412 function() {
1413 	return ZmId.VIEW_SIMPLE_ADD_APPOINTMENT;
1414 };
1415 
1416 ZmSimpleApptComposeController.prototype.doSimpleSave =
1417 function(appt, action, closeCallback, errorCallback, cancelCallback) {
1418     var ret = false;
1419     this._action = action;
1420     this._closeCallback = null;
1421     if(!appt.isValidDuration()){
1422         this._composeView.showInvalidDurationMsg();
1423     } else if (appt) {
1424         this._simpleCloseCallback  = closeCallback;
1425         this._simpleErrorCallback  = errorCallback;
1426         this._simpleCancelCallback = cancelCallback;
1427         ret = this._initiateSaveWithChecks(appt, null, ZmTimeSuggestionPrefDialog.DEFAULT_NUM_RECURRENCE);
1428     }
1429     return ret;
1430 };
1431 
1432 ZmSimpleApptComposeController.prototype._handleResponseSave =
1433 function(calItem, result) {
1434     if (this._simpleCloseCallback) {
1435         this._simpleCloseCallback.run();
1436     }
1437     appCtxt.notifyZimlets("onSaveApptSuccess", [this, calItem, result]);//notify Zimlets on success
1438 };
1439 
1440 ZmSimpleApptComposeController.prototype._getErrorSaveStatus =
1441 function(calItem, ex) {
1442     var status = ZmCalItemComposeController.prototype._getErrorSaveStatus.call(this, calItem, ex);
1443     if (!status.continueSave && this._simpleErrorCallback) {
1444         this._simpleErrorCallback.run(this);
1445     }
1446 
1447     return status;
1448 };
1449 
1450 ZmSimpleApptComposeController.prototype.initComposeView =
1451 function() {
1452 	if (!this._composeView) {
1453 		// Create an empty compose view and make it always return isDirty == true
1454 		this._composeView = this._createComposeView();
1455 		this._composeView.isDirty = function() { return true; };
1456 		return true;
1457     }
1458 	return false;
1459 };
1460 
1461 ZmSimpleApptComposeController.prototype.enableToolbar =
1462 function(enabled) { }
1463 
1464 
1465 ZmSimpleApptComposeController.prototype.showConflictDialog =
1466 function(appt, callback, inst) {
1467 	DBG.println("conflict instances :" + inst.length);
1468 
1469 	var conflictDialog = this.getConflictDialog();
1470 	conflictDialog.initialize(inst, appt, callback, this._simpleCancelCallback);
1471 	conflictDialog.popup();
1472 };
1473