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 };