1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2011, 2012, 2013, 2014, 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) 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 /**
 26  * Creates a new appointment controller to manage read-only appointment viewing.
 27  * @constructor
 28  * @class
 29  *
 30  * @author Vince Bellows
 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 ZmApptController = function(container, app, type, sessionId) {
 40     ZmCalItemComposeController.apply(this, arguments);
 41 };
 42 
 43 ZmApptController.prototype = new ZmCalItemComposeController;
 44 ZmApptController.prototype.constructor = ZmApptController;
 45 
 46 ZmApptController.prototype.isZmApptController = true;
 47 ZmApptController.prototype.toString = function() { return "ZmApptController"; };
 48 
 49 ZmApptController.DEFAULT_TAB_TEXT = ZmMsg.message;
 50 
 51 ZmApptController.viewToTab = {};
 52 
 53 ZmApptController.getDefaultViewType =
 54 function() {
 55 	return ZmId.VIEW_APPOINTMENT_READONLY;
 56 };
 57 ZmApptController.prototype.getDefaultViewType = ZmApptController.getDefaultViewType;
 58 
 59 ZmApptController.prototype._createComposeView =
 60 function() {
 61 	// override
 62     return new ZmApptView(this._container,  DwtControl.ABSOLUTE_STYLE, this);
 63 };
 64 
 65 ZmApptController.prototype._createToolBar =
 66 function() {
 67 
 68 	var buttons = [ ZmOperation.SEND_INVITE, ZmOperation.SAVE, ZmOperation.CANCEL, ZmOperation.SEP,
 69                     ZmOperation.TAG_MENU
 70                     ];
 71     var secondaryButtons = [ZmOperation.EDIT, ZmOperation.DUPLICATE_APPT, ZmOperation.SEP,
 72                             ZmOperation.REPLY, ZmOperation.REPLY_ALL, ZmOperation.FORWARD_APPT, ZmOperation.PROPOSE_NEW_TIME, ZmOperation.DELETE, ZmOperation.SEP,
 73                             ZmOperation.SHOW_ORIG
 74                             ];
 75     if (appCtxt.get(ZmSetting.PRINT_ENABLED)) {
 76 		buttons.push(ZmOperation.PRINT);
 77 	}
 78 
 79 	this._toolbar = new ZmButtonToolBar({parent:this._container, buttons:buttons, context:this._currentViewId, controller:this, secondaryButtons:secondaryButtons});
 80 	this._toolbar.addSelectionListener(ZmOperation.SAVE, this._saveListener.bind(this));
 81 	this._toolbar.addSelectionListener(ZmOperation.CANCEL, this._cancelListener.bind(this));
 82 	this._toolbar.addSelectionListener(ZmOperation.REPLY, this._replyListener.bind(this));
 83 	this._toolbar.addSelectionListener(ZmOperation.REPLY_ALL, this._replyAllListener.bind(this));
 84 	this._toolbar.addSelectionListener(ZmOperation.FORWARD_APPT, this._forwardListener.bind(this));
 85 	this._toolbar.addSelectionListener(ZmOperation.EDIT, this._editListener.bind(this));
 86 	this._toolbar.addSelectionListener(ZmOperation.PROPOSE_NEW_TIME, this._proposeTimeListener.bind(this));
 87 	this._toolbar.addSelectionListener(ZmOperation.DELETE, this._deleteListener.bind(this));
 88 	this._toolbar.addSelectionListener(ZmOperation.DUPLICATE_APPT, this._duplicateApptListener.bind(this));
 89 	this._toolbar.addSelectionListener(ZmOperation.SHOW_ORIG, this._showOrigListener.bind(this));
 90 
 91 	if (appCtxt.get(ZmSetting.PRINT_ENABLED)) {
 92 		this._toolbar.addSelectionListener(ZmOperation.PRINT, this._printListener.bind(this));
 93 	}
 94 
 95     var sendButton = this._toolbar.getButton(ZmOperation.SEND_INVITE);
 96     sendButton.setVisible(false);
 97 
 98 
 99     var tagButton = this._toolbar.getButton(ZmOperation.TAG_MENU);
100 	if (tagButton) {
101 		tagButton.noMenuBar = true;
102 		this._setupTagMenu(this._toolbar);
103 	}
104 	// change default button style to toggle for spell check button
105 	var spellCheckButton = this._toolbar.getButton(ZmOperation.SPELL_CHECK);
106 	if (spellCheckButton) {
107 		spellCheckButton.setAlign(DwtLabel.IMAGE_LEFT | DwtButton.TOGGLE_STYLE);
108 	}
109 
110 };
111 
112 ZmApptController.prototype._initToolbar =
113 function(mode) {
114     ZmCalItemComposeController.prototype._initToolbar.call(this, mode);
115     var saveButton = this._toolbar.getButton(ZmOperation.SAVE);
116     saveButton.setEnabled(false);
117 
118     var editButton = this._toolbar.getButton(ZmOperation.EDIT),
119         forwardApptButton,
120         deleteButton;
121 
122     if (editButton) {
123         if (mode === ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) {
124             if (editButton.getText() !== ZmMsg.editInstance) {
125                 editButton.setText(ZmMsg.editInstance);
126                 forwardApptButton = this._toolbar.getButton(ZmOperation.FORWARD_APPT);
127                 deleteButton = this._toolbar.getButton(ZmOperation.DELETE);
128                 forwardApptButton && forwardApptButton.setText(ZmMsg.forwardInstance);
129                 deleteButton && deleteButton.setText(ZmMsg.deleteApptInstance);
130             }
131         }
132         else if (mode === ZmCalItem.MODE_EDIT_SERIES) {
133             if (editButton.getText() !== ZmMsg.editSeries) {
134                 editButton.setText(ZmMsg.editSeries);
135                 forwardApptButton = this._toolbar.getButton(ZmOperation.FORWARD_APPT);
136                 deleteButton = this._toolbar.getButton(ZmOperation.DELETE);
137                 forwardApptButton && forwardApptButton.setText(ZmMsg.forwardSeries);
138                 deleteButton && deleteButton.setText(ZmMsg.deleteApptSeries);
139             }
140         }
141         else {
142             if (editButton.getText() !== ZmMsg.edit) {
143                 editButton.setText(ZmMsg.edit);
144                 forwardApptButton = this._toolbar.getButton(ZmOperation.FORWARD_APPT);
145                 deleteButton = this._toolbar.getButton(ZmOperation.DELETE);
146                 forwardApptButton && forwardApptButton.setText(ZmMsg.forward);
147                 deleteButton && deleteButton.setText(ZmMsg.del);
148             }
149         }
150     }
151 
152     // bug 68451: disabling edit options for trashed appointments
153     var calItem = this.getCalItem();
154     var calendar = calItem && calItem.getFolder();
155     var isTrash = calendar && calendar.nId==ZmOrganizer.ID_TRASH;
156     var isReadOnly = calendar && calendar.isReadOnly();
157 
158     if(isTrash){
159         this._disableEditForTrashedItems();
160     }
161 
162     if (appCtxt.isWebClientOffline()) {
163         this._disableEditForOffline();
164     }
165     if (isReadOnly) {
166         this._disableActionsForReadOnlyAppt();
167     }
168     if (appCtxt.isExternalAccount()) {
169         this._disableActionsForExternalAccount();
170     }
171 };
172 
173 ZmApptController.prototype._disableEditForOffline =
174 function() {
175     var actionMenu = this._toolbar.getActionsMenu();
176     if(actionMenu){
177         actionMenu.enable([
178             ZmOperation.EDIT,
179             ZmOperation.TAG,
180             ZmOperation.TAG_MENU,
181             ZmOperation.REPLY,
182             ZmOperation.REPLY_ALL,
183             ZmOperation.PROPOSE_NEW_TIME,
184             ZmOperation.DUPLICATE_APPT,
185             ZmOperation.FORWARD_APPT,
186             ZmOperation.DELETE,
187             ZmOperation.SHOW_ORIG
188         ], false);
189     }
190     var tagButton = this._toolbar.getButton(ZmOperation.TAG_MENU);
191     if (tagButton) {
192         tagButton.setEnabled(false);
193     }
194     var printButton = this._toolbar.getButton(ZmOperation.PRINT);
195     if (printButton) {
196         printButton.setEnabled(false);
197     }
198 }
199 
200 ZmApptController.prototype._disableEditForTrashedItems =
201 function() {
202     var actionMenu = this._toolbar.getActionsMenu();
203     if(actionMenu){
204         actionMenu.enable([
205                             ZmOperation.EDIT,
206                             ZmOperation.REPLY,
207                             ZmOperation.REPLY_ALL,
208                             ZmOperation.PROPOSE_NEW_TIME,
209                             ZmOperation.FORWARD_APPT
210                             ], false);
211     }
212 };
213 
214 ZmApptController.prototype._disableActionsForReadOnlyAppt =
215 function() {
216     var actionMenu = this._toolbar.getActionsMenu();
217     if(actionMenu){
218         actionMenu.enable([
219                         ZmOperation.EDIT,
220                         ZmOperation.TAG,
221                         ZmOperation.TAG_MENU,
222                         ZmOperation.FORWARD_APPT,
223                         ZmOperation.PROPOSE_NEW_TIME,
224                         ZmOperation.DELETE
225                         ], false);
226     }
227     var tagButton = this._toolbar.getButton(ZmOperation.TAG_MENU);
228 	if (tagButton) {
229         tagButton.setEnabled(false);
230     }
231 };
232 
233 ZmApptController.prototype._disableActionsForExternalAccount =
234 function() {
235     var actionMenu = this._toolbar.getActionsMenu();
236     if(actionMenu){
237         actionMenu.enable([
238                         ZmOperation.EDIT,
239                         ZmOperation.TAG,
240                         ZmOperation.TAG_MENU,
241                         ZmOperation.REPLY,
242                         ZmOperation.REPLY_ALL,
243                         ZmOperation.PROPOSE_NEW_TIME,
244                         ZmOperation.DUPLICATE_APPT,
245                         ZmOperation.FORWARD_APPT,
246                         ZmOperation.DELETE
247                         ], false);
248     }
249     var tagButton = this._toolbar.getButton(ZmOperation.TAG_MENU);
250 	if (tagButton) {
251         tagButton.setEnabled(false);
252     }
253 };
254 
255 ZmApptController.prototype._deleteListener =
256 function(ev) {
257 	var op = this.getMode();
258     var calItem = this.getCalItem();
259     if(!calItem) {
260         return;
261     }
262 
263     if (calItem.isRecurring()) {
264         var mode = (op == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE)
265             ? ZmCalItem.MODE_DELETE_INSTANCE
266             : ZmCalItem.MODE_DELETE_SERIES;
267         this._app.getCalController()._promptDeleteAppt(calItem, mode);
268     }
269     else {
270         this._app.getCalController()._deleteAppointment(calItem);
271     }
272 };
273 
274 ZmApptController.prototype._editListener =
275 function(ev) {
276 	var op = (ev && ev.item instanceof DwtMenuItem)
277 		? ev.item.getData(ZmOperation.KEY_ID) : null;
278     var calItem = this.getCalItem();
279     if(!calItem) {
280         return;
281     }
282     this._composeView.edit(ev);
283 };
284 
285 ZmApptController.prototype._replyListener =
286 function(ev) {
287 	var op = (ev && ev.item instanceof DwtMenuItem)
288 		? ev.item.getData(ZmOperation.KEY_ID) : null;
289     var calItem = this.getCalItem();
290     if(!calItem) {
291         return;
292     }
293     this._app.getCalController()._replyAppointment(calItem, false);
294 };
295 
296 ZmApptController.prototype._replyAllListener =
297 function(ev) {
298 	var op = (ev && ev.item instanceof DwtMenuItem)
299 		? ev.item.getData(ZmOperation.KEY_ID) : null;
300     var calItem = this.getCalItem();
301     if(!calItem) {
302         return;
303     }
304     this._app.getCalController()._replyAppointment(calItem, true);
305 };
306 
307 ZmApptController.prototype._saveListener =
308 function(ev) {
309     if(!this.isDirty() || !this.getOpValue()) {
310         return;
311     }
312 	var op = (ev && ev.item instanceof DwtMenuItem)
313 		? ev.item.getData(ZmOperation.KEY_ID) : null;
314 
315 
316     var calItem = this.getCalItem();
317     if(!calItem) {
318         return;
319     }
320     if (calItem.isRecurring() && !op) {
321         var mode = this.getMode();
322         op = (mode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) ? ZmOperation.VIEW_APPT_INSTANCE : ZmOperation.VIEW_APPT_SERIES;
323     }
324 
325 	this._action =  ZmCalItemComposeController.SAVE_CLOSE;
326     var saveCallback = new AjxCallback(this, this._handleSaveResponse);
327     var calViewCtrl = this._app.getCalController();
328 	// This will trigger a call to  ZmMailMsg.sendInviteReply, which updates the offline appointment ptst field
329 	// and the invite mail msg.
330     var respCallback = new AjxCallback(calViewCtrl, calViewCtrl._handleResponseHandleApptRespondAction, [calItem, this.getOpValue(), op, saveCallback]);
331 	calItem.getDetails(null, respCallback, this._errorCallback);
332 
333     //this._app.getCalController()._replyAppointment(calItem, true);
334 };
335 
336 ZmApptController.prototype._duplicateApptListener =
337 function(ev) {
338 	var op = this.getMode();
339 	var appt = this.getCalItem();
340 	var isException = (appt.isRecurring() && op == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE);
341     var calViewCtrl = this._app.getCalController();
342 	calViewCtrl.duplicateAppt(appt, {isException: isException});
343 };
344 
345 ZmApptController.prototype._showOrigListener =
346 function(ev) {
347 	var appt = this.getCalItem();
348     var calViewCtrl = this._app.getCalController();
349 	if (appt)
350 		calViewCtrl._showApptSource(appt);
351 };
352 
353 ZmApptController.prototype._handleSaveResponse =
354 function(result, value) {
355     if (appCtxt.isWebClientOffline()) {
356         // Set the value of the appt stored in-memory in the list.  Normally, this would be updated
357         // by a notification, but not offline.
358         //var appt = this.getCalItem();
359         //appt.ptst = value;
360 
361         // Update the version currently in use.  It may get updated again below, but it doesn't matter
362         this.getCurrentView().setOrigPtst(value);
363     }
364     if (this.isCloseAction()) {
365         this._closeView();
366     } else {
367         this.getCurrentView().setOrigPtst(value);
368     }
369 };
370 
371 ZmApptController.prototype.isCloseAction =
372 function() {
373     return this._action == ZmCalItemComposeController.SAVE_CLOSE || this._action == ZmCalItemComposeController.SAVE;
374 };
375 
376 ZmApptController.prototype._forwardListener =
377 function(ev) {
378 	var op = this.getMode();
379     var calItem = this.getCalItem();
380     if(!calItem) {
381         return;
382     }
383 
384     var mode = ZmCalItem.MODE_FORWARD;
385     if (calItem.isRecurring()) {
386 		mode = (op == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE)
387 			? ZmCalItem.MODE_FORWARD_SINGLE_INSTANCE
388 			: ZmCalItem.MODE_FORWARD_SERIES;
389 	}
390 
391     this._app.getCalController()._forwardAppointment(calItem, mode);
392 };
393 
394 ZmApptController.prototype._printListener =
395 function() {
396 	var calItem = this.getCalItem();
397     if(!calItem) {
398         return;
399     }
400 	var url = ["/h/printappointments?id=", calItem.invId, "&tz=", AjxTimezone.getServerId(AjxTimezone.DEFAULT)]; //bug:53493
401     if (appCtxt.isOffline) {
402         url.push("&zd=true", "&acct=", this._composeView.getApptEditView().getCalendarAccount().name);
403     }
404 	window.open(appContextPath + url.join(""), "_blank");
405 };
406 
407 ZmApptController.prototype._tagButtonListener =
408 function(ev) {
409 	var toolbar = this.getCurrentToolbar();
410 	if (ev.item.parent == toolbar) {
411 		this._setTagMenu(toolbar);
412 	}
413 };
414 
415 ZmApptController.prototype._setupTagMenu =
416 function(parent) {
417 	if (!parent) return;
418 	var tagMenu = parent.getTagMenu();
419 	if (tagMenu) {
420 		tagMenu.addSelectionListener(new AjxListener(this, this._tagListener));
421 	}
422 	if (parent instanceof ZmButtonToolBar) {
423 		var tagButton = parent.getOp(ZmOperation.TAG_MENU);
424 		if (tagButton) {
425 			tagButton.addDropDownSelectionListener(new AjxListener(this, this._tagButtonListener));
426 		}
427 	}
428 };
429 
430 ZmApptController.prototype._proposeTimeListener =
431 function(ev) {
432 	var calItem = this.getCalItem();
433     if(!calItem) {
434         return;
435     }
436     //Pass mode edit to open the appt in edit mode. The mode 'propose new time' will be added later.
437 	var mode = ZmCalItem.MODE_EDIT;
438 	if (calItem.isRecurring()) {
439 		mode = this.getMode();
440 	}
441 	var appt = calItem;
442 	var clone = ZmAppt.quickClone(appt);
443 	clone.setProposeTimeMode(true);
444 	clone.getDetails(mode, new AjxCallback(this, this._proposeTimeContinue, [clone, mode]));
445 };
446 
447 ZmApptController.prototype._proposeTimeContinue =
448 function(appt, mode) {
449 	appt.setViewMode(mode);
450 	AjxDispatcher.run("GetApptComposeController").proposeNewTime(appt);
451 };
452 
453 ZmApptController.prototype._doTag =
454 function(items, tag, doTag) {
455 
456 	var list = this._getTaggableItems(items);
457 
458 	if (doTag) {
459 		if (list.length > 0 && list.length == items.length) {
460 			// there are items to tag, and all are taggable
461 			ZmBaseController.prototype._doTag.call(this, list, tag, doTag);
462 		} else {
463 			var msg;
464 			var dlg = appCtxt.getMsgDialog();
465 			if (list.length > 0 && list.length < items.length) {
466 				// there are taggable and nontaggable items
467 				var listener = new AjxListener(this, this._handleDoTag, [dlg, list, tag, doTag]);
468 				dlg.setButtonListener(DwtDialog.OK_BUTTON, listener);
469 				msg = ZmMsg.tagReadonly;
470 			} else if (list.length == 0) {
471 				// no taggable items
472 				msg = ZmMsg.nothingToTag;
473 			}
474 			dlg.setMessage(msg);
475 			dlg.popup();
476 		}
477 	} else if (list.length > 0) {
478 		ZmBaseController.prototype._doTag.call(this, list, tag, doTag);
479 	}
480 };
481 
482 ZmApptController.prototype._doRemoveAllTags =
483 function(items) {
484 	var list = this._getTaggableItems(items);
485 	ZmBaseController.prototype._doRemoveAllTags.call(this, list);
486 };
487 
488 ZmApptController.prototype._handleDoTag =
489 function(dlg, list, tag, doTag) {
490 	dlg.popdown();
491 	ZmBaseController.prototype._doTag.call(this, list, tag, doTag);
492 };
493 
494 ZmApptController.prototype._getTaggableItems =
495 function(items) {
496 	var calItem = this.getCalItem();
497     items = [];
498     items.push(calItem);
499 	return items;
500 };
501 
502 ZmApptController.prototype.getItems =
503 function() {
504 	return this._getTaggableItems([]);
505 };
506 
507 ZmApptController.prototype.getCalItem =
508 function() {
509     var ci = this._composeView ? this._composeView._calItem : null;
510     return ci;
511 };
512 
513 ZmApptController.prototype.getOpValue =
514 function() {
515     var s = this._composeView ? this._composeView.getOpValue() : null;
516     return s;
517 };
518 
519 ZmApptController.prototype.isDirty =
520 function() {
521     var dirty = this._composeView ? this._composeView.isDirty() : false;
522     return dirty;
523 };
524 
525 ZmApptController.prototype.getMode =
526 function() {
527     var m = this._composeView ? this._composeView._mode : null;
528     return m;
529 };
530 
531 ZmApptController.prototype.getCurrentView =
532 function() {
533 	return this._composeView;
534 };
535 
536 ZmApptController.prototype.getCurrentToolbar =
537 function() {
538 	return this._toolbar;
539 };
540 
541 ZmApptController.prototype._postShowCallback =
542 function() {
543 	ZmCalItemComposeController.prototype._postShowCallback.call(this);
544     this._app.setOverviewPanelContent();
545 };
546 
547 ZmApptController.prototype.saveCalItem =
548 function(attId) {
549     var done = true;
550     if (this.isDirty()) {
551         var calItem = this.getCalItem();
552         if(calItem) {
553             var saveCallback = new AjxCallback(this, this._handleSaveResponse);
554             var calViewCtrl = this._app.getCalController();
555             var respCallback =
556                 new AjxCallback(calViewCtrl, calViewCtrl._handleResponseHandleApptRespondAction,
557                     [calItem, this.getOpValue(), null, saveCallback]);
558             calItem.getDetails(null, respCallback, this._errorCallback);
559             done = false;
560         }
561     }
562     if(done && this.isCloseAction()) {
563         this._closeView();
564     }
565 
566 };
567 
568 ZmApptController.prototype._closeView =
569 function() {
570 	this._app.popView(true, this.getCurrentViewId());
571     this._composeView.cleanup();
572 };
573 
574 ZmApptController.prototype.getKeyMapName = function() {
575 	return "viewAppointment";
576 };
577