1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 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, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Create a new, empty appointment list. 26 * @constructor 27 * @class 28 * This class represents a list of appointments. 29 * 30 * @extends ZmList 31 */ 32 ZmApptList = function() { 33 ZmList.call(this, ZmItem.APPT); 34 }; 35 36 ZmApptList.prototype = new ZmList; 37 ZmApptList.prototype.constructor = ZmApptList; 38 39 ZmApptList.prototype.isZmApptList = true; 40 ZmApptList.prototype.toString = function() { return "ZmApptList"; }; 41 42 ZmApptList.prototype.loadFromSummaryJs = 43 function(appts, noCache) { 44 if (!appts) { return; } 45 46 for (var i = 0; i < appts.length; i++) { 47 var apptNode = appts[i]; 48 var instances = apptNode ? apptNode.inst : null; 49 if (instances) { 50 var args = {list:this}; 51 for (var j = 0; j < instances.length; j++) { 52 var appt = ZmAppt.createFromDom(apptNode, args, instances[j], noCache); 53 if (appt) this.add(appt); 54 } 55 } 56 } 57 } 58 59 ZmApptList.prototype.indexOf = 60 function(obj) { 61 return this._vector.indexOf(obj); 62 }; 63 64 ZmApptList.sortVector = 65 function(vec) { 66 vec.sort(ZmCalBaseItem.compareByTimeAndDuration); 67 }; 68 69 /** 70 * Merges all the sorted vectors in the specified array into a single sorted vector. 71 * 72 * @param {AjxVector} vecArray the array 73 * @return {AjxVector} the resulting array 74 */ 75 ZmApptList.mergeVectors = 76 function(vecArray) { 77 var result = new AjxVector(); 78 if(!vecArray) { return result; } 79 80 // clone the single array case! 81 if (vecArray.length == 1) return vecArray[0].clone(); 82 for (var i=0; i < vecArray.length; i++) result.addList(vecArray[i]); 83 ZmApptList.sortVector(result); 84 return result; 85 }; 86 87 ZmApptList.toVector = 88 function(apptList, startTime, endTime, fanoutAllDay, includeReminders) { 89 var result = new AjxVector(); 90 var list = apptList.getVector(); 91 var size = list.size(); 92 for (var i = 0; i < size; i++) { 93 var ao = list.get(i); 94 var folder = ao.getFolder(); 95 if (ao.isInRange(startTime, endTime) || (ao.isAlarmInRange() && includeReminders)) { 96 if (ao.isAllDayEvent() && !fanoutAllDay) { 97 result.add(ZmAppt.quickClone(ao)); 98 } else { 99 ZmApptList._fanout(ao, result, startTime, endTime, fanoutAllDay, includeReminders); 100 } 101 } 102 } 103 ZmApptList.sortVector(result); 104 return result; 105 }; 106 107 /** 108 * fanout multi-day appoints into multiple single day appts. This has nothing to do with recurrence... 109 * TODO: should be more efficient by not fanning out appts in if part of the while if they are not in the range. 110 * 111 * @private 112 */ 113 ZmApptList._fanout = 114 function(orig, result, startTime, endTime, fanoutAllDay, includeReminders) { 115 var appt = ZmAppt.quickClone(orig); 116 var fanoutNum = 0; 117 118 // HACK: Avoid "strange" appt durations that occur at transition 119 // days for timezones w/ DST. For example, going from DST to 120 // STD, the duration for a single day is 25 hours; while the 121 // transition from STD to DST, the duration is 23 hours. So 122 // we advance 12 hours (just to be safe) and then subtract 123 // off the extra hours. 124 var origEndTime = orig.getEndTime(); 125 if (appt.isAllDayEvent()) { 126 var origEndDate = new Date(origEndTime); 127 origEndDate.setHours(0, 0, 0, 0); 128 129 appt.setEndDate(origEndDate); 130 origEndTime = origEndDate.getTime(); 131 } 132 133 //while (appt.isInRange(startTime,endTime) || (appt.isAlarmInRange() && includeReminders)) { 134 while (orig.isInRange(startTime,endTime) || (appt.isAlarmInRange() && includeReminders)) { 135 if (appt.isMultiDay()) { 136 var apptStartTime = appt.getStartTime(); 137 // bug 12205: If someone mistypes "2007" as "200", we get into 138 // a seemingly never-ending loop trying to fanout 139 // every day even *before* the startTime of the view. 140 var nextDay = new Date(apptStartTime); 141 nextDay.setDate(nextDay.getDate()+1); 142 if (origEndTime < nextDay.getTime()) { 143 nextDay = new Date(origEndTime); 144 } 145 nextDay.setHours(0,0,0,0); 146 if(AjxDateUtil.isDayShifted(nextDay)) { 147 AjxDateUtil.rollToNextDay(nextDay); 148 } 149 150 var slice = ZmAppt.quickClone(appt); 151 slice._fanoutFirst = (fanoutNum == 0); 152 slice._orig = orig; 153 slice.setEndDate(nextDay); 154 slice._fanoutLast = (slice.getEndTime() == origEndTime); 155 slice._fanoutNum = fanoutNum; 156 slice.uniqStartTime = slice.getStartTime(); // need to construct uniq id later 157 result.add(slice); 158 159 fanoutNum++; 160 appt.setStartDate(nextDay); 161 if (appt.getStartTime() >= appt.getEndTime()) 162 break; 163 } else { 164 if (orig.isInRange(startTime,endTime) || (appt.isAlarmInRange() && includeReminders) ) { 165 appt._fanoutFirst = fanoutNum == 0; 166 appt._fanoutLast = appt.getEndTime() == origEndTime; 167 if (!appt._fanoutFirst) 168 appt._orig = orig; 169 appt._fanoutNum = fanoutNum; 170 appt.uniqStartTime = appt.getStartTime(); // need to construct uniq id later 171 result.add(appt); 172 } 173 break; 174 } 175 } 176 }; 177 178 /** 179 * Gets a new appointment list containing only appointment in the given range. 180 * 181 * @param {Date} startTime the start time 182 * @param {Date} endTime the end time 183 * @author {ZmApptList} the new list 184 */ 185 ZmApptList.prototype.getSubset = 186 function(startTime, endTime) { 187 var result = new ZmApptList(); 188 var list = this.getVector(); 189 var size = list.size(); 190 for (var i=0; i < size; i++) { 191 var ao = list.get(i); 192 if (ao.isInRange(startTime, endTime)) { 193 result.add(ao); 194 } 195 } 196 return result; 197 }; 198 199 200 /** 201 * Moves a list of items to the given folder. 202 * <p> 203 * Search results are treated as though they're in a temporary folder, so that they behave as 204 * they would if they were in any other folder such as Inbox. When items that are part of search 205 * results are moved, they will disappear from the view, even though they may still satisfy the 206 * search. 207 * </p> 208 * 209 * @param {Hash} params a hash of parameters 210 * @param {Array} params.items a list of items to move 211 * @param {ZmFolder} params.folder the destination folder 212 * @param {Hash} params.attrs the additional attrs for SOAP command 213 * @param {AjxCallback} params.callback the callback to run after each sub-request 214 * @param {closure} params.finalCallback the callback to run after all items have been processed 215 * @param {int} params.count the starting count for number of items processed 216 * @param {boolean} params.noUndo true if the action is not undoable (e.g. performed as an undo) 217 * @param {String} params.actionTextKey key for optional text to display in the confirmation toast instead of the default summary. May be set explicitly to null to disable the confirmation toast entirely 218 */ 219 ZmApptList.prototype.moveItems = 220 function(params) { 221 params = Dwt.getParams(arguments, ["items", "folder", "attrs", "callback", "errorCallback" ,"finalCallback", "noUndo", "actionTextKey"]); 222 223 var params1 = AjxUtil.hashCopy(params); 224 params1.items = AjxUtil.toArray(params.items); 225 params1.attrs = params.attrs || {}; 226 if (params1.folder.id == ZmFolder.ID_TRASH) { 227 params1.actionTextKey = (params.actionTextKey !== null) ? (params.actionTextKey || 'actionTrash') : null; 228 params1.action = "trash"; 229 //This code snippet differs from the ZmList.moveItems 230 var currentView = appCtxt.getCurrentView(); 231 if(currentView) { 232 var viewController = currentView.getController(); 233 if(viewController) { 234 //Since it is a drag and drop, only one item can be dragged - so get the first element from array 235 return viewController._deleteAppointment(params1.items[0]); 236 } 237 } 238 } else { 239 params1.actionTextKey = (params.actionTextKey !== null) ? (params.actionTextKey || 'actionMove') : null; 240 params1.actionArg = params.folder.getName(false, false, true); 241 params1.action = "move"; 242 params1.attrs.l = params.folder.id; 243 } 244 245 if (appCtxt.multiAccounts) { 246 // Reset accountName for multi-account to be the respective account if we're 247 // moving a draft out of Trash. 248 // OR, 249 // check if we're moving to or from a shared folder, in which case, always send 250 // request on-behalf-of the account the item originally belongs to. 251 var folderId = params.items[0].getFolderId(); 252 var fromFolder = appCtxt.getById(folderId); 253 if ((params.items[0].isDraft && params.folder.id == ZmFolder.ID_DRAFTS) || 254 (params.folder.isRemote()) || (fromFolder.isRemote())) 255 { 256 params1.accountName = params.items[0].getAccount().name; 257 } 258 } 259 260 //Error Callback 261 params1.errorCallback = params.errorCallback; 262 263 this._itemAction(params1); 264 }; 265