diff --git a/mozilla/calendar/base/public/Makefile.in b/mozilla/calendar/base/public/Makefile.in index 7a9f68ff88b..783449e98dd 100644 --- a/mozilla/calendar/base/public/Makefile.in +++ b/mozilla/calendar/base/public/Makefile.in @@ -1,4 +1,3 @@ -# # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # @@ -21,6 +20,7 @@ # Contributor(s): # Dan Mosedale # Mike Shaver +# Matthew Willis # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or @@ -65,6 +65,8 @@ XPIDLSRCS = calIAlarmService.idl \ calIICSService.idl \ calIItemBase.idl \ calIItipItem.idl \ + calIItipProcessor.idl \ + calIItipTransport.idl \ calIRecurrenceInfo.idl \ calIRecurrenceDate.idl \ calIRecurrenceDateSet.idl \ diff --git a/mozilla/calendar/base/public/calBaseCID.h b/mozilla/calendar/base/public/calBaseCID.h index 16b31c09f70..cbbbc896288 100644 --- a/mozilla/calendar/base/public/calBaseCID.h +++ b/mozilla/calendar/base/public/calBaseCID.h @@ -97,11 +97,6 @@ #define CAL_RECURRENCEINFO_CONTRACTID \ "@mozilla.org/calendar/recurrence-info;1" -#define CAL_ITIPITEM_CID \ - { 0xb84de879, 0x4b85, 0x4d68, { 0x85, 0x50, 0xe0, 0xc5, 0x27, 0xe4, 0x6f, 0x98 } } -#define CAL_ITIPITEM_CONTRACTID \ - "@mozilla.org/calendar/itip-item;1" - #define NS_ERROR_CALENDAR_WRONG_COMPONENT_TYPE NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_CALENDAR, 1) // Until extensible xpconnect error mapping works // #define NS_ERROR_CALENDAR_IMMUTABLE NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_CALENDAR, 2) diff --git a/mozilla/calendar/base/public/calIItemBase.idl b/mozilla/calendar/base/public/calIItemBase.idl index d0d348ab754..1301641c6c2 100644 --- a/mozilla/calendar/base/public/calIItemBase.idl +++ b/mozilla/calendar/base/public/calIItemBase.idl @@ -241,6 +241,12 @@ interface calIItemBase : nsISupports // removeAttendee/addAttendee that follow the call to getAttendees. void getAttendees(out PRUint32 count, [array,size_is(count),retval] out calIAttendee attendees); + + /** + * getAttendeeById's matching is done in a case-insensitive manner to handle + * places where "MAILTO:" or similar properties are capitalized arbitrarily + * by different calendar clients. + */ calIAttendee getAttendeeById(in AUTF8String id); void removeAttendee(in calIAttendee attendee); void addAttendee(in calIAttendee attendee); diff --git a/mozilla/calendar/base/public/calIItipItem.idl b/mozilla/calendar/base/public/calIItipItem.idl index d4f99f7b129..4d3662f5004 100644 --- a/mozilla/calendar/base/public/calIItipItem.idl +++ b/mozilla/calendar/base/public/calIItipItem.idl @@ -19,7 +19,8 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Clint Talbert + * Clint Talbert + * Matthew Willis * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -46,14 +47,20 @@ interface nsISimpleEnumerator; * parser, the imip-bar UI, and the iTIP processor. It encapsulates a list of * calIItemBase objects and provides specialized iTIP methods for those items. */ -[scriptable, uuid(B84DE879-4B85-4d68-8550-E0C527E46F98)] +[scriptable, uuid(f41392ab-dcad-4bad-818f-b3d1631c4d93)] interface calIItipItem : nsISupports { - /** + /** * Initializes the item with an ics string - * @arg - in parameter - AString of ical Data + * @param - in parameter - AString of ical Data */ - void init(in AString icalData); + void init(in AUTF8String icalData); + + /** + * Creates a new calItipItem with the same attributes as the one that + * clone() is called upon. + */ + calIItipItem clone(); /** * Attribute: isSend - set to TRUE when sending this item to initiate an @@ -68,14 +75,14 @@ interface calIItipItem : nsISupports /** * Attribute: receivedMethod - method the iTIP item had upon reciept */ - attribute AString receivedMethod; + attribute AUTF8String receivedMethod; /** * Attribute: responseMethod - method that the protocol handler (or the * user) decides to use to respond to the iTIP item (could be COUNTER, * REPLY, DECLINECOUNTER, etc) */ - attribute AString responseMethod; + attribute AUTF8String responseMethod; /** * Attribute: autoResponse Set to one of the three constants below @@ -104,33 +111,47 @@ interface calIItipItem : nsISupports */ attribute calICalendar targetCalendar; + /** + * localStatus: The response that the user has made to the invitation in + * this ItipItem. + */ + attribute AUTF8String localStatus; + + /** + * Get the list of items that are encapsulated in this calIItipItem + * @returns An array of calIItemBase items that are inside this + * calIItipItem + */ + void getItemList(out unsigned long itemCount, + [retval, array, size_is(itemCount)] out calIItemBase items); + /** * Get the first item from the iTIP message * Bug XXX 351761: Need to find a way to make this use an nsISimpleEnumerator * @return calIItemBase - */ + calIItemBase getFirstItem(); - +*/ /** * Get next item from the iTIP message. If there is no next item then it * returns NULL * @return calIItemBase - */ + calIItemBase getNextItem(); - +*/ /** * Modifies a calIItemBase that is in the component list. Internally, the * interface will update the proper component. It does this via the * UID of the component by calling hasSameIds(). - * @arg in parameter - item to modify + * @param in parameter - item to modify * @return returns the new calIItemBase object for convienence */ calIItemBase modifyItem(in calIItemBase item); /** * Modifies the state of the given attendee in the item's ics - * @arg in parameter - AString containing attendee address - * @arg in parameter - AString contianing the new attendee status + * @param in parameter - AString containing attendee address + * @param in parameter - AString contianing the new attendee status */ void setAttendeeStatus(in AString attendeeId, in AString status); }; diff --git a/mozilla/calendar/base/src/Makefile.in b/mozilla/calendar/base/src/Makefile.in index babb5086e29..be293418b78 100644 --- a/mozilla/calendar/base/src/Makefile.in +++ b/mozilla/calendar/base/src/Makefile.in @@ -83,10 +83,11 @@ EXTRA_SCRIPTS = \ calCalendarManager.js \ calDateTimeFormatter.js \ calEvent.js \ - calItemBase.js \ - calItipItem.js \ calIcsParser.js \ calIcsSerializer.js \ + calItemBase.js \ + calItipItem.js \ + calItipProcessor.js \ calProtocolHandler.js \ calRecurrenceInfo.js \ calTodo.js \ diff --git a/mozilla/calendar/base/src/calEvent.js b/mozilla/calendar/base/src/calEvent.js index bd434545d6d..4783681faef 100644 --- a/mozilla/calendar/base/src/calEvent.js +++ b/mozilla/calendar/base/src/calEvent.js @@ -162,6 +162,9 @@ calEvent.prototype = { var calcomp = icssvc.createIcalComponent("VCALENDAR"); calcomp.prodid = "-//Mozilla Calendar//NONSGML Sunbird//EN"; calcomp.version = "2.0"; + if (this.hasProperty("METHOD")) { + calcomp.method = this.getProperty("METHOD"); + } calcomp.addSubcomponent(this.icalComponent); return calcomp.serializeToICS(); }, diff --git a/mozilla/calendar/base/src/calItemBase.js b/mozilla/calendar/base/src/calItemBase.js index 198bde850ab..f8d6d34422c 100644 --- a/mozilla/calendar/base/src/calItemBase.js +++ b/mozilla/calendar/base/src/calItemBase.js @@ -23,6 +23,7 @@ * Vladimir Vukicevic * Mike Shaver * Joey Minta + * Matthew Willis * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -408,9 +409,14 @@ calItemBase.prototype = { getAttendeeById: function (id) { var attendees = this.getAttendees({}); - for (var i = 0; i < attendees.length; i++) - if (attendees[i].id == id) - return attendees[i]; + var lowerCaseId = id.toLowerCase(); + for each (var attendee in attendees) { + // This match must be case insensitive to deal with differing + // cases of things like MAILTO: + if (attendee.id.toLowerCase() == lowerCaseId) { + return attendee; + } + } return null; }, @@ -418,8 +424,10 @@ calItemBase.prototype = { this.modify(); var found = false, newAttendees = []; var attendees = this.getAttendees({}); + var attIdLowerCase =attendee.id.toLowerCase(); + for (var i = 0; i < attendees.length; i++) { - if (attendees[i].id != attendee.id) + if (attendees[i].id.toLowerCase() != attIdLowerCase) newAttendees.push(attendees[i]); else found = true; diff --git a/mozilla/calendar/base/src/calItemModule.js b/mozilla/calendar/base/src/calItemModule.js index 52703029feb..216a0db1f38 100644 --- a/mozilla/calendar/base/src/calItemModule.js +++ b/mozilla/calendar/base/src/calItemModule.js @@ -22,6 +22,7 @@ * Contributor(s): * Vladimir Vukicevic * Mike Shaver + * Matthew Willis * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -139,11 +140,16 @@ const componentData = script: "calWeekTitleService.js", constructor: "calWeekTitleService"}, - {cid: Components.ID("{b84de879-4b85-4d68-8550-e0C527e46f98}"), + {cid: Components.ID("{f41392ab-dcad-4bad-818f-b3d1631c4d93}"), contractid: "@mozilla.org/calendar/itip-item;1", script: "calItipItem.js", constructor: "calItipItem"}, + {cid: Components.ID("{9787876b-0780-4464-8282-b7f86fb221e8}"), + contractid: "@mozilla.org/calendar/itip-processor;1", + script: "calItipProcessor.js", + constructor: "calItipProcessor"}, + {cid: Components.ID("{1e2fc0e2-bf5f-4d60-9f1e-5e92cf517c0b}"), contractid: "@mozilla.org/network/protocol;1?name=webcal", script: "calProtocolHandler.js", diff --git a/mozilla/calendar/base/src/calItipItem.js b/mozilla/calendar/base/src/calItipItem.js index f051736595e..18ff87981ec 100644 --- a/mozilla/calendar/base/src/calItipItem.js +++ b/mozilla/calendar/base/src/calItipItem.js @@ -19,7 +19,8 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Clint Talbert + * Clint Talbert + * Matthew Willis * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -44,37 +45,29 @@ function calItipItem() { this.mCurrentItemIndex = 0; } -var calItipItemClassInfo = { - getInterfaces: function (count) { +calItipItem.prototype = { + getInterfaces: function ciiGI(count) { var ifaces = [ - Components.interfaces.nsISupports, - Components.interfaces.calIItipItem, - Components.interfaces.nsIClassInfo + Ci.nsIClassInfo, + Ci.nsISupports, + Ci.calIItipItem ]; count.value = ifaces.length; return ifaces; }, - getHelperForLanguage: function (language) { + getHelperForLanguage: function ciiGHFL(aLanguage) { return null; }, contractID: "@mozilla.org/calendar/itip-item;1", classDescription: "Calendar iTIP item", - classID: Components.ID("{b84de879-4b85-4d68-8550-e0C527e46f98}"), + classID: Components.ID("{f41392ab-dcad-4bad-818f-b3d1631c4d93}"), implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT, - flags: 0 -}; + flags: 0, -calItipItem.prototype = { - QueryInterface: function (aIID) { - if (aIID.equals(Ci.nsIClassInfo)) { - return calItipItemClassInfo; - } - - if (aIID.equals(Ci.nsISupports) || aIID.equals(Ci.calIItipItem)) { - return this; - } else { + QueryInterface: function ciiQI(aIid) { + if (!aIid.equals(Ci.nsISupports) && !aIid.equals(Ci.calIItipItem)) { throw Components.results.NS_ERROR_NO_INTERFACE; } @@ -82,108 +75,156 @@ calItipItem.prototype = { }, mIsSend: false, - get isSend() { return this.mIsSend; }, - set isSend(value) { - this.mIsSend = value; + set isSend(aValue) { + return (this.mIsSend = aValue); }, mReceivedMethod: null, - get receivedMethod() { return this.mReceivedMethod; }, - set receivedMethod(value) { - this.mReceivedMethod = value; + set receivedMethod(aMethod) { + return (this.mReceivedMethod = aMethod.toUpperCase()); }, mResponseMethod: null, - get responseMethod() { - return this.mResponseMethod; + if (this.mIsInitialized) { + var method = null; + for each (var prop in this.mPropertiesList) { + if (prop.propertyName == "METHOD") { + method = prop.value; + break; + } + } + return method; + } else { + throw Components.results.NS_ERROR_NOT_INITIALIZED; + } }, - set responseMethod(value) { - this.mResponseMethod = value; + set responseMethod(aMethod) { + this.mResponseMethod = aMethod.toUpperCase(); + // Setting this also sets the global method attribute inside the + // encapsulated VCALENDAR. + if (this.mIsInitialized) { + var methodExists = false; + for each (var prop in this.mPropertiesList) { + if (prop.propertyName == "METHOD") { + methodExists = true; + prop.value = this.mResponseMethod; + } + } + + if (!methodExists) { + var newProp = { propertyName: "METHOD", + value: this.mResponseMethod }; + this.mPropertiesList.push(newProp); + } + } else { + throw Components.results.NS_ERROR_NOT_INITIALIZED; + } + return this.mResponseMethod; }, mAutoResponse: null, - get autoResponse() { return this.mAutoResponse; }, - set autoResponse(value) { - this.mAutoResponse = value; + set autoResponse(aValue) { + return (this.mAutoResponse = aValue); }, mTargetCalendar: null, - get targetCalendar() { return this.mTargetCalendar; }, - set targetCalendar(value) { - this.mTargetCalendar = value; + set targetCalendar(aValue) { + return (this.mTargetCalendar = aValue); }, - modifyItem: function(item) { - // Bug 348666: This will be used when we support delegation and COUNTER + mLocalStatus: null, + get localStatus() { + return this.mLocalStatus; + }, + set localStatus(aValue) { + return (this.mLocalStatus = aValue); + }, + + modifyItem: function ciiMI(item) { + // Bug 348666: This will be used when we support delegation and COUNTER. throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }, mItemList: null, + mPropertiesList: null, - init: function(icalData) { - this.mItemList = new Array(); - var icsService = Cc["@mozilla.org/calendar/ics-service;1"] - .getService(Components.interfaces.calIICSService); - var rootComp = icsService.parseICS(icalData); - var calComp; - // libical returns the vcalendar component if there is just - // one vcalendar. If there are multiple vcalendars, it returns - // an xroot component, with those vcalendar children. We need to - // handle both cases. - if (rootComp.componentType == 'VCALENDAR') { - calComp = rootComp; - } else { - calComp = rootComp.getFirstSubcomponent('VCALENDAR'); + init: function ciiI(aIcalString) { + var parser = Cc["@mozilla.org/calendar/ics-parser;1"]. + createInstance(Ci.calIIcsParser); + parser.parseString(aIcalString); + this.mItemList = parser.getItems({}); + this.mPropertiesList = parser.getProperties({}); + + // We set both methods now for safety's sake. It's the ItipProcessor's + // responsibility to properly ascertain what the correct response + // method is (using user feedback, prefs, etc.) for the given + // receivedMethod. The RFC tells us to treat items without a METHOD + // as if they were METHOD:REQUEST. + var method; + for each (var prop in this.mPropertiesList) { + if (prop.propertyName == "METHOD") { + method = prop.value; + } } - // Get the method property out of the calendar to set our methods - var method = calComp.method; - // We set both methods now for safety's sake. It is the iTIP - // processor's responsibility to properly ascertain what the correct - // response method is (using user feedback, pref's etc) for this given - // ReceivedMethod this.mReceivedMethod = method; this.mResponseMethod = method; - while (calComp) { - var subComp = calComp.getFirstSubcomponent("ANY"); - while (subComp) { - switch (subComp.componentType) { - case "VEVENT": - var event = Cc["@mozilla.org/calendar/event;1"] - .createInstance(Ci.calIEvent); - event.icalComponent = subComp; - this.mItemList.push(event); - break; - case "VTODO": - var todo = Cc["@mozilla.org/calendar/todo;1"] - .createInstance(Ci.calITodo); - todo.icalComponent = subComp; - this.mItemList.push(todo); - break; - default: - // Nothing -- Bug 185537: Implement VFREEBUSY, VJOURNAL - } - subComp = calComp.getNextSubcomponent("ANY"); - } - calComp = rootComp.getNextSubcomponent('VCALENDAR'); - } this.mIsInitialized = true; }, - getFirstItem: function() { + clone: function ciiC() { + // Iterate through all the calItems in the original calItipItem to + // concatenate all the calItems' icalStrings. + var icalString = ""; + + var itemList = this.getItemList({ }); + for (var i = 0; i < itemList.length; i++) { + icalString += itemList[i].icalString; + } + + // Create a new calItipItem and initialize it using the icalString + // from above. + var newItem = Cc["@mozilla.org/calendar/itip-item;1"]. + createInstance(Ci.calIItipItem); + newItem.init(icalString); + + // Copy over the exposed attributes. + newItem.receivedMethod = this.receivedMethod; + newItem.responseMethod = this.responseMethod; + newItem.autoResponse = this.autoResponse; + newItem.targetCalendar = this.targetCalendar; + newItem.localStatus = this.localStatus; + newItem.isSend = this.isSend; + + return newItem; + }, + + /** + * This returns both the array and the number of items. An easy way to + * call it is: var itemArray = itipItem.getItemList({ }); + */ + getItemList: function ciiGIL(itemCountRef) { + if (!this.mIsInitialized || (this.mItemList.length == 0)) { + throw Components.results.NS_ERROR_NOT_INITIALIZED; + } + itemCountRef.value = this.mItemList.length; + return this.mItemList; + }, + + /*getFirstItem: function ciiGFI() { if (!this.mIsInitialized || (this.mItemList.length == 0)) { throw Components.results.NS_ERROR_NOT_INITIALIZED; } @@ -191,7 +232,7 @@ calItipItem.prototype = { return this.mItemList[0]; }, - getNextItem: function() { + getNextItem: function ciiGNI() { if (!this.mIsInitialized || (this.mItemList.length == 0)) { throw Components.results.NS_ERROR_NOT_INITIALIZED; } @@ -201,22 +242,44 @@ calItipItem.prototype = { } else { return null; } - }, + },*/ - setAttendeeStatus: function (attendeeID, status) { - // Note that this code forces the user to respond to all items - // in the same way, which is a current limitation of the spec - if (attendeeID.match(/mailto:/i)) { - attendeeID = "mailto:" + attendeeID; // prepend mailto + /** + * Note that this code forces the user to respond to all items in the same + * way, which is a current limitation of the spec. + */ + setAttendeeStatus: function ciiSAS(aAttendeeId, aStatus) { + // Append "mailto:" to the attendee if it is missing it. + var attId = aAttendeeId.toLowerCase(); + if (!attId.match(/mailto:/i)) { + attId = "mailto:" + attId; } - for (var i=0; i < this.mItemList.length; ++i) { - var item = this.mItemList[i]; - var attendee = item.getAttendeeById(attendeeID); + + for each (var item in this.mItemList) { + var attendee = item.getAttendeeById(attId); if (attendee) { // XXX BUG 351589: workaround for updating an attendee item.removeAttendee(attendee); - attendee.participationStatus = status; - item.addAttendee(attendee); + + // Replies should not have the RSVP property. + // Since attendee.deleteProperty("RSVP") doesn't work, we must + // create a new attendee from scratch WITHOUT the RSVP + // property and copy in the other relevant data. + // XXX use deleteProperty after bug 358498 is fixed. + newAttendee = Cc["@mozilla.org/calendar/attendee;1"]. + createInstance(Ci.calIAttendee); + if (attendee.commonName) { + newAttendee.commonName = attendee.commonName; + } + if (attendee.role) { + newAttendee.role = attendee.role; + } + if (attendee.userType) { + newAttendee.userType = attendee.userType; + } + newAttendee.id = attendee.id; + newAttendee.participationStatus = aStatus; + item.addAttendee(newAttendee); } } } diff --git a/mozilla/calendar/installer/windows/packages-static b/mozilla/calendar/installer/windows/packages-static index d1f5b1c7075..a4a47067c93 100644 --- a/mozilla/calendar/installer/windows/packages-static +++ b/mozilla/calendar/installer/windows/packages-static @@ -222,7 +222,9 @@ bin\js\calIcsImportExport.js bin\js\calIcsParser.js bin\js\calIcsSerializer.js bin\js\calItemBase.js +bin\js\calItipEmailTransport.js bin\js\calItipItem.js +bin\js\calItipProcessor.js bin\js\calListFormatter.js bin\js\calMonthGridPrinter.js bin\js\calOutlookCSVImportExport.js diff --git a/mozilla/calendar/lightning/Makefile.in b/mozilla/calendar/lightning/Makefile.in index 19104eb0344..67a8d77bc6c 100644 --- a/mozilla/calendar/lightning/Makefile.in +++ b/mozilla/calendar/lightning/Makefile.in @@ -54,6 +54,7 @@ DIRS = ../../db/sqlite3/src ../../storage \ ../libical ../base ../providers \ ../import-export \ components \ + ../itip \ $(NULL) # Select a theme from which to pull our skin goodness diff --git a/mozilla/calendar/lightning/components/lightningTextCalendarConverter.js b/mozilla/calendar/lightning/components/lightningTextCalendarConverter.js index d3df0d48a49..cde7494326c 100644 --- a/mozilla/calendar/lightning/components/lightningTextCalendarConverter.js +++ b/mozilla/calendar/lightning/components/lightningTextCalendarConverter.js @@ -20,7 +20,8 @@ * * Contributor(s): * Mike Shaver - * Clint Talbert + * Clint Talbert + * Matthew Willis * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -45,8 +46,8 @@ function makeTableRow(val) { function getLightningStringBundle() { - var svc = Cc["@mozilla.org/intl/stringbundle;1"] - .getService(Ci.nsIStringBundleService); + var svc = Cc["@mozilla.org/intl/stringbundle;1"]. + getService(Ci.nsIStringBundleService); return svc.createBundle("chrome://lightning/locale/lightning.properties"); } @@ -107,18 +108,23 @@ function createHtml(event) html.body.table.appendChild(createHtmlTableSection(labelText, event.startDate.jsDate.toLocaleString())); - if (event.organizer && - (event.organizer.commonName || event.organizer.id)) { + if (event.organizer && + (event.organizer.commonName || event.organizer.id)) + { labelText = stringBundle.GetStringFromName("imipHtml.organizer"); - var orgname = event.organizer.commonName || event.organizer.id; + // Trim any instances of "mailto:" for better readibility. + var orgname = event.organizer.commonName || + event.organizer.id.replace(/mailto:/ig, ""); html.body.table.appendChild(createHtmlTableSection(labelText, orgname)); } var eventDescription = event.getProperty("DESCRIPTION"); if (eventDescription) { + // Remove the useless "Outlookism" squiggle. + var desc = eventDescription.replace("*~*~*~*~*~*~*~*~*~*", ""); + labelText = stringBundle.GetStringFromName("imipHtml.description"); - html.body.table.appendChild(createHtmlTableSection(labelText, - eventDescription)); + html.body.table.appendChild(createHtmlTableSection(labelText,desc)); } } @@ -128,32 +134,48 @@ function createHtml(event) function ltnMimeConverter() { } ltnMimeConverter.prototype = { - QueryInterface: function (aIID) { + QueryInterface: function QI(aIID) { if (!aIID.equals(Ci.nsISupports) && !aIID.equals(Ci.nsISimpleMimeConverter)) - throw Ci.NS_ERROR_NO_INTERFACE; + { + throw Components.results.NS_ERROR_NO_INTERFACE; + } return this; }, - convertToHTML: function(contentType, data) { - var event = Cc["@mozilla.org/calendar/event;1"] - .createInstance(Ci.calIEvent); + mUri: null, + get uri() { + return this.mUri; + }, + set uri(aUri) { + return (this.mUri = aUri); + }, + + convertToHTML: function lmcCTH(contentType, data) { + var event = Cc["@mozilla.org/calendar/event;1"]. + createInstance(Ci.calIEvent); event.icalString = data; var html = createHtml(event); try { - // Bug 351610: This mechanism is a little flawed - var itipItem = Cc["@mozilla.org/calendar/itip-item;1"] - .createInstance(Ci.calIItipItem); + var itipItem = Cc["@mozilla.org/calendar/itip-item;1"]. + createInstance(Ci.calIItipItem); itipItem.init(data); - var observer = Cc["@mozilla.org/observer-service;1"] - .getService(Ci.nsIObserverService); - if (observer) { - observer.notifyObservers(itipItem, "onItipItemCreation", 0); - } + + // this.mUri is the message URL that we are processing. + // We use it to get the nsMsgHeaderSink to store the calItipItem. + var msgUrl = this.mUri.QueryInterface(Ci.nsIMsgMailNewsUrl); + var sinkProps = msgUrl.msgWindow.msgHeaderSink.properties; + sinkProps.setPropertyAsInterface("itipItem", itipItem); + + // Notify the observer that the itipItem is available + var observer = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + observer.notifyObservers(null, "onItipItemCreation", 0); } catch (e) { - Components.utils.reportError("Cannot Create iTIP Item: " + e); + Components.utils.reportError("convertToHTML: " + + "Cannot create itipItem: " + e); } return html; @@ -161,30 +183,30 @@ ltnMimeConverter.prototype = { }; var myModule = { - registerSelf: function (compMgr, fileSpec, location, type) { + registerSelf: function RS(aCompMgr, aFileSpec, aLocation, aType) { debug("*** Registering Lightning text/calendar handler\n"); - compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar); + var compMgr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); compMgr.registerFactoryLocation(this.myCID, "Lightning text/calendar handler", this.myContractID, - fileSpec, - location, - type); + aFileSpec, + aLocation, + aType); var catman = Components.classes["@mozilla.org/categorymanager;1"] - .getService(Ci.nsICategoryManager); + .getService(Ci.nsICategoryManager); catman.addCategoryEntry("simple-mime-converters", "text/calendar", this.myContractID, true, true); }, - getClassObject: function (compMgr, cid, iid) { - if (!cid.equals(this.myCID)) + getClassObject: function GCO(aCompMgr, aCid, aIid) { + if (!aCid.equals(this.myCID)) { throw Components.results.NS_ERROR_NO_INTERFACE; - - if (!iid.equals(Components.interfaces.nsIFactory)) + } + if (!aIid.equals(Components.interfaces.nsIFactory)) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - + } return this.myFactory; }, @@ -193,15 +215,15 @@ var myModule = { myContractID: "@mozilla.org/lightning/mime-converter;1", myFactory: { - createInstance: function (outer, iid) { - if (outer != null) + createInstance: function mfCI(aOuter, aIid) { + if (aOuter != null) { throw Components.results.NS_ERROR_NO_AGGREGATION; - - return (new ltnMimeConverter()).QueryInterface(iid); + } + return (new ltnMimeConverter()).QueryInterface(aIid); } }, - canUnload: function(compMgr) { + canUnload: function CU(aCompMgr) { return true; } }; @@ -209,4 +231,3 @@ var myModule = { function NSGetModule(compMgr, fileSpec) { return myModule; } - diff --git a/mozilla/calendar/lightning/content/imip-bar.js b/mozilla/calendar/lightning/content/imip-bar.js index b4e6c428547..4db5942544d 100644 --- a/mozilla/calendar/lightning/content/imip-bar.js +++ b/mozilla/calendar/lightning/content/imip-bar.js @@ -19,7 +19,8 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Clint Talbert + * Clint Talbert + * Matthew Willis * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -34,62 +35,68 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ + /** - * This bar lives inside the message window, its lifetime is the lifetime of - * the main thunderbird message window. + * This bar lives inside the message window. + * Its lifetime is the lifetime of the main thunderbird message window. */ var gItipItem; -/** - * Sets up iTIP creation event listener - * When the mime parser discovers a text/calendar attachment to a message, - * it creates a calIItipItem and calls an observer. We watch for that - * observer. Bug 351610 is open so that we can find a way to make this - * communication mechanism more robust. -*/ const onItipItem = { observe: function observe(subject, topic, state) { if (topic == "onItipItemCreation") { - try { - var itipItem = - subject.QueryInterface(Components.interfaces.calIItipItem); - // We are only called upon receipt of an invite, so - // ensure that isSend is false. - itipItem.isSend = false; - - // Until the iTIPResponder code lands, we do no response - // at all. - itipItem.autoResponse = Components.interfaces.calIItipItem.NONE; - itipItem.targetCalendar = getTargetCalendar(); - - var imipMethod = getMsgImipMethod(); - if (imipMethod.length) { - itipItem.receivedMethod = imipMethod; - } else { - // Thunderbird 1.5 case, we cannot get the iMIPMethod - imipMethod = itipItem.receivedMethod; - } - - gItipItem = itipItem; - - // XXX Bug 351742: no security yet - // handleImipSecurity(imipMethod); - - setupBar(imipMethod); - - } catch (e) { - Components.utils.reportError(e); - } + checkForItipItem(); } } }; -addEventListener('messagepane-loaded', imipOnLoad, true); -addEventListener('messagepane-unloaded', imipOnUnload, true); +function checkForItipItem() +{ + var itipItem; + try { + var msgUri = GetLoadedMessage(); + var sinkProps = msgWindow.msgHeaderSink.properties; + // This property was set by LightningTextCalendarConverter.js + itipItem = sinkProps.getPropertyAsInterface("itipItem", + Components.interfaces.calIItipItem) + } catch (e) { + // This will throw on every message viewed that doesn't have the + // itipItem property set on it. So we eat the errors and move on. + + // XXX TODO: Only swallow the errors we need to. Throw all others. + return; + } + + // We are only called upon receipt of an invite, so ensure that isSend + // is false. + itipItem.isSend = false; + + // XXX Get these from preferences + itipItem.autoResponse = Components.interfaces.calIItipItem.USER; + itipItem.targetCalendar = getTargetCalendar(); + + var imipMethod = getMsgImipMethod(); + if (imipMethod.length) { + itipItem.receivedMethod = imipMethod; + } else { + // Thunderbird 1.5 case, we cannot get the imipMethod + imipMethod = itipItem.receivedMethod; + } + + gItipItem = itipItem; + + // XXX Bug 351742: no S/MIME or spoofing protection yet + // handleImipSecurity(imipMethod); + + setupBar(imipMethod); +} + +addEventListener("messagepane-loaded", imipOnLoad, true); +addEventListener("messagepane-unloaded", imipOnUnload, true); /** - * Attempt to add self to gMessageListeners defined in msgHdrViewOverlay.js + * Add self to gMessageListeners defined in msgHdrViewOverlay.js */ function imipOnLoad() { @@ -99,18 +106,20 @@ function imipOnLoad() gMessageListeners.push(listener); // Set up our observers - var observerService = Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - observerService.addObserver(onItipItem, "onItipItemCreation", false); + var observerSvc = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + observerSvc.addObserver(onItipItem, "onItipItemCreation", false); } function imipOnUnload() { - removeEventListener('messagepane-loaded', imipOnLoad, true); - removeEventListener('messagepane-unloaded', imipOnUnload, true); - var observerService = Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - observerService.removeObserver(onItipItem, "onItipItemCreation"); + removeEventListener("messagepane-loaded", imipOnLoad, true); + removeEventListener("messagepane-unloaded", imipOnUnload, true); + + var observerSvc = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + observerSvc.removeObserver(onItipItem, "onItipItemCreation"); + gItipItem = null; } @@ -119,25 +128,27 @@ function onImipStartHeaders() var imipBar = document.getElementById("imip-bar"); imipBar.setAttribute("collapsed", "true"); - // New Message is starting, clear our iMIP/iTIP stuff so that we don't set - // it by accident + // A new message is starting. + // Clear our iMIP/iTIP stuff so it doesn't contain stale information. imipMethod = ""; gItipItem = null; } -// We need an onEndHeader or else MessageListner will throw. However, -// we do not need to actually do anything in this function. +/** + * Required by MessageListener. no-op + */ function onImipEndHeaders() { + // no-op } function setupBar(imipMethod) { - // XXX - Bug 348666 - Currently we only do PUBLISH requests + // XXX - Bug 348666 - Currently we only do REQUEST requests // In the future this function will set up the proper actions // and attributes for the buttons as based on the iMIP Method var imipBar = document.getElementById("imip-bar"); - imipBar.setAttribute("collapsed","false"); + imipBar.setAttribute("collapsed", "false"); var description = document.getElementById("imip-description"); // Bug 348666: here is where we would check if this event was already @@ -146,13 +157,22 @@ function setupBar(imipMethod) description.firstChild.data = ltnGetString("lightning","imipBarText"); } - // Since there is only a PUBLISH, this is easy - var button1 = document.getElementById("imip-btn1"); - button1.removeAttribute("hidden"); - button1.setAttribute("label", ltnGetString("lightning", - "imipAddToCalendar.label")); - button1.setAttribute("oncommand", - "setAttendeeResponse('PUBLISH', 'CONFIRMED');"); + var button = document.getElementById("imip-button1"); + button.removeAttribute("hidden"); + button.setAttribute("label", ltnGetString("lightning", + "imipAcceptInvitation.label")); + button.setAttribute("oncommand", + "setAttendeeResponse('ACCEPTED', 'CONFIRMED');"); + + if (imipMethod == "REQUEST") { + // Then create a DECLINE button + button = document.getElementById("imip-button2"); + button.removeAttribute("hidden"); + button.setAttribute("label", ltnGetString("lightning", + "imipDeclineInvitation.label")); + button.setAttribute("oncommand", + "setAttendeeResponse('DECLINED', 'CONFIRMED');"); + } } function getMsgImipMethod() @@ -171,10 +191,40 @@ function getMsgRecipient() var msgURI = GetLoadedMessage(); var msgHdr = messenger.messageServiceFromURI(msgURI) .messageURIToMsgHdr(msgURI); - // msgHdr.recipients is always the one recipient - the one looking at - // the email, which is who we are interested in. + + // msgHdr recipients can be a comma separated list of recipients. + // We then compare against the defaultIdentity to find ourselves. + // XXX This won't always work: + // Users with multiple accounts and invites going to the non-default + // account, Users with email aliases where the defaultIdentity.email + // doesn't match the recipient, etc. if (msgHdr) { - imipRecipient = msgHdr.recipients; + var recipientList = msgHdr.recipients; + // Remove any spaces + recipientList = recipientList.split(" ").join(""); + recipientList = recipientList.split(","); + + var emailSvc = Cc["@mozilla.org/calendar/itip-transport;1?type=email"]. + getService(Ci.calIItipTransport); + var me = emailSvc.defaultIdentity; + + var lt; + var gt; + for each (var recipient in recipientList) { + // Deal with style addresses + lt = recipient.indexOf("<"); + gt = recipient.indexOf(">"); + + // I chose 6 since is the shortest technically valid email + // address I could come up with. + if ((lt >= 0) && (gt >= 6)) { + recipient = recipient.substring(lt+1, gt); + } + + if (recipient.toLowerCase() == me.toLowerCase()) { + imipRecipient = recipient; + } + } } return imipRecipient; } @@ -184,8 +234,8 @@ function getMsgRecipient() */ function getTargetCalendar() { - var calMgr = Components.classes["@mozilla.org/calendar/manager;1"] - .getService(Components.interfaces.calICalendarManager); + var calMgr = Cc["@mozilla.org/calendar/manager;1"]. + getService(Ci.calICalendarManager); var cals = calMgr.getCalendars({}); return cals[0]; } @@ -198,69 +248,97 @@ function setAttendeeResponse(type, eventStatus) { var myAddress = getMsgRecipient(); if (type && gItipItem) { - switch (type) { // We set the attendee status appropriately + // We set the attendee status appropriately + switch (type) { case "ACCEPTED": case "TENTATIVE": case "DECLINED": gItipItem.setAttendeeStatus(myAddress, type); - doResponse(); - break; + // fall through case "REPLY": case "PUBLISH": doResponse(eventStatus); break; default: - // Nothing -- if given nothing then the attendee wishes to - // disregard the mail, so no further action required + // no-op. The attendee wishes to disregard the mail, so no + // further action is required. break; } } - - finishItipAction(type, eventStatus); } /** - * doResponse performs the iTIP action for the current iTIPItem that we + * doResponse performs the iTIP action for the current ItipItem that we * parsed from the email. - * Takes an optional parameter to set the event STATUS property + * @param aLocalStatus optional parameter to set the event STATUS property. + * aLocalStatus can be empty, "TENTATIVE", "CONFIRMED", or "CANCELLED" */ -function doResponse(eventStatus) +function doResponse(aLocalStatus) { - // XXX For now, just add the item to the calendar + // calIOperationListener so that we can properly return status to the + // imip-bar + var operationListener = { + onOperationComplete: + function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) { + // Call finishItipAction to set the status of the operation + finishItipAction(aOperationType, aStatus, aDetail); + }, + + onGetResult: + function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) { + // no-op + } + }; + // The spec is unclear if we must add all the items or if the // user should get to pick which item gets added. var cal = getTargetCalendar(); - var item = gItipItem.getFirstItem(); - while (item != null) { - if (eventStatus.length) - item.status = eventStatus; - cal.addItem(item, null); - item = gItipItem.getNextItem(); + if (aLocalStatus != null) { + gItipItem.localStatus = aLocalStatus; } + + var itipProc = Cc["@mozilla.org/calendar/itip-processor;1"]. + createInstance(Ci.calIItipProcessor); + + itipProc.processItipItem(gItipItem, operationListener); } /** * Bug 348666 (complete iTIP support) - This gives the user an indication * that the Action occurred. - * In the future we want to store the status of invites that you have added - * to your calendar and provide the ability to request updates from the - * organizer. This function will be responsible for setting up and - * maintaining the proper lists of events and informing the user as to the - * state of the iTIP action - * Additionally, this function should ONLY be called once we KNOW what the - * status of the event addition was. It must wait for a clear signal from the - * iTIP Processor as to whether or not the event added/failed to add or - * what have you. It should alert the user to the true status of the operation + * + * In the future, this will store the status of the invitation in the + * invitation manager. This will enable us to provide the ability to request + * updates from the organizer and to suggest changes to invitations. + * + * Currently, this is called from our calIOperationListener that is sent to + * the ItipProcessor. This conveys the status of the local iTIP processing + * on your calendar. It does not convey the success or failure of sending a + * response to the ItipItem. */ -function finishItipAction(type, eventStatus) +function finishItipAction(aOperationType, aStatus, aDetail) { - // For now, we just do something very simple - var description = document.getElementById("imip-description"); - if (description.firstChild.data) { - description.firstChild.data = ltnGetString("lightning", - "imipAddedItemToCal"); + // For now, we just state the status for the user something very simple + var desc = document.getElementById("imip-description"); + if (desc.firstChild != null) { + if (Components.isSuccessCode(aStatus)) { + desc.firstChild.data = ltnGetString("lightning", + "imipAddedItemToCal"); + document.getElementById("imip-button1").setAttribute("hidden", + true); + document.getElementById("imip-button2").setAttribute("hidden", + true); + } else { + // Bug 348666: When we handle more iTIP methods, we need to create + // more sophisticated error handling. + document.getElementById("imip-bar").setAttribute("collapsed", true); + var msg = "Invitation could not be processed. Status: " + aStatus; + if (aDetail) { + msg += "\nDetails: " + aDetail; + } + // Defined in import-export + showError(msg); + } } - document.getElementById("imip-btn1").setAttribute("hidden", true); } - diff --git a/mozilla/calendar/lightning/content/messenger-overlay-sidebar.xul b/mozilla/calendar/lightning/content/messenger-overlay-sidebar.xul index 3af01935a64..ca7b53b0580 100644 --- a/mozilla/calendar/lightning/content/messenger-overlay-sidebar.xul +++ b/mozilla/calendar/lightning/content/messenger-overlay-sidebar.xul @@ -226,7 +226,8 @@ &lightning.imipbar.description; -