1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a preferences page for managing calendar prefs
 26  * @constructor
 27  * @class
 28  * This class adds specialized handling for managing calendar ACLs that control whether
 29  * events can be added to the user's calendar, and who can see the user's free/busy info.
 30  *
 31  * @author Conrad Damon
 32  *
 33  * @param {DwtControl}	parent			the containing widget
 34  * @param {Object}	section			which page we are
 35  * @param {ZmPrefController}	controller		the prefs controller
 36  * 
 37  * @extends		ZmPreferencesPage
 38  * 
 39  * @private
 40  */
 41 
 42 ZmCalendarPrefsPage = function(parent, section, controller) {
 43 
 44 	ZmPreferencesPage.apply(this, arguments);
 45 
 46 	ZmCalendarPrefsPage.TEXTAREA = {};
 47 	ZmCalendarPrefsPage.TEXTAREA[ZmSetting.CAL_FREE_BUSY_ACL]	= ZmSetting.CAL_FREE_BUSY_ACL_USERS;
 48 	ZmCalendarPrefsPage.TEXTAREA[ZmSetting.CAL_INVITE_ACL]		= ZmSetting.CAL_INVITE_ACL_USERS;
 49 	ZmCalendarPrefsPage.SETTINGS	= [ZmSetting.CAL_FREE_BUSY_ACL, ZmSetting.CAL_INVITE_ACL];
 50 	ZmCalendarPrefsPage.RIGHTS		= [ZmSetting.RIGHT_VIEW_FREE_BUSY, ZmSetting.RIGHT_INVITE];
 51 
 52 	this._currentSelection = {};
 53 	this._initAutocomplete();
 54 };
 55 
 56 ZmCalendarPrefsPage.prototype = new ZmPreferencesPage;
 57 ZmCalendarPrefsPage.prototype.constructor = ZmCalendarPrefsPage;
 58 
 59 ZmCalendarPrefsPage.prototype.isZmCalendarPrefsPage = true;
 60 ZmCalendarPrefsPage.prototype.toString = function() { return "ZmCalendarPrefsPage"; };
 61 
 62 ZmCalendarPrefsPage.prototype.reset =
 63 function(useDefaults) {
 64 	ZmPreferencesPage.prototype.reset.apply(this, arguments);
 65 	var settings = ZmCalendarPrefsPage.SETTINGS;
 66 	for (var i = 0; i < settings.length; i++) {
 67 		this._checkPermTextarea(settings[i]);
 68     }
 69     if(this._workHoursControl) {
 70         this._workHoursControl.reset();
 71     }
 72 };
 73 
 74 ZmCalendarPrefsPage.prototype.showMe =
 75 function() {
 76 	this._acl = appCtxt.getACL();
 77 	if (this._acl && !this._acl._loaded) {
 78 		var respCallback = this._doShowMe.bind(this);
 79 		this._acl.load(respCallback);
 80 	} else {
 81 		this._doShowMe();
 82 	}
 83 };
 84 
 85 ZmCalendarPrefsPage.prototype._doShowMe =
 86 function() {
 87 	var settings = ZmCalendarPrefsPage.SETTINGS;
 88 	var rights = ZmCalendarPrefsPage.RIGHTS;
 89 	for (var i = 0; i < settings.length; i++) {
 90 		this._setACLValues(settings[i], rights[i]);
 91 	}
 92 
 93 	var active = appCtxt.getActiveAccount();
 94 	this._isAclSupported = !appCtxt.multiAccounts || appCtxt.isFamilyMbox || (!active.isMain && active.isZimbraAccount);
 95 
 96 	ZmPreferencesPage.prototype.showMe.call(this);
 97 };
 98 
 99 ZmCalendarPrefsPage.prototype._setupCustom = function (id, setup, value) {
100     switch(id) {
101         case "CAL_WORKING_HOURS":
102             var el = document.getElementById([this._htmlElId, id].join("_"));
103             if(el) {
104                 this._workHoursControl = new ZmWorkHours(this, id, value, "WorkHours");
105                 this._workHoursControl.reparentHtmlElement(el);
106             }
107             this.setFormObject(id, this._workHoursControl);
108             break;
109     }
110     
111 };
112 
113 ZmCalendarPrefsPage.prototype._getTemplateData =
114 function() {
115 	var data = ZmPreferencesPage.prototype._getTemplateData.call(this);
116 	data.domain = appCtxt.getUserDomain();
117 	data.isAclSupported = this._isAclSupported;
118 
119 	return data;
120 };
121 
122 ZmCalendarPrefsPage.prototype._createControls =
123 function() {
124 	ZmPreferencesPage.prototype._createControls.apply(this, arguments);
125 
126 	var settings = ZmCalendarPrefsPage.SETTINGS;
127 
128 	for (var i = 0; i < settings.length; i++) {
129 		var textarea = this.getFormObject(ZmCalendarPrefsPage.TEXTAREA[settings[i]]);
130 		if (textarea && this._acList) {
131 			this._acList.handle(textarea.getInputElement());
132 			this._checkPermTextarea(settings[i]);
133 		}
134 	}
135 };
136 
137 // Sets values for calendar ACL-related settings.
138 ZmCalendarPrefsPage.prototype._setACLValues =
139 function(setting, right) {
140 	var gt = this._acl.getGranteeType(right);
141 	this._currentSelection[setting] = gt;
142 
143 	appCtxt.set(setting, gt);
144 	var list = this._acl.getGrantees(right);
145 	var textDisplay = list.join("\n");
146 	appCtxt.set(ZmCalendarPrefsPage.TEXTAREA[setting], textDisplay);
147 
148 	this._acl.getGranteeType(right);
149 	// Set up the preference initial value (derived from ACL data) so that the
150 	// pref is not incorrectly detected as dirty in the _checkSection call.
151 	var pref = appCtxt.getSettings().getSetting(setting);
152 	pref.origValue = this._currentSelection[setting];
153 	pref = appCtxt.getSettings().getSetting(ZmCalendarPrefsPage.TEXTAREA[setting]);
154 	pref.origValue = textDisplay;
155 };
156 
157 /**
158  * ZmPrefView.getChangedPrefs() doesn't quite work for performing a dirty check on this page since
159  * it only returns true if a changed setting is stored in LDAP (has a 'name' property in its ZmSetting
160  * object). This override checks the ACL-related settings to see if they changed.
161  */
162 ZmCalendarPrefsPage.prototype.isDirty =
163 function(section, list, errors) {
164 	var dirty = this._controller.getPrefsView()._checkSection(section, this, true, true, list, errors);
165     if(!dirty && this._workHoursControl) {
166         dirty = this._workHoursControl.isDirty();
167     }
168 	if (!dirty && this._isAclSupported) {
169 		this._findACLChanges();
170 		dirty = (this._grants.length || this._revokes.length);
171 	}
172 	return dirty;
173 };
174 
175 ZmCalendarPrefsPage.prototype._checkPermTextarea =
176 function(setting) {
177 	var radioGroup = this.getFormObject(setting);
178 	var val = radioGroup && radioGroup.getValue();
179 	var textarea = this.getFormObject(ZmCalendarPrefsPage.TEXTAREA[setting]);
180 	if (textarea && val) {
181 		textarea.setEnabled(val == ZmSetting.ACL_USER);
182 	}
183 };
184 
185 ZmCalendarPrefsPage.prototype._setupRadioGroup =
186 function(id, setup, value) {
187 	var control = ZmPreferencesPage.prototype._setupRadioGroup.apply(this, arguments);
188 	var radioGroup = this.getFormObject(id);
189 	if (id == ZmSetting.CAL_FREE_BUSY_ACL || id == ZmSetting.CAL_INVITE_ACL) {
190 		radioGroup.addSelectionListener(new AjxListener(this, this._checkPermTextarea, [id]));
191 	}
192 	return control;
193 };
194 
195 ZmCalendarPrefsPage.prototype.getPreSaveCallback =
196 function() {
197 	return new AjxCallback(this, this._preSave);
198 };
199 
200 ZmCalendarPrefsPage.prototype.getPostSaveCallback =
201 function() {
202 	return new AjxCallback(this, this._postSave);
203 };
204 
205 ZmCalendarPrefsPage.prototype._postSave =
206 function(callback) {
207     var settings = appCtxt.getSettings();
208     var workHoursSetting = settings.getSetting(ZmSetting.CAL_WORKING_HOURS);
209     workHoursSetting._notify(ZmEvent.E_MODIFY);
210 	if (this._workHoursControl) {
211 		this._workHoursControl.reloadWorkHours(this._workHoursControl.getValue());
212 	}
213 
214     /**
215      * Post save, restore the value of pref zimbraPrefCalendarApptReminderWarningTimevalue
216      * for 'never' and 'at time of event'. In function ZmCalendarApp.setDefaultReminderTimePrefValueOnSave,
217      * if the user has chosen 'never' or 'at time of event' option in default reminder select option,
218      * then on save we make value of never to 0 and 'at time of event' to -1, we are undoing that change here.
219      **/
220 
221     var defaultWarningTime = settings.getSetting(ZmSetting.CAL_REMINDER_WARNING_TIME).getValue();
222     if (defaultWarningTime === -1 || defaultWarningTime === 0) { // never or 'at time of event' was chosen in defaultreminderpref dropdown
223         defaultWarningTime === -1 ? (defaultWarningTime = 0) : (defaultWarningTime = -1);
224         settings.getSetting(ZmSetting.CAL_REMINDER_WARNING_TIME).setValue(defaultWarningTime);
225         appCtxt.getSettings().getSetting('CAL_REMINDER_WARNING_TIME').origValue = defaultWarningTime;
226     }
227     if (callback instanceof AjxCallback) {
228 		callback.run();
229 	}
230 };
231 
232 ZmCalendarPrefsPage.prototype._preSave =
233 function(callback) {
234 	if (this._isAclSupported) {
235 		this._findACLChanges();
236 	}
237 	if (callback) {
238 		callback.run();
239 	}
240 };
241 
242 ZmCalendarPrefsPage.prototype._findACLChanges =
243 function() {
244 	var settings = ZmCalendarPrefsPage.SETTINGS;
245 	var rights = ZmCalendarPrefsPage.RIGHTS;
246 	this._grants = [];
247 	this._revokes = [];
248 	for (var i = 0; i < settings.length; i++) {
249 		var result = this._getACLChanges(settings[i], rights[i]);
250 		this._grants = this._grants.concat(result.grants);
251 		this._revokes = this._revokes.concat(result.revokes);
252 	}
253 };
254 
255 ZmCalendarPrefsPage.prototype._getACLChanges =
256 function(setting, right) {
257 
258 	var curType = appCtxt.get(setting);
259 	var curUsers = (curType == ZmSetting.ACL_USER) ? this._acl.getGrantees(right) : [];
260 	var curUsersInfo = (curType == ZmSetting.ACL_USER) ? this._acl.getGranteesInfo(right) : [];
261 	var zidHash = {};
262 	for (var i = 0; i < curUsersInfo.length; i++) {
263 		zidHash[curUsersInfo[i].grantee] = curUsersInfo[i].zid;
264 	}
265 	var curHash = AjxUtil.arrayAsHash(curUsers);
266 
267 	var radioGroup = this.getFormObject(setting);
268 		var newType = radioGroup.getValue();
269 	var radioGroupChanged = (newType != this._currentSelection[setting]);
270 
271 	var newUsers = [];
272 	if (newType == ZmSetting.ACL_USER) {
273 		var textarea = this.getFormObject(ZmCalendarPrefsPage.TEXTAREA[setting]);
274 		var val = textarea.getValue();
275 		var users = val.split(/[\n,;]/);
276 		for (var i = 0; i < users.length; i++) {
277 			var user = AjxStringUtil.trim(users[i]);
278 			if (!user) { continue; }
279 			if (zidHash[user] != user) {
280 				user = (user.indexOf('@') == -1) ? [user, appCtxt.getUserDomain()].join('@') : user;
281 			}
282 			newUsers.push(user);
283 		}
284 		newUsers.sort();
285 	}
286 
287 	var newHash = AjxUtil.arrayAsHash(newUsers);
288 
289 	var contacts = AjxDispatcher.run("GetContacts");
290 	var grants = [];
291 	var revokes = [];
292 	if (newUsers.length > 0) {
293 		for (var i = 0; i < newUsers.length; i++) {
294 			var user = newUsers[i];
295 			if (!curHash[user]) {
296 				var contact = contacts.getContactByEmail(user);
297 				var gt = (contact && contact.isGroup()) ? ZmSetting.ACL_GROUP : ZmSetting.ACL_USER;
298 				var ace = new ZmAccessControlEntry({grantee:user, granteeType:gt, right:right});
299 				grants.push(ace);
300 			}
301 		}
302 	}
303 	if (curUsers.length > 0) {
304 		for (var i = 0; i < curUsers.length; i++) {
305 			var user = curUsers[i];
306 			var zid = (curUsersInfo[i]) ? curUsersInfo[i].zid : null;
307 			if (!newHash[user]) {
308 				var contact = contacts.getContactByEmail(user);
309 				var gt = (contact && contact.isGroup()) ? ZmSetting.ACL_GROUP : ZmSetting.ACL_USER;
310 				var ace = new ZmAccessControlEntry({grantee: (user!=zid) ? user : null, granteeType:gt, right:right, zid: zid});
311 				revokes.push(ace);
312 			}
313 		}
314 	}
315 
316 	var userAdded = (grants.length > 0);
317 	var userRemoved = (revokes.length > 0);
318 
319 	var denyAll = (radioGroupChanged && (newType == ZmSetting.ACL_NONE));
320 
321 	if ((newType == ZmSetting.ACL_USER) && (userAdded || userRemoved || radioGroupChanged)) {
322         revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_DOMAIN));
323 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_AUTH));
324 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_PUBLIC));
325 		
326 		if (newUsers.length == 0) {
327 			denyAll = true;
328 		}
329 	}
330 
331 	// deny all
332 	if (denyAll) {
333 		revokes = [];
334 		grants = [];
335 
336         revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_DOMAIN));
337 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_USER));
338 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_GROUP));
339 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_PUBLIC));
340 
341 		//deny all
342 		var ace = new ZmAccessControlEntry({granteeType: ZmSetting.ACL_AUTH, right:right, negative: true});
343 		grants.push(ace);
344 	}
345 
346 	//allow all users
347 	if (radioGroupChanged && (newType == ZmSetting.ACL_PUBLIC)) {
348 		grants = [];
349 		revokes = [];
350 
351 		//grant all
352 		var ace = new ZmAccessControlEntry({granteeType: ZmSetting.ACL_PUBLIC, right:right});
353 		grants.push(ace);
354 
355 		//revoke all other aces
356 		revokes = this._acl.getACLByGranteeType(right, ZmSetting.ACL_USER);
357         revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_DOMAIN));
358 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_GROUP));
359 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_AUTH));
360 	}
361 
362 	if (radioGroupChanged && (newType == ZmSetting.ACL_AUTH)) {
363 		grants = [];
364 		revokes = [];
365 
366 		//grant all
367 		var ace = new ZmAccessControlEntry({granteeType: ZmSetting.ACL_AUTH, right:right});
368 		grants.push(ace);
369 
370 		//revoke all other aces
371 		revokes = this._acl.getACLByGranteeType(right, ZmSetting.ACL_USER);
372         revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_DOMAIN));
373 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_GROUP));
374 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_PUBLIC));
375 	}
376 
377     if (radioGroupChanged && (newType == ZmSetting.ACL_DOMAIN)) {
378 		grants = [];
379 		revokes = [];
380 
381 		//grant all
382 		var ace = new ZmAccessControlEntry({granteeType: ZmSetting.ACL_DOMAIN, right:right, grantee:appCtxt.getUserDomain()});
383 		grants.push(ace);
384 
385 		//revoke all other aces
386 		revokes = this._acl.getACLByGranteeType(right, ZmSetting.ACL_USER);
387         revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_GROUP));
388 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_AUTH));
389 		revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_PUBLIC));
390 	}
391 
392 	return {grants:grants, revokes:revokes};
393 };
394 
395 ZmCalendarPrefsPage.prototype.addCommand =
396 function(batchCmd) {
397     if (this._isAclSupported) {
398         var respCallback = new AjxCallback(this, this._handleResponseACLChange);
399         if (this._revokes.length) {
400             this._acl.revoke(this._revokes, respCallback, batchCmd);
401         }
402         if (this._grants.length) {
403             this._acl.grant(this._grants, respCallback, batchCmd);
404         }
405     }
406 
407     if(this._workHoursControl) {
408         if(this._workHoursControl.isValid()) {
409             var value = this._workHoursControl.getValue(),
410                     soapDoc = AjxSoapDoc.create("ModifyPrefsRequest", "urn:zimbraAccount"),
411                     node = soapDoc.set("pref", value),
412                     respCallback = new AjxCallback(this, this._postSaveBatchCmd, [value]);
413             node.setAttribute("name", "zimbraPrefCalendarWorkingHours");
414             batchCmd.addNewRequestParams(soapDoc, respCallback);
415         }
416         else {
417             throw new AjxException(ZmMsg.calendarWorkHoursInvalid);
418         }
419     }
420 };
421 
422 ZmCalendarPrefsPage.prototype._postSaveBatchCmd =
423 function(value) {
424     appCtxt.set(ZmSetting.CAL_WORKING_HOURS, value);
425     var firstDayOfWeek = appCtxt.get(ZmSetting.CAL_FIRST_DAY_OF_WEEK) || 0;
426 
427     if(this._workHoursControl) {
428         // Check if either work days have changed or first day of week has changed.
429         // Need to reload the browser either way to show the correct changes.
430         if(this._workHoursControl.getDaysChanged() ||
431                 parseInt(this._workHoursControl._workDaysCheckBox[0].getValue()) != firstDayOfWeek) {
432             this._workHoursControl.setDaysChanged(false);
433             var cd = appCtxt.getYesNoMsgDialog();
434             cd.reset();
435             cd.registerCallback(DwtDialog.YES_BUTTON, this._newWorkHoursYesCallback, this, [skin, cd]);
436             cd.setMessage(ZmMsg.workingDaysRestart, DwtMessageDialog.WARNING_STYLE);
437             cd.popup();
438         }
439     }
440 };
441 
442 ZmCalendarPrefsPage.prototype._newWorkHoursYesCallback =
443 function(skin, dialog) {
444 	dialog.popdown();
445 	window.onbeforeunload = null;
446 	var url = AjxUtil.formatUrl();
447 	DBG.println(AjxDebug.DBG1, "Working days change, redirect to: " + url);
448 	ZmZimbraMail.sendRedirect(url); // redirect to self to force reload
449 };
450 
451 ZmCalendarPrefsPage.prototype._handleResponseACLChange =
452 function(aces) {
453 	if (aces && !(aces instanceof Array)) { aces = [aces]; }
454 
455 	if (aces && aces.length) {
456 		for (var i = 0; i < aces.length; i++) {
457 			var ace = aces[i];
458 			var setting = (ace.right == ZmSetting.RIGHT_INVITE) ? ZmSetting.CAL_INVITE_ACL : ZmSetting.CAL_FREE_BUSY_ACL;
459 			this._setACLValues(setting, ace.right);
460 		}
461 	}
462 };
463 
464 ZmCalendarPrefsPage.prototype._initAutocomplete =
465 function() {
466 	if (appCtxt.get(ZmSetting.CONTACTS_ENABLED) && appCtxt.get(ZmSetting.GAL_AUTOCOMPLETE_ENABLED)) {
467 		var contactsClass = appCtxt.getApp(ZmApp.CONTACTS);
468 		var contactsLoader = contactsClass.getContactList;
469 		var params = {
470 			dataClass:	appCtxt.getAutocompleter(),
471 			separator:	";",
472 			matchValue:	ZmAutocomplete.AC_VALUE_EMAIL,
473 			options:	{galOnly:true},
474 			contextId:	this.toString()
475 		};
476 		this._acList = new ZmAutocompleteListView(params);
477 	}
478 };
479 
480 /**
481  * Creates the WorkHours custom control
482  * 
483  * @constructor
484  * @param parent
485  * @param id
486  * @param value work hours string
487  * @param templateId
488  */
489 ZmWorkHours = function(parent, id, value, templateId) {
490 	DwtComposite.call(this, {parent:parent, id: Dwt.getNextId(id)});
491 
492 
493     this._workDaysCheckBox = [];
494     this._startTimeSelect = null;
495     this._endTimeSelect = null;
496     this._customDlg = null;
497     this._customBtn = null;
498     this.reloadWorkHours(value);
499     this._radioNormal = null;
500     this._radioCustom = null;
501     this._daysChanged = false;
502     this._setContent(templateId);
503 };
504 
505 ZmWorkHours.STR_DAY_SEP = ",";
506 ZmWorkHours.STR_TIME_SEP = ":";
507 
508 ZmWorkHours.prototype = new DwtComposite;
509 ZmWorkHours.prototype.constructor = ZmWorkHours;
510 
511 ZmWorkHours.prototype.toString =
512 function() {
513 	return "ZmWorkHours";
514 };
515 
516 ZmWorkHours.prototype.getTabGroup = ZmWorkHours.prototype.getTabGroupMember;
517 
518 ZmWorkHours.prototype.reloadWorkHours =
519 function(value) {
520     value = value || appCtxt.get(ZmSetting.CAL_WORKING_HOURS);
521     var workHours = this._workHours = this.decodeWorkingHours(value),
522         dayIdx = new Date().getDay();
523     this._startTime = new Date();
524     this._endTime = new Date();
525     this._startTime.setHours(workHours[dayIdx].startTime/100, workHours[dayIdx].startTime%100, 0);
526     this._endTime.setHours(workHours[dayIdx].endTime/100, workHours[dayIdx].endTime%100, 0);
527     this._isCustom = this._isCustomTimeSet();
528     if(this._customDlg) {
529         this._customDlg.reloadWorkHours(workHours);
530     }
531 };
532 
533 ZmWorkHours.prototype.reset =
534 function() {
535     if (!this.isDirty()) { return; }
536     var i,
537         workHours = this._workHours;
538 
539     this._startTimeSelect.set(this._startTime);
540     this._endTimeSelect.set(this._endTime);
541 
542     for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
543         this._workDaysCheckBox[i].setSelected(workHours[i].isWorkingDay);
544     }
545 
546 	this._radioGroup.setSelectedId(this._isCustom ? this._radioCustomId : this._radioNormalId);
547 
548     // Reset the custom work hours dialog as well
549     if (this._customDlg) {
550         this._customDlg.reset();
551     }
552 };
553 
554 ZmWorkHours.prototype.isDirty =
555 function() {
556 	var i,
557         isDirty = false,
558         workHours = this._workHours,
559         tf = new AjxDateFormat("HHmm"),
560         startInputTime = tf.format(this._startTimeSelect.getValue()),
561         endInputTime = tf.format(this._endTimeSelect.getValue()),
562         isCustom = this._radioCustom.isSelected();
563 
564 
565     if(!isCustom) {
566         for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
567             if(this._workDaysCheckBox[i].isSelected() != workHours[i].isWorkingDay) {
568                 this.setDaysChanged(true);
569                 isDirty = true;
570                 break;
571             }
572         }
573 
574         if(startInputTime != workHours[0].startTime || endInputTime != workHours[0].endTime) {
575             isDirty = true;
576         }
577     }
578     else if(this._customDlg) {
579         isDirty = this._customDlg.isDirty();
580     }
581 
582     if (!isCustom && this._isCustom) { //switching to normal should trigger dirty anyway.
583         isDirty = true;
584     }
585 
586 	return isDirty;
587 };
588 
589 ZmWorkHours.prototype.setDaysChanged =
590 function(value) {
591     var isCustom = this._radioCustom.isSelected();
592     if(isCustom && this._customDlg) {
593         this._customDlg.setDaysChanged(value);
594     }
595     else {
596         this._daysChanged = value;
597     }
598 };
599 
600 ZmWorkHours.prototype.getDaysChanged =
601 function() {
602     var isCustom = this._radioCustom.isSelected();
603     if(isCustom && this._customDlg) {
604         return this._customDlg.getDaysChanged();
605     }
606     else {
607         return this._daysChanged;
608     }
609 };
610 
611 ZmWorkHours.prototype.isValid =
612 function() {
613     var tf = new AjxDateFormat("HHmm"),
614         startInputTime = tf.format(this._startTimeSelect.getValue()),
615         endInputTime = tf.format(this._endTimeSelect.getValue());
616     if(this._radioCustom.isSelected() && this._customDlg) {
617         return this._customDlg.isValid();        
618     }
619     return startInputTime < endInputTime ? true : false;
620 };
621 
622 ZmWorkHours.prototype.decodeWorkingHours =
623 function(wHrsString) {
624     if(wHrsString === 0) {
625         return [];
626     }
627 	var wHrsPerDay = wHrsString.split(ZmWorkHours.STR_DAY_SEP),
628         i,
629         wHrs = [],
630         wDay,
631         w,
632         idx;
633 
634     for(i=0; i<wHrsPerDay.length; i++) {
635         wDay = wHrsPerDay[i].split(ZmWorkHours.STR_TIME_SEP);
636         w = {};
637 		w.dayOfWeek = wDay[0] - 1;
638 		w.isWorkingDay = (wDay[1] === "Y");
639         w.startTime = wDay[2];
640         w.endTime = wDay[3];
641 
642 		wHrs[i] = w;
643     }
644     return wHrs;
645 };
646 
647 ZmWorkHours.prototype.encodeWorkingHours =
648 function() {
649     var i,
650         tf = new AjxDateFormat("HHmm"),
651         startInputTime = tf.format(this._startTimeSelect.getValue()),
652         endInputTime = tf.format(this._endTimeSelect.getValue()),
653         dayStr,
654         wDaysStr = [];
655 
656     for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
657         dayStr = [];
658 		dayStr.push(parseInt(this._workDaysCheckBox[i].getValue()) + 1);
659 		if(this._workDaysCheckBox[i].isSelected()) {
660             dayStr.push("Y");
661         }
662         else {
663             dayStr.push("N");
664         }
665         dayStr.push(startInputTime);
666         dayStr.push(endInputTime);
667         wDaysStr.push(dayStr.join(ZmWorkHours.STR_TIME_SEP));
668     }
669     return wDaysStr.join(ZmWorkHours.STR_DAY_SEP);
670 };
671 
672 ZmWorkHours.prototype._isCustomTimeSet =
673 function() {
674     var i,
675         w = this._workHours;
676     for (i=1;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
677         if(w[i].startTime != w[i-1].startTime || w[i].endTime != w[i-1].endTime) {
678             return true;
679         }
680     }
681     return false;
682 };
683 
684 ZmWorkHours.prototype._closeCustomDialog =
685 function(value) {
686     this._customDlg.popdown();
687 };
688 
689 ZmWorkHours.prototype._closeCancelCustomDialog = function(value) {
690     if (this._customDlg.isDirty()) {
691         this._customDlg.reset();
692     }
693     this._customDlg.popdown();
694 };
695 
696 ZmWorkHours.prototype._openCustomizeDlg =
697 function() {
698     if(!this._customDlg) {
699         this._customDlg = new ZmCustomWorkHoursDlg(appCtxt.getShell(), "CustomWorkHoursDlg", this._workHours);
700         this._customDlg.initialize(this._workHours);
701         this._customDlg.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._closeCustomDialog, [true]));
702         this._customDlg.setButtonListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._closeCancelCustomDialog, [false]));
703     }
704     this._customDlg.popup();
705 };
706 
707 ZmWorkHours.prototype.getValue =
708 function() {
709     if(this._radioCustom.isSelected()) { 
710         if(this._customDlg) {
711             return this._customDlg.getValue();
712         }
713         else {
714             this._radioNormal.setSelected(true);
715             this._radioCustom.setSelected(false);
716             this._toggleNormalCustom();
717         }
718     }
719     return this.encodeWorkingHours();
720 };
721 
722 ZmWorkHours.prototype._toggleNormalCustom =
723 function() {
724     var i;
725     if(this._radioNormal.isSelected()) {
726         this._startTimeSelect.setEnabled(true);
727         this._endTimeSelect.setEnabled(true);
728         for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
729             this._workDaysCheckBox[i].setEnabled(true);
730         }
731         this._customBtn.setEnabled(false);
732     }
733     else {
734         for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
735             this._workDaysCheckBox[i].setEnabled(false);
736         }
737         this._startTimeSelect.setEnabled(false);
738         this._endTimeSelect.setEnabled(false);
739         this._customBtn.setEnabled(true);
740     }
741 };
742 
743 ZmWorkHours.prototype._setContent =
744 function(templateId) {
745 	var i,
746         el,
747         checkbox,
748         customBtn,
749         workHours = this._workHours,
750         startTimeSelect,
751         endTimeSelect,
752         radioNormal,
753         radioCustom,
754         radioGroup,
755         selectedRadioId,
756         radioIds = {},
757         radioName = this._htmlElId + "_normalCustom",
758         isCustom = this._isCustom;
759 
760     this.getHtmlElement().innerHTML = AjxTemplate.expand("prefs.Pages#"+templateId, {id:this._htmlElId});
761     //fill the containers for the work days and work time
762     var firstDayOfWeek = appCtxt.get(ZmSetting.CAL_FIRST_DAY_OF_WEEK) || 0;
763 
764 	// Figure out where in the workHours array the info for firstDayOfWeek is located.
765 	// The first item in array is associated with the first day of the week that has been set.
766 	// This determines how the checkboxes will be displayed for work week days. Getting
767 	// the correct position in the array for first day of week will allow the correct
768 	// workHours[].isworkingDay value to be set.
769 	var startingIndex = 0;
770 	for (i = 0; i < AjxDateUtil.DAYS_PER_WEEK; i++) {
771 		if (workHours[i].dayOfWeek == firstDayOfWeek) {
772 			startingIndex = i;
773 			break;
774 		}
775 	}
776 
777 	for (i = 0; i < AjxDateUtil.DAYS_PER_WEEK; i++) {
778 		var dayIndex = (i + startingIndex) % AjxDateUtil.DAYS_PER_WEEK;
779 		var dayOfWeek = (i + firstDayOfWeek) % AjxDateUtil.DAYS_PER_WEEK;
780 
781         checkbox = new DwtCheckbox({parent:this, parentElement:(this._htmlElId + "_CAL_WORKING_DAY_" + i)});
782 		checkbox.setText(AjxDateUtil.WEEKDAY_MEDIUM[dayOfWeek]);
783 		checkbox.setToolTipContent(AjxDateUtil.WEEKDAY_LONG[dayOfWeek]);
784 		checkbox.setValue(dayOfWeek);
785 		checkbox.setSelected(workHours[dayIndex].isWorkingDay);
786 		checkbox.setEnabled(!isCustom)
787         this._workDaysCheckBox.push(checkbox);
788     }
789 
790     radioNormal = new DwtRadioButton({parent:this, name:radioName, parentElement:(this._htmlElId + "_CAL_WORKING_HOURS_NORMAL")});
791     radioNormal.setSelected(!isCustom);
792     var radioNormalId = radioNormal.getInputElement().id;
793     radioIds[radioNormalId] = radioNormal;
794     this._radioNormal = radioNormal;
795     this._radioNormalId = radioNormalId;
796 
797     el = document.getElementById(this._htmlElId + "_CAL_WORKING_START_TIME");
798     startTimeSelect = new DwtTimeInput(this, DwtTimeInput.START, el);
799     startTimeSelect.set(this._startTime);
800     startTimeSelect.setEnabled(!isCustom);
801     this._startTimeSelect = startTimeSelect;
802 
803     el = document.getElementById(this._htmlElId + "_CAL_WORKING_END_TIME");
804     endTimeSelect = new DwtTimeInput(this, DwtTimeInput.END, el);
805     endTimeSelect.set(this._endTime);
806     endTimeSelect.setEnabled(!isCustom);
807     this._endTimeSelect = endTimeSelect;
808 
809     radioCustom = new DwtRadioButton({parent:this, name:radioName, parentElement:(this._htmlElId + "_CAL_WORKING_HOURS_CUSTOM")});
810     radioCustom.setSelected(isCustom);
811     var radioCustomId = radioCustom.getInputElement().id;
812     radioIds[radioCustomId] = radioCustom;
813     this._radioCustom = radioCustom;
814     this._radioCustomId = radioCustomId;
815 
816     radioGroup = new DwtRadioButtonGroup(radioIds, isCustom ? radioCustomId : radioNormalId);
817 
818     radioGroup.addSelectionListener(new AjxListener(this, this._toggleNormalCustom));
819     this._radioGroup = radioGroup;
820 
821     customBtn = new DwtButton({parent:this, parentElement:(this._htmlElId + "_CAL_CUSTOM_WORK_HOURS")});
822     customBtn.setText(ZmMsg.calendarCustomBtnTitle);
823     customBtn.addSelectionListener(new AjxListener(this, this._openCustomizeDlg));
824     customBtn.setEnabled(isCustom);
825     this._customBtn = customBtn;
826 };
827 
828 /**
829  * Create the custom work hours dialog box
830  * @param parent
831  * @param templateId
832  * @param workHours the work hours parsed array
833  */
834 ZmCustomWorkHoursDlg = function (parent, templateId, workHours) {
835     DwtDialog.call(this, {parent:parent});
836     this._workHours = workHours;
837     this._startTimeSelect = [];
838     this._endTimeSelect = [];
839     this._workDaysCheckBox = [];
840     var contentHtml = AjxTemplate.expand("prefs.Pages#"+templateId, {id:this._htmlElId});
841     this.setContent(contentHtml);
842 	this.setTitle(ZmMsg.calendarCustomDlgTitle);
843     this._daysChanged = false;
844 };
845 
846 ZmCustomWorkHoursDlg.prototype = new DwtDialog;
847 ZmCustomWorkHoursDlg.prototype.constructor = ZmCustomWorkHoursDlg;
848 
849 ZmCustomWorkHoursDlg.prototype.initialize = function(workHours) {
850     var i,
851         el,
852         checkbox,
853         startTimeSelect,
854         endTimeSelect,
855         inputTime;
856 
857     workHours = workHours || this._workHours;
858 
859     for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
860         //fill the containers for the work days and work time
861         el = document.getElementById(this._htmlElId + "_CAL_WORKING_START_TIME_"+i);
862         startTimeSelect = new DwtTimeInput(this, DwtTimeInput.START, el);
863         inputTime = new Date();
864         inputTime.setHours(workHours[i].startTime/100, workHours[i].startTime%100, 0);
865         startTimeSelect.set(inputTime);
866         startTimeSelect.setEnabled(workHours[i].isWorkingDay);
867         this._startTimeSelect.push(startTimeSelect);
868 
869 
870         inputTime = new Date();
871         inputTime.setHours(workHours[i].endTime/100, workHours[i].endTime%100, 0);
872         el = document.getElementById(this._htmlElId + "_CAL_WORKING_END_TIME_"+i);
873         endTimeSelect = new DwtTimeInput(this, DwtTimeInput.END, el);
874         endTimeSelect.set(inputTime);
875         endTimeSelect.setEnabled(workHours[i].isWorkingDay);
876         this._endTimeSelect.push(endTimeSelect);
877 
878 
879         checkbox = new DwtCheckbox({parent:this, parentElement:(this._htmlElId + "_CAL_WORKING_DAY_" + i)});
880         checkbox.setText(AjxDateUtil.WEEKDAY_MEDIUM[i]);
881 	    checkbox.setSelected(workHours[i].isWorkingDay);
882         checkbox.addSelectionListener(new AjxListener(this, this._setTimeInputEnabled, [i, checkbox]));
883         this._workDaysCheckBox.push(checkbox);
884     }
885 };
886 
887 ZmCustomWorkHoursDlg.prototype.reset =
888 function() {
889     var i,
890     inputTime;
891 
892     for (i = 0; i < AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
893         inputTime = new Date();
894         inputTime.setHours(this._workHours[i].startTime/100, this._workHours[i].startTime%100, 0);
895         this._startTimeSelect[i].set(inputTime);
896         this._startTimeSelect[i].setEnabled(this._workHours[i].isWorkingDay);
897 
898         inputTime = new Date();
899         inputTime.setHours(this._workHours[i].endTime/100, this._workHours[i].endTime%100, 0);
900         this._endTimeSelect[i].set(inputTime);
901         this._endTimeSelect[i].setEnabled(this._workHours[i].isWorkingDay);
902 
903         this._workDaysCheckBox[i].setSelected(this._workHours[i].isWorkingDay);
904     }
905 }
906 
907 ZmCustomWorkHoursDlg.prototype.reloadWorkHours =
908 function(workHours) {
909     workHours = workHours || appCtxt.get(ZmSetting.CAL_WORKING_HOURS);
910     this._workHours = workHours;
911 };
912 
913 ZmCustomWorkHoursDlg.prototype._setTimeInputEnabled =
914 function(idx, checkbox) {
915     this._startTimeSelect[idx].setEnabled(checkbox.isSelected());
916     this._endTimeSelect[idx].setEnabled(checkbox.isSelected());
917 };
918 
919 ZmCustomWorkHoursDlg.prototype.isDirty =
920 function() {
921     var i,
922         workHours = this._workHours,
923         tf = new AjxDateFormat("HHmm"),
924         startInputTime,
925         endInputTime;
926 
927     for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
928         if(this._workDaysCheckBox[i].isSelected() != workHours[i].isWorkingDay) {
929             this.setDaysChanged(true);
930             return true;
931         }
932     }
933     for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
934         startInputTime = tf.format(this._startTimeSelect[i].getValue());
935         endInputTime = tf.format(this._endTimeSelect[i].getValue());
936         
937         if(startInputTime != workHours[i].startTime
938         || endInputTime != workHours[i].endTime
939         || this._workDaysCheckBox[i].isSelected() != workHours[i].isWorkingDay) {
940             return true;
941         }
942     }
943     return false;
944 };
945 
946 ZmCustomWorkHoursDlg.prototype.popup =
947 function() {
948 	DwtDialog.prototype.popup.call(this);
949 };
950 
951 ZmCustomWorkHoursDlg.prototype.isValid =
952 function() {
953     var i,
954         tf = new AjxDateFormat("HHmm"),
955         startInputTime,
956         endInputTime;
957 
958     for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
959         if(this._workDaysCheckBox[i].isSelected()) {
960             startInputTime = tf.format(this._startTimeSelect[i].getValue());
961             endInputTime = tf.format(this._endTimeSelect[i].getValue());
962 
963             if(startInputTime > endInputTime) {
964                 return false;
965             }
966         }
967     }
968     return true;
969 
970 };
971 
972 ZmCustomWorkHoursDlg.prototype.getValue =
973 function() {
974     var i,
975         tf = new AjxDateFormat("HHmm"),
976         startInputTime,
977         endInputTime,
978         dayStr,
979         wDaysStr = [];
980 
981     for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) {
982         startInputTime = tf.format(this._startTimeSelect[i].getValue());
983         endInputTime = tf.format(this._endTimeSelect[i].getValue());
984         dayStr = [];
985         dayStr.push(i+1);
986         if(this._workDaysCheckBox[i].isSelected()) {
987             dayStr.push("Y");
988         }
989         else {
990             dayStr.push("N");
991         }
992         dayStr.push(startInputTime);
993         dayStr.push(endInputTime);
994         wDaysStr.push(dayStr.join(ZmWorkHours.STR_TIME_SEP));
995     }
996     return wDaysStr.join(ZmWorkHours.STR_DAY_SEP);
997 };
998 
999 ZmCustomWorkHoursDlg.prototype.setDaysChanged =
1000 function(value) {
1001     this._daysChanged = value;
1002 };
1003 
1004 ZmCustomWorkHoursDlg.prototype.getDaysChanged =
1005 function() {
1006     return this._daysChanged;
1007 };
1008 
1009 
1010