1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file defines a calendar appointment invite. 27 * 28 */ 29 30 /** 31 * Creates an invite. 32 * @class 33 * This class represents an invite to a calendar appointment. 34 * 35 * @extends ZmModel 36 */ 37 ZmInvite = function() { 38 ZmModel.call(this); 39 }; 40 41 ZmInvite.prototype = new ZmModel; 42 ZmInvite.prototype.constructor = ZmInvite; 43 44 45 // Consts 46 ZmInvite.CHANGES_LOCATION = "location"; 47 ZmInvite.CHANGES_SUBJECT = "subject"; 48 ZmInvite.CHANGES_RECURRENCE = "recurrence"; 49 ZmInvite.CHANGES_TIME = "time"; 50 ZmInvite.TASK = "task"; 51 52 53 /** 54 * Returns a string representation of the object. 55 * 56 * @return {String} a string representation of the object 57 */ 58 ZmInvite.prototype.toString = 59 function() { 60 return "ZmInvite: name=" + this.name + " id=" + this.id; 61 }; 62 63 /** 64 * Function that will be used to send requests. This should be set via ZmInvite.setSendFunction. 65 * 66 * @private 67 */ 68 ZmInvite._sendFun = null; 69 70 // Class methods 71 72 /** 73 * Creates the invite from the DOM. 74 * 75 * @param {Object} node the node 76 * @return {ZmInvite} the newly created invite 77 */ 78 ZmInvite.createFromDom = 79 function(node) { 80 var invite = new ZmInvite(); 81 invite.components = node[0].comp; 82 invite.replies = node[0].replies; 83 invite.id = node[0].id; 84 // not sure why components are null .. but. 85 if (invite.components == null) { 86 invite.components = [{}]; 87 invite.components.empty = true; 88 } 89 var inv = node[0]; 90 if (inv.tz) { 91 for (var i = 0; i < inv.tz.length; i++) { 92 // get known rule 93 var tz = inv.tz[i]; 94 var rule = AjxTimezone.getRule(tz.id); 95 96 // get known rule that exactly matches tz definition 97 if (!rule) { 98 var tzrule = { 99 standard: tz.standard ? AjxUtil.createProxy(tz.standard[0]) : {}, 100 daylight: tz.daylight ? AjxUtil.createProxy(tz.daylight[0]) : null 101 }; 102 tzrule.standard.offset = tz.stdoff; 103 delete tzrule.standard._object_; 104 if (tz.daylight) { 105 tzrule.daylight.offset = tz.dayoff; 106 delete tzrule.daylight._object_; 107 } 108 109 rule = AjxTimezone.getRule(tz.id, tzrule); 110 if (rule) { 111 var alias = AjxUtil.createProxy(rule); 112 alias.aliasId = rule.clientId; 113 alias.clientId = tz.id; 114 alias.serverId = tz.id; 115 AjxTimezone.addRule(alias); 116 } 117 } 118 119 // add custom rule to known list 120 if (!rule) { 121 rule = { clientId: tz.id, serverId: tz.id, autoDetected: true }; 122 if (tz.daylight) { 123 rule.standard = AjxUtil.createProxy(tz.standard[0]); 124 rule.standard.offset = tz.stdoff; 125 rule.standard.trans = AjxTimezone.createTransitionDate(rule.standard); 126 127 rule.daylight = AjxUtil.createProxy(tz.daylight[0]); 128 rule.daylight.offset = tz.dayoff; 129 rule.daylight.trans = AjxTimezone.createTransitionDate(rule.daylight); 130 } 131 else { 132 rule.standard = { offset: tz.stdoff }; 133 } 134 AjxTimezone.addRule(rule); 135 } 136 } 137 } 138 invite.type = inv && inv.type ? inv.type : "appt"; 139 return invite; 140 }; 141 142 /** 143 * Sets the message id. 144 * 145 * @param {String} id the message id 146 */ 147 ZmInvite.prototype.setMessageId = 148 function (id) { 149 this.msgId = id; 150 }; 151 152 /** 153 * Gets the message id. 154 * 155 * @return {String} the message id 156 */ 157 ZmInvite.prototype.getMessageId = 158 function() { 159 return this.msgId; 160 }; 161 162 /** 163 * Gets the component. 164 * 165 * @param {String} id the component id 166 * @return {Object} the component 167 */ 168 ZmInvite.prototype.getComponent = 169 function(id) { 170 return this.components[id]; 171 }; 172 173 /** 174 * Gets the components. 175 * 176 * @return {Array} an array of components 177 */ 178 ZmInvite.prototype.getComponents = 179 function () { 180 return this.components; 181 }; 182 183 /** 184 * Checks if the invite has multiple components. 185 * 186 * @return {Boolean} <code>true</code> if the invite has one or more components 187 */ 188 ZmInvite.prototype.hasMultipleComponents = 189 function() { 190 return (this.components.length > 1); 191 }; 192 193 /** 194 * Checks if the invite has other attendees. 195 * 196 * @param {int} compNum the component number 197 * @return {Boolean} <code>true</code> if the invite has more than one other attendee 198 */ 199 ZmInvite.prototype.hasOtherAttendees = 200 function(compNum) { 201 var cn = compNum || 0; 202 return this.components[cn].at && this.components[cn].at.length > 0; 203 }; 204 205 /** 206 * Checks if the invite has other individual (non-location & resource) attendees. 207 * 208 * @param {int} compNum the component number 209 * @return {Boolean} <code>true</code> if the invite has more than one other individual attendee 210 */ 211 ZmInvite.prototype.hasOtherIndividualAttendees = 212 function(compNum) { 213 var cn = compNum || 0; 214 var att = this.components[cn].at; 215 var otherFound = false; 216 217 if (att && att.length) { 218 for (var i = 0; i < att.length; i++) { 219 if (!att[i].cutype || (att[i].cutype == ZmCalendarApp.CUTYPE_INDIVIDUAL)) { 220 otherFound = true; 221 break; 222 } 223 } 224 } 225 return otherFound; 226 }; 227 228 /** 229 * Gets the event name. 230 * 231 * @param {int} compNum the component number 232 * @return {String} the name or <code>null</code> for none 233 */ 234 ZmInvite.prototype.getEventName = 235 function(compNum) { 236 var cn = compNum || 0; 237 return this.components[cn] ? this.components[cn].name : null; 238 }; 239 240 /** 241 * Gets the alarm. 242 * 243 * @param {int} compNum the component number 244 * @return {String} the alarm or <code>null</code> for none 245 */ 246 ZmInvite.prototype.getAlarm = 247 function(compNum) { 248 var cn = compNum || 0; 249 return this.components[cn] ? this.components[cn].alarm : null; 250 }; 251 252 /** 253 * Gets the invite method. 254 * 255 * @param {int} compNum the component number 256 * @return {String} the method or <code>null</code> for none 257 */ 258 ZmInvite.prototype.getInviteMethod = 259 function(compNum) { 260 var cn = compNum || 0; 261 return this.components[cn] ? this.components[cn].method : null; 262 }; 263 264 /** 265 * Gets the sequence no 266 * 267 * @param {int} compNum the component number 268 * @return {String} the sequence no 269 */ 270 ZmInvite.prototype.getSequenceNo = 271 function(compNum) { 272 var cn = compNum || 0; 273 return this.components[cn] ? this.components[cn].seq : null; 274 }; 275 276 /** 277 * Gets the organizer email. 278 * 279 * @param {int} compNum the component number 280 * @return {String} the organizer email or <code>null</code> for none 281 */ 282 ZmInvite.prototype.getOrganizerEmail = 283 function(compNum) { 284 var cn = compNum || 0; 285 return (this.components[cn] && this.components[cn].or && this.components[cn].or.url) 286 ? (this.components[cn].or.url.replace("MAILTO:", "")) : null; 287 }; 288 289 /** 290 * Gets the organizer name. 291 * 292 * @param {int} compNum the component number 293 * @return {String} the organizer name or <code>null</code> for none 294 */ 295 ZmInvite.prototype.getOrganizerName = 296 function(compNum) { 297 var cn = compNum || 0; 298 return (this.components[cn] && this.components[cn].or) 299 ? (this.components[cn].or.d || this.components[cn].or.url) : null; 300 }; 301 302 /** 303 * Gets the sent by. 304 * 305 * @param {int} compNum the component number 306 * @return {String} the sent by or <code>null</code> for none 307 */ 308 ZmInvite.prototype.getSentBy = 309 function(compNum) { 310 var cn = compNum || 0; 311 return (this.components[cn] && this.components[cn].or) 312 ? this.components[cn].or.sentBy : null; 313 }; 314 315 /** 316 * Checks if is organizer. 317 * 318 * @param {int} compNum the component number 319 * @return {Boolean} <code>true</code> if is organizer 320 */ 321 ZmInvite.prototype.isOrganizer = 322 function(compNum) { 323 var cn = compNum || 0; 324 return this.components[cn] ? (!!this.components[cn].isOrg) : false; 325 }; 326 327 /** 328 * Gets the RSVP. 329 * 330 * @param {int} compNum the component number 331 * @return {String} the RSVP or <code>null</code> for none 332 */ 333 ZmInvite.prototype.shouldRsvp = 334 function(compNum){ 335 var cn = compNum || 0; 336 return this.components[cn] ? this.components[cn].rsvp : null; 337 }; 338 339 /** 340 * Gets the recurrence. 341 * 342 * @param {int} compNum the component number 343 * @return {ZmRecurrence} the recurrence 344 */ 345 ZmInvite.prototype.getRecurrenceRules = 346 function(compNum) { 347 var cn = compNum || 0; 348 return this.components[cn].recur; 349 }; 350 351 /** 352 * Gets the attendees. 353 * 354 * @param {int} compNum the component number 355 * @return {Array} an array of attendees or an empty array for none 356 */ 357 ZmInvite.prototype.getAttendees = 358 function(compNum) { 359 var cn = compNum || 0; 360 var att = this.components[cn].at; 361 var list = []; 362 363 if (!(att && att.length)) { return list; } 364 365 for (var i = 0; i < att.length; i++) { 366 if (!att[i].cutype || (att[i].cutype == ZmCalendarApp.CUTYPE_INDIVIDUAL)) { 367 list.push(att[i]); 368 } 369 } 370 return list; 371 }; 372 373 /** 374 * Gets the replies. 375 * 376 * @param {int} compNum the component number 377 * @return {String} the reply 378 */ 379 ZmInvite.prototype.getReplies = 380 function(compNum) { 381 var cn = compNum || 0; 382 return (this.replies && this.replies[cn]) ? this.replies[cn].reply : null; 383 }; 384 385 /** 386 * Gets the resources. 387 * 388 * @param {int} compNum the component number 389 * @return {Array} an array of resources 390 */ 391 ZmInvite.prototype.getResources = 392 function(compNum) { 393 var cn = compNum || 0; 394 var att = this.components[cn].at; 395 var list = []; 396 397 if (!(att && att.length)) { return list; } 398 399 for (var i = 0; i < att.length; i++) { 400 if (att[i].cutype == ZmCalendarApp.CUTYPE_RESOURCE || 401 att[i].cutype == ZmCalendarApp.CUTYPE_ROOM ) { 402 list.push(att[i]); 403 } 404 } 405 return list; 406 }; 407 408 /** 409 * Gets the except id. 410 * 411 * @param {int} compNum the component number 412 * @return {String} the except id 413 */ 414 ZmInvite.prototype.getExceptId = 415 function(compNum) { 416 var cn = compNum || 0; 417 return (this.components[cn] && this.components[cn].exceptId) 418 ? this.components[cn].exceptId[0] : null; 419 }; 420 421 /** 422 * Gets the appointment id. 423 * 424 * @param {int} compNum the component number 425 * @return {String} the id 426 */ 427 ZmInvite.prototype.getAppointmentId = 428 function(compNum) { 429 var cn = compNum || 0; 430 return this.components[cn].apptId; 431 }; 432 433 /** 434 * Gets the status. 435 * 436 * @param {int} compNum the component number 437 * @return {String} the status 438 */ 439 ZmInvite.prototype.getStatus = 440 function(compNum) { 441 var cn = compNum || 0; 442 return this.components[cn].status; 443 }; 444 445 /** 446 * Gets the transparency. 447 * 448 * @param {int} compNum the component number 449 * @return {String} the transparent value 450 */ 451 ZmInvite.prototype.getTransparency = 452 function(compNum) { 453 var cn = compNum || 0; 454 return this.components[cn].transp; 455 }; 456 457 /** 458 * Checks if the invite is empty. 459 * 460 * @return {Boolean} <code>true</code> if the invite is empty 461 */ 462 ZmInvite.prototype.isEmpty = 463 function() { 464 return Boolean(this.components.empty); 465 }; 466 467 /** 468 * Checks if the invite is an exception. 469 * 470 * @param {int} compNum the component number 471 * @return {Boolean} <code>true</code> if exception 472 */ 473 ZmInvite.prototype.isException = 474 function(compNum) { 475 var cn = compNum || 0; 476 return this.components[cn] ? this.components[cn].ex : false; 477 }; 478 479 /** 480 * Checks if the invite is recurring. 481 * 482 * @param {int} compNum the component number 483 * @return {Boolean} <code>true</code> if recurring 484 * @see #getRecurrenceRules 485 */ 486 ZmInvite.prototype.isRecurring = 487 function(compNum) { 488 var cn = compNum || 0; 489 return this.components[cn] ? this.components[cn].recur : false; 490 }; 491 492 /** 493 * Checks if the invite is an all day event. 494 * 495 * @param {int} compNum the component number 496 * @return {Boolean} <code>true</code> if an all day event 497 */ 498 ZmInvite.prototype.isAllDayEvent = 499 function(compNum) { 500 var cn = compNum || 0; 501 return this.components[cn] ? this.components[cn].allDay == "1" : false; 502 }; 503 504 /** 505 * Checks if the invite is multi-day. 506 * 507 * @param {int} compNum the component number 508 * @return {Boolean} <code>true</code> if the invite is multi-day 509 */ 510 ZmInvite.prototype.isMultiDay = 511 function(compNum) { 512 var cn = compNum || 0; 513 var sd = this.getServerStartDate(cn); 514 var ed = this.getServerEndDate(cn); 515 516 if(!sd) return false; 517 518 return (sd.getDate() != ed.getDate()) || (sd.getMonth() != ed.getMonth()) || (sd.getFullYear() != ed.getFullYear()); 519 }; 520 521 /** 522 * Gets the description html. 523 * 524 * @param {int} compNum the component number 525 * @return {String} the description html or <code>null</code> for none 526 */ 527 ZmInvite.prototype.getComponentDescriptionHtml = 528 function(compNum) { 529 var cn = compNum || 0; 530 var comp = this.components[cn]; 531 if (comp == null) { return; } 532 533 var desc = comp.descHtml; 534 var content = desc && desc[0]._content || null; 535 if (!content) { 536 var txtContent = comp.desc; 537 txtContent = (txtContent && txtContent[0]._content) || null; 538 content = txtContent ? AjxStringUtil.convertToHtml(txtContent) : null; 539 if (!content) { 540 var msg = appCtxt.getById(this.getMessageId()); 541 if (msg && msg.hasContentType) { 542 content = msg.hasContentType(ZmMimeTable.TEXT_HTML) ? msg.getBodyContent(ZmMimeTable.TEXT_HTML) : null; 543 if (!content) { 544 txtContent = msg.getTextBodyPart(); 545 content = txtContent ? AjxStringUtil.convertToHtml(txtContent) : null; 546 } 547 } 548 } 549 if (!content) { 550 content = this.getApptSummary(true); 551 } 552 } 553 if (!content) { 554 var comment = this.getComponentComment(); 555 content = comment && AjxStringUtil.convertToHtml(comment); 556 } 557 return content; 558 }; 559 560 /** 561 * Gets the description. 562 * 563 * @param {int} compNum the component number 564 * @return {String} the description or <code>null</code> for none 565 */ 566 ZmInvite.prototype.getComponentDescription = 567 function(compNum) { 568 var cn = compNum || 0; 569 var comp = this.components[cn]; 570 if (comp == null) { return; } 571 572 var desc = comp.desc; 573 var content = desc && desc[0]._content || null; 574 if (!content) { 575 content = this.getComponentComment(); 576 } 577 if (!content) { 578 var htmlContent = comp.descHtml; 579 htmlContent = (htmlContent && htmlContent[0]._content) || null; 580 if (!htmlContent && this.type != ZmInvite.TASK) { 581 content = this.getApptSummary(); 582 } 583 } 584 return content; 585 }; 586 587 /** 588 * Gets the comment. 589 * 590 * @param {int} compNum the component number 591 * @return {String} the comment or <code>null</code> for none 592 */ 593 ZmInvite.prototype.getComponentComment = 594 function(compNum) { 595 var cn = compNum || 0; 596 var comp = this.components[cn]; 597 if (comp == null) { return; } 598 599 var comment = comp.comment; 600 return comment && comment[0]._content || null; 601 }; 602 603 /** 604 * Gets the server end time. 605 * 606 * @param {int} compNum the component number 607 * @return {String} the end time 608 */ 609 ZmInvite.prototype.getServerEndTime = 610 function(compNum) { 611 var cn = compNum || 0; 612 if (this.components[cn] == null) { return; } 613 614 if (this._serverEndTime == null) { 615 if (this.components[cn].e != null ) { 616 this._serverEndTime = this.components[cn].e[0].d; 617 } else if (this.components[cn].s) { 618 // get the duration 619 var dur = this.components[cn].dur; 620 var dd = dur && dur[0].d || 0; 621 var weeks = dur && dur[0].w || 0; 622 var hh = dur && dur[0].h || 0; 623 var mm = dur && dur[0].m || 0; 624 var ss = dur && dur[0].s || 0; 625 var t = parseInt(ss) + (parseInt(mm) * 60) + (parseInt(hh) * 3600) + (parseInt(dd) * 24 * 3600) + (parseInt(weeks) * 7 * 24 * 3600); 626 // parse the start date 627 var start = this.components[cn].s[0].d; 628 var yyyy = parseInt(start.substr(0,4), 10); 629 var MM = parseInt(start.substr(4,2), 10); 630 var dd = parseInt(start.substr(6,2), 10); 631 var d = new Date(yyyy, MM -1, dd); 632 if (start.charAt(8) == 'T') { 633 hh = parseInt(start.substr(9,2), 10); 634 mm = parseInt(start.substr(11,2), 10); 635 ss = parseInt(start.substr(13,2), 10); 636 d.setHours(hh, mm, ss, 0); 637 } 638 // calculate the end date -- start + offset; 639 var endDate = new Date(d.getTime() + (t * 1000)); 640 641 // put the end date into server DURATION format. 642 MM = AjxDateUtil._pad(d.getMonth() + 1); 643 dd = AjxDateUtil._pad(d.getDate()); 644 hh = AjxDateUtil._pad(d.getHours()); 645 mm = AjxDateUtil._pad(d.getMinutes()); 646 ss = AjxDateUtil._pad(d.getSeconds()); 647 yyyy = d.getFullYear(); 648 this._serverEndTime = [yyyy,MM,dd,"T",hh,mm,ss].join(""); 649 } 650 } 651 return this._serverEndTime; 652 }; 653 654 /** 655 * Gets the server end date. 656 * 657 * @param {int} compNum the component number 658 * @return {Date} the end date 659 */ 660 ZmInvite.prototype.getServerEndDate = 661 function(compNum, noSpecialUtcCase) { 662 var cn = compNum || 0; 663 return AjxDateUtil.parseServerDateTime(this.getServerEndTime(cn), noSpecialUtcCase); 664 }; 665 666 /** 667 * Gets the server start time. 668 * 669 * @param {int} compNum the component number 670 * @return {Date} the start time 671 */ 672 ZmInvite.prototype.getServerStartTime = 673 function(compNum) { 674 var cn = compNum || 0; 675 return this.components[cn] && this.components[cn].s 676 ? this.components[cn].s[0].d : null; 677 }; 678 679 /** 680 * Gets the server start date. 681 * 682 * @param {int} compNum the component number 683 * @return {Date} the start date 684 */ 685 ZmInvite.prototype.getServerStartDate = 686 function(compNum, noSpecialUtcCase) { 687 var cn = compNum || 0; 688 return AjxDateUtil.parseServerDateTime(this.getServerStartTime(cn), noSpecialUtcCase); 689 }; 690 691 /** 692 * Gets start date from exception ID. 693 * 694 * @param {int} compNum the component number 695 */ 696 697 ZmInvite.prototype.getStartDateFromExceptId = 698 function(compNum) { 699 var cn = compNum || 0; 700 return AjxDateUtil.parseServerDateTime(this.components[cn] && this.components[cn].exceptId 701 ? this.components[cn].exceptId[0].d : null); 702 }; 703 704 /** 705 * Gets the server start time timezone. 706 * 707 * @param {int} compNum the component number 708 * @return {String} the timezone 709 */ 710 ZmInvite.prototype.getServerStartTimeTz = 711 function(compNum) { 712 var cn = compNum || 0; 713 if (this.components[cn] == null) { return; } 714 715 if (this._serverStartTimeZone == null) { 716 var startTime = this.getServerStartTime(); 717 this._serverStartTimeZone = startTime && startTime.charAt(startTime.length -1) == 'Z' 718 ? AjxTimezone.GMT_NO_DST 719 : (this.components[cn].s ? this.components[cn].s[0].tz : null); 720 } 721 return this._serverStartTimeZone; 722 }; 723 724 /** 725 * Gets the server end time timezone. 726 * 727 * @param {int} compNum the component number 728 * @return {String} the timezone 729 */ 730 ZmInvite.prototype.getServerEndTimeTz = 731 function(compNum) { 732 var cn = compNum || 0; 733 var endComp = this.components[cn] && this.components[cn].e; 734 if (!endComp) { return null; } 735 736 if (!this._serverEndTimeZone) { 737 var endTime = this.getServerEndTime(); 738 this._serverEndTimeZone = (endTime && endTime.charAt(endTime.length -1) == 'Z') 739 ? AjxTimezone.GMT_NO_DST : endComp[0].tz; 740 } 741 return this._serverEndTimeZone; 742 }; 743 744 /** 745 * Gets the duration text. 746 * 747 * @param {int} compNum the component number 748 * @param {Boolean} emptyAllDay <code>true</code> to return an empty string "" if all day event. 749 * @param {Boolean} startOnly <code>true</code> to include start only 750 * @param {Boolean} isText <code>true</code> to return as text, not html 751 * @param {Date} startDate Optional. Start date to use instead of the original start date 752 * @param {Date} endDate Optional. End date to use instead of the original end date 753 * 754 * @return {String} the duration 755 */ 756 ZmInvite.prototype.getDurationText = 757 function(compNum, emptyAllDay, startOnly, isText, startDate, endDate) { 758 var component = this.components[compNum]; 759 var sd = startDate || this.getServerStartDate(compNum); 760 var ed = endDate || this.getServerEndDate(compNum); 761 if (!sd && !ed) { return ""; } 762 763 // all day 764 if (this.isAllDayEvent(compNum)) { 765 if (emptyAllDay) { return ""; } 766 767 if (this.isMultiDay(compNum)) { 768 var dateFormatter = AjxDateFormat.getDateInstance(); 769 var startDay = dateFormatter.format(sd); 770 var endDay = dateFormatter.format(ed); 771 772 if (!ZmInvite._daysFormatter) { 773 ZmInvite._daysFormatter = new AjxMessageFormat(ZmMsg.durationDays); 774 } 775 return ZmInvite._daysFormatter.format([startDay, endDay]); 776 } 777 return sd ? AjxDateFormat.getDateInstance(AjxDateFormat.FULL).format(sd) : ""; 778 } 779 780 var dateFormatter = AjxDateFormat.getDateInstance(AjxDateFormat.FULL); 781 var timeFormatter = AjxDateFormat.getTimeInstance(AjxDateFormat.SHORT); 782 783 var a = sd ? [dateFormatter.format(sd), isText ? " " : "<br>"] : []; 784 if (startOnly) { 785 a.push(sd ? timeFormatter.format(sd) : ""); 786 } 787 else { 788 var startHour = sd ? timeFormatter.format(sd) : ""; 789 var endHour = timeFormatter.format(ed); 790 791 if (!ZmInvite._hoursFormatter) { 792 ZmInvite._hoursFormatter = new AjxMessageFormat(ZmMsg.durationHours); 793 } 794 a.push(ZmInvite._hoursFormatter.format([startHour, endHour])); 795 } 796 return a.join(""); 797 }; 798 799 /** 800 * Gets the name. 801 * 802 * @param {int} compNum the component number 803 * @return {String} the name 804 */ 805 ZmInvite.prototype.getName = 806 function(compNum) { 807 var cn = compNum || 0; 808 return this.components[cn] ? this.components[cn].name : null; 809 }; 810 811 /** 812 * Gets the free busy. 813 * 814 * @param {int} compNum the component number 815 * @return {String} the free busy 816 */ 817 ZmInvite.prototype.getFreeBusy = 818 function(compNum) { 819 var cn = compNum || 0; 820 return this.components[cn] ? this.components[cn].fb : null; 821 }; 822 823 /** 824 * Gets the privacy. 825 * 826 * @param {int} compNum the component number 827 * @return {String} the privacy 828 */ 829 ZmInvite.prototype.getPrivacy = 830 function(compNum) { 831 var cn = compNum || 0; 832 return this.components[cn] ? this.components[cn]["class"] : null; 833 }; 834 835 /** 836 * Gets the x-prop. 837 * 838 * @param {int} compNum the component number 839 * @return {String} the x-prop 840 */ 841 ZmInvite.prototype.getXProp = 842 function(compNum) { 843 var cn = compNum || 0; 844 return this.components[cn] ? this.components[cn]["xprop"] : null; 845 }; 846 847 /** 848 * Gets the location. 849 * 850 * @param {int} compNum the component number 851 * @return {String} the location 852 */ 853 ZmInvite.prototype.getLocation = 854 function(compNum) { 855 var cn = compNum || 0; 856 return this.components[cn] ? this.components[cn].loc : null; 857 }; 858 859 /** 860 * Gets the recurrence id (ridZ) - applicable to recurring appointment . 861 * 862 * @param {int} compNum the component number 863 * @return {String} the recurrence id, null for non-recurring appointment 864 */ 865 ZmInvite.prototype.getRecurrenceId = 866 function(compNum) { 867 var cn = compNum || 0; 868 return this.components[cn] ? this.components[cn].ridZ : null; 869 }; 870 871 /** 872 * Gets the tool tip in HTML for this invite. 873 * 874 * <p> 875 * <strong>Note:</strong> This method assumes that there are currently one and only one component object on the invite. 876 * </p> 877 * 878 * @return {String} the tool tip 879 */ 880 ZmInvite.prototype.getToolTip = 881 function() { 882 if (this._toolTip) 883 return this._toolTip; 884 885 var compNum = 0; 886 887 var html = []; 888 var idx = 0; 889 890 html[idx++] = "<table cellpadding=0 cellspacing=0 border=0 >"; 891 html[idx++] = "<tr valign='center'><td colspan=2 align='left'>"; 892 html[idx++] = "<div style='border-bottom: 1px solid black;'>"; 893 html[idx++] = "<table cellpadding=0 cellspacing=0 border=0 width=100%>"; 894 html[idx++] = "<tr valign='center'><td><b>"; 895 896 // IMGHACK - added outer table for new image changes... 897 html[idx++] = "<div style='white-space:nowrap'><table border=0 cellpadding=0 cellspacing=0 style='display:inline'><tr>"; 898 if (this.hasOtherAttendees(compNum)) { 899 html[idx++] = "<td>"; 900 html[idx++] = AjxImg.getImageHtml("ApptMeeting"); 901 html[idx++] = "</td>"; 902 } 903 904 if (this.isException(compNum)) { 905 html[idx++] = "<td>"; 906 html[idx++] = AjxImg.getImageHtml("ApptException"); 907 html[idx++] = "</td>"; 908 } 909 else if (this.isRecurring(compNum)) { 910 html[idx++] = "<td>"; 911 html[idx++] = AjxImg.getImageHtml("ApptRecur"); 912 html[idx++] = "</td>"; 913 } 914 915 html[idx++] = "</tr></table> "; 916 html[idx++] = AjxStringUtil.htmlEncode(this.getName(compNum)); 917 html[idx++] = " </div></b></td><td align='right'>"; 918 html[idx++] = AjxImg.getImageHtml("Appointment"); 919 html[idx++] = "</td></table></div></td></tr>"; 920 921 var when = this.getDurationText(compNum, false, false); 922 idx = this._addEntryRow(ZmMsg.when, when, html, idx, false, null, true); 923 if (this.isRecurring(compNum)) { 924 if (!this._recurBlurb) { 925 AjxDispatcher.require(["MailCore", "CalendarCore"]); 926 var recur = new ZmRecurrence(); 927 recur.parse(this.getRecurrenceRules(compNum)); 928 this._recurBlurb = recur.getBlurb(); 929 } 930 idx = this._addEntryRow(ZmMsg.repeats, this._recurBlurb, html, idx, true, null, true); 931 } 932 idx = this._addEntryRow(ZmMsg.location, this.getLocation(compNum), html, idx, false); 933 934 html[idx++] = "</table>"; 935 this._toolTip = html.join(""); 936 937 return this._toolTip; 938 }; 939 940 /** 941 * Gets the Appt summary. 942 * 943 * @param {Boolean} isHtml <code>true</code> to return summary as HTML 944 * @return {String} the appt summary 945 */ 946 ZmInvite.prototype.getApptSummary = 947 function(isHtml) { 948 var msg = appCtxt.getById(this.getMessageId()); 949 var appt; 950 951 if (msg) { 952 AjxDispatcher.require(["MailCore", "CalendarCore"]); 953 appt = new ZmAppt(); 954 appt.setFromMessage(msg); 955 } 956 957 return appt ? appt.getSummary(isHtml) : this.getSummary(isHtml); 958 }; 959 960 /** 961 * Gets the summary. 962 * 963 * @param {Boolean} isHtml <code>true</code> to return summary as HTML 964 * @return {String} the summary 965 */ 966 ZmInvite.prototype.getSummary = 967 function(isHtml) { 968 if (this.isRecurring()) { 969 if (!this._recurBlurb) { 970 AjxDispatcher.require(["MailCore", "CalendarCore"]); 971 var recur = new ZmRecurrence(); 972 recur.setRecurrenceRules(this.getRecurrenceRules(), this.getServerStartDate()); 973 this._recurBlurb = recur.getBlurb(); 974 } 975 } 976 977 var buf = []; 978 var i = 0; 979 980 if (!this._summaryHtmlLineFormatter) { 981 this._summaryHtmlLineFormatter = new AjxMessageFormat("<tr><th align='left'>{0}</th><td>{1} {2}</td></tr>"); 982 this._summaryTextLineFormatter = new AjxMessageFormat("{0} {1} {2}"); 983 } 984 var formatter = isHtml ? this._summaryHtmlLineFormatter : this._summaryTextLineFormatter; 985 986 var params = []; 987 988 if (isHtml) { 989 buf[i++] = "<p>\n<table border='0'>\n"; 990 } 991 992 var orgName = this.getOrganizerName(); 993 if (orgName) { 994 params = [ZmMsg.organizerLabel, orgName, ""]; 995 buf[i++] = formatter.format(params); 996 buf[i++] = "\n"; 997 } 998 999 var whenSummary = this.getDurationText(0, false, false, true); 1000 if (whenSummary) { 1001 params = [ZmMsg.whenLabel, whenSummary, ""]; 1002 buf[i++] = formatter.format(params); 1003 buf[i++] = "\n"; 1004 } 1005 1006 var locationSummary = this.getLocation(); 1007 if (locationSummary) { 1008 params = [ZmMsg.locationLabel, locationSummary, ""]; 1009 buf[i++] = formatter.format(params); 1010 buf[i++] = "\n"; 1011 } 1012 1013 if (this._recurBlurb) { 1014 params = [ZmMsg.repeatLabel, this._recurBlurb, ""]; 1015 buf[i++] = formatter.format(params); 1016 buf[i++] = "\n"; 1017 } 1018 1019 if (isHtml) { 1020 buf[i++] = "</table>\n"; 1021 } 1022 buf[i++] = isHtml ? "<div>" : "\n\n"; 1023 buf[i++] = ZmItem.NOTES_SEPARATOR; 1024 // bug fix #7835 - add <br> after DIV otherwise Outlook lops off 1st char 1025 buf[i++] = isHtml ? "</div><br>" : "\n\n"; 1026 1027 return buf.join(""); 1028 }; 1029 1030 /** 1031 * Adds a row to the tool tip. 1032 * 1033 * @private 1034 */ 1035 ZmInvite.prototype._addEntryRow = 1036 function(field, data, html, idx, wrap, width, asIs) { 1037 if (data != null && data != "") { 1038 html[idx++] = "<tr valign='top'><td align='right' style='padding-right: 5px;'><b><div style='white-space:nowrap'>"; 1039 html[idx++] = AjxMessageFormat.format(ZmMsg.makeLabel, AjxStringUtil.htmlEncode(field)); 1040 html[idx++] = "</div></b></td><td align='left'><div style='white-space:"; 1041 html[idx++] = wrap ? "wrap;" : "nowrap;"; 1042 if (width) { 1043 html[idx++] = "width:"; 1044 html[idx++] = width; 1045 html[idx++] = "px;"; 1046 } 1047 html[idx++] = "'>"; 1048 html[idx++] = asIs ? data : AjxStringUtil.htmlEncode(data); 1049 html[idx++] = "</div></td></tr>"; 1050 } 1051 return idx; 1052 }; 1053 1054 /** 1055 * Checks the invite has acceptable components. 1056 * 1057 * @return {Boolean} <code>true</code> if the invite has acceptable components 1058 */ 1059 ZmInvite.prototype.hasAcceptableComponents = 1060 function() { 1061 for (var i in this.components) { 1062 if (this.getStatus(i) != ZmCalendarApp.STATUS_CANC) { 1063 return true; 1064 } 1065 } 1066 1067 return false; 1068 }; 1069 1070 /** 1071 * Checks the invite has a reply method. 1072 * 1073 * @param {int} compNum the component number 1074 * @return {Boolean} <code>true</code> if the invite has a method that REQUIRES a reply (ironically NOT REPLY method but rather REQUEST or PUBLISH) 1075 */ 1076 ZmInvite.prototype.hasInviteReplyMethod = 1077 function(compNum) { 1078 var methodName = this.getInviteMethod(compNum); 1079 var publishOrRequest = (methodName == ZmCalendarApp.METHOD_REQUEST || 1080 methodName == ZmCalendarApp.METHOD_PUBLISH); 1081 return ((methodName == null) || publishOrRequest); 1082 }; 1083 1084 /** 1085 * Checks the invite has a counter method. 1086 * 1087 * @param {int} compNum the component number 1088 * @return {Boolean} <code>true</code> if the invite has a counter method 1089 */ 1090 ZmInvite.prototype.hasCounterMethod = 1091 function(compNum) { 1092 return (this.getInviteMethod(compNum) == ZmCalendarApp.METHOD_COUNTER); 1093 }; 1094 1095 /** 1096 * returns proposed time from counter invite 1097 * 1098 * @param {int} compNum the component number 1099 * @return {string} proposed time as formatted string 1100 */ 1101 ZmInvite.prototype.getProposedTimeStr = 1102 function(compNum) { 1103 var methodName = this.getInviteMethod(compNum); 1104 if (methodName == ZmCalendarApp.METHOD_COUNTER) { 1105 return this.getDurationText(compNum, false, false, true); 1106 } 1107 return ""; 1108 }; 1109 1110 ZmInvite.prototype.getChanges = 1111 function(compNum) { 1112 var cn = compNum || 0; 1113 var changesStr = this.components[cn] && this.components[cn].changes; 1114 var changesArr = changesStr && changesStr.split(","); 1115 if (changesArr && changesArr.length > 0) { 1116 var changes = {}; 1117 for (var i = 0; i < changesArr.length; i++) { 1118 changes[changesArr[i]] = true; 1119 } 1120 return changes; 1121 } 1122 1123 return null; 1124 }; 1125 1126 /** 1127 * Returns true if this invite has attendees, one of which replied back with an 1128 * "actioned" response (e.g. accept/decline/tentative) 1129 */ 1130 ZmInvite.prototype.hasAttendeeResponse = 1131 function() { 1132 var att = this.getAttendees(); 1133 return (att.length > 0 && att[0].ptst != ZmCalBaseItem.PSTATUS_NEEDS_ACTION); 1134 }; 1135 1136 /** 1137 * Checks if this invite has html description. 1138 * 1139 * @return {Boolean} <code>true</code> if this invite has HTML description 1140 */ 1141 ZmInvite.prototype.isHtmlInvite = 1142 function() { 1143 var comp = this.getComponent(0); 1144 var htmlContent = comp && comp.descHtml; 1145 return (htmlContent && htmlContent[0] && htmlContent[0]._content) ? true : false; 1146 }; 1147