1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 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) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file defines a Zimbra calendar item. 27 * 28 */ 29 30 /** 31 * @class 32 * This class represents a calendar item. 33 * 34 * @param {constant} type the item type 35 * @param {ZmList} list the list 36 * @param {int} id the task id 37 * @param {String} folderId the folder id 38 * 39 * @extends ZmCalBaseItem 40 */ 41 ZmCalItem = function(type, list, id, folderId) { 42 if (arguments.length == 0) { return; } 43 44 ZmCalBaseItem.call(this, type, list, id, folderId); 45 46 this.notesTopPart = null; // ZmMimePart containing children w/ message parts 47 this.attachments = null; 48 this.viewMode = ZmCalItem.MODE_NEW; 49 this._recurrence = new ZmRecurrence(this); 50 this._noBusyOverlay = null; 51 this._sendNotificationMail = true; 52 this.identity = null; 53 this.isProposeTimeMode = false; 54 this.isForwardMode = false; 55 this.alarmActions = new AjxVector(); 56 this.alarmActions.add(ZmCalItem.ALARM_DISPLAY); 57 this._useAbsoluteReminder = false; 58 this._ignoreVersion=false; //to ignore revision related attributes(ms & rev) during version conflict 59 }; 60 61 ZmCalItem.prototype = new ZmCalBaseItem; 62 ZmCalItem.prototype.constructor = ZmCalItem; 63 64 /** 65 * Returns a string representation of the object. 66 * 67 * @return {String} a string representation of the object 68 */ 69 ZmCalItem.prototype.toString = 70 function() { 71 return "ZmCalItem"; 72 }; 73 74 // Consts 75 76 /** 77 * Defines the "new" mode. 78 */ 79 ZmCalItem.MODE_NEW = "NEW"; // Changing those constants from numbers to strings to be easier for debugging. I could not deal with 2,3 etc anymore. 80 /** 81 * Defines the "edit" mode. 82 */ 83 ZmCalItem.MODE_EDIT = "EDIT"; 84 85 /** 86 * Defines the "copy single instance" mode. 87 */ 88 ZmCalItem.MODE_COPY_SINGLE_INSTANCE = "COPY_INST"; 89 90 /** 91 * Defines the "edit single instance" mode. 92 */ 93 ZmCalItem.MODE_EDIT_SINGLE_INSTANCE = "EDIT_INST"; 94 /** 95 * Defines the "edit series" mode. 96 */ 97 ZmCalItem.MODE_EDIT_SERIES = "EDIT_SER"; 98 /** 99 * Defines the "delete" mode. 100 */ 101 ZmCalItem.MODE_DELETE = "DELETE"; 102 /** 103 * Defines the "delete instance" mode. 104 */ 105 ZmCalItem.MODE_DELETE_INSTANCE = "DELETE_INST"; 106 /** 107 * Defines the "delete series" mode. 108 */ 109 ZmCalItem.MODE_DELETE_SERIES = "DELETE_SER"; 110 /** 111 * Defines the "new from quick" mode. 112 */ 113 ZmCalItem.MODE_NEW_FROM_QUICKADD = "NEW_FROM_QUICK"; 114 /** 115 * Defines the "get" mode. 116 */ 117 ZmCalItem.MODE_GET = "GET"; 118 /** 119 * Defines the "forward" mode. 120 */ 121 ZmCalItem.MODE_FORWARD = "FORWARD"; 122 /** 123 * Defines the "forward single instance" mode. 124 */ 125 ZmCalItem.MODE_FORWARD_SINGLE_INSTANCE = "FORWARD_INST"; 126 /** 127 * Defines the "forward series" mode. 128 */ 129 ZmCalItem.MODE_FORWARD_SERIES = "FORWARD_SER"; 130 /** 131 * Defines the "forward" mode. 132 */ 133 ZmCalItem.MODE_FORWARD_INVITE = "FORWARD_INV"; 134 /** 135 * Defines the "propose" mode. 136 */ 137 ZmCalItem.MODE_PROPOSE_TIME = "PROPOSE_TIME"; 138 139 /** 140 * Defines the "purge" (delete from trash) mode. 141 */ 142 ZmCalItem.MODE_PURGE = 15; //keeping this and the last one as 15 as I am not sure if it's a bug or intentional that they are the same 143 144 /** 145 * Defines the "last" mode index constant. 146 */ 147 ZmCalItem.MODE_LAST = 15; 148 149 ZmCalItem.FORWARD_MAPPING = {}; 150 ZmCalItem.FORWARD_MAPPING[ZmCalItem.MODE_FORWARD] = ZmCalItem.MODE_EDIT; 151 ZmCalItem.FORWARD_MAPPING[ZmCalItem.MODE_FORWARD_SINGLE_INSTANCE] = ZmCalItem.MODE_EDIT_SINGLE_INSTANCE; 152 ZmCalItem.FORWARD_MAPPING[ZmCalItem.MODE_FORWARD_SERIES] = ZmCalItem.MODE_EDIT_SERIES; 153 ZmCalItem.FORWARD_MAPPING[ZmCalItem.MODE_FORWARD_INVITE] = ZmCalItem.MODE_EDIT; 154 155 /** 156 * Defines the "low" priority. 157 */ 158 ZmCalItem.PRIORITY_LOW = 9; 159 ZmCalItem.PRIORITY_LOW_RANGE = [6,7,8,9]; 160 161 /** 162 * Defines the "normal" priority. 163 */ 164 ZmCalItem.PRIORITY_NORMAL = 5; 165 ZmCalItem.PRIORITY_NORMAL_RANGE = [0,5]; 166 /** 167 * Defines the "high" priority. 168 */ 169 ZmCalItem.PRIORITY_HIGH = 1; 170 ZmCalItem.PRIORITY_HIGH_RANGE = [1,2,3,4]; 171 172 /** 173 * Defines the "chair" role. 174 */ 175 ZmCalItem.ROLE_CHAIR = "CHA"; 176 /** 177 * Defines the "required" role. 178 */ 179 ZmCalItem.ROLE_REQUIRED = "REQ"; 180 /** 181 * Defines the "optional" role. 182 */ 183 ZmCalItem.ROLE_OPTIONAL = "OPT"; 184 /** 185 * Defines the "non-participant" role. 186 */ 187 ZmCalItem.ROLE_NON_PARTICIPANT = "NON"; 188 189 ZmCalItem.SERVER_WEEK_DAYS = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"]; 190 191 ZmCalItem.ATTACHMENT_CHECKBOX_NAME = "__calAttCbox__"; 192 ZmCalItem.ATT_LINK_IMAGE = "mainImage"; 193 ZmCalItem.ATT_LINK_MAIN = "main"; 194 ZmCalItem.ATT_LINK_DOWNLOAD = "download"; 195 196 /** 197 * Defines "minutes "reminder units. 198 */ 199 ZmCalItem.REMINDER_UNIT_MINUTES = "minutes"; 200 /** 201 * Defines "hours" reminder units. 202 */ 203 ZmCalItem.REMINDER_UNIT_HOURS = "hours"; 204 /** 205 * Defines "days" reminder units. 206 */ 207 ZmCalItem.REMINDER_UNIT_DAYS = "days"; 208 /** 209 * Defines "weeks" reminder units. 210 */ 211 ZmCalItem.REMINDER_UNIT_WEEKS = "weeks"; 212 /** 213 * Defines "none" reminder. 214 */ 215 ZmCalItem.REMINDER_NONE = "none"; 216 217 // Alarm actions 218 ZmCalItem.ALARM_DISPLAY = "DISPLAY"; 219 ZmCalItem.ALARM_EMAIL = "EMAIL"; 220 ZmCalItem.ALARM_DEVICE_EMAIL = "DEVICE_EMAIL"; // SMS 221 222 // Duration Checks 223 ZmCalItem.MSEC_LIMIT_PER_WEEK = AjxDateUtil.MSEC_PER_DAY * 7; 224 // Because recurrences can be on the first (or 2nd, 3rd...) Day-of-week of a 225 // month, play it safe and make the limit 5 weeks 226 ZmCalItem.MSEC_LIMIT_PER_MONTH = AjxDateUtil.MSEC_PER_DAY * 7 * 5; 227 ZmCalItem.MSEC_LIMIT_PER_YEAR = AjxDateUtil.MSEC_PER_DAY * 366; 228 229 230 // Getters 231 232 /** 233 * @private 234 */ 235 ZmCalItem.prototype.getCompNum = function() { return this.compNum || "0"; }; 236 237 /** 238 * Gets the folder. 239 * 240 * @return {Object} the folder 241 */ 242 ZmCalItem.prototype.getFolder = function() { }; // override if necessary 243 244 /** 245 * Gets the organizer. 246 * 247 * @return {String} the organizer 248 */ 249 ZmCalItem.prototype.getOrganizer = function() { return this.organizer || ""; }; 250 251 /** 252 * Gets the organizer name. 253 * 254 * @return {String} the organizer name 255 */ 256 ZmCalItem.prototype.getOrganizerName = function() { return this.organizerName; }; 257 258 259 /** 260 * Gets the sent by. 261 * 262 * @return {String} the sent by 263 */ 264 ZmCalItem.prototype.getSentBy = function() { return this.sentBy || ""; }; 265 266 /** 267 * Gets the original start date. 268 * 269 * @return {Date} the original start date 270 */ 271 ZmCalItem.prototype.getOrigStartDate = function() { return this._origStartDate || this.startDate; }; 272 273 /** 274 * Gets the original start time. 275 * 276 * @return {Date} the original start time 277 */ 278 ZmCalItem.prototype.getOrigStartTime = function() { return this.getOrigStartDate().getTime(); }; 279 280 /** 281 * Gets the original end date. 282 * 283 * @return {Date} the original end date 284 */ 285 ZmCalItem.prototype.getOrigEndDate = function() { return this._origEndDate || this.endDate; }; 286 287 /** 288 * Gets the original end time. 289 * 290 * @return {Date} the original end time 291 */ 292 ZmCalItem.prototype.getOrigEndTime = function() { return this.getOrigEndDate().getTime(); }; 293 294 /** 295 * Gets the original calendar item. 296 * 297 * @return {ZmCalItem} the original calendar item 298 */ 299 ZmCalItem.prototype.getOrig = function() { return this._orig; }; 300 301 /** 302 * Gets the original timezone. 303 * 304 * @return {Date} the original timezone 305 */ 306 ZmCalItem.prototype.getOrigTimezone = function() { return this._origTimezone || this.timezone; }; 307 308 /** 309 * Gets the recurrence "blurb". 310 * 311 * @return {String} the recurrence blurb 312 * @see ZmRecurrence 313 */ 314 ZmCalItem.prototype.getRecurBlurb = function() { return this._recurrence.getBlurb(); }; 315 316 /** 317 * Gets the recurrence. 318 * 319 * @return {ZmRecurrence} the recurrence 320 */ 321 ZmCalItem.prototype.getRecurrence = function() { return this._recurrence; }; 322 323 /** 324 * Gets the recurrence "type". 325 * 326 * @return {String} the recurrence type 327 * @see ZmRecurrence 328 */ 329 ZmCalItem.prototype.getRecurType = function() { return this._recurrence.repeatType; }; 330 331 /** 332 * Gets the timezone. 333 * 334 * @return {AjxTimezone} the timezone 335 */ 336 ZmCalItem.prototype.getTimezone = function() { return this.timezone; }; 337 338 /** 339 * Gets the summary. 340 * 341 * @param {Boolean} isHtml <code>true</code> to return as html 342 * @return {String} the summary 343 */ 344 ZmCalItem.prototype.getSummary = function(isHtml) { }; // override if necessary 345 346 /** 347 * Gets the tool tip. 348 * 349 * @param {ZmController} controller the controller 350 * @return {String} the tool tip 351 */ 352 ZmCalItem.prototype.getToolTip = function(controller) { }; // override if necessary 353 /** 354 * Checks if this item has a custom recurrence. 355 * 356 * @return {Boolean} <code>true</code> for a custom recurrence 357 */ 358 ZmCalItem.prototype.isCustomRecurrence = function() { return this._recurrence.repeatCustom == "1" || this._recurrence.repeatEndType != "N"; }; 359 /** 360 * Checks if this item is an organizer. 361 * 362 * @return {Boolean} <code>true</code> for an organizer 363 */ 364 ZmCalItem.prototype.isOrganizer = function() { return (typeof(this.isOrg) === 'undefined') || (this.isOrg == true); }; 365 /** 366 * Checks if this item is recurring. 367 * 368 * @return {Boolean} <code>true</code> for recurrence 369 */ 370 ZmCalItem.prototype.isRecurring = function() { return (this.recurring || (this._rawRecurrences != null)); }; 371 /** 372 * Checks if this item has attachments. 373 * 374 * @return {Boolean} <code>true</code> if this item has attachments 375 */ 376 ZmCalItem.prototype.hasAttachments = function() { return this.getAttachments() != null; }; 377 /** 378 * Checks if this item has attendee type. 379 * 380 * @return {Boolean} always returns <code>false</code>; override if necessary 381 */ 382 ZmCalItem.prototype.hasAttendeeForType = function(type) { return false; }; // override if necessary 383 /** 384 * Checks if this item has attendees. 385 * 386 * @return {Boolean} always returns <code>false</code>; override if necessary 387 */ 388 ZmCalItem.prototype.hasAttendees = function() { return false; }; // override if necessary 389 /** 390 * Checks if this item has person attendees. 391 * 392 * @return {Boolean} always returns <code>false</code>; override if necessary 393 */ 394 ZmCalItem.prototype.hasPersonAttendees = function() { return false; }; // override if necessary 395 396 // Setters 397 /** 398 * Sets all day event. 399 * 400 * @param {Boolean} isAllDay <code>true</code> for an all day event 401 */ 402 ZmCalItem.prototype.setAllDayEvent = function(isAllDay) { this.allDayEvent = isAllDay ? "1" : "0"; }; 403 /** 404 * Sets the name. 405 * 406 * @param {String} newName the name 407 */ 408 ZmCalItem.prototype.setName = function(newName) { this.name = newName; }; 409 /** 410 * Sets the organizer. 411 * 412 * @param {String} organizer the organizer 413 */ 414 ZmCalItem.prototype.setOrganizer = function(organizer) { this.organizer = organizer != "" ? organizer : null; }; 415 /** 416 * Sets the repeat type. 417 * 418 * @param {constant} repeatType the repeat type 419 */ 420 ZmCalItem.prototype.setRecurType = function(repeatType) { this._recurrence.repeatType = repeatType; }; 421 /** 422 * Sets the item type. 423 * 424 * @param {constant} newType the item type 425 */ 426 ZmCalItem.prototype.setType = function(newType) { this.type = newType; }; 427 /** 428 * Sets the original timezone. 429 * 430 * @param {Object} timezone the timezone 431 */ 432 ZmCalItem.prototype.setOrigTimezone = function(timezone) { this._origTimezone = timezone; }; 433 434 /** 435 * Sets the folder id. 436 * 437 * @param {String} folderId the folder id 438 */ 439 ZmCalItem.prototype.setFolderId = 440 function(folderId) { 441 this.folderId = folderId || ZmOrganizer.ID_CALENDAR; 442 }; 443 444 /** 445 * Gets the "local" folder id even for remote folders. Otherwise, just use <code>this.folderId</code>. 446 * 447 * @return {ZmFolder|String} the folder or folder id 448 */ 449 ZmCalItem.prototype.getLocalFolderId = 450 function() { 451 var fid = this.folderId; 452 if (this.isShared()) { 453 var folder = appCtxt.getById(this.folderId); 454 if (folder) 455 fid = folder.id; 456 } 457 return fid; 458 }; 459 460 /** 461 * Sets the end date. 462 * 463 * @param {Date} endDate the end date 464 * @param {Boolean} keepCache if <code>true</code>, keep the cache; <code>false</code> to reset the cache 465 */ 466 ZmCalItem.prototype.setEndDate = 467 function(endDate, keepCache) { 468 if (this._origEndDate == null && this.endDate != null && this.endDate != "") { 469 this._origEndDate = new Date(this.endDate.getTime()); 470 } 471 this.endDate = new Date(endDate instanceof Date ? endDate.getTime(): endDate); 472 if (!keepCache) 473 this._resetCached(); 474 }; 475 476 /** 477 * Sets the start date. 478 * 479 * @param {Date} startDate the start date 480 * @param {Boolean} keepCache if <code>true</code>, keep the cache; <code>false</code> to reset the cache 481 */ 482 ZmCalItem.prototype.setStartDate = 483 function(startDate, keepCache) { 484 if (this._origStartDate == null && this.startDate != null && this.startDate != "") { 485 this._origStartDate = new Date(this.startDate.getTime()); 486 } 487 this.startDate = new Date(startDate instanceof Date ? startDate.getTime() : startDate); 488 489 if (!keepCache) { 490 this._resetCached(); 491 } 492 493 // recurrence should reflect start date 494 if (this.recurring && this._recurrence) { 495 this._recurrence.setRecurrenceStartTime(this.startDate.getTime()); 496 } 497 }; 498 499 /** 500 * Sets the timezone. 501 * 502 * @param {AjxTimezone} timezone the timezone 503 * @param {Boolean} keepCache if <code>true</code>, keep the cache; <code>false</code> to reset the cache 504 */ 505 ZmCalItem.prototype.setTimezone = 506 function(timezone, keepCache) { 507 if (this._origTimezone == null) { 508 this._origTimezone = timezone; 509 } 510 this.timezone = timezone; 511 if (!keepCache) { 512 this._resetCached(); 513 } 514 }; 515 516 /** 517 * Sets the end timezone. 518 * 519 * @param {AjxTimezone} timezone the timezone 520 */ 521 ZmCalItem.prototype.setEndTimezone = 522 function(timezone) { 523 if (this._origEndTimezone == null) { 524 this._origEndTimezone = timezone; 525 } 526 this.endTimezone = timezone; 527 }; 528 529 /** 530 * Sets the view mode, and resets any other fields that should not be set for that view mode. 531 * 532 * @param {constant} mode the mode (see <code>ZmCalItem.MODE_</code> constants) 533 */ 534 ZmCalItem.prototype.setViewMode = 535 function(mode) { 536 this.viewMode = mode || ZmCalItem.MODE_NEW; 537 538 if (this.viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) 539 this._recurrence.repeatType = "NON"; 540 }; 541 542 /** 543 * Gets the view mode 544 */ 545 ZmCalItem.prototype.getViewMode = 546 function(mode) { 547 return this.viewMode; 548 }; 549 550 /** 551 * Gets the notes part. This method will walk the notesParts array looking for 552 * the first part that matches given content type. 553 * 554 * @param {constant} contentType the content type (see {@link ZmMimeTable.TEXT_PLAIN}) 555 * @return {String} the content 556 * 557 * @see ZmMimeTable 558 */ 559 ZmCalItem.prototype.getNotesPart = 560 function(contentType) { 561 if (this.notesTopPart) { 562 var ct = contentType || ZmMimeTable.TEXT_PLAIN; 563 var content = this.notesTopPart.getContentForType(ct); 564 565 // if requested content type not found, try the other 566 if (!content) { 567 if (ct == ZmMimeTable.TEXT_PLAIN) { 568 var div = document.createElement("div"); 569 content = this.notesTopPart.getContentForType(ZmMimeTable.TEXT_HTML); 570 div.innerHTML = content || ""; 571 var text = AjxStringUtil.convertHtml2Text(div); 572 return text.substring(1); // above func prepends \n due to div 573 } else if (ct == ZmMimeTable.TEXT_HTML) { 574 content = AjxStringUtil.convertToHtml(this.notesTopPart.getContentForType(ZmMimeTable.TEXT_PLAIN)); 575 } 576 } 577 return content; 578 } else { 579 return this.fragment; 580 } 581 }; 582 583 /** 584 * Gets the remote folder owner. 585 * 586 * @return {String} the "owner" of remote/shared item folder this item belongs to 587 */ 588 ZmCalItem.prototype.getRemoteFolderOwner = 589 function() { 590 // bug fix #18855 - dont return the folder owner if moving betw. accounts 591 var controller = AjxDispatcher.run("GetCalController"); 592 if (controller.isMovingBetwAccounts(this, this.folderId)) { 593 return null; 594 } 595 596 var folder = this.getFolder(); 597 var owner = folder && folder.link && folder.owner; 598 599 var acct = (!owner && appCtxt.multiAccounts && folder.getAccount()); 600 if (acct) { 601 owner = acct.name; 602 } 603 return owner; 604 }; 605 606 /** 607 * Checks if the item is read-only. 608 * 609 * @return {Boolean} <code>true</code> if the item is read-only 610 */ 611 ZmCalItem.prototype.isReadOnly = 612 function() { 613 var folder = this.getFolder(); 614 615 if (appCtxt.multiAccounts) { 616 var orgAcct = appCtxt.accountList.getAccountByEmail(this.organizer); 617 var calAcct = appCtxt.accountList.getAccountByEmail(folder.getAccount().getEmail()); 618 if (orgAcct == calAcct) { 619 return false; 620 } 621 } 622 // TODO: Correct this method in order to return fasle for users with manager/admin rights 623 return (!this.isOrganizer() || (folder.link && folder.isReadOnly())); 624 }; 625 626 /** 627 * Checks if the folder containing the item is read-only by the . 628 * 629 * @return {Boolean} <code>true</code> if the folder is read-only 630 */ 631 ZmCalItem.prototype.isFolderReadOnly = 632 function() { 633 var folder = this.getFolder(); 634 return (folder && folder.isReadOnly()); 635 }; 636 637 /* 638 * To check whether version has been ignored 639 * */ 640 ZmCalItem.prototype.isVersionIgnored=function(){ 641 return this._ignoreVersion; 642 } 643 644 /* 645 * Method to set _ignoreVersion as true when conflict arises and false otherwise. 646 * If true, the next soap request wont be sent with revision related attributes like ms&rev. 647 * */ 648 ZmCalItem.prototype.setIgnoreVersion=function(isIgnorable){ 649 this._ignoreVersion=isIgnorable; 650 } 651 652 /** 653 * Resets the repeat weekly days. 654 */ 655 ZmCalItem.prototype.resetRepeatWeeklyDays = 656 function() { 657 if (this.startDate) { 658 this._recurrence.repeatWeeklyDays = [ZmCalItem.SERVER_WEEK_DAYS[this.startDate.getDay()]]; 659 } 660 }; 661 662 /** 663 * Resets the repeat monthly day months list. 664 */ 665 ZmCalItem.prototype.resetRepeatMonthlyDayList = 666 function() { 667 if (this.startDate) { 668 this._recurrence.repeatMonthlyDayList = [this.startDate.getDate()]; 669 } 670 }; 671 672 /** 673 * Resets the repeat yearly months list. 674 */ 675 ZmCalItem.prototype.resetRepeatYearlyMonthsList = 676 function(mo) { 677 this._recurrence.repeatYearlyMonthsList = mo; 678 }; 679 680 /** 681 * Resets the repeat custom day of week. 682 */ 683 ZmCalItem.prototype.resetRepeatCustomDayOfWeek = 684 function() { 685 if (this.startDate) { 686 this._recurrence.repeatCustomDayOfWeek = ZmCalItem.SERVER_WEEK_DAYS[this.startDate.getDay()]; 687 } 688 }; 689 690 /** 691 * Checks if the item is overlapping. 692 * 693 * @param {ZmCalItem} other the other item to check 694 * @param {Boolean} checkFolder <code>true</code> to check the folder id 695 * @return {Boolean} <code>true</code> if the items overlap; <code>false</code> if the items do not overlap or the item folder ids do not match 696 */ 697 ZmCalItem.prototype.isOverlapping = 698 function(other, checkFolder) { 699 if (checkFolder && this.folderId != other.folderId) { return false; } 700 701 var tst = this.getStartTime(); 702 var tet = this.getEndTime(); 703 var ost = other.getStartTime(); 704 var oet = other.getEndTime(); 705 706 return (tst < oet) && (tet > ost); 707 }; 708 709 /** 710 * Checks if this item is in range. 711 * 712 * @param {Date} startTime the start range 713 * @param {Date} endTime the end range 714 * @return {Boolean} <code>true</code> if the item is in range 715 */ 716 ZmCalItem.prototype.isInRange = 717 function(startTime, endTime) { 718 var tst = this.getStartTime(); 719 var tet = this.getEndTime(); 720 return (tst < endTime && tet > startTime); 721 }; 722 723 /** 724 * Checks whether the duration of this item is valid. 725 * 726 * @return {Boolean} <code>true</code> if the item possess valid duration. 727 */ 728 ZmCalItem.prototype.isValidDuration = 729 function(){ 730 731 var startTime = this.getStartTime(); 732 var endTime = this.getEndTime(); 733 734 if(this.endTimezone && this.endTimezone!=this.timezone){ 735 var startOffset = AjxTimezone.getRule(this.timezone).standard.offset; 736 var endOffset = AjxTimezone.getRule(this.endTimezone).standard.offset; 737 738 startTime = startTime - (startOffset*60000); 739 endTime = endTime - (endOffset*60000); 740 } 741 742 return (startTime<=endTime); 743 744 } 745 /** 746 * Checks whether the duration of this item is valid with respect to the 747 * recurrence period. For example, if the item repeats daily, its duration 748 * should not be longer than a day. 749 * 750 * This can get very complicated due to custom repeat rules. So the 751 * limitation is just set on the repeat type. The purpose is to prevent 752 * (as has happened) someone creating a repeating appt where they set the 753 * duration to be the span the appt is in effect over a year instead of its 754 * duration during the day. For example, repeat daily, start = Jan 1 2014, 755 * end = July 1 2014. See Bug 87993. 756 * 757 * @return {Boolean} <code>true</code> if the item possess valid duration. 758 */ 759 ZmCalItem.prototype.isValidDurationRecurrence = function() { 760 var valid = true; 761 var recurType = this.getRecurType(); 762 var duration = this.getDuration(); 763 switch (recurType) { 764 case ZmRecurrence.DAILY: valid = duration <= AjxDateUtil.MSEC_PER_DAY; break; 765 case ZmRecurrence.WEEKLY: valid = duration <= ZmCalItem.MSEC_LIMIT_PER_WEEK; break; 766 case ZmRecurrence.MONTHLY: valid = duration <= ZmCalItem.MSEC_LIMIT_PER_MONTH; break; 767 case ZmRecurrence.YEARLY: valid = duration <= ZmCalItem.MSEC_LIMIT_PER_YEAR; break; 768 default: break; 769 } 770 return valid; 771 } 772 773 /** 774 * @private 775 */ 776 ZmCalItem.prototype.parseAlarmData = 777 function() { 778 if (!this.alarmData) { return; } 779 780 for (var i = 0; i < this.alarmData.length; i++) { 781 var alarm = this.alarmData[i].alarm; 782 if (alarm) { 783 for (var j = 0; j < alarm.length; j++) { 784 this.parseAlarm(alarm[j]); 785 } 786 } 787 } 788 }; 789 790 /** 791 * @private 792 */ 793 ZmCalItem.prototype.parseAlarm = 794 function(tmp) { 795 if (!tmp) { return; } 796 797 var s, m, h, d, w; 798 var trigger = tmp.trigger; 799 var rel = (trigger && (trigger.length > 0)) ? trigger[0].rel : null; 800 s = (rel && (rel.length > 0)) ? rel[0].s : null; 801 m = (rel && (rel.length > 0)) ? rel[0].m : null; 802 d = (rel && (rel.length > 0)) ? rel[0].d : null; 803 h = (rel && (rel.length > 0)) ? rel[0].h : null; 804 w = (rel && (rel.length > 0)) ? rel[0].w : null; 805 806 this._reminderMinutes = -1; 807 if (tmp.action == ZmCalItem.ALARM_DISPLAY) { 808 if (s == 0) { // at time of event 809 this._reminderMinutes = 0; 810 } 811 if (m != null) { 812 this._reminderMinutes = m; 813 this._origReminderUnits = ZmCalItem.REMINDER_UNIT_MINUTES; 814 } 815 if (h != null) { 816 h = parseInt(h); 817 this._reminderMinutes = h * 60; 818 this._origReminderUnits = ZmCalItem.REMINDER_UNIT_HOURS; 819 } 820 if (d != null) { 821 d = parseInt(d); 822 this._reminderMinutes = d * 24 * 60; 823 this._origReminderUnits = ZmCalItem.REMINDER_UNIT_DAYS; 824 } 825 if (w != null) { 826 w = parseInt(w); 827 this._reminderMinutes = w * 7 * 24 * 60; 828 this._origReminderUnits = ZmCalItem.REMINDER_UNIT_WEEKS; 829 } 830 } 831 }; 832 833 /** 834 * Checks if the start date is in range. 835 * 836 * @param {Date} startTime the start time of the range 837 * @param {Date} endTime the end time of the range 838 * @return {Boolean} <code>true</code> if the start date of this item is within range 839 */ 840 ZmCalItem.prototype.isStartInRange = 841 function(startTime, endTime) { 842 var tst = this.getStartTime(); 843 return (tst < endTime && tst >= startTime); 844 }; 845 846 /** 847 * Checks if the end date is in range. 848 * 849 * @param {Date} startTime the start time of the range 850 * @param {Date} endTime the end time of the range 851 * @return {Boolean} <code>true</code> if the end date of this item is within range 852 */ 853 ZmCalItem.prototype.isEndInRange = 854 function(startTime, endTime) { 855 var tet = this.getEndTime(); 856 return (tet <= endTime && tet > startTime); 857 }; 858 859 /** 860 * Sets the date range. 861 * 862 * @param {Hash} rangeObject a hash of <code>startDate</code> and <code>endDate</code> 863 * @param {Object} instance not used 864 * @param {Object} parentValue not used 865 * @param {Object} refPath not used 866 */ 867 ZmCalItem.prototype.setDateRange = 868 function (rangeObject, instance, parentValue, refPath) { 869 var s = rangeObject.startDate; 870 var e = rangeObject.endDate; 871 this.endDate.setTime(rangeObject.endDate.getTime()); 872 this.startDate.setTime(rangeObject.startDate.getTime()); 873 }; 874 875 /** 876 * Gets the date range. 877 * 878 * @param {Object} instance not used 879 * @param {Object} current not used 880 * @param {Object} refPath not used 881 * @return {Hash} a hash of <code>startDate</code> and <code>endDate</code> 882 */ 883 ZmCalItem.prototype.getDateRange = 884 function(instance, current, refPath) { 885 return { startDate:this.startDate, endDate: this.endDate }; 886 }; 887 888 /** 889 * Sets the attachments. 890 * 891 * @param {String} ids a comma delimited string of ids 892 */ 893 ZmCalItem.prototype.setAttachments = 894 function(ids) { 895 this.attachments = []; 896 897 if (ids && ids.length > 0) { 898 var split = ids.split(','); 899 for (var i = 0 ; i < split.length; i++) { 900 this.attachments[i] = { id:split[i] }; 901 } 902 } 903 }; 904 905 /** 906 * Gets the attachments. 907 * 908 * @return {Array} an array of attachments or <code>null</code> for none 909 */ 910 ZmCalItem.prototype.getAttachments = 911 function() { 912 var attachs = this.message ? this.message.attachments : null; 913 if (attachs) { 914 if (this._validAttachments == null) { 915 this._validAttachments = []; 916 for (var i = 0; i < attachs.length; ++i) { 917 if (this.message.isRealAttachment(attachs[i]) || attachs[i].contentType == ZmMimeTable.TEXT_CAL) { 918 this._validAttachments.push(attachs[i]); 919 } 920 } 921 } 922 return this._validAttachments.length > 0 ? this._validAttachments : null; 923 } 924 return null; 925 }; 926 927 /** 928 * Removes an attachment. 929 * 930 * @param {Object} part the attachment part to remove 931 */ 932 ZmCalItem.prototype.removeAttachment = 933 function(part) { 934 if (this._validAttachments && this._validAttachments.length > 0) { 935 for (var i = 0; i < this._validAttachments.length; i++) { 936 if (this._validAttachments[i].part == part) { 937 this._validAttachments.splice(i,1); 938 break; 939 } 940 } 941 } 942 }; 943 944 /** 945 * Gets the start hour in short date format. 946 * 947 * @return {String} the start hour 948 */ 949 ZmCalItem.prototype.getShortStartHour = 950 function() { 951 var formatter = AjxDateFormat.getTimeInstance(AjxDateFormat.SHORT); 952 return formatter.format(this.startDate); 953 }; 954 955 /** 956 * Gets the unique start date. 957 * 958 * @return {Date} the start date 959 */ 960 ZmCalItem.prototype.getUniqueStartDate = 961 function() { 962 if (this._uniqueStartDate == null && this.uniqStartTime) { 963 this._uniqueStartDate = new Date(this.uniqStartTime); 964 } 965 return this._uniqueStartDate; 966 }; 967 968 /** 969 * Gets the unique end date. 970 * 971 * @return {Date} the end date 972 */ 973 ZmCalItem.prototype.getUniqueEndDate = 974 function() { 975 if (this._uniqueEndDate == null && this.uniqStartTime) { 976 this._uniqueEndDate = new Date(this.uniqStartTime + this.getDuration()); 977 } 978 return this._uniqueEndDate; 979 }; 980 981 /** 982 * Gets the details. 983 * 984 * @param {constant} viewMode the view mode 985 * @param {AjxCallback} callback the callback 986 * @param {AjxCallback} errorCallback the callback on error 987 * @param {Boolean} ignoreOutOfDate if <code>true</code>, ignore out of date items 988 * @param {Boolean} noBusyOverlay if <code>true</code>, no busy overlay 989 * @param {ZmBatchCommand} batchCmd set if part of a batch operation 990 */ 991 ZmCalItem.prototype.getDetails = 992 function(viewMode, callback, errorCallback, ignoreOutOfDate, noBusyOverlay, batchCmd) { 993 var mode = viewMode || this.viewMode; 994 995 var seriesMode = mode == ZmCalItem.MODE_EDIT_SERIES; 996 var fetchSeriesMsg = (seriesMode && this.message && !this.message.seriesMode); 997 if (this.message == null || fetchSeriesMsg) { 998 var id = seriesMode ? (this.seriesInvId || this.invId || this.id) : this.invId; 999 this.message = new ZmMailMsg(id); 1000 if (this._orig) { 1001 this._orig.message = this.message; 1002 } 1003 var respCallback = new AjxCallback(this, this._handleResponseGetDetails, [mode, this.message, callback]); 1004 var respErrorCallback = (!ignoreOutOfDate) 1005 ? (new AjxCallback(this, this._handleErrorGetDetails, [mode, callback, errorCallback])) 1006 : errorCallback; 1007 1008 var acct = appCtxt.isOffline && this.getFolder().getAccount(); 1009 var params = { 1010 callback: respCallback, 1011 errorCallback: respErrorCallback, 1012 noBusyOverlay: noBusyOverlay, 1013 ridZ: (seriesMode ? null : this.ridZ), 1014 batchCmd: batchCmd, 1015 accountName: (acct && acct.name) 1016 }; 1017 this.message.load(params); 1018 } else { 1019 this.setFromMessage(this.message, mode); 1020 if (callback) { 1021 callback.run(); 1022 } 1023 } 1024 }; 1025 1026 /** 1027 * @private 1028 */ 1029 ZmCalItem.prototype.convertToLocalTimezone = 1030 function() { 1031 var apptTZ = this.getTimezone(); 1032 var localTZ = AjxTimezone.getServerId(AjxTimezone.DEFAULT); 1033 var sd = this.startDate; 1034 var ed = this.endDate; 1035 if(apptTZ != localTZ) { 1036 var offset1 = AjxTimezone.getOffset(AjxTimezone.DEFAULT, sd); 1037 var offset2 = AjxTimezone.getOffset(AjxTimezone.getClientId(apptTZ), sd); 1038 sd.setTime(sd.getTime() + (offset1 - offset2)*60*1000); 1039 ed.setTime(ed.getTime() + (offset1 - offset2)*60*1000); 1040 this.setTimezone(localTZ); 1041 this.setEndTimezone(localTZ); 1042 } 1043 }; 1044 1045 1046 /** 1047 * @private 1048 */ 1049 ZmCalItem.prototype._handleResponseGetDetails = 1050 function(mode, message, callback, result) { 1051 // msg content should be text, so no need to pass callback to setFromMessage() 1052 this.setFromMessage(message, mode); 1053 message.seriesMode = (mode == ZmCalItem.MODE_EDIT_SERIES); 1054 1055 //overwrite proposed time 1056 if(this._orig && this._orig.proposedInvite) { 1057 var invite = this._orig.proposedInvite; 1058 var start = invite.getServerStartTime(); 1059 var end = invite.getServerEndTime(); 1060 if (start) this.setStartDate(AjxDateUtil.parseServerDateTime(start, true)); 1061 if (end) this.setEndDate(AjxDateUtil.parseServerDateTime(end, true)); 1062 1063 //set timezone from proposed invite 1064 var tz = invite.getServerStartTimeTz(); 1065 this.setTimezone(tz || AjxTimezone.getServerId(AjxTimezone.DEFAULT)); 1066 1067 // record whether the start/end dates are in UTC 1068 this.startsInUTC = start ? start.charAt(start.length-1) == "Z" : null; 1069 this.endsInUTC = end && start ? end.charAt(start.length-1) == "Z" : null; 1070 1071 //set all the fields that are not generated in GetAppointmentResponse - accept proposal mode 1072 this.status = invite.components[0].status; 1073 1074 //convert proposed invite timezone to local timezone 1075 this.convertToLocalTimezone(); 1076 this.isAcceptingProposal = true; 1077 } 1078 if (callback) callback.run(result); 1079 }; 1080 1081 /** 1082 * @private 1083 */ 1084 ZmCalItem.prototype._handleErrorGetDetails = 1085 function(mode, callback, errorCallback, ex) { 1086 if (ex.code == "mail.INVITE_OUT_OF_DATE") { 1087 var jsonObj = {}, 1088 requestName = this._getRequestNameForMode(ZmCalItem.MODE_GET), 1089 request = jsonObj[requestName] = { 1090 _jsns : "urn:zimbraMail" 1091 }, 1092 respCallback = new AjxCallback(this, this._handleErrorGetDetails2, [mode, callback, errorCallback]), 1093 params; 1094 1095 request.id = this.id; 1096 params = { 1097 jsonObj: jsonObj, 1098 asyncMode: true, 1099 callback: respCallback, 1100 errorCallback: errorCallback 1101 }; 1102 appCtxt.getAppController().sendRequest(params); 1103 return true; 1104 } 1105 if (ex.code == "account.ACCOUNT_INACTIVE") { 1106 var msg = ex.msg ? ex.msg.split(':') : null, 1107 acctEmailId = msg ? msg[1] : '', 1108 msgDlg = appCtxt.getMsgDialog(); 1109 msgDlg.setMessage(AjxMessageFormat.format(ZmMsg.accountInactiveError, acctEmailId), DwtMessageDialog.CRITICAL_STYLE); 1110 msgDlg.popup(); 1111 return true; 1112 } 1113 if (errorCallback) { 1114 return errorCallback.run(ex); 1115 } 1116 return false; 1117 }; 1118 1119 /** 1120 * @private 1121 */ 1122 ZmCalItem.prototype._handleErrorGetDetails2 = 1123 function(mode, callback, errorCallback, result) { 1124 // Update invId and force a message reload 1125 var invite = this._getInviteFromError(result); 1126 this.invId = [this.id, invite.id].join("-"); 1127 this.message = null; 1128 var ignoreOutOfDate = true; 1129 this.getDetails(mode, callback, errorCallback, ignoreOutOfDate); 1130 }; 1131 1132 /** 1133 * Sets the from message. 1134 * 1135 * @param {String} message the message 1136 * @param {constant} viewMode the view mode 1137 * 1138 * @private 1139 */ 1140 ZmCalItem.prototype.setFromMessage = 1141 function(message, viewMode) { 1142 if (message == this._currentlyLoaded) { return; } 1143 1144 if (message.invite) { 1145 this.isOrg = message.invite.isOrganizer(); 1146 this.organizer = message.invite.getOrganizerEmail(); 1147 this.organizerName = message.invite.getOrganizerName(); 1148 this.sentBy = message.invite.getSentBy(); 1149 this.name = message.invite.getName() || message.subject; 1150 this.isException = message.invite.isException(); 1151 this.recurring = message.invite.isRecurring(); 1152 this.location = message.invite.getLocation(); 1153 this.seq = message.invite.getSequenceNo(); 1154 this.allDayEvent = message.invite.isAllDayEvent(); 1155 if(message.invite.id) { 1156 this.invId = this.id + "-" + message.invite.id; 1157 } 1158 this._setTimeFromMessage(message, viewMode); 1159 this._setExtrasFromMessage(message); 1160 this._setRecurrence(message); 1161 } 1162 this._setNotes(message); 1163 this.getAttachments(); 1164 1165 this._currentlyLoaded = message; 1166 }; 1167 1168 /** 1169 * Sets the required data from saved response 1170 * 1171 * @param {Object} result create/moify appt response 1172 */ 1173 ZmCalItem.prototype.setFromSavedResponse = 1174 function(result) { 1175 this.invId = result.invId; 1176 if(this.message) { 1177 this.message.rev = result.rev; 1178 this.message.ms = result.ms; 1179 } 1180 1181 if(this.viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE && !this.isException) { 1182 this.isException = true; 1183 } 1184 }; 1185 1186 /** 1187 * Sets the from mail message. This method gets called when a mail item 1188 * is dragged onto the item and we 1189 * need to load the mail item and parse the right parts to show in {@link ZmCalItemEditView}. 1190 * 1191 * @param {String} message the message 1192 * @param {String} subject the subject 1193 * 1194 * @private 1195 */ 1196 ZmCalItem.prototype.setFromMailMessage = 1197 function(message, subject) { 1198 this.name = subject; 1199 this._setNotes(message); 1200 // set up message so attachments work 1201 this.message = message; 1202 this.invId = message.id; 1203 }; 1204 1205 /** 1206 * Sets the notes (text/plain). 1207 * 1208 * @param {String} notes the notes 1209 */ 1210 ZmCalItem.prototype.setTextNotes = 1211 function(notes) { 1212 this.notesTopPart = new ZmMimePart(); 1213 this.notesTopPart.setContentType(ZmMimeTable.TEXT_PLAIN); 1214 this.notesTopPart.setContent(notes); 1215 }; 1216 1217 /** 1218 * @private 1219 */ 1220 ZmCalItem.prototype._setTimeFromMessage = 1221 function(message, viewMode) { 1222 // For instance of recurring appointment, start date is generated from unique 1223 // start time sent in appointment summaries. Associated message will contain 1224 // only the original start time. 1225 var start = message.invite.getServerStartTime(); 1226 var end = message.invite.getServerEndTime(); 1227 if (viewMode === ZmCalItem.MODE_EDIT_SINGLE_INSTANCE || viewMode === ZmCalItem.MODE_FORWARD_SINGLE_INSTANCE 1228 || viewMode === ZmCalItem.MODE_COPY_SINGLE_INSTANCE) { 1229 var usd = this.getUniqueStartDate(); 1230 if (usd) { 1231 this.setStartDate(usd); 1232 } 1233 1234 var ued = this.getUniqueEndDate(); 1235 if (ued) { 1236 if (this.isAllDayEvent() && viewMode === ZmCalItem.MODE_COPY_SINGLE_INSTANCE) { 1237 //special case - copying and all day event. The day it ends is a one too many days. Creating a copy gets confused otherwise and adds that day. 1238 ued.setDate(ued.getDate() - 1); 1239 } 1240 this.setEndDate(ued); 1241 } 1242 if (viewMode === ZmCalItem.MODE_COPY_SINGLE_INSTANCE) { 1243 viewMode = ZmCalItem.MODE_EDIT_SINGLE_INSTANCE; // kinda hacky - the copy mode has run its course. Now treat it like edit mode. Would be less impact. 1244 } 1245 } 1246 else { 1247 if (start) this.setStartDate(AjxDateUtil.parseServerDateTime(start)); 1248 if (end) this.setEndDate(AjxDateUtil.parseServerDateTime(end)); 1249 } 1250 1251 // record whether the start/end dates are in UTC 1252 this.startsInUTC = start ? start.charAt(start.length-1) == "Z" : null; 1253 this.endsInUTC = end && start ? end.charAt(start.length-1) == "Z" : null; 1254 1255 // record timezone 1256 var timezone; 1257 if (viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE || viewMode == ZmCalItem.MODE_DELETE_INSTANCE || viewMode == ZmCalItem.MODE_FORWARD_SINGLE_INSTANCE) { 1258 timezone = AjxTimezone.getServerId(AjxTimezone.DEFAULT); 1259 this.setTimezone(timezone); 1260 this.setEndTimezone(timezone); 1261 } 1262 else { 1263 var serverId = !this.startsInUTC && message.invite.getServerStartTimeTz(); 1264 timezone = serverId || AjxTimezone.getServerId(AjxTimezone.DEFAULT); 1265 this.setTimezone(timezone); 1266 var endServerId = !this.endsInUTC && message.invite.getServerEndTimeTz(); 1267 this.setEndTimezone(endServerId || AjxTimezone.getServerId(AjxTimezone.DEFAULT)); 1268 if(!this.startsInUTC && message.invite.getServerEndTimeTz()) this.setEndTimezone(message.invite.getServerEndTimeTz()); 1269 } 1270 1271 var tzrule = AjxTimezone.getRule(AjxTimezone.getClientId(this.getTimezone())); 1272 if (tzrule) { 1273 if (tzrule.aliasId) { 1274 tzrule = AjxTimezone.getRule(tzrule.aliasId) || tzrule; 1275 } 1276 this.setTimezone(tzrule.serverId); 1277 } 1278 1279 tzrule = AjxTimezone.getRule(AjxTimezone.getClientId(this.endTimezone)); 1280 if (tzrule) { 1281 if (tzrule.aliasId) { 1282 tzrule = AjxTimezone.getRule(tzrule.aliasId) || tzrule; 1283 } 1284 this.setEndTimezone(tzrule.serverId); 1285 } 1286 }; 1287 1288 /** 1289 * Override to add specific initialization but remember to call 1290 * the base implementation. 1291 * 1292 * @private 1293 */ 1294 ZmCalItem.prototype._setExtrasFromMessage = 1295 function(message) { 1296 this._setAlarmFromMessage(message); 1297 }; 1298 1299 ZmCalItem.prototype._setAlarmFromMessage = 1300 function(message) { 1301 this._reminderMinutes = -1; 1302 var alarm = message.invite.getAlarm(); 1303 if (alarm) { 1304 for (var i = 0; i < alarm.length; i++) { 1305 var alarmInst = alarm[i]; 1306 if (!alarmInst) continue; 1307 1308 var action = alarmInst.action; 1309 if (action == ZmCalItem.ALARM_DISPLAY) { 1310 this.parseAlarm(alarmInst); 1311 // NOTE: No need to add a display alarm because it's 1312 // NOTE: added by default in the constructor. 1313 continue; 1314 } 1315 1316 // NOTE: Both email and device-email/sms reminders appear 1317 // NOTE: as "EMAIL" alarms but we distinguish between them 1318 // NOTE: upon loading. 1319 if (action == ZmCalItem.ALARM_EMAIL) { 1320 var emails = alarmInst.at; 1321 if (!emails) continue; 1322 for (var j = 0; j < emails.length; j++) { 1323 var email = emails[j].a; 1324 if (email == appCtxt.get(ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ADDRESS)) { 1325 action = ZmCalItem.ALARM_DEVICE_EMAIL; 1326 } 1327 this.addReminderAction(action); 1328 } 1329 } 1330 } 1331 } 1332 }; 1333 1334 /** 1335 * @private 1336 */ 1337 ZmCalItem.prototype._setRecurrence = 1338 function(message) { 1339 var recurRules = message.invite.getRecurrenceRules(); 1340 1341 if (recurRules) 1342 this._recurrence.parse(recurRules); 1343 1344 if (this._recurrence.repeatWeeklyDays == null) 1345 this.resetRepeatWeeklyDays(); 1346 1347 if (this._recurrence.repeatMonthlyDayList == null) 1348 this.resetRepeatMonthlyDayList(); 1349 }; 1350 1351 /** 1352 * We are removing starting 2 \n's for the bug 21823 1353 * XXX - this does not look very efficient 1354 * 1355 * @private 1356 */ 1357 ZmCalItem.prototype._getCleanHtml2Text = 1358 function(dwtIframe) { 1359 var textContent; 1360 var idoc = dwtIframe ? dwtIframe.getDocument() : null; 1361 var body = idoc ? idoc.body : null; 1362 if (body) { 1363 var html = body.innerHTML.replace(/\n/ig, ""); 1364 body.innerHTML = html.replace(/<!--.*-->/ig, ""); 1365 var firstChild = body.firstChild; 1366 var removeN = (firstChild && firstChild.tagName && firstChild.tagName.toLocaleLowerCase() == "p"); 1367 textContent = AjxStringUtil.convertHtml2Text(body); 1368 if (removeN) { 1369 textContent = textContent.replace(/\n\n/i, ""); 1370 } 1371 } 1372 return textContent; 1373 }; 1374 1375 /** 1376 * @private 1377 */ 1378 ZmCalItem.prototype._setNotes = 1379 function(message) { 1380 1381 if(!(message.isZmMailMsg)) { return; } 1382 this.notesTopPart = new ZmMimePart(); 1383 1384 var htmlContent = message.getBodyContent(ZmMimeTable.TEXT_HTML); 1385 if (htmlContent) { 1386 htmlContent = htmlContent.replace(/<title\s*>.*\/title>/ig,""); 1387 if (!this._includeEditReply) { 1388 htmlContent = this._trimNotesSummary(htmlContent, true); 1389 } 1390 } 1391 1392 if (htmlContent) { 1393 // create a temp iframe to create a proper DOM tree 1394 var params = {parent:appCtxt.getShell(), hidden:true, html:htmlContent}; 1395 var textContent = message.getInviteDescriptionContentValue(ZmMimeTable.TEXT_PLAIN); 1396 if (!textContent) { //only go through this pain if textContent is somehow not available from getInviteDescriptionContentValue (no idea if this could happen). 1397 var dwtIframe = new DwtIframe(params); 1398 textContent = this._getCleanHtml2Text(dwtIframe); 1399 // bug: 23034 this hidden iframe under shell is adding more space 1400 // which breaks calendar column view 1401 var iframe = dwtIframe.getIframe(); 1402 if (iframe && iframe.parentNode) { 1403 iframe.parentNode.removeChild(iframe); 1404 } 1405 delete dwtIframe; 1406 } 1407 1408 // create two more mp's for text and html content types 1409 var textPart = new ZmMimePart(); 1410 textPart.setContentType(ZmMimeTable.TEXT_PLAIN); 1411 textPart.setContent(textContent); 1412 1413 var htmlPart = new ZmMimePart(); 1414 htmlPart.setContentType(ZmMimeTable.TEXT_HTML); 1415 htmlPart.setContent(htmlContent); 1416 1417 this.notesTopPart.setContentType(ZmMimeTable.MULTI_ALT); 1418 this.notesTopPart.children.add(textPart); 1419 this.notesTopPart.children.add(htmlPart); 1420 } else { 1421 var textContent = message.getBodyContent(ZmMimeTable.TEXT_PLAIN); 1422 if (!this._includeEditReply) { 1423 textContent = this._trimNotesSummary(textContent); 1424 } 1425 this.notesTopPart.setContentType(ZmMimeTable.TEXT_PLAIN); 1426 this.notesTopPart.setContent(textContent); 1427 } 1428 }; 1429 1430 /** 1431 * Gets the mail notification option. 1432 * 1433 * @return {Boolean} <code>true</code> if the mail notification is set; <code>false</code> otherwise 1434 */ 1435 ZmCalItem.prototype.getMailNotificationOption = 1436 function() { 1437 return this._sendNotificationMail; 1438 }; 1439 1440 /** 1441 * Sets the mail notification option. 1442 * 1443 * @param {Boolean} sendNotificationMail <code>true</code> to set the mail notification 1444 */ 1445 ZmCalItem.prototype.setMailNotificationOption = 1446 function(sendNotificationMail) { 1447 this._sendNotificationMail = sendNotificationMail; 1448 }; 1449 1450 /** 1451 * Sets the exception details to request 1452 * 1453 * @param {Element} comp comp element of request object 1454 */ 1455 ZmCalItem.prototype.addExceptionDetails = 1456 function(comp) { 1457 var exceptId = comp.exceptId = {}, 1458 allDay = this._orig ? this._orig.allDayEvent : this.allDayEvent, 1459 timezone, 1460 sd; 1461 1462 if (allDay != "1") { 1463 sd = AjxDateUtil.getServerDateTime(this.getOrigStartDate(), this.startsInUTC); 1464 // bug fix #4697 (part 2) 1465 timezone = this.getOrigTimezone(); 1466 if (!this.startsInUTC && timezone) { 1467 exceptId.tz = timezone; 1468 } 1469 exceptId.d = sd; 1470 } 1471 else { 1472 sd = AjxDateUtil.getServerDate(this.getOrigStartDate()); 1473 exceptId.d = sd; 1474 } 1475 }; 1476 1477 /** 1478 * Saves the item. 1479 * 1480 * @param {String} attachmentId the id of the already uploaded attachment 1481 * @param {AjxCallback} callback the callback triggered once request for appointment save is complete 1482 * @param {AjxCallback} errorCallback the callback triggered if error during appointment save request 1483 * @param {Array} notifyList the optional sublist of attendees to be notified (if different from original list of attendees) 1484 */ 1485 ZmCalItem.prototype.save = 1486 function(attachmentId, callback, errorCallback, notifyList) { 1487 var needsExceptionId = false, 1488 jsonObj = {}, 1489 requestName = this._getRequestNameForMode(this.viewMode, this.isException), 1490 request = jsonObj[requestName] = { 1491 _jsns : "urn:zimbraMail" 1492 }, 1493 accountName, 1494 invAndMsg, 1495 comp; 1496 1497 if (this.viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE && 1498 !this.isException) 1499 { 1500 this._addInviteAndCompNum(request); 1501 needsExceptionId = true; 1502 } 1503 else if (this.viewMode == ZmCalItem.MODE_EDIT || 1504 this.viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE || 1505 this.viewMode == ZmCalItem.MODE_EDIT_SERIES) 1506 { 1507 this._addInviteAndCompNum(request); 1508 needsExceptionId = this.isException; 1509 } 1510 1511 accountName = this.getRemoteFolderOwner(); 1512 invAndMsg = this._setRequestAttributes(request, attachmentId, notifyList, accountName); 1513 1514 comp = invAndMsg.inv.comp[0]; 1515 if (needsExceptionId) { 1516 this.addExceptionDetails(comp); 1517 } else { 1518 // set recurrence rules for appointment (but not for exceptions!) 1519 this._recurrence.setJson(comp); 1520 } 1521 1522 //set alarm data 1523 this._setAlarmData(comp); 1524 1525 this._sendRequest(null, accountName, callback, errorCallback, jsonObj, requestName); 1526 }; 1527 1528 ZmCalItem.prototype._setAlarmData = 1529 function(comp) { 1530 1531 var useAbs = this._useAbsoluteReminder, 1532 time = useAbs ? this._reminderAbs : this._reminderMinutes; 1533 1534 if (time == null || time === -1) { 1535 return; 1536 } 1537 1538 for (var i = 0, len = this.alarmActions.size(); i < len; i++) { 1539 var email = null; 1540 var action = this.alarmActions.get(i); 1541 if (action == ZmCalItem.ALARM_EMAIL) { 1542 email = appCtxt.get(ZmSetting.CAL_EMAIL_REMINDERS_ADDRESS); 1543 if (!email) { 1544 continue; 1545 } 1546 } 1547 if (action == ZmCalItem.ALARM_DEVICE_EMAIL) { 1548 email = appCtxt.get(ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ADDRESS); 1549 if (!email) { 1550 continue; 1551 } 1552 // NOTE: treat device email alarm as a standard email alarm 1553 action = ZmCalItem.ALARM_EMAIL; 1554 } 1555 var alarms = comp.alarm = comp.alarm || []; 1556 var alarm = {action: action}; 1557 alarms.push(alarm); 1558 var trigger = alarm.trigger = {}; 1559 this._setReminderUnits(trigger, time); 1560 this._addXPropsToAlarm(alarm); 1561 if (email) { 1562 alarm.at = {a: email}; 1563 } 1564 } 1565 }; 1566 1567 /** 1568 * @private 1569 */ 1570 ZmCalItem.prototype._setReminderUnits = 1571 function(trigger, time) { 1572 time = time || 0; 1573 var useAbs = this._useAbsoluteReminder, 1574 rel = trigger[useAbs ? "abs" : "rel"] = {}; 1575 if (useAbs) { 1576 rel.d = time; 1577 } 1578 else { 1579 rel.m = time; 1580 //default option is to remind before appt start 1581 rel.related = "START"; 1582 rel.neg = "1"; 1583 } 1584 }; 1585 1586 /** 1587 * @private 1588 */ 1589 ZmCalItem.prototype._addXPropsToAlarm = 1590 function(alarmNode) { 1591 if (!this.alarmData) { 1592 return; 1593 } 1594 var alarmData = (this.alarmData && this.alarmData.length > 0)? this.alarmData[0] : null, 1595 alarm = alarmData ? alarmData.alarm : null, 1596 alarmInst = (alarm && alarm.length > 0) ? alarm[0] : null; 1597 1598 this._setAlarmXProps(alarmInst, alarmNode); 1599 }; 1600 1601 /** 1602 * @private 1603 */ 1604 ZmCalItem.prototype._setAlarmXProps = 1605 function(alarmInst, alarmNode) { 1606 var xprops = (alarmInst && alarmInst.xprop) ? alarmInst.xprop : null, 1607 i, 1608 x, 1609 xprop; 1610 1611 if (!xprops) { 1612 return; 1613 } 1614 // bug 28924: preserve x props 1615 xprops = (xprops instanceof Array) ? xprops : [xprops]; 1616 1617 for (i = 0; i < xprops.length; i++) { 1618 xprop = xprops[i]; 1619 if (xprop && xprop.name) { 1620 x = alarmNode.xprop = {}; 1621 x.name = xprop.name; 1622 if (xprop.value != null) { 1623 x.value = xprop.value; 1624 } 1625 this._addXParamToRequest(x, xprop.xparam); 1626 } 1627 } 1628 }; 1629 1630 /** 1631 * Sets reminder minutes. 1632 * 1633 * @param {int} minutes the minutes 1634 */ 1635 ZmCalItem.prototype.setReminderMinutes = 1636 function(minutes) { 1637 this._reminderMinutes = minutes; 1638 }; 1639 1640 /** 1641 * Sets the reminder units 1642 * 1643 * @param {int} reminderValue the reminder value 1644 * @param {int} reminderUnits the reminder units 1645 */ 1646 ZmCalItem.prototype.setReminderUnits = 1647 function(reminderValue, reminderUnits, sendEmail) { 1648 if (!reminderValue) { 1649 this._reminderMinutes = 0; 1650 return; 1651 } 1652 reminderValue = parseInt(reminderValue + ""); 1653 this._reminderMinutes = ZmCalendarApp.convertReminderUnits(reminderValue, reminderUnits); 1654 this._reminderSendEmail = sendEmail; 1655 }; 1656 1657 /** 1658 * Adds the given action to this appt's reminders. A type of action can only be added once. 1659 * 1660 * @param {constant} action alarm action 1661 */ 1662 ZmCalItem.prototype.addReminderAction = 1663 function(action) { 1664 this.alarmActions.add(action, null, true); 1665 }; 1666 1667 /** 1668 * Removes the given action from this appt's reminders. 1669 * 1670 * @param {constant} action alarm action 1671 */ 1672 ZmCalItem.prototype.removeReminderAction = 1673 function(action) { 1674 this.alarmActions.remove(action); 1675 }; 1676 1677 /** 1678 * Deletes/cancels appointment/invite 1679 * 1680 * @param {int} mode designated what kind of delete op is this? 1681 * @param {ZmMailMsg} msg the message to be sent in lieu of delete 1682 * @param {AjxCallback} callback the callback to trigger after delete 1683 * @param {AjxCallback} errorCallback the error callback to trigger 1684 * @param {ZmBatchCommand} batchCmd set if part of a batch operation 1685 */ 1686 ZmCalItem.prototype.cancel = 1687 function(mode, msg, callback, errorCallback, batchCmd) { 1688 this.setViewMode(mode); 1689 if (msg) { 1690 // REVISIT: We explicitly set the bodyParts of the message b/c 1691 // ZmComposeView#getMsg only sets topPart on new message that's returned. 1692 // And ZmCalItem#_setNotes calls ZmMailMsg#getBodyPart. 1693 var bodyParts = []; 1694 var childParts = (msg._topPart.contentType == ZmMimeTable.MULTI_ALT) 1695 ? msg._topPart.children.getArray() 1696 : [msg._topPart]; 1697 for (var i = 0; i < childParts.length; i++) { 1698 bodyParts.push(childParts[i]); 1699 } 1700 msg.setBodyParts(bodyParts); 1701 this._setNotes(msg); 1702 this._doCancel(mode, callback, msg, batchCmd); 1703 } else { 1704 // To get the attendees for this appointment, we have to get the message. 1705 var respCallback = new AjxCallback(this, this._doCancel, [mode, callback, null, batchCmd]); 1706 var cancelErrorCallback = new AjxCallback(this, this._handleCancelError, [mode, callback, errorCallback]); 1707 if (this._blobInfoMissing && mode != ZmCalItem.MODE_DELETE_SERIES) { 1708 this.showBlobMissingDlg(); 1709 } else { 1710 this.getDetails(null, respCallback, cancelErrorCallback); 1711 } 1712 } 1713 }; 1714 1715 /** 1716 * @private 1717 */ 1718 ZmCalItem.prototype.showBlobMissingDlg = 1719 function() { 1720 var msgDialog = appCtxt.getMsgDialog(); 1721 msgDialog.setMessage(ZmMsg.apptBlobMissing, DwtMessageDialog.INFO_STYLE); 1722 msgDialog.popup(); 1723 }; 1724 1725 /** 1726 * @private 1727 */ 1728 ZmCalItem.prototype._handleCancelError = 1729 function(mode, callback, errorCallback, ex) { 1730 1731 if (ex.code == "mail.NO_SUCH_BLOB") { 1732 //bug: 19033, cannot delete instance of appt with missing blob info 1733 if (this.isRecurring() && mode != ZmCalItem.MODE_DELETE_SERIES) { 1734 this._blobInfoMissing = true; 1735 this.showBlobMissingDlg(); 1736 return true; 1737 } else { 1738 this._doCancel(mode, callback, this.message); 1739 } 1740 return true; 1741 } 1742 1743 if (errorCallback) { 1744 return errorCallback.run(ex); 1745 } 1746 1747 return false; 1748 }; 1749 1750 /** 1751 * @private 1752 */ 1753 ZmCalItem.prototype.setCancelFutureInstances = 1754 function(cancelFutureInstances) { 1755 this._cancelFutureInstances = cancelFutureInstances; 1756 }; 1757 1758 ZmCalItem.prototype._sendCancelMsg = 1759 function(callback){ 1760 this.save(null, callback); 1761 }; 1762 1763 /** 1764 * @private 1765 */ 1766 ZmCalItem.prototype._doCancel = 1767 function(mode, callback, msg, batchCmd, result) { 1768 var folderId = this.getFolder().nId, 1769 jsonObj = {}, 1770 requestName, 1771 request, 1772 action, 1773 accountName = this.getRemoteFolderOwner(), 1774 recurrence, 1775 untilDate, 1776 inst, 1777 allDay, 1778 format, 1779 clientId, 1780 m, 1781 e, 1782 i, 1783 j, 1784 type, 1785 vector, 1786 count, 1787 addr, 1788 subject, 1789 mailFromAddress, 1790 isOrganizer; 1791 1792 if (folderId == ZmOrganizer.ID_TRASH) { 1793 mode = ZmCalItem.MODE_PURGE; 1794 requestName = this._getRequestNameForMode(mode); 1795 request = jsonObj[requestName] = { 1796 _jsns : "urn:zimbraMail" 1797 }; 1798 action = request.action = {}; 1799 action.op = "delete"; 1800 action.id = this.id; 1801 if (batchCmd) { 1802 batchCmd.addRequestParams(jsonObj, callback); 1803 } else { 1804 this._sendRequest(null, accountName, callback, null, jsonObj, requestName); 1805 } 1806 } 1807 else { 1808 if (mode == ZmCalItem.MODE_DELETE_SERIES && this._cancelFutureInstances && this.getOrigStartDate().getTime() != this.getStartTime()) { 1809 1810 recurrence = this._recurrence; 1811 untilDate = new Date(this.getOrigStartDate().getTime()); 1812 untilDate.setTime(untilDate.getTime() - AjxDateUtil.MSEC_PER_DAY); 1813 recurrence.repeatEndDate = untilDate; 1814 recurrence.repeatEndType = "D"; 1815 1816 this.viewMode = ZmCalItem.MODE_EDIT_SERIES; 1817 this._sendCancelMsg(callback); 1818 return; 1819 } 1820 1821 if (mode == ZmCalItem.MODE_DELETE || 1822 mode == ZmCalItem.MODE_DELETE_SERIES || 1823 mode == ZmCalItem.MODE_DELETE_INSTANCE) 1824 { 1825 requestName = this._getRequestNameForMode(mode); 1826 request = jsonObj[requestName] = { 1827 _jsns : "urn:zimbraMail" 1828 }; 1829 1830 this._addInviteAndCompNum(request); 1831 1832 // Exceptions should be treated as instances (bug 15817) 1833 if (mode == ZmCalItem.MODE_DELETE_INSTANCE || this.isException) { 1834 request.s = this.getOrigStartTime(); 1835 inst = request.inst = {}; 1836 allDay = this.isAllDayEvent(); 1837 format = allDay ? AjxDateUtil.getServerDate : AjxDateUtil.getServerDateTime; 1838 inst.d = format(this.getOrigStartDate()); 1839 if (!allDay && this.timezone) { 1840 inst.tz = this.timezone; 1841 1842 clientId = AjxTimezone.getClientId(this.timezone); 1843 ZmTimezone.set(request, clientId, null, true); 1844 } 1845 } 1846 m = request.m = {}; 1847 e = m.e = []; 1848 isOrganizer = this.isOrganizer(); 1849 if (isOrganizer) { 1850 if (!this.inviteNeverSent) { 1851 // NOTE: We only use the explicit list of addresses if sending via 1852 // a message compose. 1853 if (msg) { 1854 for (i = 0; i < ZmMailMsg.ADDRS.length; i++) { 1855 type = ZmMailMsg.ADDRS[i]; 1856 1857 // if on-behalf-of, dont set the from address and 1858 // don't set the reset-from (only valid when receiving a message) 1859 if ((accountName && type == AjxEmailAddress.FROM) || 1860 (type == AjxEmailAddress.RESENT_FROM)) { 1861 continue; 1862 } 1863 1864 vector = msg.getAddresses(type); 1865 count = vector.size(); 1866 for (j = 0; j < count; j++) { 1867 addr = vector.get(j); 1868 e.push({ 1869 a: addr.getAddress(), 1870 t: AjxEmailAddress.toSoapType[type] 1871 }); 1872 } 1873 } 1874 1875 // set from address to on-behalf-of if applicable 1876 if (accountName) { 1877 e.push({ 1878 a: accountName, 1879 t: AjxEmailAddress.toSoapType[AjxEmailAddress.FROM] 1880 }); 1881 } 1882 } 1883 else { 1884 this._addAttendeesToRequest(null, m, null, accountName); 1885 } 1886 } 1887 mailFromAddress = this.getMailFromAddress(); 1888 if (mailFromAddress) { 1889 e.push({ 1890 a : mailFromAddress, 1891 t : AjxEmailAddress.toSoapType[AjxEmailAddress.FROM] 1892 }); 1893 } 1894 } 1895 subject = (msg && msg.subject) ? msg.subject : ([ZmMsg.cancelled, ": ", this.name].join("")); 1896 m.su = subject; 1897 this._addNotesToRequest(m, true); 1898 1899 if (batchCmd) { 1900 batchCmd.addRequestParams(jsonObj, callback); 1901 } 1902 else { 1903 this._sendRequest(null, accountName, callback, null, jsonObj, requestName); 1904 } 1905 } 1906 else { 1907 if (callback) callback.run(); 1908 } 1909 } 1910 }; 1911 1912 /** 1913 * Gets the mail from address. 1914 * 1915 * @return {String} the address 1916 */ 1917 ZmCalItem.prototype.getMailFromAddress = 1918 function() { 1919 var mailFromAddress = appCtxt.get(ZmSetting.MAIL_FROM_ADDRESS); 1920 if(mailFromAddress) { 1921 return (mailFromAddress instanceof Array) ? mailFromAddress[0] : mailFromAddress; 1922 } 1923 }; 1924 1925 // Returns canned text for meeting invites. 1926 // - Instances of recurring meetings should send out information that looks very 1927 // much like a simple appointment. 1928 /** 1929 * Gets the summary as text. 1930 * 1931 * @return {String} the summary 1932 */ 1933 ZmCalItem.prototype.getTextSummary = 1934 function() { 1935 return this.getSummary(false); 1936 }; 1937 1938 /** 1939 * Gets the summary as HTML. 1940 * 1941 * @return {String} the summary 1942 */ 1943 ZmCalItem.prototype.getHtmlSummary = 1944 function() { 1945 return this.getSummary(true); 1946 }; 1947 1948 1949 1950 // Private / Protected methods 1951 1952 /** 1953 * @private 1954 */ 1955 ZmCalItem.prototype._getTextSummaryTime = 1956 function(isEdit, fieldstr, extDate, start, end, hasTime) { 1957 var showingTimezone = appCtxt.get(ZmSetting.CAL_SHOW_TIMEZONE); 1958 1959 var buf = []; 1960 var i = 0; 1961 1962 if (extDate) { 1963 buf[i++] = AjxDateUtil.longComputeDateStr(extDate); 1964 buf[i++] = ", "; 1965 } 1966 if (this.isAllDayEvent()) { 1967 buf[i++] = ZmMsg.allDay; 1968 } else { 1969 var formatter = AjxDateFormat.getTimeInstance(); 1970 if (start) 1971 buf[i++] = formatter.format(start); 1972 if (start && end) 1973 buf[i++] = " - "; 1974 if (end) 1975 buf[i++] = formatter.format(end); 1976 1977 if (showingTimezone) { 1978 buf[i++] = " "; 1979 buf[i++] = AjxTimezone.getLongName(AjxTimezone.getClientId(this.timezone)); 1980 } 1981 } 1982 // NOTE: This relies on the fact that setModel creates a clone of the 1983 // appointment object and that the original object is saved in 1984 // the clone as the _orig property. 1985 if (isEdit && ((this._orig && this._orig.isAllDayEvent() != this.isAllDayEvent()) || hasTime)) { 1986 buf[i++] = " "; 1987 buf[i++] = ZmMsg.apptModifiedStamp; 1988 } 1989 buf[i++] = "\n"; 1990 1991 return buf.join(""); 1992 }; 1993 1994 /** 1995 * Uses indexOf() rather than a regex since IE didn't split on the regex correctly. 1996 * 1997 * @private 1998 */ 1999 ZmCalItem.prototype._trimNotesSummary = 2000 function(notes, isHtml) { 2001 if (notes) { 2002 var idx = notes.indexOf(ZmItem.NOTES_SEPARATOR); 2003 if (idx != -1) { 2004 notes = notes.substr(idx + ZmItem.NOTES_SEPARATOR.length); 2005 if (isHtml) { 2006 // If HTML content is generated from text content \n are replaced with br 2007 // Remove the leading <br> added 2008 notes = notes.replace(/^<br><br>/i, ""); 2009 notes = notes.replace(/^<\/div><br>/i, ""); 2010 // Removes </body></html> if that is all that is left. Reduces the html to "" in that case, 2011 // so that later checks don't detect HTML notes. 2012 notes = notes.replace(/^<\/body><\/html>/i, ""); 2013 } 2014 else { 2015 notes = notes.replace(/^\n\n/i, ""); 2016 } 2017 } 2018 } 2019 return AjxStringUtil.trim(notes); 2020 }; 2021 2022 /** 2023 * @private 2024 */ 2025 ZmCalItem.prototype._resetCached = 2026 function() { 2027 delete this._startTimeUniqId; this._startTimeUniqId = null; 2028 delete this._validAttachments; this._validAttachments = null; 2029 delete this.tooltip; this.tooltip = null; 2030 }; 2031 2032 /** 2033 * @private 2034 */ 2035 ZmCalItem.prototype._getTTDay = 2036 function(d) { 2037 return DwtCalendar.getDayFormatter().format(d); 2038 }; 2039 2040 /** 2041 * @private 2042 */ 2043 ZmCalItem.prototype._addInviteAndCompNum = 2044 function(request) { 2045 var id; 2046 if(this.message && !this.isVersionIgnored()){ 2047 request.ms = this.message.ms; 2048 request.rev = this.message.rev; 2049 2050 } 2051 if (this.viewMode == ZmCalItem.MODE_EDIT_SERIES || this.viewMode == ZmCalItem.MODE_DELETE_SERIES) { 2052 if (this.recurring && this.seriesInvId != null) { 2053 request.id = this.seriesInvId; 2054 request.comp = this.getCompNum(); 2055 } 2056 } else { 2057 if (this.invId != null && this.invId != -1) { 2058 id = this.invId; 2059 2060 // bug: 41530 - for offline, make sure id is fully qualified if moving across accounts 2061 if (appCtxt.multiAccounts && 2062 this._orig && 2063 this._orig.getFolder().getAccount() != this.getFolder().getAccount()) 2064 { 2065 id = ZmOrganizer.getSystemId(this.invId, this._orig.getFolder().getAccount(), true); 2066 } 2067 2068 request.id = id; 2069 request.comp = this.getCompNum(); 2070 } 2071 } 2072 }; 2073 2074 /** 2075 * @private 2076 */ 2077 ZmCalItem.prototype._getDefaultBlurb = 2078 function(cancel, isHtml) { 2079 var buf = []; 2080 var i = 0; 2081 var singleInstance = this.viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE || 2082 this.viewMode == ZmCalItem.MODE_DELETE_INSTANCE; 2083 2084 if (isHtml) buf[i++] = "<h3>"; 2085 2086 if(this.isProposeTimeMode) { 2087 buf[i++] = ZmMsg.subjectNewTime; 2088 }else if (cancel) { 2089 buf[i++] = singleInstance ? ZmMsg.apptInstanceCanceled : ZmMsg.apptCanceled; 2090 } else if(!this.isForwardMode || this.isOrganizer()){ 2091 if (!this.inviteNeverSent && ( this.viewMode == ZmCalItem.MODE_EDIT || 2092 this.viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE || 2093 this.viewMode == ZmCalItem.MODE_EDIT_SERIES ) ) 2094 { 2095 buf[i++] = singleInstance ? ZmMsg.apptInstanceModified : ZmMsg.apptModified; 2096 } 2097 else 2098 { 2099 buf[i++] = ZmMsg.apptNew; 2100 } 2101 }else { 2102 buf[i++] = ZmMsg.apptForwarded; 2103 } 2104 2105 if (isHtml) buf[i++] = "</h3>"; 2106 2107 buf[i++] = "\n\n"; 2108 buf[i++] = this.getSummary(isHtml); 2109 2110 return buf.join(""); 2111 }; 2112 2113 // Server request calls 2114 2115 /** 2116 * @private 2117 */ 2118 ZmCalItem.prototype._getRequestNameForMode = 2119 function(mode, isException) { 2120 // override 2121 }; 2122 2123 /** 2124 * @private 2125 */ 2126 ZmCalItem.prototype._getInviteFromError = 2127 function(result) { 2128 // override 2129 }; 2130 2131 /** 2132 * @private 2133 */ 2134 ZmCalItem.prototype._setRequestAttributes = 2135 function(request, attachmentId, notifyList, accountName) { 2136 2137 var m = request.m = {}, 2138 calendar = this.getFolder(), 2139 acct = calendar.getAccount(), 2140 isOnBehalfOf = accountName && acct && acct.name != accountName, 2141 mailFromAddress, 2142 identityUser, 2143 displayName, 2144 validAttLen, 2145 attachNode, 2146 organizer, 2147 isPrimary, 2148 identityC, 2149 identity, 2150 isRemote, 2151 addrObj, 2152 orgName, 2153 comps, 2154 comp, 2155 user, 2156 addr, 2157 inv, 2158 org, 2159 mid, 2160 me, 2161 e, 2162 i; 2163 //m.setAttribute("l", (isOnBehalfOf ? this.getFolder().rid : this.folderId)); 2164 m.l = (isOnBehalfOf ? this.getFolder().rid : this.folderId); 2165 inv = m.inv = {}; 2166 e = m.e = []; 2167 if (this.uid != null && this.uid != -1 && !this.isSharedCopy) { 2168 inv.uid = this.uid; 2169 } 2170 2171 comps = inv.comp = []; 2172 comp = comps[0] = {}; 2173 comp.at = []; 2174 // attendees 2175 this._addAttendeesToRequest(comp, m, notifyList, accountName); 2176 2177 identity = this.identity; 2178 isPrimary = identity == null || identity.isDefault; 2179 isRemote = calendar.isRemote(); 2180 2181 //FROM Address 2182 mailFromAddress = this.getMailFromAddress(); 2183 if (this.isOrganizer() && !accountName && (mailFromAddress || isRemote || !isPrimary)) { 2184 if(mailFromAddress){ 2185 addr = mailFromAddress; 2186 }else{ 2187 if(isRemote){ 2188 addr = this.organizer; 2189 }else if(identity){ 2190 addr = identity.sendFromAddress; 2191 displayName = identity.sendFromDisplay; 2192 } 2193 } 2194 addrObj = { 2195 a : addr, 2196 t : AjxEmailAddress.toSoapType[AjxEmailAddress.FROM] 2197 }; 2198 if(!displayName && addr == appCtxt.get(ZmSetting.USERNAME)){ 2199 displayName = appCtxt.get(ZmSetting.DISPLAY_NAME); 2200 } 2201 if(displayName){ 2202 addrObj.p = displayName; 2203 } 2204 e.push(addrObj); 2205 if (identity && identity.isFromDataSource && !isRemote) { 2206 this._addIdentityFrom(identity, e, m); 2207 } 2208 } 2209 2210 //SENDER Address 2211 if (isRemote) { 2212 if (!identity) { 2213 identityC = appCtxt.getIdentityCollection(); 2214 identity = identityC && identityC.defaultIdentity; 2215 } 2216 if (identity) { 2217 addr = identity.sendFromAddress; 2218 displayName = identity.sendFromDisplay; 2219 } 2220 else { 2221 addr = appCtxt.get(ZmSetting.USERNAME); 2222 displayName = appCtxt.get(ZmSetting.DISPLAY_NAME); 2223 } 2224 addrObj = { 2225 a : addr, 2226 t : AjxEmailAddress.toSoapType[AjxEmailAddress.SENDER] 2227 }; 2228 if (displayName) { 2229 addrObj.p = displayName; 2230 } 2231 e.push(addrObj); 2232 } 2233 2234 this._addExtrasToRequest(request, comp); 2235 this._addDateTimeToRequest(request, comp); 2236 this._addXPropsToRequest(comp); 2237 2238 // subject/location 2239 m.su = this.name; 2240 comp.name = this.name; 2241 this._addLocationToRequest(comp); 2242 2243 // notes 2244 this._addNotesToRequest(m); 2245 2246 // set organizer - but not for local account 2247 if (!(appCtxt.isOffline && acct.isMain)) { 2248 me = (appCtxt.multiAccounts) ? acct.getEmail() : appCtxt.get(ZmSetting.USERNAME); 2249 if (!identity) { 2250 identityC = appCtxt.getIdentityCollection(acct); 2251 identity = identityC && identityC.defaultIdentity; 2252 } 2253 if (identity) { //If !Identity then consider the default identity 2254 identityUser = identity.sendFromAddress; 2255 displayName = identity.sendFromDisplay; 2256 } 2257 user = mailFromAddress || identityUser || me; 2258 organizer = this.organizer || user; 2259 org = comp.or = {}; 2260 org.a = organizer; 2261 if (isRemote) { 2262 org.sentBy = user; // if on-behalf of, set sentBy 2263 } 2264 orgName = (organizer == identityUser) ? displayName : (ZmApptViewHelper.getAddressEmail(organizer)).getName(); 2265 if (orgName) { 2266 org.d = orgName; 2267 } 2268 } 2269 2270 // handle attachments 2271 this.flagLocal(ZmItem.FLAG_ATTACH, false); 2272 this.getAttachments(); // bug 22874: make sure to populate _validAttachments 2273 if (attachmentId != null || 2274 (this._validAttachments != null && this._validAttachments.length)) 2275 { 2276 attachNode = request.m.attach = {}; 2277 if (attachmentId){ 2278 attachNode.aid = attachmentId; 2279 this.flagLocal(ZmItem.FLAG_ATTACH, true); 2280 } 2281 2282 if (this._validAttachments) { 2283 validAttLen = this._validAttachments.length; 2284 attachNode.mp = []; 2285 for (i = 0; i < validAttLen; i++) { 2286 2287 mid = (this.invId || this.message.id); 2288 if ((mid.indexOf(":") < 0) && calendar.isRemote()) { 2289 mid = (appCtxt.getActiveAccount().id + ":" + mid); 2290 } 2291 attachNode.mp.push({ 2292 mid : mid, 2293 part : this._validAttachments[i].part 2294 }); 2295 } 2296 if (validAttLen > 0) { 2297 this.flagLocal(ZmItem.FLAG_ATTACH, true); 2298 } 2299 } 2300 } 2301 2302 return {'inv': inv, 'm': m }; 2303 }; 2304 2305 ZmCalItem.prototype._addIdentityFrom = 2306 function(identity, e, m) { 2307 var dataSource = appCtxt.getDataSourceCollection().getById(identity.id), 2308 provider, 2309 doNotAddSender, 2310 addrObj, 2311 displayName; 2312 2313 if (dataSource) { 2314 provider = ZmDataSource.getProviderForAccount(dataSource); 2315 doNotAddSender = provider && provider._nosender; 2316 // main account is "sender" 2317 if (!doNotAddSender) { 2318 e.push({ 2319 t : AjxEmailAddress.toSoapType[AjxEmailAddress.SENDER], 2320 p : appCtxt.get(ZmSetting.DISPLAY_NAME) || "" 2321 }); 2322 } 2323 // mail is "from" external account 2324 addrObj = { 2325 t : AjxEmailAddress.toSoapType[AjxEmailAddress.SENDER], 2326 a : dataSource.getEmail() 2327 }; 2328 if (appCtxt.get(ZmSetting.DEFAULT_DISPLAY_NAME)) { 2329 displayName = dataSource.identity && dataSource.identity.sendFromDisplay; 2330 displayName = displayName || dataSource.userName || dataSource.getName(); 2331 if(displayName) { 2332 addrObj.p = displayName; 2333 } 2334 } 2335 e.push(addrObj); 2336 } 2337 }; 2338 2339 /** 2340 * @private 2341 */ 2342 ZmCalItem.prototype._addExtrasToRequest = 2343 function(request, comp) { 2344 if (this.priority) { 2345 comp.priority = this.priority; 2346 } 2347 comp.status = this.status; 2348 }; 2349 2350 /** 2351 * @private 2352 */ 2353 ZmCalItem.prototype._addXPropsToRequest = 2354 function(comp) { 2355 var message = this.message ? this.message : null, 2356 invite = (message && message.invite) ? message.invite : null, 2357 xprops = invite ? invite.getXProp() : null, 2358 xprop, 2359 x, 2360 i; 2361 if (!xprops) { return; } 2362 comp.xprop = []; 2363 // bug 16024: preserve x props 2364 xprops = (xprops instanceof Array) ? xprops : [xprops]; 2365 2366 for (i = 0; i < xprops.length; i++) { 2367 xprop = xprops[i], 2368 x = {}; 2369 if (xprop && xprop.name) { 2370 x.name = xprop.name; 2371 if (xprop.value != null) { 2372 x.value = xprop.value; 2373 } 2374 this._addXParamToRequest(x, xprop.xparam); 2375 comp.xprop.push(x); 2376 } 2377 } 2378 }; 2379 2380 /** 2381 * @private 2382 */ 2383 ZmCalItem.prototype._addXParamToRequest = 2384 function(xprop, xparams) { 2385 if (!xparams) { 2386 return; 2387 } 2388 2389 xparams = (xparams instanceof Array) ? xparams : [xparams]; 2390 var xparam = xprop.xparam = [], 2391 xObj = {}, 2392 j, 2393 x; 2394 2395 for (j = 0; j < xparams.length; j++) { 2396 x = xparams[j]; 2397 xObj = {}; 2398 if (x && x.name) { 2399 xObj.name = x.name; 2400 if (x.value != null) { 2401 xObj.value = x.value; 2402 } 2403 xparam.push(xObj); 2404 } 2405 } 2406 }; 2407 2408 /** 2409 * @private 2410 */ 2411 ZmCalItem.prototype._addDateTimeToRequest = 2412 function(request, comp) { 2413 // always(?) set all day 2414 comp.allDay = this.allDayEvent + ""; 2415 // timezone 2416 var tz, 2417 clientId, 2418 s, 2419 sd, 2420 e, 2421 ed; 2422 if (this.timezone) { 2423 clientId = AjxTimezone.getClientId(this.timezone); 2424 ZmTimezone.set(request, clientId, null, true); 2425 tz = this.timezone; 2426 } 2427 2428 // start date 2429 if (this.startDate) { 2430 s = comp.s = {}; 2431 if (!this.isAllDayEvent()) { 2432 sd = AjxDateUtil.getServerDateTime(this.startDate, this.startsInUTC); 2433 2434 // set timezone if not utc date/time 2435 if (!this.startsInUTC && tz && tz.length) { 2436 s.tz = tz; 2437 } 2438 s.d = sd; 2439 } 2440 else { 2441 s.d = AjxDateUtil.getServerDate(this.startDate); 2442 } 2443 } 2444 2445 2446 if(this.endTimezone) { 2447 tz = this.endTimezone; 2448 } 2449 2450 // end date 2451 if (this.endDate) { 2452 e = comp.e = {}; 2453 if (!this.isAllDayEvent()) { 2454 ed = AjxDateUtil.getServerDateTime(this.endDate, this.endsInUTC); 2455 2456 // set timezone if not utc date/time 2457 if (!this.endsInUTC && tz && tz.length) { 2458 e.tz = tz; 2459 } 2460 e.d = ed; 2461 2462 } else { 2463 e.d = AjxDateUtil.getServerDate(this.endDate); 2464 } 2465 } 2466 }; 2467 2468 /** 2469 * @private 2470 */ 2471 ZmCalItem.prototype._addAttendeesToRequest = 2472 function(inv, m, notifyList, accountName) { 2473 // if this appt is on-behalf-of, set the from address to that person 2474 if (this.isOrganizer() && accountName) { 2475 m.e.push({ 2476 a : accountName, 2477 t : AjxEmailAddress.toSoapType[AjxEmailAddress.FROM] 2478 }); 2479 } 2480 }; 2481 2482 /** 2483 * @private 2484 */ 2485 ZmCalItem.prototype._addNotesToRequest = 2486 function(m, cancel) { 2487 2488 var hasAttendees = this.hasAttendees(), 2489 tprefix = hasAttendees ? this._getDefaultBlurb(cancel) : "", 2490 hprefix = hasAttendees ? this._getDefaultBlurb(cancel, true) : "", 2491 mp = m.mp = {"mp" : []}, 2492 numSubParts, 2493 part, 2494 pct, 2495 content, 2496 ntp, 2497 tcontent, 2498 hcontent, 2499 html, 2500 i; 2501 2502 mp.ct = ZmMimeTable.MULTI_ALT; 2503 numSubParts = this.notesTopPart ? this.notesTopPart.children.size() : 0; 2504 if (numSubParts > 0) { 2505 for (i = 0; i < numSubParts; i++) { 2506 part = this.notesTopPart.children.get(i); 2507 pct = part.getContentType(); 2508 2509 if (pct == ZmMimeTable.TEXT_HTML) { 2510 var htmlContent = part.getContent(); 2511 htmlContent = AjxStringUtil.defangHtmlContent(htmlContent); 2512 content = "<html><body id='htmlmode'>" + (this._includeEditReply ? htmlContent : AjxBuffer.concat(hprefix, htmlContent)) + "</body></html>"; 2513 } else { 2514 content = this._includeEditReply ? part.getContent() : AjxBuffer.concat(tprefix, part.getContent()); 2515 } 2516 mp.mp.push({ 2517 ct : pct, 2518 content : content 2519 }); 2520 } 2521 } else { 2522 ntp = this.notesTopPart; 2523 tcontent = ntp ? ntp.getContent() : ""; 2524 pct = ntp ? ntp.getContentType() : ZmMimeTable.TEXT_PLAIN; 2525 mp.mp.push({ 2526 ct : pct 2527 }); 2528 if (pct == ZmMimeTable.TEXT_HTML) { 2529 //bug fix #9592 - html encode the text before setting it as the "HTML" part 2530 hcontent = AjxStringUtil.nl2br(AjxStringUtil.htmlEncode(tcontent)); 2531 html = "<html><body>" + (this._includeEditReply ? hcontent : AjxBuffer.concat(hprefix, hcontent)) + "</body></html>"; 2532 mp.mp[0].content = html; 2533 } 2534 else { 2535 mp.mp[0].content = (this._includeEditReply ? tcontent : AjxBuffer.concat(tprefix, tcontent)); 2536 } 2537 } 2538 }; 2539 2540 2541 /** 2542 * Gets a string representation of the invite content. 2543 * 2544 * @param {Boolean} isHtml if <code>true</code>, get HTML content 2545 * @return {String} a string representation of the invite 2546 */ 2547 ZmCalItem.prototype.getInviteDescription = 2548 function(isHtml) { 2549 var hasAttendees = this.hasAttendees(); 2550 var tprefix = hasAttendees ? this.getSummary(false) : ""; 2551 var hprefix = hasAttendees ? this.getSummary(true) : ""; 2552 2553 var notes = this.getNotesPart(isHtml ? ZmMimeTable.TEXT_HTML : ZmMimeTable.TEXT_PLAIN); 2554 return AjxBuffer.concat(isHtml ? hprefix : tprefix, notes) 2555 }; 2556 2557 /** 2558 * @private 2559 */ 2560 ZmCalItem.prototype.setIncludeEditReply = 2561 function(includeEditReply) { 2562 this._includeEditReply = includeEditReply; 2563 }; 2564 2565 /** 2566 * @private 2567 */ 2568 ZmCalItem.prototype._sendRequest = 2569 function(soapDoc, accountName, callback, errorCallback, jsonObj, requestName) { 2570 var responseName = soapDoc ? soapDoc.getMethod().nodeName.replace("Request", "Response") : requestName.replace("Request", "Response"); 2571 var respCallback = new AjxCallback(this, this._handleResponseSend, [responseName, callback]); 2572 if (!jsonObj) { 2573 appCtxt.getAppController().sendRequest({soapDoc:soapDoc, asyncMode:true, accountName:accountName, callback:respCallback, errorCallback:errorCallback}); 2574 } 2575 else { 2576 appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, accountName:accountName, callback:respCallback, errorCallback:errorCallback}); 2577 } 2578 }; 2579 2580 /** 2581 * @private 2582 */ 2583 ZmCalItem.prototype._loadFromDom = 2584 function(calItemNode, instNode) { 2585 ZmCalBaseItem.prototype._loadFromDom.call(this, calItemNode, instNode); 2586 2587 this.isOrg = this._getAttr(calItemNode, instNode, "isOrg"); 2588 var org = calItemNode.or; 2589 this.organizer = org && org.a; 2590 this.sentBy = org && org.sentBy; 2591 this.invId = this._getAttr(calItemNode, instNode, "invId"); 2592 this.compNum = this._getAttr(calItemNode, instNode, "compNum") || "0"; 2593 this.parseAlarmData(this.alarmData); 2594 this.seriesInvId = this.recurring ? calItemNode.invId : null; 2595 this.ridZ = instNode && instNode.ridZ; 2596 2597 if (calItemNode.tn) { 2598 this._parseTagNames(calItemNode.tn); 2599 } 2600 if (calItemNode.f) { 2601 this._parseFlags(calItemNode.f); 2602 } 2603 }; 2604 2605 // Callbacks 2606 2607 /** 2608 * @private 2609 */ 2610 ZmCalItem.prototype._handleResponseSend = 2611 function(respName, callback, result) { 2612 var resp = result.getResponse(); 2613 2614 // branch for different responses 2615 var response = resp[respName]; 2616 if (response.uid != null) { 2617 this.uid = response.uid; 2618 } 2619 2620 var msgNode; 2621 //echo=1 sends back echo response node, process it 2622 if(response.echo){ 2623 msgNode = response.echo; 2624 if(msgNode.length > 0){ 2625 msgNode = msgNode[0]; 2626 } 2627 } 2628 msgNode = msgNode ? msgNode.m : response.m; 2629 if (msgNode != null) { 2630 if(msgNode.length > 0){ 2631 msgNode = msgNode[0]; 2632 } 2633 var oldInvId = this.invId; 2634 this.invId = msgNode.id; 2635 if (AjxUtil.isSpecified(oldInvId) && oldInvId != this.invId){ 2636 this.message = null; 2637 }else if(msgNode){ 2638 this.message = new ZmMailMsg(msgNode.id); 2639 this.message._loadFromDom(msgNode); 2640 delete this._validAttachments; 2641 this._validAttachments = null; 2642 this.getAttachments(); 2643 } 2644 } 2645 2646 this._messageNode = null; 2647 2648 if (callback) { 2649 callback.run(response); 2650 } 2651 }; 2652 2653 ZmCalItem.prototype.processErrorSave = 2654 function(ex) { 2655 // TODO: generalize error message for calItem instead of just Appt 2656 var status = { 2657 continueSave: false, 2658 errorMessage: "" 2659 }; 2660 if (ex.code == ZmCsfeException.MAIL_SEND_ABORTED_ADDRESS_FAILURE) { 2661 var invalid = ex.getData(ZmCsfeException.MAIL_SEND_ADDRESS_FAILURE_INVALID); 2662 var invalidMsg = (invalid && invalid.length) 2663 ? AjxMessageFormat.format(ZmMsg.apptSendErrorInvalidAddresses, AjxStringUtil.htmlEncode(invalid.join(", "))) : null; 2664 status.errorMessage = ZmMsg.apptSendErrorAbort + "<br/>" + invalidMsg; 2665 } else if (ex.code == ZmCsfeException.MAIL_SEND_PARTIAL_ADDRESS_FAILURE) { 2666 var invalid = ex.getData(ZmCsfeException.MAIL_SEND_ADDRESS_FAILURE_INVALID); 2667 status.errorMessage = (invalid && invalid.length) 2668 ? AjxMessageFormat.format(ZmMsg.apptSendErrorPartial, AjxStringUtil.htmlEncode(invalid.join(", "))) 2669 : ZmMsg.apptSendErrorAbort; 2670 } else if(ex.code == ZmCsfeException.MAIL_MESSAGE_TOO_BIG) { 2671 status.errorMessage = (this.type == ZmItem.TASK) ? ZmMsg.taskSaveErrorToobig : ZmMsg.apptSaveErrorToobig; 2672 } else if (ex.code == ZmCsfeException.MAIL_INVITE_OUT_OF_DATE) { 2673 if(!this.isVersionIgnored()){ 2674 this.setIgnoreVersion(true); 2675 status.continueSave = true; 2676 } 2677 else{ 2678 status.errorMessage = ZmMsg.inviteOutOfDate; 2679 this.setIgnoreVersion(false); 2680 } 2681 } else if (ex.code == ZmCsfeException.MAIL_NO_SUCH_CALITEM) { 2682 status.errorMessage = ex.getErrorMsg([ex.getData("itemId")]); 2683 } else if (ex.code == ZmCsfeException.MAIL_QUOTA_EXCEEDED) { 2684 if(this.type == ZmItem.APPT){ 2685 status.errorMessage=ZmMsg.errorQuotaExceededAppt; 2686 } else if(this.type == ZmItem.TASK){ 2687 status.errorMessage=ZmMsg.errorQuotaExceededTask; 2688 } 2689 } 2690 else if (ex.code === ZmCsfeException.MUST_BE_ORGANIZER) { 2691 status.errorMessage = ZmMsg.mustBeOrganizer; 2692 } 2693 2694 return status; 2695 }; 2696 2697 ZmCalItem.prototype.setProposedTimeCallback = 2698 function(callback) { 2699 this._proposedTimeCallback = callback; 2700 }; 2701 2702 ZmCalItem.prototype.handlePostSaveCallbacks = 2703 function() { 2704 if(this._proposedTimeCallback) this._proposedTimeCallback.run(this); 2705 this.setIgnoreVersion(false); 2706 }; 2707 2708 // Static methods 2709 2710 ZmCalItem.isPriorityHigh = function(priority) { 2711 return AjxUtil.arrayContains(ZmCalItem.PRIORITY_HIGH_RANGE, priority); 2712 }; 2713 ZmCalItem.isPriorityLow = function(priority) { 2714 return AjxUtil.arrayContains(ZmCalItem.PRIORITY_LOW_RANGE, priority); 2715 }; 2716 ZmCalItem.isPriorityNormal = function(priority) { 2717 return AjxUtil.arrayContains(ZmCalItem.PRIORITY_NORMAL_RANGE, priority); 2718 }; 2719 2720 /** 2721 * Gets the priority label. 2722 * 2723 * @param {int} priority the priority (see <code>ZmCalItem.PRIORITY_</code> constants) 2724 * @return {String} the priority label 2725 * 2726 */ 2727 ZmCalItem.getLabelForPriority = 2728 function(priority) { 2729 if (ZmCalItem.isPriorityLow(priority)) { 2730 return ZmMsg.low; 2731 } 2732 if (ZmCalItem.isPriorityNormal(priority)) { 2733 return ZmMsg.normal; 2734 } 2735 if (ZmCalItem.isPriorityHigh(priority)) { 2736 return ZmMsg.high; 2737 } 2738 return ""; 2739 }; 2740 2741 /** 2742 * Gets the priority image. 2743 * 2744 * @param {ZmTask} task the task 2745 * @param {int} id the id 2746 * @return {String} the priority image 2747 */ 2748 ZmCalItem.getImageForPriority = 2749 function(task, id) { 2750 if (ZmCalItem.isPriorityLow(task.priority)) { 2751 return id 2752 ? AjxImg.getImageHtml("PriorityLow_list", null, ["id='", id, "'"].join("")) 2753 : AjxImg.getImageHtml("PriorityLow_list"); 2754 } else if (ZmCalItem.isPriorityHigh(task.priority)) { 2755 return id 2756 ? AjxImg.getImageHtml("PriorityHigh_list", null, ["id='", id, "'"].join("")) 2757 : AjxImg.getImageHtml("PriorityHigh_list"); 2758 } 2759 return ""; 2760 }; 2761 2762 /** 2763 * Gets the status label. 2764 * 2765 * @param {int} status the status (see <code>ZmCalendarApp.STATUS_</code> constants) 2766 * @return {String} the status label 2767 * 2768 * @see ZmCalendarApp 2769 */ 2770 ZmCalItem.getLabelForStatus = 2771 function(status) { 2772 switch (status) { 2773 case ZmCalendarApp.STATUS_CANC: return ZmMsg.cancelled; 2774 case ZmCalendarApp.STATUS_COMP: return ZmMsg.completed; 2775 case ZmCalendarApp.STATUS_DEFR: return ZmMsg.deferred; 2776 case ZmCalendarApp.STATUS_INPR: return ZmMsg.inProgress; 2777 case ZmCalendarApp.STATUS_NEED: return ZmMsg.notStarted; 2778 case ZmCalendarApp.STATUS_WAIT: return ZmMsg.waitingOn; 2779 } 2780 return ""; 2781 }; 2782 2783 /** 2784 * Gets the participation status label. 2785 * 2786 * @param {int} status the status (see <code>ZmCalBaseItem.PSTATUS_</code> constants) 2787 * @return {String} the status label 2788 * 2789 * @see ZmCalBaseItem 2790 */ 2791 ZmCalItem.getLabelForParticipationStatus = 2792 function(status) { 2793 switch (status) { 2794 case ZmCalBaseItem.PSTATUS_ACCEPT: return ZmMsg.ptstAccept; 2795 case ZmCalBaseItem.PSTATUS_DECLINED: return ZmMsg.ptstDeclined; 2796 case ZmCalBaseItem.PSTATUS_DEFERRED: return ZmMsg.ptstDeferred; 2797 case ZmCalBaseItem.PSTATUS_DELEGATED: return ZmMsg.ptstDelegated; 2798 case ZmCalBaseItem.PSTATUS_NEEDS_ACTION:return ZmMsg.ptstNeedsAction; 2799 case ZmCalBaseItem.PSTATUS_COMPLETED: return ZmMsg.completed; 2800 case ZmCalBaseItem.PSTATUS_TENTATIVE: return ZmMsg.ptstTentative; 2801 case ZmCalBaseItem.PSTATUS_WAITING: return ZmMsg.ptstWaiting; 2802 } 2803 return ""; 2804 }; 2805 2806 /** 2807 * Gets the participation status icon. 2808 * 2809 * @param {int} status the status (see <code>ZmCalBaseItem.PSTATUS_</code> constants) 2810 * @return {String} the status icon or an empty string if status not set 2811 * 2812 * @see ZmCalBaseItem 2813 */ 2814 ZmCalItem.getParticipationStatusIcon = 2815 function(status) { 2816 switch (status) { 2817 case ZmCalBaseItem.PSTATUS_ACCEPT: return "Check"; 2818 case ZmCalBaseItem.PSTATUS_DECLINED: return "Cancel"; 2819 case ZmCalBaseItem.PSTATUS_DEFERRED: return "QuestionMark"; 2820 case ZmCalBaseItem.PSTATUS_DELEGATED: return "Plus"; 2821 case ZmCalBaseItem.PSTATUS_NEEDS_ACTION:return "NeedsAction"; 2822 case ZmCalBaseItem.PSTATUS_COMPLETED: return "Completed"; 2823 case ZmCalBaseItem.PSTATUS_TENTATIVE: return "QuestionMark"; 2824 case ZmCalBaseItem.PSTATUS_WAITING: return "Minus"; 2825 } 2826 return ""; 2827 }; 2828 2829 /** 2830 * @private 2831 */ 2832 ZmCalItem._getTTDay = 2833 function(d, format) { 2834 format = format || AjxDateFormat.SHORT; 2835 var formatter = AjxDateFormat.getDateInstance(); 2836 return formatter.format(d); 2837 }; 2838