1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file defines a task. 27 */ 28 29 /** 30 * @class 31 * 32 * This class represents a task. 33 * 34 * @author Parag Shah 35 * 36 * @param {Object} list the list 37 * @param {int} id the task id 38 * @param {String} folderId the folder id 39 * 40 * @extends ZmCalItem 41 */ 42 ZmTask = function(list, id, folderId) { 43 ZmCalItem.call(this, ZmItem.TASK, list, id, folderId); 44 45 this.priority = ZmCalItem.PRIORITY_NORMAL; 46 this.pComplete = 0; 47 this.status = ZmCalendarApp.STATUS_NEED; 48 this.startDate = null; 49 this.endDate = null; 50 this.remindDate = new Date(); 51 this.alarm = false; 52 this._useAbsoluteReminder = true; 53 }; 54 55 ZmTask.prototype = new ZmCalItem; 56 ZmTask.prototype.constructor = ZmTask; 57 58 59 // Consts 60 61 /** 62 * @private 63 */ 64 ZmTask.PCOMPLETE_INT = 10; 65 66 /** 67 * Used to make our own copy because the form will modify the date object by 68 * calling its setters instead of replacing it with a new date object. 69 * 70 * @private 71 */ 72 ZmTaskClone = function() { }; 73 ZmTask.quickClone = 74 function(task) { 75 ZmTaskClone.prototype = task; 76 77 var newTask = new ZmTaskClone(); 78 newTask.startDate = task.startDate ? (new Date(task.startDate.getTime())) : null; 79 newTask.endDate = task.endDate ? (new Date(task.endDate.getTime())) : null; 80 newTask._uniqId = Dwt.getNextId(); 81 82 newTask._validAttachments = AjxUtil.createProxy(task._validAttachments); 83 84 if (!newTask._orig) 85 newTask._orig = task; 86 87 newTask.type = ZmItem.TASK; 88 89 return newTask; 90 }; 91 92 /** 93 * Creates a task from the DOM. 94 * 95 * @param {Object} taskNode the task 96 * @param {Hash} args arguments 97 * @param {Object} instNode (not used) 98 * 99 * @return {ZmTask} the task 100 */ 101 ZmTask.createFromDom = 102 function(taskNode, args, instNode) { 103 // NOTE: passing ID implies this item should get cached! 104 var task = new ZmTask(args.list, taskNode.id); 105 task._loadFromDom(taskNode, instNode); 106 107 return task; 108 }; 109 110 111 // Public Methods 112 113 /** 114 * Returns a string representation of the object. 115 * 116 * @return {String} a string representation of the object 117 */ 118 ZmTask.prototype.toString = 119 function() { 120 return "ZmTask"; 121 }; 122 123 /** 124 * Gets the icon. 125 * 126 * @return {String} the icon 127 */ 128 ZmTask.prototype.getIcon = function() { return "Task"; }; 129 130 /** 131 * Gets the folder. 132 * 133 * @return {ZmTaskFolder} the folder 134 */ 135 ZmTask.prototype.getFolder = 136 function() { 137 return appCtxt.getById(this.folderId); 138 }; 139 140 /** 141 * 142 * 143 * @return {id} task id 144 */ 145 ZmTask.prototype.getId = 146 function() { 147 return this.id; 148 }; 149 /** 150 * Gets the summary. 151 * 152 * @private 153 */ 154 ZmTask.prototype.getSummary = 155 function(isHtml) { 156 // TODO 157 }; 158 159 /** 160 * Gets the tool tip. 161 * 162 * @private 163 */ 164 ZmTask.prototype.getToolTip = 165 function(controller) { 166 // TODO 167 DBG.println("------------ TODO: getTooltip! --------------"); 168 }; 169 170 /** 171 * @private 172 */ 173 ZmTask.prototype.notifyModify = 174 function(obj) { 175 ZmItem.prototype.notifyModify.call(this, obj); 176 177 this.uid = obj.uid; 178 if (obj.l) this.folderId = obj.l; 179 180 // update this task with notify data 181 this._loadFromDom(obj); 182 this._notify(ZmEvent.E_MODIFY, obj); 183 }; 184 185 /** 186 * Checks if this task is past due. 187 * 188 * @return {Boolean} <code>true</code> if the task is past due 189 */ 190 ZmTask.prototype.isPastDue = 191 function() { 192 return (this.endDate && ((new Date()).getTime() > this.endDate.getTime())); 193 }; 194 195 /** 196 * Gets the end time. 197 * 198 * @return {Date} the end time 199 */ 200 ZmTask.prototype.getEndTime = function() { return this.endDate ? this.endDate.getTime() : null; }; // end time in ms 201 202 203 /** 204 * Gets the start time. 205 * 206 * @return {Date} the start time 207 */ 208 ZmTask.prototype.getStartTime = function() { return this.startDate ? this.startDate.getTime() : null; }; // start time in ms 209 210 211 /** 212 * Checks if the task is complete. 213 * 214 * @return {Boolean} <code>true</code> if the task is complete 215 */ 216 ZmTask.prototype.isComplete = 217 function() { 218 return (this.pComplete == 100) || (this.status == ZmCalendarApp.STATUS_COMP); 219 }; 220 221 /** 222 * Gets the percent complete (between 0 and 100). 223 * 224 * @return {int} the percentage complete 225 */ 226 ZmTask.prototype.getPercentComplete = 227 function() { 228 return this.pComplete; 229 }; 230 231 /** 232 * Gets the status. 233 * 234 * @return {int} the status 235 * 236 * @see ZmCalendarApp.STATUS_COMP 237 * @see ZmCalendarApp.STATUS_DEFR 238 * @see ZmCalendarApp.STATUS_INPR 239 * @see ZmCalendarApp.STATUS_NEED 240 * @see ZmCalendarApp.STATUS_WAIT 241 * 242 * @see ZmCalItem.getLabelForStatus 243 */ 244 ZmTask.prototype.getStatus = 245 function() { 246 return this.status; 247 } 248 249 /** 250 * Gets the priority. 251 * 252 * @return {int} the priority 253 * 254 * @see ZmCalItem.PRIORITY_LOW 255 * @see ZmCalItem.PRIORITY_NORMAL 256 * @see ZmCalItem.PRIORITY_HIGH 257 * @see ZmCalItem.getLabelForPriority 258 * @see ZmCalItem.getImageForPriority 259 */ 260 ZmTask.prototype.getPriority = 261 function() { 262 return this.priority; 263 } 264 265 /** 266 * Simplify deleting/canceling of Tasks by just not worrying about attendees, 267 * recurrence, etc. and always assume it will use BatchRequest. At some point, 268 * when Tasks supports attendees (aka assignment) and/or recurrence, this method 269 * will have to go thru ZmCalItem (share code with ZmAppt). 270 * 271 * @param mode [Int] Required constant. Usually ZmCalItem.MODE_DELETE 272 * @param batchCmd [ZmBatchCommand] Required API for batch request 273 * 274 * @private 275 */ 276 ZmTask.prototype.cancel = 277 function(mode, batchCmd) { 278 this.setViewMode(mode); 279 var jsonObj = {}, 280 requestName = this._getRequestNameForMode(mode), 281 request = jsonObj[requestName] = { 282 _jsns : "urn:zimbraMail" 283 }; 284 this._addInviteAndCompNum(request); 285 286 // NOTE: we dont bother w/ handling the response - since UI gets updated via notifications 287 batchCmd.addRequestParams(jsonObj); 288 }; 289 290 /** 291 * Gets the "owner" of remote/shared calItem folder this calItem belongs to. 292 * 293 * @return {ZmFolder} the folder 294 */ 295 ZmTask.prototype.getRemoteFolderOwner = 296 function() { 297 // bug fix #18855 - dont return the folder owner if moving betw. accounts 298 var controller = AjxDispatcher.run("GetTaskController"); 299 if (controller.isMovingBetwAccounts(this, this.folderId)) { 300 return null; 301 } 302 var folder = this.getFolder(); 303 return (folder && folder.link) ? folder.owner : null; 304 }; 305 306 // Private/protected methods 307 308 /** 309 * @private 310 */ 311 ZmTask.prototype._getDefaultFolderId = 312 function() { 313 return ZmOrganizer.ID_TASKS; 314 }; 315 316 /** 317 * @private 318 */ 319 ZmTask.prototype._loadFromDom = 320 function(node, instNode) { 321 var inv = node.inv ? node.inv[0] : null 322 var comp = inv ? inv.comp[0] : null; 323 324 if (!node.id) this.id = node.id; 325 // always re-compute invId if given since its mutable 326 if (node.invId) { 327 this.invId = node.invId; 328 } else if (inv) { 329 var remoteIndex = inv.id; 330 remoteIndex = remoteIndex.toString().indexOf(":"); 331 if (remoteIndex != -1) { 332 this.invId = this.id + "-" + inv.id.substring(remoteIndex+1); 333 } else { 334 this.invId = [node.id, inv.id].join("-"); 335 } 336 } 337 this.uid = node.uid; // XXX: what is this? 338 339 if (node.l) this.folderId = node.l; 340 if (node.s) this.size = node.s; 341 if (node.sf) this.sf = node.sf; 342 343 this.allDayEvent = (instNode ? instNode.allDay : null || node.allDay) ? "1" : "0"; 344 345 var nodeInst = node.inst && node.inst.length > 0 ? node.inst[0] : null; 346 var tzo = this.tzo = nodeInst && nodeInst.tzo != null ? parseInt(nodeInst.tzo) : 0; 347 var tzoDue = this.tzoDue = nodeInst && nodeInst.tzoDue != null ? parseInt(nodeInst.tzoDue) : 0; 348 349 if (nodeInst && nodeInst.s) { 350 var adjustMs = this.isAllDayEvent() ? (tzo + new Date(parseInt(nodeInst.s,10)).getTimezoneOffset()*60*1000) : 0; 351 var startTime = parseInt(nodeInst.s,10) + adjustMs; 352 this.startDate = new Date(startTime); 353 this.uniqStartTime = this.startDate.getTime(); 354 } else { 355 if (comp) { 356 this.startDate = null; 357 if (comp.s && comp.s[0].d) { 358 var start = comp.s[0].d; 359 var yyyy = parseInt(start.substr(0,4), 10); 360 var MM = parseInt(start.substr(4,2), 10); 361 var dd = parseInt(start.substr(6,2), 10); 362 this.startDate = new Date(yyyy, MM -1, dd); 363 } 364 } 365 } 366 367 if (nodeInst && nodeInst.dueDate) { 368 var adjustMs = this.isAllDayEvent() ? (tzoDue + new Date(parseInt(nodeInst.dueDate,10)).getTimezoneOffset()*60*1000) : 0; 369 var endTime = parseInt(nodeInst.dueDate,10) + adjustMs; 370 this.endDate = new Date(endTime); 371 } else { 372 if(comp) { 373 this.endDate = null; 374 if (comp.e && comp.e[0].d) { 375 var end = comp.e[0].d; 376 var yyyy = parseInt(end.substr(0,4), 10); 377 var MM = parseInt(end.substr(4,2), 10); 378 var dd = parseInt(end.substr(6,2), 10); 379 this.endDate = new Date(yyyy, MM -1, dd); 380 } 381 } 382 } 383 384 if(node.alarm){ 385 if(node.alarm) this.alarm = node.alarm; 386 if(node.alarmData) this.alarmData = this._getAttr(node, comp, "alarmData"); 387 } else { 388 if(comp && comp.alarm && (comp.alarm.length > 0) ){ 389 this.alarm = node.alarm = true; 390 this.alarmData = node.alarmData = comp.alarm; 391 } 392 } 393 394 if (node.name || comp) this.name = this._getAttr(node, comp, "name"); 395 if (node.loc || comp) this.location = this._getAttr(node, comp, "loc"); 396 if (node.allDay || comp) this.setAllDayEvent(this._getAttr(node, comp, "allDay")); 397 if (node.priority || comp) this.priority = parseInt(this._getAttr(node, comp, "priority")); 398 if (node.percentComplete || comp) this.pComplete = parseInt(this._getAttr(node, comp, "percentComplete")); 399 if (node.status || comp) this.status = this._getAttr(node, comp, "status"); 400 if (node.isOrg || comp) this.isOrg = new Boolean(this._getAttr(node, comp, "isOrg")); 401 if (node.or || comp) this.organizer = node.or ? node.or.a : (comp.or ? comp.or.a : null); 402 if (node.ptst || comp) this.ptst = this._getAttr(node, comp, "ptst"); 403 if (node.compNum != null) this.compNum = (this._getAttr(node, comp, "compNum") || "0"); 404 405 if (node.f) this._parseFlags(node.f); 406 if (node.tn) { 407 this._parseTagNames(node.tn); 408 } 409 410 this.type = ZmItem.TASK; 411 }; 412 413 /** 414 * Checks if alarm is in range (based on current time). 415 * 416 * @return {Boolean} <code>true</code> if the alarm is in range 417 */ 418 ZmTask.prototype.isAlarmInRange = 419 function() { 420 if (!this.alarmData) { return false; } 421 422 var alarmData = this.alarmData[0]; 423 424 if (!alarmData) { return false; } 425 426 this._nextAlarmTime = new Date(alarmData.nextAlarm); 427 this._alarmInstStart = this.adjustMS(alarmData.alarmInstStart, this.tzo); 428 429 var currentTime = (new Date()).getTime(); 430 431 return this._nextAlarmTime <= currentTime; 432 }; 433 434 /** 435 * @private 436 */ 437 ZmTask.prototype._getAttr = 438 function(node, comp, name) { 439 if (node[name] != null) return node[name]; 440 if (comp) return comp[name]; 441 return null; 442 }; 443 444 /** 445 * Checks if alarm is modified. 446 * 447 * @return {Boolean} <code>true</code> if the alarm is modified 448 */ 449 ZmTask.prototype.isAlarmModified = 450 function() { 451 if(this._orig.alarm == true && this.alarm == false) { 452 return true; 453 } 454 return false; 455 } 456 457 /** 458 * Checks if this item is multi-day. 459 * 460 * @return {Boolean} <code>true</code> if start date and end date are on different days 461 * 462 * @see #getStartTime 463 * @see #getEndTime 464 */ 465 ZmTask.prototype.isMultiDay = 466 function() { 467 var start = this.startDate; 468 var end = this.endDate; 469 470 if(!start && !end) { return false; } 471 472 if(!start) { return false; } 473 474 //bug:55197 for task both startdate & enddate time is 00:00:00, so skipping the time based logic to check multiday or not 475 /* 476 if (end.getHours() == 0 && end.getMinutes() == 0 && end.getSeconds() == 0) { 477 // if end is the beginning of day, then disregard that it 478 // technically crossed a day boundary for the purpose of 479 // determining if it is a multi-day appt 480 end = new Date(end.getTime() - 2 * AjxDateUtil.MSEC_PER_HOUR); 481 }*/ 482 483 return (start.getDate() != end.getDate()) || 484 (start.getMonth() != end.getMonth()) || 485 (start.getFullYear() != end.getFullYear()); 486 }; 487 488 /** 489 * @private 490 */ 491 ZmTask.prototype._setExtrasFromMessage = 492 function(message) { 493 ZmCalItem.prototype._setExtrasFromMessage.apply(this, arguments); 494 495 this.location = message.invite.getLocation(); 496 }; 497 498 /** 499 * @private overriden to set endDate to be null only if endDate is empty 500 * @param message 501 * @param viewMode 502 */ 503 ZmTask.prototype._setTimeFromMessage = 504 function(message, viewMode) { 505 ZmCalItem.prototype._setTimeFromMessage.apply(this, arguments); 506 if(message.invite.components[0].s == null){ 507 this.startDate = null; 508 } 509 }; 510 511 /** 512 * @private 513 */ 514 ZmTask.prototype.parseAlarm = 515 function(tmp) { 516 if (!tmp) { return; } 517 518 var d; 519 var trigger = (tmp) ? tmp.trigger : null; 520 var abs = (trigger && (trigger.length > 0)) ? trigger[0].abs : null; 521 d = (abs && (abs.length > 0)) ? abs[0].d : null; 522 523 this._reminderMinutes = 0; 524 if (tmp && (tmp.action == "DISPLAY")) { 525 if (d != null) { 526 this._reminderAbs = d; 527 this.remindDate = d ? AjxDateUtil.parseServerDateTime(d) : null; 528 } 529 } 530 }; 531 532 533 /** 534 * @private 535 */ 536 ZmTask.prototype._getRequestNameForMode = 537 function(mode, isException) { 538 switch (mode) { 539 case ZmCalItem.MODE_NEW: 540 return "CreateTaskRequest"; 541 542 case ZmCalItem.MODE_EDIT_SINGLE_INSTANCE: 543 return !isException 544 ? "CreateTaskExceptionRequest" 545 : "ModifyTaskRequest"; 546 547 case ZmCalItem.MODE_EDIT: 548 case ZmCalItem.MODE_EDIT_SERIES: 549 return "ModifyTaskRequest"; 550 551 case ZmCalItem.MODE_DELETE: 552 case ZmCalItem.MODE_DELETE_SERIES: 553 case ZmCalItem.MODE_DELETE_INSTANCE: 554 return "CancelTaskRequest"; 555 556 case ZmCalItem.MODE_GET: 557 return "GetTaskRequest"; 558 } 559 560 return null; 561 }; 562 563 /** 564 * @private 565 */ 566 ZmTask.prototype._addExtrasToRequest = 567 function(request, comp) { 568 ZmCalItem.prototype._addExtrasToRequest.call(this, request, comp); 569 570 comp.percentComplete = this.pComplete; 571 572 // TODO - set "completed" if applicable 573 }; 574 575 /** 576 * @private 577 */ 578 ZmTask.prototype._getInviteFromError = 579 function(result) { 580 return (result._data.GetTaskResponse.task[0].inv[0]); 581 }; 582 583 /** 584 * @private 585 */ 586 ZmTask.prototype.setTaskReminder = 587 function(absStr) { 588 this._reminderAbs = absStr; 589 }; 590 591 /** 592 * Checks if the task is in the trash. 593 * 594 * @param {ZmTask} task the task 595 * @return {Boolean} <code>true</code> if in trash 596 */ 597 ZmTask.isInTrash = 598 function(task) { 599 var folderId = (task instanceof ZmTask) ? task.folderId : task.l; 600 var folder = appCtxt.getById(folderId); 601 return (folder && folder.isInTrash()); 602 }; 603