1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 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) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a calendar.
 26  * @constructor
 27  * @class
 28  *
 29  * @author Andy Clark
 30  *
 31  * @param {Hash}		params			a hash of parameters:
 32  * @param {int}			params.id		the numeric ID
 33  * @param {String}		params.name		the name
 34  * @param {ZmOrganizer}	params.parent	the parent organizer
 35  * @param {ZmTree}		params.tree		the tree model that contains this organizer
 36  * @param {constant}	params.color	the color for this calendar
 37  * @param {String}		params.url		the URL for this organizer's feed
 38  * @param {String}		params.owner	the owner of this calendar
 39  * @param {String}		params.zid		the Zimbra id of owner, if remote share
 40  * @param {String}		params.rid		the remote id of organizer, if remote share
 41  * @param {String}		params.restUrl	the REST URL of this organizer
 42  * 
 43  * @extends		ZmFolder
 44  */
 45 ZmCalendar = function(params) {
 46 	params.type = ZmOrganizer.CALENDAR;
 47 	ZmFolder.call(this, params);
 48 };
 49 
 50 ZmCalendar.prototype = new ZmFolder;
 51 ZmCalendar.prototype.constructor = ZmCalendar;
 52 
 53 ZmCalendar.prototype.isZmCalendar = true;
 54 ZmCalendar.prototype.toString = function() { return "ZmCalendar"; };
 55 
 56 // Consts
 57 
 58 ZmCalendar.ID_CALENDAR = ZmOrganizer.ID_CALENDAR;
 59 
 60 // Public methods
 61 
 62 /**
 63  * Creates a new calendar. The color and flags will be set later in response
 64  * to the create notification. This function is necessary because calendar
 65  * creation needs custom error handling.
 66  * 
 67  * @param	{Hash}	params		a hash of parameters
 68  */
 69 ZmCalendar.create =
 70 function(params) {
 71 	params.errorCallback = new AjxCallback(null, ZmCalendar._handleErrorCreate, params);
 72 	ZmOrganizer.create(params);
 73 };
 74 
 75 ZmCalendar._handleErrorCreate =
 76 function(params, ex) {
 77 	if (params.url && (ex.code == ZmCsfeException.SVC_PARSE_ERROR)) {
 78 		msg = AjxMessageFormat.format(ZmMsg.calFeedInvalid, params.url);
 79 		ZmOrganizer._showErrorMsg(msg);
 80 		return true;
 81 	}
 82 	return ZmOrganizer._handleErrorCreate(params, ex);
 83 };
 84 
 85 /**
 86  * Gets the icon.
 87  * 
 88  * @return	{String}	the icon
 89  */
 90 ZmCalendar.prototype.getIcon = 
 91 function() {
 92 	if (this.nId == ZmOrganizer.ID_ROOT)	{ return null; }
 93 	if (this.link)							{ return "SharedCalendarFolder"; }
 94 	return "CalendarFolder";
 95 };
 96 
 97 /**
 98  * Sets the free/busy.
 99  * 
100  * @param	{Boolean}	        exclude		    if <code>true</code>, exclude free busy
101  * @param	{AjxCallback}	    callback		the callback
102  * @param	{AjxCallback}	    errorCallback	the error callback
103  * @param   {ZmBatchCommand}    batchCmd        optional batch command
104  */
105 ZmCalendar.prototype.setFreeBusy = 
106 function(exclude, callback, errorCallback, batchCmd) {
107 	if (this.excludeFreeBusy == exclude) { return; }
108 	// NOTE: Don't need to store the value since the response will
109 	//       report that the object was modified.
110 	this._organizerAction({action: "fb", attrs: {excludeFreeBusy: exclude ? "1" : "0"},
111                            callback: callback, errorCallback: errorCallback, batchCmd: batchCmd});
112 };
113 
114 ZmCalendar.prototype.setChecked = 
115 function(checked, batchCmd) {
116 	if (this.isChecked == checked) { return; }
117 	this.checkAction(checkAction, batchCmd);
118 };
119 
120 ZmCalendar.prototype.checkAction = 
121 function(checked, batchCmd) {
122 	var action = checked ? "check" : "!check";
123 	var checkedCallback = new AjxCallback(this, this.checkedCallback, [checked]);
124 	this._organizerAction({action: action, batchCmd: batchCmd,callback: checkedCallback});
125 };
126 
127 ZmCalendar.prototype.checkedCallback = 
128 function(checked, result) {
129 	var overviewController = appCtxt.getOverviewController();
130 	var treeController = overviewController.getTreeController(this.type);
131 	var overviewId = appCtxt.getCurrentApp().getOverviewId();
132 	var treeView = treeController.getTreeView(overviewId);
133 
134 	if (treeView && this.id && treeView._treeItemHash[this.id]) {
135 		treeView._treeItemHash[this.id].setChecked(checked);
136 	}
137 };
138 
139 /**
140  * Checks if the given object(s) may be placed in this folder.
141  *
142  * For calendars being dragged, the current target cannot:
143  *   - Be the parent of the dragged calendar
144  *   - Be the dragged calendar
145  *   - Be an ancestor of the dragged calendar
146  *   - Contain a calendar with the same name as the dragged calendar
147  *   - Be a shared calendar
148  *
149  * @param {Object}	what		the object(s) to possibly move into this folder (item or organizer)
150  * @return	{Boolean}	<code>true</code> if the object may be placed in this folder
151  */
152 ZmCalendar.prototype.mayContain =
153 function(what) {
154     if (!what) { return true; }
155 
156     var invalid = false;
157     if (what instanceof ZmCalendar) {
158         // Calendar DnD, possibly nesting calendars
159         invalid = ((what.parent == this) ||  (what.id == this.id)  || this.isChildOf(what) ||
160                    (!this.isInTrash() && this.hasChild(what.name)) || this.link);
161     } else {
162         //exclude the deleted folders
163         if(this.noSuchFolder) return invalid;
164 
165 		if (this.nId == ZmOrganizer.ID_ROOT) {
166 			// cannot drag anything onto root folder
167 			invalid = true;
168 		} else if (this.link) {
169 			// cannot drop anything onto a read-only addrbook
170 			invalid = this.isReadOnly();
171 		}
172 
173 		if (!invalid) {
174 			// An item or an array of items is being moved
175 			var items = (what instanceof Array) ? what : [what];
176 			var item = items[0];
177 
178 			// can't move items to folder they're already in; we're okay if
179 			// we have one item from another folder
180             if (item && item.folderId) {
181                 invalid = true;
182                 for (var i = 0; i < items.length; i++) {
183                     var folder = appCtxt.getById(items[i].folderId);
184                     if(items[i].isReadOnly() && folder.owner != this.owner) {
185                         invalid = true;
186                         break;
187                     }
188                     if (item.viewMode == ZmCalItem.MODE_NEW || folder != this) {
189                         invalid = false;
190                         break;
191                     }
192 
193                 }
194             }
195 		}
196 
197 	}
198 
199 	return !invalid;
200 };
201 
202 
203 // Callbacks
204 
205 ZmCalendar.prototype.notifyCreate =
206 function(obj) {
207 	var calendar = ZmFolderTree.createFromJs(this, obj, this.tree);
208 	var index = ZmOrganizer.getSortIndex(calendar, ZmFolder.sortCompareNonMail);
209 	this.children.add(calendar, index);
210 	calendar._notify(ZmEvent.E_CREATE);
211 };
212 
213 ZmCalendar.prototype.notifyModify =
214 function(obj) {
215 	ZmFolder.prototype.notifyModify.call(this, obj);
216 
217 	var doNotify = false;
218 	var fields = {};
219 	if (obj.f != null && !obj._isRemote) {
220 		this._parseFlags(obj.f);
221 		// TODO: Should a F_EXCLUDE_FB property be added to ZmOrganizer?
222 		//       It doesn't make sense to require the base class to know about
223 		//       all the possible fields in sub-classes. So I'm just using the
224 		//       modified property name as the key.
225 		fields["excludeFreeBusy"] = true;
226 		doNotify = true;
227 	}
228 
229 	if (doNotify) {
230 		this._notify(ZmEvent.E_MODIFY, {fields: fields});
231 	}
232 };
233 
234 ZmCalendar.prototype.notifyDelete =
235 function(obj) {
236 
237     if(this.isRemote() && !this._deleteAction){
238         var overviewController = appCtxt.getOverviewController();
239         var treeController = overviewController.getTreeController(this.type);
240         var overviewId = appCtxt.getCurrentApp().getOverviewId();
241         var treeView = treeController && treeController.getTreeView(overviewId);
242         var node = treeView && treeView.getTreeItemById(this.id);
243         this.noSuchFolder = true;
244         if(node) {
245             node.setText(this.getName(true));
246         }
247     }else{
248         ZmFolder.prototype.notifyDelete.call(this, obj);
249     }
250 };
251 
252 ZmCalendar.prototype._delete = function(){
253     this._deleteAction = true;
254     ZmFolder.prototype._delete.call(this);
255 };
256 
257 // Static methods
258 
259 /**
260  * Checks the calendar name.
261  * 
262  * @param	{String}	name		the name to check
263  * @return	{String}	the valid calendar name
264  */
265 ZmCalendar.checkName =
266 function(name) {
267 	return ZmOrganizer.checkName(name);
268 };
269 
270 ZmCalendar.prototype.supportsPrivatePermission =
271 function() {
272 	return true;
273 };
274 
275 // overriding ZmFolder.prototype.supportsPublicAccess
276 ZmCalendar.prototype.supportsPublicAccess =
277 function() {
278 	// calendars can be accessed outside of ZCS
279 	return true;
280 };
281 
282 ZmCalendar.prototype.getRestUrl =
283 function(acct, noRemote) {
284 
285     if(!appCtxt.multiAccounts){
286         return ZmFolder.prototype.getRestUrl.call(this, noRemote);
287     }
288 
289 	// return REST URL as seen by server
290 	if (this.restUrl) {
291 		return this.restUrl;
292 	}
293 
294 	// if server doesn't tell us what URL to use, do our best to generate
295 	url = this._generateRestUrl(acct);
296 	DBG.println(AjxDebug.DBG3, "NO REST URL FROM SERVER. GENERATED URL: " + url);
297 
298 	return url;
299 };
300 
301 ZmCalendar.prototype._generateRestUrl =
302 function(acct) {
303 	var loc = document.location;
304 	var owner = this.getOwner();
305 	var uname = owner || appCtxt.get(ZmSetting.USERNAME);
306     if (appCtxt.multiAccounts) {
307         uname = appCtxt.get(ZmSetting.USERNAME, null, acct)
308     }
309 	var m = uname.match(/^(.*)@(.*)$/);
310 	var host = loc.host || (m && m[2]);
311 
312 	// REVISIT: What about port? For now assume other host uses same port
313 	if (loc.port && loc.port != 80 && (owner == appCtxt.get(ZmSetting.USERNAME))) {
314 		host = host + ":" + loc.port;
315 	}
316 
317 	return [
318 		loc.protocol, "//", host, "/service/user/", uname, "/",
319 		AjxStringUtil.urlEncode(this.getSearchPath(true))
320 	].join("");
321 };
322 
323 
324 /**
325  * Checks if the calendar is read-only.
326  *
327  * @return	{Boolean}	<code>true</code> if read-only
328  */
329 ZmCalendar.prototype.isReadOnly =
330 function() {
331 	if (this.isFeed()) {
332 		return true; //feed calendar is read-only
333 	}
334 	return ZmFolder.prototype.isReadOnly.call(this);
335 };
336 
337 
338 /**
339  * Checks if the calendar supports public access.
340  *
341  * @return	{Boolean}	always returns <code>true</code>
342  */
343 ZmCalendar.prototype.supportsPublicAccess =
344 function() {
345 	// Overridden to allow sharing of calendar outside of ZCS
346 	return true;
347 };