Bug 457203 - iTIP overhaul. r=philipp

--HG--
rename : calendar/base/src/calUtils.jsm => calendar/base/modules/calUtils.jsm
This commit is contained in:
Daniel Boelzle [:dbo] 2008-11-05 12:08:51 +01:00
Родитель c37f6bbb94
Коммит e178c7de30
43 изменённых файлов: 1474 добавлений и 1690 удалений

Просмотреть файл

@ -46,7 +46,7 @@ include $(DEPTH)/config/autoconf.mk
MODULE = calbase
MODULE_NAME = calBaseModule
DIRS = public src build
DIRS = public src modules build
# Select a theme from which to pull our skin goodness
# OS X: pinstripe

Просмотреть файл

@ -36,6 +36,8 @@
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://calendar/modules/calItipUtils.jsm");
var gInvitationsRequestManager = {
mRequestStatusList: {},
@ -246,7 +248,7 @@ InvitationsManager.prototype = {
aDetail) {
if (Components.isSuccessCode(aStatus) &&
aOperationType == Components.interfaces.calIOperationListener.MODIFY) {
checkAndSendItipMessage(aDetail, aOperationType, this.mOldItem);
cal.itip.checkAndSend(aOperationType, aDetail, this.mOldItem);
this.mInvitationsManager.deleteItem(aDetail);
this.mInvitationsManager.addItem(aDetail);
}

Просмотреть файл

@ -37,35 +37,6 @@
*
* ***** END LICENSE BLOCK ***** */
function opCompleteListener(aOriginalItem, aOuterListener) {
this.mOriginalItem = aOriginalItem;
this.mOuterListener = aOuterListener;
}
opCompleteListener.prototype = {
mOriginalItem: null,
mOuterListener: null,
onOperationComplete: function oCL_onOperationComplete(aCalendar, aStatus, aOpType, aId, aItem) {
if (Components.isSuccessCode(aStatus)) {
// we may optionally shift the whole check and send mail messages to
// calProviderBase.notifyOperationComplete (with adding an oldItem parameter).
// I am not yet sure what to do for mixed mode invitations, e.g.
// some users on the attendee list are caldav users and get REQUESTs into their inbox,
// other get emailed... For now let's do both.
checkAndSendItipMessage(aItem, aOpType, this.mOriginalItem);
}
if (this.mOuterListener) {
this.mOuterListener.onOperationComplete.apply(this.mOuterListener,
arguments);
}
},
onGetItem: function oCL_onGetResult(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
}
};
/* all params are optional */
function createEventWithDialog(calendar, startDate, endDate, summary, event, aForceAllday) {
const kDefaultTimezone = calendarDefaultTimezone();
@ -376,56 +347,6 @@ function promptOccurrenceModification(aItem, aNeedsFuture, aAction) {
return [pastItem, futureItem, type];
}
/**
* Read default alarm settings from user preferences and apply them to
* the event/todo passed in.
*
* @param aItem The event or todo the settings should be applied to.
*/
function setDefaultAlarmValues(aItem)
{
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
var alarmsBranch = prefService.getBranch("calendar.alarms.");
if (isEvent(aItem)) {
try {
if (alarmsBranch.getIntPref("onforevents") == 1) {
var alarmOffset = Components.classes["@mozilla.org/calendar/duration;1"]
.createInstance(Components.interfaces.calIDuration);
var units = alarmsBranch.getCharPref("eventalarmunit");
alarmOffset[units] = alarmsBranch.getIntPref("eventalarmlen");
alarmOffset.isNegative = true;
aItem.alarmOffset = alarmOffset;
aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
}
} catch (ex) {
Components.utils.reportError(
"Failed to apply default alarm settings to event: " + ex);
}
} else if (isToDo(aItem)) {
try {
if (alarmsBranch.getIntPref("onfortodos") == 1) {
// You can't have an alarm if the entryDate doesn't exist.
if (!aItem.entryDate) {
aItem.entryDate = getSelectedDay() &&
getSelectedDay().clone() || now();
}
var alarmOffset = Components.classes["@mozilla.org/calendar/duration;1"]
.createInstance(Components.interfaces.calIDuration);
var units = alarmsBranch.getCharPref("todoalarmunit");
alarmOffset[units] = alarmsBranch.getIntPref("todoalarmlen");
alarmOffset.isNegative = true;
aItem.alarmOffset = alarmOffset;
aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
}
} catch (ex) {
Components.utils.reportError(
"Failed to apply default alarm settings to task: " + ex);
}
}
}
// Undo/Redo code
function getTransactionMgr() {
return Components.classes["@mozilla.org/calendar/transactionmanager;1"]
@ -433,12 +354,11 @@ function getTransactionMgr() {
}
function doTransaction(aAction, aItem, aCalendar, aOldItem, aListener) {
var innerListener = new opCompleteListener(aOldItem, aListener);
getTransactionMgr().createAndCommitTxn(aAction,
aItem,
aCalendar,
aOldItem,
innerListener);
aListener ? aListener : null);
updateUndoRedoMenu();
}
@ -478,171 +398,3 @@ function updateUndoRedoMenu() {
goUpdateCommand("cmd_undo");
goUpdateCommand("cmd_redo");
}
/**
* Checks to see if the attendees were added or changed between the original
* and new item. If there is a change, it launches the calIItipTransport
* service and sends the invitations
*/
function checkAndSendItipMessage(aItem, aOpType, aOriginalItem) {
var transport = aItem.calendar.getProperty("itip.transport");
if (!transport) { // Only send if there's a transport for the calendar
return;
}
transport = transport.QueryInterface(Components.interfaces.calIItipTransport);
var invitedAttendee = ((calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport) &&
aItem.calendar.isInvitation(aItem))
? aItem.calendar.getInvitedAttendee(aItem) : null);
if (invitedAttendee) { // actually is an invitation copy, fix attendee list to send REPLY
if (aItem.calendar.canNotify("REPLY", aItem)) {
return; // provider does that
}
var origInvitedAttendee = (aOriginalItem && aOriginalItem.getAttendeeById(invitedAttendee.id));
if (aOpType == Components.interfaces.calIOperationListener.DELETE) {
// in case the attendee has just deleted the item, we want to send out a DECLINED REPLY:
origInvitedAttendee = invitedAttendee;
invitedAttendee = invitedAttendee.clone();
invitedAttendee.participationStatus = "DECLINED";
}
// has this been a PARTSTAT change?
if (aItem.organizer &&
(!origInvitedAttendee ||
(origInvitedAttendee.participationStatus != invitedAttendee.participationStatus))) {
aItem = aItem.clone();
aItem.removeAllAttendees();
aItem.addAttendee(invitedAttendee);
var itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
.createInstance(Components.interfaces.calIItipItem);
itipItem.init(calGetSerializedItem(aItem));
itipItem.targetCalendar = aItem.calendar;
itipItem.autoResponse = Components.interfaces.calIItipItem.USER;
itipItem.responseMethod = "REPLY";
transport.sendItems(1, [aItem.organizer], itipItem);
}
return;
}
if (aItem.getProperty("X-MOZ-SEND-INVITATIONS") != "TRUE") { // Only send invitations/cancellations
// if the user checked the checkbox
return;
}
if (aOpType == Components.interfaces.calIOperationListener.DELETE) {
calSendItipMessage(transport, aItem, "CANCEL", aItem.getAttendees({}));
return;
} // else ADD, MODIFY:
var originalAtt = (aOriginalItem ? aOriginalItem.getAttendees({}) : []);
var itemAtt = aItem.getAttendees({});
var canceledAttendees = [];
if (itemAtt.length > 0 || originalAtt.length > 0) {
var attMap = {};
for each (var att in originalAtt) {
attMap[att.id.toLowerCase()] = att;
}
for each (var att in itemAtt) {
if (att.id.toLowerCase() in attMap) {
// Attendee was in original item.
delete attMap[att.id.toLowerCase()];
}
}
for each (var cancAtt in attMap) {
canceledAttendees.push(cancAtt);
}
}
var autoResponse = false; // confirm to send email
// Check to see if some part of the item was updated, if so, re-send invites
if (!aOriginalItem || aItem.generation != aOriginalItem.generation) { // REQUEST
var requestItem = aItem.clone();
if (!requestItem.organizer) {
var organizer = Components.classes["@mozilla.org/calendar/attendee;1"]
.createInstance(Components.interfaces.calIAttendee);
organizer.id = requestItem.calendar.getProperty("organizerId");
organizer.commonName = requestItem.calendar.getProperty("organizerCN");
organizer.role = "REQ-PARTICIPANT";
organizer.participationStatus = "ACCEPTED";
organizer.isOrganizer = true;
requestItem.organizer = organizer;
}
// Fix up our attendees for invitations using some good defaults
var recipients = [];
var itemAtt = requestItem.getAttendees({});
requestItem.removeAllAttendees();
for each (var attendee in itemAtt) {
attendee = attendee.clone();
attendee.role = "REQ-PARTICIPANT";
attendee.participationStatus = "NEEDS-ACTION";
attendee.rsvp = "TRUE";
requestItem.addAttendee(attendee);
recipients.push(attendee);
}
if (recipients.length > 0) {
calSendItipMessage(transport, requestItem, "REQUEST", recipients, autoResponse);
autoResponse = true; // don't ask again
}
}
// Cancel the event for all canceled attendees
if (canceledAttendees.length > 0) {
var cancelItem = aOriginalItem.clone();
cancelItem.removeAllAttendees();
for each (var att in canceledAttendees) {
cancelItem.addAttendee(att);
}
calSendItipMessage(transport, cancelItem, "CANCEL", canceledAttendees, autoResponse);
}
}
function calSendItipMessage(aTransport, aItem, aMethod, aRecipientsList, autoResponse) {
if (aRecipientsList.length == 0) {
return;
}
if (calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport) &&
aItem.calendar.canNotify(aMethod, aItem)) {
return; // provider will handle that
}
var itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
.createInstance(Components.interfaces.calIItipItem);
// We have to modify our item a little, so we clone it.
var item = aItem.clone();
// We fake Sequence ID support.
item.setProperty("SEQUENCE", item.generation);
// Initialize and set our properties on the item
itipItem.init(calGetSerializedItem(item));
itipItem.responseMethod = aMethod;
itipItem.targetCalendar = item.calendar;
itipItem.autoResponse = (autoResponse
? Components.interfaces.calIItipItem.AUTO
: Components.interfaces.calIItipItem.USER);
// XXX I don't know whether the below are used at all, since we don't use the itip processor
itipItem.isSend = true;
// Send it!
aTransport.sendItems(aRecipientsList.length, aRecipientsList, itipItem);
}
function calGetSerializedItem(aItem) {
var serializer = Components.classes["@mozilla.org/calendar/ics-serializer;1"]
.createInstance(Components.interfaces.calIIcsSerializer);
serializer.addItems([aItem], 1);
return serializer.serializeToString();
}

Просмотреть файл

@ -36,6 +36,9 @@
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://calendar/modules/calUtils.jsm");
Components.utils.import("resource://calendar/modules/calItipUtils.jsm");
function onLoad() {
var args = window.arguments[0];
var item = args.calendarEvent;
@ -74,8 +77,8 @@ function onLoad() {
var attendee = calendar.getInvitedAttendee(item);
if (attendee) {
// if this is an unresponded invitation, preset our default alarm values:
if (attendee.participationStatus == "NEEDS-ACTION") {
setDefaultAlarmValues(item);
if (!item.alarmOffset && (attendee.participationStatus == "NEEDS-ACTION")) {
cal.setDefaultAlarmValues(item);
}
window.attendee = attendee.clone();

Просмотреть файл

@ -351,16 +351,16 @@ function loadDialog(item) {
// figure out what the title of the dialog should be and set it
updateTitle();
var sendInvitesCheckbox = document.getElementById("send-invitations-checkbox");
let notifyCheckbox = document.getElementById("notify-attendees-checkbox");
if (canNotifyAttendees(item.calendar, item)) {
// visualize that the server will send out mail:
sendInvitesCheckbox.checked = true;
notifyCheckbox.checked = true;
} else {
var itemProp = item.getProperty("X-MOZ-SEND-INVITATIONS");
sendInvitesCheckbox.checked = (item.calendar.getProperty("imip.identity") &&
((itemProp === null)
? getPrefSafe("calendar.itip.notify", true)
: (itemProp == "TRUE")));
let itemProp = item.getProperty("X-MOZ-SEND-INVITATIONS");
notifyCheckbox.checked = (item.calendar.getProperty("imip.identity") &&
((itemProp === null)
? getPrefSafe("calendar.itip.notify", true)
: (itemProp == "TRUE")));
}
updateAttendees();
@ -1539,9 +1539,9 @@ function updateCalendar() {
gIsReadOnly = calendar.readOnly;
if (!canNotifyAttendees(calendar, item) && calendar.getProperty("imip.identity")) {
enableElement("send-invitations-checkbox");
enableElement("notify-attendees-checkbox");
} else {
disableElement("send-invitations-checkbox");
disableElement("notify-attendees-checkbox");
}
// update the accept button
@ -1889,11 +1889,11 @@ function saveItem() {
item.addAttendee(attendee);
}
var sendInvitesCheckbox = document.getElementById("send-invitations-checkbox");
if (sendInvitesCheckbox.disabled || document.getElementById("event-grid-attendee-row-2").collapsed) {
let notifyCheckbox = document.getElementById("notify-attendees-checkbox");
if (notifyCheckbox.disabled || document.getElementById("event-grid-attendee-row-2").collapsed) {
item.deleteProperty("X-MOZ-SEND-INVITATIONS");
} else {
item.setProperty("X-MOZ-SEND-INVITATIONS", sendInvitesCheckbox.checked ? "TRUE" : "FALSE");
item.setProperty("X-MOZ-SEND-INVITATIONS", notifyCheckbox.checked ? "TRUE" : "FALSE");
}
}

Просмотреть файл

@ -701,7 +701,7 @@
</row>
<row id="event-grid-attendee-row-2" align="center">
<spacer/>
<checkbox id="send-invitations-checkbox"
<checkbox id="notify-attendees-checkbox"
class="lightning-only"
label="&newevent.attendees.notify.label;"
pack="start"/>

Просмотреть файл

@ -35,6 +35,8 @@
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://calendar/modules/calUtils.jsm");
function onLoad() {
var operationListener = {
onOperationComplete: function oL_onOperationComplete(aCalendar,
@ -114,8 +116,8 @@ function fillJobQueue(queue) {
var newCalendarItem = oldCalendarItem.clone();
// set default alarm on unresponded items that have not been declined:
if (oldStatus == "NEEDS-ACTION" && newStatus != "DECLINED") {
setDefaultAlarmValues(newCalendarItem);
if (!newCalendarItem.alarmOffset && (oldStatus == "NEEDS-ACTION") && (newStatus != "DECLINED")) {
cal.setDefaultAlarmValues(newCalendarItem);
}
richListItem.setCalendarItemParticipationStatus(newCalendarItem,

Просмотреть файл

@ -56,9 +56,7 @@
<!-- Javascript includes -->
<script type="application/javascript" src="chrome://calendar/content/calendar-invitations-dialog.js"/>
<script type="application/javascript" src="chrome://calendar/content/calUtils.js"/>
<script type="application/javascript" src="chrome://calendar/content/calendar-ui-utils.js"/>
<script type="application/javascript" src="chrome://calendar/content/calendar-item-editing.js"/>
<script type="application/javascript" >
var invitationsText = "&calendar.invitations.dialog.invitations.text;";

Просмотреть файл

@ -0,0 +1,52 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Sun Microsystems code.
#
# The Initial Developer of the Original Code is
# Sun Microsystems, Inc.
# Portions created by the Initial Developer are Copyright (C) 2008
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Daniel Boelzle <daniel.boelzle@sun.com>
#
# 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
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = calbase
EXTRA_JS_MODULES = \
calUtils.jsm \
calItipUtils.jsm \
$(NULL)
include $(topsrcdir)/config/rules.mk

Просмотреть файл

@ -0,0 +1,781 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Sun Microsystems code.
*
* The Initial Developer of the Original Code is
* Sun Microsystems, Inc.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Boelzle <daniel.boelzle@sun.com>
* Philipp Kewisch <mozilla@kewis.ch>
* Clint Talbert <ctalbert.moz@gmail.com>
* Matthew Willis <lilmatt@mozilla.com>
*
* 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
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://gre/modules/debug.js");
Components.utils.import("resource://calendar/modules/calUtils.jsm");
/*
* Scheduling and iTIP helper code;
* don't use deliberately, because it'll be moved into interfaces/components.
*
* May replace the current calItipProcessor.js code soon.
*/
EXPORTED_SYMBOLS = ["cal"]; // even though it's defined in calUtils.jsm, import needs this
cal.itip = {
/**
* Gets the sequence/revision number, either of the passed item or
* the last received one of an attendee; see
* <http://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04#section-7.1>.
*/
getSequence: function cal_itip_getSequence(item) {
let seq = null;
if (item instanceof Components.interfaces.calIAttendee) {
seq = item.getProperty("RECEIVED-SEQUENCE");
} else if (item) {
// Unless the below is standardized, we store the last original
// REQUEST/PUBLISH SEQUENCE in X-MOZ-RECEIVED-SEQUENCE to test against it
// when updates come in:
seq = item.getProperty("X-MOZ-RECEIVED-SEQUENCE");
if (seq === null) {
seq = item.getProperty("SEQUENCE");
}
// Make sure we don't have a pre Outlook 2007 appointment, but if we do
// use Microsoft's Sequence number. I <3 MS
if ((seq === null) || (seq == "0")) {
seq = item.getProperty("X-MICROSOFT-CDO-APPT-SEQUENCE");
}
}
if (seq === null) {
return 0;
} else {
seq = parseInt(seq, 10);
return (isNaN(seq) ? 0 : seq);
}
},
/**
* Gets the stamp date-time, either of the passed item or
* the last received one of an attendee; see
* <http://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04#section-7.2>.
*/
getStamp: function cal_itip_getStamp(item) {
let dtstamp = null;
if (item instanceof Components.interfaces.calIAttendee) {
let st = item.getProperty("RECEIVED-DTSTAMP");
if (st) {
dtstamp = cal.createDateTime(st);
}
} else if (item) {
// Unless the below is standardized, we store the last original
// REQUEST/PUBLISH DTSTAMP in X-MOZ-RECEIVED-DTSTAMP to test against it
// when updates come in:
let st = item.getProperty("X-MOZ-RECEIVED-DTSTAMP");
if (st) {
dtstamp = cal.createDateTime(st);
} else {
// xxx todo: are there similar X-MICROSOFT-CDO properties to be considered here?
dtstamp = item.stampTime;
}
}
return dtstamp;
},
/**
* Compares sequences and/or stamps of two parties; returns -1, 0, +1.
*/
compare: function cal_itip_compare(item1, item2) {
let seq1 = cal.itip.getSequence(item1);
let seq2 = cal.itip.getSequence(item2);
if (seq1 > seq2) {
return 1;
} else if (seq1 < seq2) {
return -1;
} else {
let st1 = cal.itip.getStamp(item1);
let st2 = cal.itip.getStamp(item2);
if (st1 && st2) {
return st1.compare(st2);
} else if (!st1 && st2) {
return -1;
} else if (st1 && !st2) {
return 1;
} else {
return 0;
}
}
},
/**
* Scope: iTIP message receiver
*
* Checks the passed iTIP item and calls the passed function with options offered.
*
* @param itipItem iTIP item
* @param optionsFunc function being called with parameters: itipItem, resultCode, actionFunc
* The action func has a property |method| showing the options:
* * REFRESH -- send the latest item (sent by attendee(s))
* * PUBLISH -- initial publish, no reply (sent by organizer)
* * PUBLISH:UPDATE -- update of a published item (sent by organizer)
* * REQUEST -- initial invitation (sent by organizer)
* * REQUEST:UPDATE -- rescheduling invitation, has major change (sent by organizer)
* * REQUEST:UPDATE-MINOR -- update of invitation, minor change (sent by organizer)
* * REPLY -- invitation reply (sent by attendee(s))
* * CANCEL -- invitation cancel (sent by organizer)
*/
processItipItem: function cal_itip_processItipItem(itipItem, optionsFunc) {
switch (itipItem.receivedMethod.toUpperCase()) {
case "REFRESH":
case "PUBLISH":
case "REQUEST":
case "CANCEL":
case "REPLY": {
// Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
// same ID, this simplifies our searching, we can just look for Item[0].id
let itemList = itipItem.getItemList({});
if (itemList.length > 0) {
itipItem.targetCalendar.getItem(itemList[0].id,
new ItipFindItemListener(itipItem, optionsFunc));
} else if (optionsFunc) {
optionsFunc(itipItem, Components.results.NS_OK);
}
break;
}
default: {
if (optionsFunc) {
optionsFunc(itipItem, Components.results.NS_ERROR_NOT_IMPLEMENTED);
}
break;
}
}
},
/**
* Scope: iTIP message sender
*
* Checks to see if e.g. attendees were added/removed or an item has been
* deleted and sends out appropriate iTIP messages.
*/
checkAndSend: function cal_itip_checkAndSend(aOpType, aItem, aOriginalItem) {
if (aOriginalItem && aOriginalItem.recurrenceId && !aItem.recurrenceId && aItem.recurrenceInfo) {
// sanity check: assure aItem doesn't refer to the master
aItem = aItem.recurrenceInfo.getOccurrenceFor(aOriginalItem.recurrenceId);
cal.ASSERT(aItem, "unexpected!");
if (!aItem) {
return;
}
}
let autoResponse = { value: false }; // controls confirm to send email only once
let invitedAttendee = ((cal.calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport) &&
aItem.calendar.isInvitation(aItem))
? aItem.calendar.getInvitedAttendee(aItem) : null);
if (invitedAttendee) { // actually is an invitation copy, fix attendee list to send REPLY
if (aItem.organizer) {
let origInvitedAttendee = (aOriginalItem && aOriginalItem.getAttendeeById(invitedAttendee.id));
if (aOpType == Components.interfaces.calIOperationListener.DELETE) {
// in case the attendee has just deleted the item, we want to send out a DECLINED REPLY:
origInvitedAttendee = invitedAttendee;
invitedAttendee = invitedAttendee.clone();
invitedAttendee.participationStatus = "DECLINED";
}
// We want to send a REPLY send if:
// - there has been a PARTSTAT change
// - in case of an organizer SEQUENCE bump we'd go and reconfirm our PARTSTAT
if (!origInvitedAttendee ||
(origInvitedAttendee.participationStatus != invitedAttendee.participationStatus) ||
(aOriginalItem && (cal.itip.getSequence(aItem) != cal.itip.getSequence(aOriginalItem)))) {
aItem = aItem.clone();
aItem.removeAllAttendees();
aItem.addAttendee(invitedAttendee);
sendMessage(aItem, "REPLY", [aItem.organizer], autoResponse);
}
}
return;
}
if (aItem.getProperty("X-MOZ-SEND-INVITATIONS") != "TRUE") { // Only send invitations/cancellations
// if the user checked the checkbox
return;
}
if (aOpType == Components.interfaces.calIOperationListener.DELETE) {
sendMessage(aItem, "CANCEL", aItem.getAttendees({}), autoResponse);
return;
} // else ADD, MODIFY:
let originalAtt = (aOriginalItem ? aOriginalItem.getAttendees({}) : []);
let itemAtt = aItem.getAttendees({});
let canceledAttendees = [];
if (itemAtt.length > 0 || originalAtt.length > 0) {
let attMap = {};
for each (let att in originalAtt) {
attMap[att.id.toLowerCase()] = att;
}
for each (let att in itemAtt) {
if (att.id.toLowerCase() in attMap) {
// Attendee was in original item.
delete attMap[att.id.toLowerCase()];
}
}
for each (let cancAtt in attMap) {
canceledAttendees.push(cancAtt);
}
}
// Check to see if some part of the item was updated, if so, re-send REQUEST
if (!aOriginalItem || aItem.generation != aOriginalItem.generation) { // REQUEST
let requestItem = aItem.clone();
// check whether it's a simple UPDATE (no SEQUENCE change) or real (RE)REQUEST,
// in case of time or location/description change.
let isUpdate = (aOriginalItem && (cal.itip.getSequence(aItem) == cal.itip.getSequence(aOriginalItem)));
if (!requestItem.organizer) {
let organizer = createAttendee();
organizer.id = requestItem.calendar.getProperty("organizerId");
organizer.commonName = requestItem.calendar.getProperty("organizerCN");
organizer.role = "REQ-PARTICIPANT";
organizer.participationStatus = "ACCEPTED";
organizer.isOrganizer = true;
requestItem.organizer = organizer;
}
// Fix up our attendees for invitations using some good defaults
let recipients = [];
let itemAtt = requestItem.getAttendees({});
if (!isUpdate) {
requestItem.removeAllAttendees();
}
for each (let attendee in itemAtt) {
if (!isUpdate) {
attendee = attendee.clone();
attendee.role = "REQ-PARTICIPANT";
attendee.participationStatus = "NEEDS-ACTION";
attendee.rsvp = "TRUE";
requestItem.addAttendee(attendee);
}
recipients.push(attendee);
}
if (recipients.length > 0) {
sendMessage(requestItem, "REQUEST", recipients, autoResponse);
}
}
// Cancel the event for all canceled attendees
if (canceledAttendees.length > 0) {
let cancelItem = aOriginalItem.clone();
cancelItem.removeAllAttendees();
for each (let att in canceledAttendees) {
cancelItem.addAttendee(att);
}
sendMessage(cancelItem, "CANCEL", canceledAttendees, autoResponse);
}
},
/**
* Bumps the SEQUENCE in case of a major change; XXX todo may need more fine-tuning.
*/
prepareSequence: function cal_itip_prepareSequence(newItem, oldItem) {
if (cal.isInvitation(newItem)) {
return newItem; // invitation copies don't bump the SEQUENCE
}
if (newItem.recurrenceId && !oldItem.recurrenceId && oldItem.recurrenceInfo) {
// XXX todo: there's still the bug that modifyItem is called with mixed occurrence/parent,
// find original occurrence
oldItem = oldItem.recurrenceInfo.getOccurrenceFor(newItem.recurrenceId);
cal.ASSERT(oldItem, "unexpected!");
if (!oldItem) {
return newItem;
}
}
function hashMajorProps(aItem) {
let propStrings = [];
function addProps(item) {
if (item) {
const majorProps = {
DTSTART: true,
DTEND: true,
DURATION: true,
DUE: true,
RDATE: true,
RRULE: true,
EXDATE: true,
STATUS: true,
LOCATION: true
};
cal.calIterateIcalComponent(
item.icalComponent,
function(subComp) {
for (let prop = subComp.getFirstProperty("ANY");
prop;
prop = subComp.getNextProperty("ANY")) {
if (majorProps[prop.propertyName]) {
propStrings.push(item.recurrenceId + "#" + prop.icalString);
}
}
},
cal.isEvent(item) ? "VEVENT" : "VTODO");
}
}
addProps(aItem);
let rec = (aItem && aItem.recurrenceInfo);
if (rec) {
rec.getExceptionIds({}).forEach(
function(rid) {
addProps(rec.getExceptionFor(rid, false));
});
}
propStrings.sort();
return propStrings.join("");
}
let h1 = hashMajorProps(newItem);
let h2 = hashMajorProps(oldItem);
if (h1 != h2) {
newItem = newItem.clone();
// bump SEQUENCE, it never decreases (mind undo scenario here)
newItem.setProperty("SEQUENCE",
String(Math.max(cal.itip.getSequence(oldItem),
cal.itip.getSequence(newItem)) + 1));
}
return newItem;
}
};
/** local to this module file
* Sets the received info either on the passed attendee or item object.
*
* @param item either calIAttendee or calIItemBase
* @param itipItemItem received iTIP item
*/
function setReceivedInfo(item, itipItemItem) {
item.setProperty((item instanceof Components.interfaces.calIAttendee) ? "RECEIVED-SEQUENCE"
: "X-MOZ-RECEIVED-SEQUENCE",
String(cal.itip.getSequence(itipItemItem)));
let dtstamp = cal.itip.getStamp(itipItemItem);
if (dtstamp) {
item.setProperty((item instanceof Components.interfaces.calIAttendee) ? "RECEIVED-DTSTAMP"
: "X-MOZ-RECEIVED-DTSTAMP",
dtstamp.getInTimezone(cal.UTC()).icalString);
}
}
/** local to this module file
* Creates an organizer calIAttendee object based on the calendar's configured organizer id.
*
* @return calIAttendee object
*/
function createOrganizer(aCalendar) {
let orgId = aCalendar.getProperty("organizerId");
if (!orgId) {
return null;
}
let organizer = cal.createAttendee();
organizer.id = orgId;
organizer.commonName = aCalendar.getProperty("organizerCN");
organizer.role = "REQ-PARTICIPANT";
organizer.participationStatus = "ACCEPTED";
organizer.isOrganizer = true;
return organizer;
}
/** local to this module file
* Sends an iTIP message using the passed item's calendar transport.
*
* @param aItem iTIP item to be sent
* @param aMethod iTIP method
* @param aRecipientsList an array of calIAttendee objects the message should be sent to
* @param autoResponse an inout object whether the transport should ask before sending
*/
function sendMessage(aItem, aMethod, aRecipientsList, autoResponse) {
if (aRecipientsList.length == 0) {
return;
}
if (cal.calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport) &&
aItem.calendar.canNotify(aMethod, aItem)) {
return; // provider will handle that
}
let aTransport = aItem.calendar.getProperty("itip.transport");
if (!aTransport) { // can only send if there's a transport for the calendar
return;
}
aTransport = aTransport.QueryInterface(Components.interfaces.calIItipTransport);
let itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
.createInstance(Components.interfaces.calIItipItem);
itipItem.init(cal.getSerializedItem(aItem));
itipItem.responseMethod = aMethod;
itipItem.targetCalendar = aItem.calendar;
itipItem.autoResponse = ((autoResponse && autoResponse.value) ? Components.interfaces.calIItipItem.AUTO
: Components.interfaces.calIItipItem.USER);
if (autoResponse) {
autoResponse.value = true; // auto every following
}
// XXX I don't know whether the below are used at all, since we don't use the itip processor
itipItem.isSend = true;
aTransport.sendItems(aRecipientsList.length, aRecipientsList, itipItem);
}
/** local to this module file
* An operation listener that is used on calendar operations which checks and sends further iTIP
* messages based on the calendar action.
*
* @param opListener operation listener to forward
* @param oldItem the previous item before modification (if any)
*/
function ItipOpListener(opListener, oldItem) {
this.mOpListener = opListener;
this.mOldItem = oldItem;
}
ItipOpListener.prototype = {
onOperationComplete: function ItipOpListener_onOperationComplete(aCalendar,
aStatus,
aOperationType,
aId,
aDetail) {
cal.ASSERT(Components.isSuccessCode(aStatus), "error on iTIP processing");
if (Components.isSuccessCode(aStatus)) {
cal.itip.checkAndSend(aOperationType, aDetail, this.mOldItem);
}
if (this.mOpListener) {
this.mOpListener.onOperationComplete(aCalendar,
aStatus,
aOperationType,
aId,
aDetail);
}
},
onGetResult: function ItipOpListener_onGetResult(aCalendar,
aStatus,
aItemType,
aDetail,
aCount,
aItems) {
}
};
/** local to this module file
* An operation listener triggered by cal.itip.processItipItem() for lookup of the sent iTIP item's UID.
*
* @param itipItem sent iTIP item
* @param optionsFunc options func, see cal.itip.processItipItem()
*/
function ItipFindItemListener(itipItem, optionsFunc) {
this.mItipItem = itipItem;
this.mOptionsFunc = optionsFunc;
this.mFoundItems = [];
}
ItipFindItemListener.prototype = {
mItipItem: null,
mOptionsFunc: null,
mFoundItems: null,
onOperationComplete: function ItipFindItemListener_onOperationComplete(aCalendar,
aStatus,
aOperationType,
aId,
aDetail) {
let rc = Components.results.NS_OK;
const method = this.mItipItem.receivedMethod.toUpperCase();
let actionMethod = method;
let operations = [];
if (this.mFoundItems.length > 0) {
cal.LOG("iTIP on " + method + ": found items.");
switch (method) {
// XXX todo: there's still a potential flaw, if multiple PUBLISH/REPLY/REQUEST on
// occurrences happen at once; those lead to multiple
// occurrence modifications. Since those modifications happen
// implicitly on the parent (ics/memory/storage calls modifyException),
// the generation check will fail. We should really consider to allow
// deletion/modification/addition of occurrences directly on the providers,
// which would ease client code a lot.
case "REFRESH":
case "PUBLISH":
case "REQUEST":
case "REPLY":
for each (let itipItemItem in this.mItipItem.getItemList({})) {
for each (let item in this.mFoundItems) {
let rid = itipItemItem.recurrenceId; // XXX todo support multiple
if (rid) { // actually a REPLY to single occurrence(s)
if (item.recurrenceInfo) {
item = item.recurrenceInfo.getOccurrenceFor(rid);
if (!item) {
continue;
}
} else if (item.recurrenceId && (item.recurrenceId.compare(rid) != 0)) {
// filter out non-parentless occurrences (future)
continue;
}
if (!itipItemItem.parentItem) { // install parent, if known from previous
// REQUEST/PUBLISH messages
itipItemItem.parentItem = newItem.parentItem;
}
}
switch (method) {
case "REFRESH": { // xxx todo test
let attendees = itipItemItem.getAttendees({});
cal.ASSERT(attendees.length == 1, "invalid number of attendees in REFRESH!");
if (attendees.length > 0) {
let action = function(opListener) {
if (!item.organizer) {
let org = createOrganizer(item.calendar);
if (org) {
item = item.clone();
item.organizer = org;
}
}
sendMessage(item, "REQUEST", attendees, true /* don't ask */);
};
operations.push(action);
}
break;
}
case "PUBLISH":
cal.ASSERT(itipItemItem.getAttendees({}).length == 0,
"invalid number of attendees in PUBLISH!");
if (item.calendar.getProperty("itip.disableRevisionChecks") ||
cal.itip.compare(itipItemItem, item) > 0) {
let newItem = itipItemItem.clone(); // xxx todo, this is dirty
setReceivedInfo(newItem, itipItemItem);
newItem.calendar = item.calendar;
newItem.generation = item.generation;
newItem.alarmOffset = item.alarmOffset;
newItem.alarmRelated = item.alarmRelated;
newItem.alarmLastAck = item.alarmLastAck;
let action = function(opListener) {
return newItem.calendar.modifyItem(newItem, item, opListener);
};
actionMethod = method + ":UPDATE";
operations.push(action);
}
break;
case "REQUEST":
if (item.calendar.getProperty("itip.disableRevisionChecks") ||
cal.itip.compare(itipItemItem, item) > 0) {
let newItem = itipItemItem.clone(); // xxx todo, this is dirty
setReceivedInfo(newItem, itipItemItem);
newItem.calendar = item.calendar;
newItem.generation = item.generation;
newItem.alarmOffset = item.alarmOffset;
newItem.alarmRelated = item.alarmRelated;
newItem.alarmLastAck = item.alarmLastAck;
let att = cal.getInvitedAttendee(newItem);
if (!att) { // fall back to using configured organizer
att = createOrganizer(newItem.calendar);
if (att) {
att.isOrganizer = false;
newItem.addAttendee(att);
}
}
if (att) {
let action = function(opListener, partStat) {
if (partStat) {
att.participationStatus = partStat;
}
return newItem.calendar.modifyItem(
newItem, item, new ItipOpListener(opListener, item));
};
let isMinorUpdate = (cal.itip.getSequence(newItem) ==
cal.itip.getSequence(item));
actionMethod = (isMinorUpdate ? method + ":UPDATE-MINOR"
: method + ":UPDATE");
operations.push(action);
}
}
break;
case "REPLY": {
let attendees = itipItemItem.getAttendees({});
cal.ASSERT(attendees.length == 1, "invalid number of attendees in REPLY!");
if (attendees.length > 0 &&
(item.calendar.getProperty("itip.disableRevisionChecks") ||
(cal.itip.compare(itipItemItem, item.getAttendeeById(attendees[0].id)) > 0))) {
// accepts REPLYs from previously uninvited attendees:
let newItem = item.clone();
let att = (item.getAttendeeById(attendees[0].id) || attendees[0]);
newItem.removeAttendee(att);
att = att.clone();
setReceivedInfo(att, itipItemItem);
att.participationStatus = attendees[0].participationStatus;
newItem.addAttendee(att);
let action = function(opListener) {
return newItem.calendar.modifyItem(
newItem, item,
newItem.calendar.getProperty("itip.notify-replies")
? new ItipOpListener(opListener, item)
: opListener);
};
operations.push(action);
}
break;
}
}
}
}
break;
case "CANCEL": {
let modifiedItems = {};
for each (let itipItemItem in this.mItipItem.getItemList({})) {
for each (let item in this.mFoundItems) {
let rid = itipItemItem.recurrenceId; // XXX todo support multiple
if (rid) { // actually a CANCEL of occurrence(s)
if (item.recurrenceInfo) {
// collect all occurrence deletions into a single parent modification:
let newItem = modifiedItems[item.id];
if (!newItem) {
newItem = item.clone();
modifiedItems[item.id] = newItem;
operations.push(
function(opListener) {
return newItem.calendar.modifyItem(newItem, item, opListener);
});
}
newItem.recurrenceInfo.removeOccurrenceAt(rid);
} else if (item.recurrenceId && (item.recurrenceId.compare(rid) == 0)) {
// parentless occurrence to be deleted (future)
operations.push(
function(opListener) {
return item.calendar.deleteItem(item, opListener);
});
}
} else {
operations.push(
function(opListener) {
return item.calendar.deleteItem(item, opListener);
});
}
}
}
break;
}
default:
rc = Components.results.NS_ERROR_NOT_IMPLEMENTED;
break;
}
} else { // not found:
cal.LOG("iTIP on " + method + ": no existing items.");
for each (let itipItemItem in this.mItipItem.getItemList({})) {
switch (method) {
case "REQUEST":
case "PUBLISH": {
let this_ = this;
let action = function(opListener, partStat) {
let newItem = itipItemItem.clone();
setReceivedInfo(newItem, itipItemItem);
newItem.calendar = this_.mItipItem.targetCalendar;
if (partStat) {
if (partStat != "DECLINED") {
cal.setDefaultAlarmValues(newItem);
}
let att = cal.getInvitedAttendee(newItem);
if (!att) { // fall back to using configured organizer
att = createOrganizer(newItem.calendar);
if (att) {
att.isOrganizer = false;
newItem.addAttendee(att);
}
}
if (att) {
att.participationStatus = partStat;
} else {
cal.ASSERT(att, "no attendee to reply REQUEST!");
return;
}
} else {
cal.ASSERT(itipItemItem.getAttendees({}).length == 0,
"invalid number of attendees in PUBLISH!");
}
return newItem.calendar.addItem(newItem,
(method == "REQUEST")
? new ItipOpListener(opListener, null)
: opListener);
};
operations.push(action);
break;
}
case "CANCEL": // has already been processed
break;
default:
rc = Components.results.NS_ERROR_NOT_IMPLEMENTED;
break;
}
}
}
cal.LOG("iTIP operations: " + operations.length);
let actionFunc = null;
if (operations.length > 0) {
actionFunc = function execOperations(opListener, partStat) {
for each (let op in operations) {
try {
op(opListener, partStat);
} catch (exc) {
cal.ERROR(exc);
}
}
};
actionFunc.method = actionMethod;
}
this.mOptionsFunc(this.mItipItem, rc, actionFunc);
},
onGetResult: function ItipFindItemListener_onGetResult(aCalendar,
aStatus,
aItemType,
aDetail,
aCount,
aItems) {
if (Components.isSuccessCode(aStatus)) {
this.mFoundItems = this.mFoundItems.concat(aItems);
}
}
};

Просмотреть файл

@ -37,7 +37,7 @@
// New code must not load/import calUtils.js, but should use calUtils.jsm.
var EXPORTED_SYMBOLS = ["cal"];
EXPORTED_SYMBOLS = ["cal"];
let cal = {
// new code should land here,
// and more code should be moved from calUtils.js into this object to avoid
@ -45,6 +45,8 @@ let cal = {
getIOService: generateServiceAccessor("@mozilla.org/network/io-service;1",
Components.interfaces.nsIIOService2),
getObserverService: generateServiceAccessor("@mozilla.org/observer-service;1",
Components.interfaces.nsIObserverService),
/**
* Loads an array of calendar scripts into the passed scope.
@ -59,8 +61,7 @@ let cal = {
let ioService = cal.getIOService();
if (!baseDir) {
baseDir = __LOCATION__.parent.parent;
baseDir = baseDir.clone();
baseDir = __LOCATION__.parent.parent.clone();
baseDir.append("calendar-js");
}
@ -82,6 +83,64 @@ let cal = {
return (!tz.icalComponent && !tz.isUTC && !tz.isFloating);
},
/**
* Iterates an array of items, i.e. the passed item including all
* overridden instances of a recurring series.
*
* @param items array of items
*/
itemIterator: function cal_itemIterator(items) {
return {
__iterator__: function itemIterator_() {
for each (let item in items) {
yield item;
let rec = item.recurrenceInfo;
if (rec) {
for each (let exid in rec.getExceptionIds({})) {
yield rec.getExceptionFor(exid, false);
}
}
}
}
};
},
/**
* Shortcut function to serialize an item (including all overridden items).
*/
getSerializedItem: function cal_getSerializedItem(aItem) {
let serializer = Components.classes["@mozilla.org/calendar/ics-serializer;1"]
.createInstance(Components.interfaces.calIIcsSerializer);
serializer.addItems([aItem], 1);
return serializer.serializeToString();
},
/**
* Shortcut function to check whether an item is an invitation copy.
*/
isInvitation: function cal_isInvitation(aItem) {
let isInvitation = false;
let calendar = aItem.calendar;
if (cal.calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
isInvitation = calendar.isInvitation(aItem);
}
return isInvitation;
},
/**
* Shortcut function to get the invited attendee of an item.
*/
getInvitedAttendee: function cal_getInvitedAttendee(aItem, aCalendar) {
if (!aCalendar) {
aCalendar = aItem.calendar;
}
let invitedAttendee = null;
if (cal.calInstanceOf(aCalendar, Components.interfaces.calISchedulingSupport)) {
invitedAttendee = aCalendar.getInvitedAttendee(aItem);
}
return invitedAttendee;
},
// The below functions will move to some different place once the
// unifinder tress are consolidated.
@ -245,7 +304,6 @@ let cal = {
aStringArray.sort(compare);
return aStringArray;
}
};
// local to this module;

Просмотреть файл

@ -71,7 +71,6 @@ XPIDLSRCS = calIAlarm.idl \
calIImportExport.idl \
calIItemBase.idl \
calIItipItem.idl \
calIItipProcessor.idl \
calIItipTransport.idl \
calIOperation.idl \
calIPeriod.idl \

Просмотреть файл

@ -114,9 +114,6 @@ interface calIItemBase : nsISupports
// last time a "significant change" was made to this item
readonly attribute calIDateTime stampTime;
// indicate such a "significant change"
void updateStampTime();
// the calICalendar to which this event belongs
attribute calICalendar calendar;

Просмотреть файл

@ -133,29 +133,6 @@ interface calIItipItem : nsISupports
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().
* @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
* @param attendeeId - AString containing attendee address

Просмотреть файл

@ -1,55 +0,0 @@
/* -*- Mode: idl; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Simdesk Technologies code.
*
* The Initial Developer of the Original Code is Simdesk Technologies Inc.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Clint Talbert <ctalbert.moz@gmail.com>
* Eva Or <evaor1012@yahoo.ca>
* Matthew Willis <lilmatt@mozilla.com>
*
* 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
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface calIItipItem;
interface calIOperationListener;
[scriptable, uuid(9787876b-0780-4464-8282-b7f86fb221e8)]
interface calIItipProcessor : nsISupports
{
/**
* Processes the given calItipItem based on the settings inside it.
* @param in calIItipItem itipItem A calItipItem to process.
* @param in calIIOperationListener An operation Listener to report status
* @return PRBool Whether processing succeeded or not.
*/
boolean processItipItem(in calIItipItem itipItem, in calIOperationListener aListener);
};

Просмотреть файл

@ -94,7 +94,6 @@ EXTRA_SCRIPTS = \
calIcsSerializer.js \
calItemBase.js \
calItipItem.js \
calItipProcessor.js \
calProtocolHandler.js \
calRecurrenceInfo.js \
calRelation.js \
@ -109,10 +108,6 @@ EXTRA_SCRIPTS = \
calTimezoneService.js \
$(NULL)
EXTRA_JS_MODULES = \
calUtils.jsm \
$(NULL)
# Use NSINSTALL to make the directory, as there's no mtime to preserve.
libs:: $(EXTRA_SCRIPTS)
if test ! -d $(FINAL_TARGET)/calendar-js; then $(NSINSTALL) -D $(FINAL_TARGET)/calendar-js; fi

Просмотреть файл

@ -1,4 +1,3 @@
/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
@ -51,7 +50,6 @@ function calEvent() {
this.eventPromotedProps = {
"DTSTART": true,
"DTEND": true,
"DTSTAMP": true,
__proto__: this.itemBasePromotedProps
}
}
@ -174,6 +172,7 @@ calEvent.prototype = {
throw Components.results.NS_ERROR_INVALID_ARG;
}
// xxx todo: Bug 463195 make sure object is properly reset
this.setItemBaseFromICS(event);
this.mapPropsFromICS(event, this.icsEventPropMap);

Просмотреть файл

@ -1,5 +1,4 @@
/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* ***** BEGIN LICENSE BLOCK *****
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
@ -57,8 +56,8 @@ function QueryInterface(aIID) {
calIcsParser.prototype.parseString =
function ip_parseString(aICSString, aTzProvider) {
var rootComp = getIcsService().parseICS(aICSString, aTzProvider);
var calComp;
let rootComp = getIcsService().parseICS(aICSString, aTzProvider);
let 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.
@ -68,9 +67,9 @@ function ip_parseString(aICSString, aTzProvider) {
calComp = rootComp.getFirstSubcomponent('VCALENDAR');
}
var unexpandedItems = [];
var uid2parent = {};
var excItems = [];
let uid2parent = {};
let excItems = [];
let fakedParents = {};
let tzErrors = {};
function checkTimezone(item, dt) {
@ -95,7 +94,7 @@ function ip_parseString(aICSString, aTzProvider) {
while (calComp) {
// Get unknown properties
var prop = calComp.getFirstProperty("ANY");
let prop = calComp.getFirstProperty("ANY");
while (prop) {
if (prop.propertyName != "VERSION" &&
prop.propertyName != "PRODID") {
@ -104,15 +103,15 @@ function ip_parseString(aICSString, aTzProvider) {
prop = calComp.getNextProperty("ANY");
}
var prodId = calComp.getFirstProperty("PRODID");
var isFromOldSunbird;
let prodId = calComp.getFirstProperty("PRODID");
let isFromOldSunbird;
if (prodId) {
isFromOldSunbird = prodId.value == "-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN";
}
var subComp = calComp.getFirstSubcomponent("ANY");
let subComp = calComp.getFirstSubcomponent("ANY");
while (subComp) {
var item = null;
let item = null;
switch (subComp.componentType) {
case "VEVENT":
item = cal.createEvent();
@ -144,9 +143,9 @@ function ip_parseString(aICSString, aTzProvider) {
item = fixOldSunbirdExceptions(item);
}
var rid = item.recurrenceId;
let rid = item.recurrenceId;
if (!rid) {
unexpandedItems.push(item);
this.mItems.push(item);
if (item.recurrenceInfo) {
uid2parent[item.id] = item;
}
@ -162,19 +161,31 @@ function ip_parseString(aICSString, aTzProvider) {
}
// tag "exceptions", i.e. items with rid:
for each (var item in excItems) {
var parent = uid2parent[item.id];
if (parent) {
parent.recurrenceInfo.modifyException(item, true);
} else { // a parentless one
for each (let item in excItems) {
let parent = uid2parent[item.id];
if (!parent) { // a parentless one, fake a master and override it's occurrence
parent = isEvent(item) ? createEvent() : createTodo();
parent.id = item.id;
parent.setProperty("DTSTART", item.recurrenceId);
parent.setProperty("X-MOZ-FAKED-MASTER", "1"); // this tag might be useful in the future
parent.recurrenceInfo = cal.createRecurrenceInfo(parent);
fakedParents[item.id] = true;
uid2parent[item.id] = parent;
this.mItems.push(parent);
}
if (item.id in fakedParents) {
let rdate = Components.classes["@mozilla.org/calendar/recurrence-date;1"]
.createInstance(Components.interfaces.calIRecurrenceDate);
rdate.date = item.recurrenceId;
parent.recurrenceInfo.appendRecurrenceItem(rdate);
// we'll keep the parentless-API until we switch over using itip-process for import (e.g. in dnd code)
this.mParentlessItems.push(item);
}
parent.recurrenceInfo.modifyException(item, true);
}
for each (var item in unexpandedItems) {
this.mItems.push(item);
}
for (let e in tzErrors) { // if any error has occurred
// Use an alert rather than a prompt because problems may appear in
// remote subscribed calendars the user cannot change.

Просмотреть файл

@ -35,7 +35,7 @@
*
* ***** END LICENSE BLOCK ***** */
// Import
Components.utils.import("resource://calendar/modules/calUtils.jsm");
function calIcsSerializer() {
this.wrappedJSObject = this;
@ -102,6 +102,9 @@ function is_getIcalComponent() {
var calComp = getIcsService().createIcalComponent("VCALENDAR");
calSetProdidVersion(calComp);
// xxx todo: think about that the below code doesn't clone the properties/components,
// thus ownership is moved to returned VCALENDAR...
for each (var prop in this.mProperties) {
calComp.addProperty(prop);
}
@ -109,18 +112,9 @@ function is_getIcalComponent() {
calComp.addSubcomponent(comp);
}
for each (var item in this.mItems) {
for each (let item in cal.itemIterator(this.mItems)) {
calComp.addSubcomponent(item.icalComponent);
var rec = item.recurrenceInfo;
if (rec != null) {
var exceptions = rec.getExceptionIds({});
for each (var exid in exceptions) {
var ex = rec.getExceptionFor(exid, false);
if (ex != null) {
calComp.addSubcomponent(ex.icalComponent);
}
}
}
}
return calComp;
}

Просмотреть файл

@ -1,4 +1,3 @@
/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
@ -137,15 +136,17 @@ calItemBase.prototype = {
},
ensureNotDirty: function() {
if (!this.mDirty)
if (!this.mDirty) {
return;
}
if (this.mImmutable) {
dump ("### Something tried to undirty a dirty immutable event!\n");
throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
}
this.setProperty("LAST-MODIFIED", jsDateToDateTime(new Date()));
var now = jsDateToDateTime(new Date());
this.setProperty("LAST-MODIFIED", now);
this.setProperty("DTSTAMP", now.clone());
this.mDirty = false;
},
@ -202,9 +203,7 @@ calItemBase.prototype = {
this.mProperties = new calPropertyBag();
this.mPropertyParams = {};
this.setProperty("CREATED", now.clone());
this.setProperty("LAST-MODIFIED", now.clone());
this.setProperty("DTSTAMP", now);
this.setProperty("CREATED", now);
this.mAttendees = null;
@ -222,8 +221,6 @@ calItemBase.prototype = {
// for subclasses to use; copies the ItemBase's values
// into m. aNewParent is optional
cloneItemBaseInto: function (m, aNewParent) {
this.ensureNotDirty();
m.mImmutable = false;
m.mIsProxy = this.mIsProxy;
m.mParentItem = (calTryWrappedJSObject(aNewParent) || this.mParentItem);
@ -269,8 +266,6 @@ calItemBase.prototype = {
}
}
m.mDirty = false;
if (this.mAttachments) {
m.mAttachments = this.mAttachments.concat([]);
}
@ -296,6 +291,8 @@ calItemBase.prototype = {
}
m.alarmRelated = this.alarmRelated;
m.mDirty = this.mDirty;
return m;
},
@ -331,19 +328,8 @@ calItemBase.prototype = {
},
get stampTime() {
var prop = this.getProperty("DTSTAMP");
if (prop && prop.isValid)
return prop;
return this.getProperty("LAST-MODIFIED");
},
updateStampTime: function() {
// can't update the stamp time on an immutable event
if (this.mImmutable)
return;
this.modify();
this.setProperty("DTSTAMP", jsDateToDateTime(new Date()));
this.ensureNotDirty();
return this.getProperty("DTSTAMP");
},
get propertyEnumerator() {
@ -410,26 +396,28 @@ calItemBase.prototype = {
},
setProperty: function (aName, aValue) {
if (aName == "LAST-MODIFIED") {
this.mDirty = false;
} else {
this.modify();
}
this.modify();
aName = aName.toUpperCase();
if (aValue || !isNaN(parseInt(aValue, 10))) {
this.mProperties.setProperty(aName.toUpperCase(), aValue);
this.mProperties.setProperty(aName, aValue);
} else {
this.deleteProperty(aName);
}
if (aName == "LAST-MODIFIED") {
// setting LAST-MODIFIED cleans/undirties the item, we use this for preserving DTSTAMP
this.mDirty = false;
}
},
deleteProperty: function (aName) {
this.modify();
aName = aName.toUpperCase();
if (this.mIsProxy) {
// deleting a proxy's property will mark the bag's item as null, so we could
// distinguish it when enumerating/getting properties from the undefined ones.
this.mProperties.setProperty(aName.toUpperCase(), null);
this.mProperties.setProperty(aName, null);
} else {
this.mProperties.deleteProperty(aName.toUpperCase());
this.mProperties.deleteProperty(aName);
}
},
@ -639,7 +627,8 @@ calItemBase.prototype = {
"ATTACH": true,
"CATEGORIES": true,
"ORGANIZER": true,
"RECURRENCE-ID": true
"RECURRENCE-ID": true,
"X-MOZ-LASTACK": true
},
icsBasePropMap: [
@ -823,8 +812,6 @@ calItemBase.prototype = {
},
fillIcalComponentFromBase: function (icalcomp) {
// Make sure that the LMT and ST are updated
this.updateStampTime();
this.ensureNotDirty();
this.mapPropsToICS(icalcomp, this.icsBasePropMap);

Просмотреть файл

@ -184,11 +184,6 @@ const componentData =
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",

Просмотреть файл

@ -43,11 +43,13 @@
*/
function calItipItem() {
this.wrappedJSObject = this;
this.mIsInitialized = false;
this.mCurrentItemIndex = 0;
}
calItipItem.prototype = {
mIsInitialized: false,
// nsIClassInfo:
getInterfaces: function ciiGI(count) {
var ifaces = [
Components.interfaces.nsIClassInfo,
@ -68,13 +70,8 @@ calItipItem.prototype = {
implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
flags: 0,
QueryInterface: function ciiQI(aIid) {
if (!aIid.equals(Components.interfaces.nsISupports) &&
!aIid.equals(Components.interfaces.calIItipItem)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
QueryInterface: function ciiQI(aIID) {
return doQueryInterface(this, calItipItem.prototype, aIID, null, this);
},
mIsSend: false,
@ -85,7 +82,7 @@ calItipItem.prototype = {
return (this.mIsSend = aValue);
},
mReceivedMethod: null,
mReceivedMethod: "REQUEST",
get receivedMethod() {
return this.mReceivedMethod;
},
@ -93,44 +90,16 @@ calItipItem.prototype = {
return (this.mReceivedMethod = aMethod.toUpperCase());
},
mResponseMethod: null,
mResponseMethod: "REPLY",
get responseMethod() {
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(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 {
if (!this.mIsInitialized) {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
}
return this.mResponseMethod;
},
set responseMethod(aMethod) {
return (this.mResponseMethod = aMethod.toUpperCase());
},
mAutoResponse: null,
get autoResponse() {
@ -164,33 +133,57 @@ calItipItem.prototype = {
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,
mItemList: {},
init: function ciiI(aIcalString) {
var parser = Components.classes["@mozilla.org/calendar/ics-parser;1"].
createInstance(Components.interfaces.calIIcsParser);
let parser = Components.classes["@mozilla.org/calendar/ics-parser;1"]
.createInstance(Components.interfaces.calIIcsParser);
parser.parseString(aIcalString, null);
this.mItemList = parser.getItems({});
this.mPropertiesList = parser.getProperties({});
// User specific alarms as well as X-MOZ- properties are irrelevant w.r.t. iTIP messages,
// should not be sent out and should not be relevant for incoming messages,
// - User specific alarms as well as X-MOZ- properties are irrelevant w.r.t. iTIP messages,
// should not be sent out and should not be relevant for incoming messages
// - faked master items
// so clean them out:
for each (var item in this.mItemList) {
function cleanItem(item) {
// the following changes will bump LAST-MODIFIED/DTSTAMP, we want to preserve the originals:
let stamp = item.stampTime;
let lastModified = item.lastModifiedTime;
item.alarmOffset = null;
var propEnum = item.propertyEnumerator;
item.alarmLastAck = null;
item.deleteProperty("RECEIVED-SEQUENCE");
item.deleteProperty("RECEIVED-DTSTAMP");
let propEnum = item.propertyEnumerator;
while (propEnum.hasMoreElements()) {
var prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
if (prop.name.substr(0, "X-MOZ-".length) == "X-MOZ-") {
let prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
let pname = prop.name;
if (pname != "X-MOZ-FAKED-MASTER" && pname.substr(0, "X-MOZ-".length) == "X-MOZ-") {
item.deleteProperty(prop.name);
}
}
// never publish an organizer's RECEIVED params:
item.getAttendees({}).forEach(
function(att) {
att.deleteProperty("RECEIVED-SEQUENCE");
att.deleteProperty("RECEIVED-DTSTAMP");
});
item.setProperty("DTSTAMP", stamp);
item.setProperty("LAST-MODIFIED", lastModified); // need to be last to undirty the item
}
this.mItemList = [];
for each (let item in cal.itemIterator(parser.getItems({}))) {
cleanItem(item);
// only push non-faked master items or
// the overridden instances of faked master items
// to the list:
if (item == item.parentItem) {
if (!item.hasProperty("X-MOZ-FAKED-MASTER")) {
this.mItemList.push(item);
}
} else if (item.parentItem.hasProperty("X-MOZ-FAKED-MASTER")) {
this.mItemList.push(item);
}
}
// We set both methods now for safety's sake. It's the ItipProcessor's
@ -198,44 +191,28 @@ calItipItem.prototype = {
// 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) {
for each (var prop in parser.getProperties({})) {
if (prop.propertyName == "METHOD") {
method = prop.value;
this.mReceivedMethod = prop.value;
this.mResponseMethod = prop.value;
break;
}
}
this.mReceivedMethod = method;
this.mResponseMethod = method;
this.mIsInitialized = true;
},
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 = Components.classes["@mozilla.org/calendar/itip-item;1"].
createInstance(Components.interfaces.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.identity = this.identity;
newItem.localStatus = this.localStatus;
newItem.isSend = this.isSend;
let newItem = new calItipItem();
newItem.mItemList = this.mItemList.map(function(item) { return item.clone(); });
newItem.mReceivedMethod = this.mReceivedMethod;
newItem.mResponseMethod = this.mResponseMethod;
newItem.mAutoResponse = this.mAutoResponse;
newItem.mTargetCalendar = this.mTargetCalendar;
newItem.mIdentity = this.mIdentity;
newItem.mLocalStatus = this.mLocalStatus;
newItem.mIsSend = this.mIsSend;
newItem.mIsInitialized = this.mIsInitialized;
return newItem;
},
@ -244,33 +221,13 @@ calItipItem.prototype = {
* call it is: var itemArray = itipItem.getItemList({ });
*/
getItemList: function ciiGIL(itemCountRef) {
if (!this.mIsInitialized || (this.mItemList.length == 0)) {
if (!this.mIsInitialized) {
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;
}
this.mCurrentItemIndex = 0;
return this.mItemList[0];
},
getNextItem: function ciiGNI() {
if (!this.mIsInitialized || (this.mItemList.length == 0)) {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
}
++this.mCurrentItemIndex;
if (this.mCurrentItemIndex < this.mItemList.length) {
return this.mItemList[this.mCurrentItemIndex];
} else {
return null;
}
},*/
/**
* Note that this code forces the user to respond to all items in the same
* way, which is a current limitation of the spec.
@ -278,7 +235,7 @@ calItipItem.prototype = {
setAttendeeStatus: function ciiSAS(aAttendeeId, aStatus) {
// Append "mailto:" to the attendee if it is missing it.
aAttendeeId = aAttendeeId.toLowerCase();
if (!aAttendeeId.match(/mailto:/i)) {
if (!aAttendeeId.match(/^mailto:/i)) {
aAttendeeId = ("mailto:" + aAttendeeId);
}

Просмотреть файл

@ -1,444 +0,0 @@
/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Simdesk Technologies code.
*
* The Initial Developer of the Original Code is Simdesk Technologies Inc.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Clint Talbert <ctalbert.moz@gmail.com>
* Eva Or <evaor1012@yahoo.ca>
* Matthew Willis <lilmatt@mozilla.com>
* Philipp Kewisch <mozilla@kewis.ch>
* Daniel Boelzle <daniel.boelzle@sun.com>
*
* 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
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Operations on the calendar
const CAL_ITIP_PROC_ADD_OP = 1;
const CAL_ITIP_PROC_UPDATE_OP = 2;
const CAL_ITIP_PROC_DELETE_OP = 3;
/**
* Constructor of calItipItem object
*/
function calItipProcessor() {
this.wrappedJSObject = this;
}
calItipProcessor.prototype = {
getInterfaces: function cipGI(count) {
var ifaces = [
Components.interfaces.nsIClassInfo,
Components.interfaces.nsISupports,
Components.interfaces.calIItipProcessor
];
count.value = ifaces.length;
return ifaces;
},
getHelperForLanguage: function cipGHFL(aLanguage) {
return null;
},
contractID: "@mozilla.org/calendar/itip-processor;1",
classDescription: "Calendar iTIP processor",
classID: Components.ID("{9787876b-0780-4464-8282-b7f86fb221e8}"),
implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
flags: 0,
QueryInterface: function cipQI(aIid) {
if (!aIid.equals(Components.interfaces.nsIClassInfo) &&
!aIid.equals(Components.interfaces.nsISupports) &&
!aIid.equals(Components.interfaces.calIItipProcessor))
{
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
},
mIsUserInvolved: false,
get isUserInvolved() {
return this.mIsUserInvolved;
},
set isUserInvolved(aValue) {
return (this.mIsUserInvolved = aValue);
},
/**
* Processes the given calItipItem based on the settings inside it.
* @param calIItipItem A calItipItem to process.
* @param calIOperationListener A calIOperationListener to return status
* @return boolean Whether processing succeeded or not.
*/
processItipItem: function cipPII(aItipItem, aListener) {
// Sanity check the input
if (!aItipItem) {
throw new Components.Exception("processItipItem: " +
"Invalid or non-existant " +
"itipItem passed in.",
Components.results.NS_ERROR_INVALID_ARG);
}
// Clone the passed in itipItem like a sheep.
var respItipItem = aItipItem.clone();
var recvMethod = respItipItem.receivedMethod;
respItipItem.responseMethod = this._suggestResponseMethod(recvMethod);
var respMethod = respItipItem.responseMethod;
var autoResponse = respItipItem.autoResponse;
var targetCalendar = respItipItem.targetCalendar;
// Sanity checks using the first item
var itemList = respItipItem.getItemList({ });
var calItem = itemList[0];
if (!calItem) {
throw new Error ("processItipItem: " +
"getFirstItem() found no items!");
}
var calItemType = this._getCalItemType(calItem);
if (!calItemType) {
throw new Error ("processItipItem: " +
"_getCalItemType() found no item type!");
}
// Sanity check that mRespMethod is a valid response per the spec.
if (!this._isValidResponseMethod(recvMethod, respMethod, calItemType)) {
throw new Error ("processItipItem: " +
"_isValidResponseMethod() found an invalid " +
"response method: " + respMethod);
}
// Check to see if we have an existing item or not, then continue
// processing appropriately
for (var i = 0; i < itemList.length; i++) {
this._isExistingItem(itemList[i], aItipItem, recvMethod, respMethod,
targetCalendar, aListener);
}
// Send the appropriate response
// figure out a good way to determine when a response is needed!
if (recvMethod != respMethod) {
// XXX discuss: does it make sense to check targetCalendar.canNotify(respMethod, ...) here?
// _isExistingItem will store the item
this._getTransport(targetCalendar).sendItems(1, [calItem.organizer], respItipItem);
}
},
/* Continue processing the iTip Item now that we have determined whether
* there is an existing item or not.
*/
_continueProcessingItem: function cipCPI(newItem, existingItem, aItipItem,
recvMethod, respMethod, calAction,
targetCalendar, aListener) {
var invitedAttendee = null;
// we should make calISchedulingSupport mandatory
if (calInstanceOf(newItem.calendar, Components.interfaces.calISchedulingSupport)) {
invitedAttendee = newItem.calendar.getInvitedAttendee(newItem);
}
if (!invitedAttendee && aItipItem.identity) { // try to fall back to itip item's identity
invitedAttendee = newItem.getAttendeeById(this._getTransport(aItipItem.targetCalendar).scheme + ":" +
aItipItem.identity);
}
switch (recvMethod) {
case "REQUEST":
if (invitedAttendee) {
// Only add to calendar if we accepted invite
if (invitedAttendee.participationStatus == "DECLINED") {
break;
}
} else {
LOG("no attendee found.");
return;
} // else fall through
case "PUBLISH":
if (!this._processCalendarAction(newItem,
existingItem,
calAction,
targetCalendar,
aListener))
{
throw new Error ("processItipItem: " +
"_processCalendarAction failed!");
}
break;
case "REPLY":
case "REFRESH":
case "ADD":
case "CANCEL":
case "COUNTER":
case "DECLINECOUNTER":
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
default:
throw new Error("processItipItem: " +
"Received unknown method: " +
recvMethod);
}
// The below is somehow hard to understand --
// _isExistingItem modifies the itip item's calendar items, we should change that.
// Moreover it doesn't work if getItem is performed async.
// TODO bug 431127: This is email specific -> Move to transport
// When replying, the reply must only contain the ORGANIZER and the
// status of the ATTENDEE that represents ourselves. Therefore we must
// remove all other ATTENDEEs from the itipItem we send back.
if (respMethod == "REPLY") {
// Get the id that represents me.
newItem.removeAllAttendees();
ASSERT(invitedAttendee, "attendee unknown!");
newItem.addAttendee(invitedAttendee);
}
},
/**
* @return integer The next recommended iTIP state.
*/
_suggestResponseMethod: function cipSRM(aRecvMethod) {
switch (aRecvMethod) {
case "REQUEST":
return "REPLY";
case "REFRESH":
case "COUNTER":
return "REQUEST";
case "PUBLISH":
case "REPLY":
case "ADD":
case "CANCEL":
case "DECLINECOUNTER":
return aRecvMethod;
default:
throw new Error("_suggestResponseMethod: " +
"Received unknown method: " +
aRecvMethod);
}
},
/**
* Given mRecvMethod and mRespMethod, this checks that mRespMethod is
* valid according to the spec.
*
* @return boolean Whether or not mRespMethod is valid.
*/
_isValidResponseMethod: function cipIAR(aRecvMethod,
aRespMethod,
aCalItemType) {
switch (aRecvMethod) {
// We set response to ADD automatically, but if the GUI did not
// find the event the user may set it to REFRESH as per the spec.
// These are the only two valid responses.
case "ADD":
if (!(aRespMethod == "ADD" ||
(aRespMethod == "REFRESH" &&
// REFRESH is not a valid response to an ADD for VJOURNAL
(aCalItemType == Components.interfaces.calIEvent ||
aCalItemType == Components.interfaces.calITodo))))
{
return false;
}
break;
// Valid responses to COUNTER are REQUEST or DECLINECOUNTER.
case "COUNTER":
if (!(aRespMethod == "REQUEST" ||
aRespMethod == "DECLINECOUNTER"))
{
return false;
}
break;
// Valid responses to REQUEST are:
// REPLY (accept or error)
// REQUEST (delegation, inviting someone else)
// COUNTER (propose a change)
case "REQUEST":
if (!(aRespMethod == "REPLY" ||
aRespMethod == "REQUEST" ||
aRespMethod == "COUNTER"))
{
return false;
}
break;
// REFRESH should respond with a request
case "REFRESH":
if (aRespMethod == "REQUEST") {
return false;
}
break;
// The rest are easiest represented as:
// (aRecvMethod != aRespMethod) == return false
case "PUBLISH":
case "CANCEL":
case "REPLY":
case "PUBLISH":
case "DECLINECOUNTER":
if (aRespMethod != aRecvMethod) {
return false;
}
break;
default:
throw new Error("_isValidResponseMethod: " +
"Received unknown method: " +
aRecvMethod);
}
// If we got to here, then the combination is valid.
return true;
},
/**
* Helper to return whether an item is an event, todo, etc.
*/
_getCalItemType: function cipGCIT(aCalItem) {
if (isEvent(aCalItem)) {
return Components.interfaces.calIEvent;
} else if (isToDo(aCalItem)) {
return Components.interfaces.calITodo;
}
throw new Error ("_getCalItemType: " +
"mCalItem item type is unknown");
},
/**
* This performs the actual add/update/delete of an event on the user's
* calendar.
*/
_processCalendarAction: function cipPCA(aCalItem,
aExistingItem,
aOperation,
aTargetCalendar,
aListener) {
switch (aOperation) {
case CAL_ITIP_PROC_ADD_OP:
aTargetCalendar.addItem(aCalItem, aListener);
// XXX Change this to reflect the success or failure of adding
// the item to the calendar.
return true;
case CAL_ITIP_PROC_UPDATE_OP:
// To udpate, we must require the existing item to be set
if (!aExistingItem)
throw new Error("_processCalendarAction: Item to update not found");
// TODO: Handle generation properly - Bug 418345
aCalItem.generation = aExistingItem.generation;
// We also have to ensure that the calendar is set properly on
// the new item, or items with alarms will throw during the
// notification process
aCalItem.calendar = aExistingItem.calendar;
aTargetCalendar.modifyItem(aCalItem, aExistingItem, aListener);
return true;
case CAL_ITIP_PROC_DELETE_OP:
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
default:
throw new Error("_processCalendarAction: " +
"Undefined Operation: " + aOperation);
}
// If you got to here, something went horribly, horribly wrong.
return false;
},
/**
* Helper function to determine if this item already exists on this calendar
* or not. It then calls _continueProcessingItem setting calAction and
* existingItem appropirately
*/
_isExistingItem: function cipIEI(aCalItem, aItipItem, aRecvMethod, aRespMethod,
aTargetCal, aListener) {
var foundItemListener = {
itipProcessor: this,
mFoundItem: null,
onOperationComplete:
function (aCalendar, aStatus, aOperationType, aId, aDetail) {
if (Components.isSuccessCode(aStatus)) {
this.itipProcessor._continueProcessingItem(aCalItem,
this.mFoundItem,
aItipItem,
aRecvMethod,
aRespMethod,
(this.mFoundItem
? CAL_ITIP_PROC_UPDATE_OP
: CAL_ITIP_PROC_ADD_OP),
aTargetCal,
aListener);
}
},
onGetResult:
function onget(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
if (Components.isSuccessCode(aStatus) && aCount && aItems[0]) {
this.mFoundItem = aItems[0]; // take any
}
}
};
if (aTargetCal) {
aTargetCal.getItem(aCalItem.id, foundItemListener);
} else {
// Then we do not have a target calendar to search,
// this is probably a DECLINE reply or some other such response,
// allow it to pass through
this._continueProcessingItem(aCalItem, null, aItipItem, aRecvMethod,
aRespMethod, null, aTargetCal, aListener);
}
},
/**
* Centralized location for obtaining the proper transport. If a calendar is
* specified, the transport is taken from the provider. Otherwise, the
* default email transport is returned.
*
* Its ok to assume there is an itip.transport here, since if it would
* return null (i.e imip is disabled) then we never get here, since the
* respective calendar will not be available as a target calendar.
*/
_getTransport: function cipGT(aCalendar) {
if (aCalendar) {
return aCalendar.getProperty("itip.transport")
.QueryInterface(Components.interfaces.calIItipTransport);
} else {
return Components.classes["@mozilla.org/calendar/itip-transport;1?type=email"]
.getService(Components.interfaces.calIItipTransport);
}
}
}

Просмотреть файл

@ -749,7 +749,6 @@ calRecurrenceInfo.prototype = {
} else {
itemtoadd = anItem.cloneShallow(this.mBaseItem);
}
itemtoadd.makeImmutable();
// we're going to assume that the recurrenceId is valid here,
// because presumably the item came from one of our functions

Просмотреть файл

@ -1,4 +1,3 @@
/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
@ -51,7 +50,6 @@ function calTodo() {
this.todoPromotedProps = {
"DTSTART": true,
"DTEND": true,
"DTSTAMP": true,
"DUE": true,
"COMPLETED": true,
__proto__: this.itemBasePromotedProps
@ -202,6 +200,7 @@ calTodo.prototype = {
throw Components.results.NS_ERROR_INVALID_ARG;
}
// xxx todo: Bug 463195 make sure object is properly reset
this.setItemBaseFromICS(todo);
this.mapPropsFromICS(todo, this.icsEventPropMap);
this.mIsAllDay = this.mStartDate && this.mStartDate.isDate;

Просмотреть файл

@ -153,13 +153,19 @@ calTransaction.prototype = {
aOperationType,
aId,
aDetail) {
if (aStatus == Components.results.NS_OK &&
(aOperationType == Components.interfaces.calIOperationListener.ADD ||
aOperationType == Components.interfaces.calIOperationListener.MODIFY)) {
if (this.mIsDoTransaction) {
this.mItem = aDetail;
} else {
this.mOldItem = aDetail;
if (Components.isSuccessCode(aStatus)) {
cal.itip.checkAndSend(aOperationType,
aDetail,
this.mIsDoTransaction ? this.mOldItem : this.mItem);
if (aOperationType == Components.interfaces.calIOperationListener.ADD ||
aOperationType == Components.interfaces.calIOperationListener.MODIFY) {
if (this.mIsDoTransaction) {
this.mItem = aDetail;
} else {
this.mOldItem = aDetail;
}
}
}
if (this.mListener) {
@ -199,7 +205,7 @@ calTransaction.prototype = {
this.mOldCalendar.deleteItem(this.mOldItem, this);
this.mCalendar.addItem(this.mItem, this);
} else {
this.mCalendar.modifyItem(this.mItem,
this.mCalendar.modifyItem(cal.itip.prepareSequence(this.mItem, this.mOldItem),
this.mOldItem,
this);
}
@ -225,7 +231,8 @@ calTransaction.prototype = {
this.mCalendar.deleteItem(this.mItem, this);
this.mOldCalendar.addItem(this.mOldItem, this);
} else {
this.mCalendar.modifyItem(this.mOldItem, this.mItem, this);
this.mCalendar.modifyItem(cal.itip.prepareSequence(this.mOldItem, this.mItem),
this.mItem, this);
}
break;
case 'delete':

Просмотреть файл

@ -37,6 +37,8 @@
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://gre/modules/debug.js");
/* This file contains commonly used functions in a centralized place so that
* various components (and other js scopes) don't need to replicate them. Note
* that loading this file twice in the same scope will throw errors.
@ -915,6 +917,7 @@ function LOG(aArg) {
string = aArg;
}
// xxx todo consider using function debug()
dump(string + '\n');
getConsoleService().logStringMessage(string);
}
@ -985,12 +988,11 @@ function ASSERT(aCondition, aMessage, aCritical) {
return;
}
var string = "Assert failed: " + aMessage + '\n' + STACK(null, 1);
NS_ASSERT(aCondition, aMessage);
if (aCritical) {
let string = "Assert failed: " + aMessage + '\n' + STACK(null, 1);
throw new Components.Exception(string,
aCritical === true ? Components.results.NS_ERROR_UNEXPECTED : aCritical);
} else {
Components.utils.reportError(string);
}
}
@ -1408,16 +1410,24 @@ function sameDay(date1, date2) {
/**
* Iterates all components inside the passed ical component and calls the passed function.
* If the called function returns false, iteration is stopped.
*
* @param icalComp an ICS component
* @param func functor that will be executed on sub components
* @param compType optional component type to filter, defaults to "ANY"
*/
function calIterateIcalComponent(icalComp, func) {
function calIterateIcalComponent(icalComp, func, compType) {
if (icalComp) {
if (icalComp.componentType != "VCALENDAR") {
return func(icalComp);
if (!compType) {
compType = "ANY";
}
var ctype = icalComp.componentType;
if (ctype != "VCALENDAR") {
return (ctype == compType ? func(icalComp) : true);
}
for (var subComp = icalComp.getFirstSubcomponent("ANY");
subComp;
subComp = icalComp.getNextSubcomponent("ANY")) {
if (!calIterateIcalComponent(subComp, func)) {
if (!calIterateIcalComponent(subComp, func, compType)) {
return false;
}
}
@ -1868,6 +1878,56 @@ function binaryInsert(itemArray, item, comptor, discardDuplicates) {
return newIndex;
}
/**
* Read default alarm settings from user preferences and apply them to
* the event/todo passed in.
*
* @param aItem The event or todo the settings should be applied to.
*/
function setDefaultAlarmValues(aItem)
{
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
var alarmsBranch = prefService.getBranch("calendar.alarms.");
if (isEvent(aItem)) {
try {
if (alarmsBranch.getIntPref("onforevents") == 1) {
var alarmOffset = Components.classes["@mozilla.org/calendar/duration;1"]
.createInstance(Components.interfaces.calIDuration);
var units = alarmsBranch.getCharPref("eventalarmunit");
alarmOffset[units] = alarmsBranch.getIntPref("eventalarmlen");
alarmOffset.isNegative = true;
aItem.alarmOffset = alarmOffset;
aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
}
} catch (ex) {
Components.utils.reportError(
"Failed to apply default alarm settings to event: " + ex);
}
} else if (isToDo(aItem)) {
try {
if (alarmsBranch.getIntPref("onfortodos") == 1) {
// You can't have an alarm if the entryDate doesn't exist.
if (!aItem.entryDate) {
aItem.entryDate = getSelectedDay() &&
getSelectedDay().clone() || now();
}
var alarmOffset = Components.classes["@mozilla.org/calendar/duration;1"]
.createInstance(Components.interfaces.calIDuration);
var units = alarmsBranch.getCharPref("todoalarmunit");
alarmOffset[units] = alarmsBranch.getIntPref("todoalarmlen");
alarmOffset.isNegative = true;
aItem.alarmOffset = alarmOffset;
aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
}
} catch (ex) {
Components.utils.reportError(
"Failed to apply default alarm settings to task: " + ex);
}
}
}
function getCompositeCalendar() {
if (getCompositeCalendar.mObject === undefined) {
getCompositeCalendar.mObject = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]

Просмотреть файл

@ -360,6 +360,8 @@ xpicleanup
# bug 446366 (renamed interface)
js/calWeekTitleService.js
js/calItipProcessor.js
# bug 431775 (remove unused gopher images)
res/html/gopher-audio.gif
res/html/gopher-binary.gif

Просмотреть файл

@ -236,7 +236,6 @@ bin\calendar-js\calIcsParser.js
bin\calendar-js\calIcsSerializer.js
bin\calendar-js\calItemBase.js
bin\calendar-js\calItipItem.js
bin\calendar-js\calItipProcessor.js
bin\calendar-js\calListFormatter.js
bin\calendar-js\calMemoryCalendar.js
bin\calendar-js\calMonthGridPrinter.js

Просмотреть файл

@ -213,23 +213,9 @@ calItipEmailTransport.prototype = {
WARN("No email identity configured for calendar " + aItem.targetCalendar.name);
}
}
if (!identity) {
if (aItem.identity) {
// try to find proper identity/account for the itipItem's identity:
var itipIdentity = aItem.identity.toLowerCase();
calIterateEmailIdentities(
function(identity_, account_) {
if (identity_.email.toLowerCase() == itipIdentity) {
identity = identity_;
account = account_;
return false;
}
return true;
});
} else { // use some default identity/account:
identity = this.mDefaultIdentity;
account = this.mDefaultAccount;
}
if (!identity) { // use some default identity/account:
identity = this.mDefaultIdentity;
account = this.mDefaultAccount;
}
var compatMode = 0;
@ -343,7 +329,7 @@ calItipEmailTransport.prototype = {
? "Return-path: " + aIdentity.replyTo + "\r\n" : "") +
"From: " + aIdentity.email + "\r\n" +
"To: " + aToList + "\r\n" +
encodeMimeHeader("Subject: " + aSubject) + "\r\n");
encodeMimeHeader("Subject: " + aSubject.replace(/(\n|\r\n)/, "|")) + "\r\n");
switch (compatMode) {
case 1:
mailText += ("Content-class: urn:content-classes:calendarmessage\r\n" +

Просмотреть файл

@ -18,6 +18,8 @@
"ROLE","icalparameter_role","CHAIR;REQ-PARTICIPANT;OPT-PARTICIPANT;NON-PARTICIPANT"
"RSVP","icalparameter_rsvp","TRUE;FALSE"
"SENT-BY","const char*",
"RECEIVED-SEQUENCE","const char*",
"RECEIVED-DTSTAMP","const char*",
"TZID","const char*",
"VALUE","icalparameter_value","BINARY;BOOLEAN;DATE;DURATION;FLOAT;INTEGER;PERIOD;RECUR;TEXT;URI;ERROR;DATE-TIME;UTC-OFFSET;CAL-ADDRESS"
"X","const char*",

1 #Name C Type Enumeration Values
18 ROLE icalparameter_role CHAIR;REQ-PARTICIPANT;OPT-PARTICIPANT;NON-PARTICIPANT
19 RSVP icalparameter_rsvp TRUE;FALSE
20 SENT-BY const char*
21 RECEIVED-SEQUENCE const char*
22 RECEIVED-DTSTAMP const char*
23 TZID const char*
24 VALUE icalparameter_value BINARY;BOOLEAN;DATE;DURATION;FLOAT;INTEGER;PERIOD;RECUR;TEXT;URI;ERROR;DATE-TIME;UTC-OFFSET;CAL-ADDRESS
25 X const char*

Просмотреть файл

@ -1,6 +1,6 @@
ACTION VALUE X
ATTACH FMTTYPE ENCODING VALUE X
ATTENDEE CN CUTYPE DELEGATED-FROM DELEGATED-TO DIR LANGUAGE MEMBER PARTSTAT ROLE RSVP SENT-BY X
ATTENDEE CN CUTYPE DELEGATED-FROM DELEGATED-TO DIR LANGUAGE MEMBER PARTSTAT ROLE RSVP SENT-BY RECEIVED-SEQUENCE RECEIVED-DTSTAMP X
CALSCALE X
CATEGORIES LANGUAGE X
CLASS X

Просмотреть файл

@ -200,23 +200,24 @@ ltnMimeConverter.prototype = {
},
convertToHTML: function lmcCTH(contentType, data) {
var parser = Components.classes["@mozilla.org/calendar/ics-parser;1"]
.createInstance(Components.interfaces.calIIcsParser);
parser.parseString(data, null);
var event = null;
calIterateIcalComponent(getIcsService().parseICS(data, null),
function(icalComp) {
if (icalComp.componentType == "VEVENT") {
event = createEvent();
event.icalComponent = icalComp;
}
return (icalComp.componentType != "VEVENT");
});
for each (var item in parser.getItems({})) {
if (isEvent(item)) {
event = item;
break;
}
}
if (!event) {
return;
}
var html = createHtml(event);
try {
var itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"].
createInstance(Components.interfaces.calIItipItem);
var itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
.createInstance(Components.interfaces.calIItipItem);
itipItem.init(data);
// this.mUri is the message URL that we are processing.

Просмотреть файл

@ -37,330 +37,41 @@
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://calendar/modules/calUtils.jsm");
Components.utils.import("resource://calendar/modules/calItipUtils.jsm");
/**
* This bar lives inside the message window.
* Its lifetime is the lifetime of the main thunderbird message window.
*/
var gItipItem;
var gCalItemsArrayFound = [];
const onItipItem = {
observe: function observe(subject, topic, state) {
if (topic == "onItipItemCreation") {
checkForItipItem(subject);
}
}
};
/**
* Function to get a composite calendar of all registered read-write calendars.
*
* @return composite calendar
*/
function createItipCompositeCalendar() {
var compCal = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
.createInstance(Components.interfaces.calICompositeCalendar);
getCalendarManager().getCalendars({}).filter(isCalendarWritable).forEach(
function(cal) {
compCal.addCalendar(cal);
});
return compCal;
}
function checkForItipItem(subject) {
var itipItem;
try {
if (!subject) {
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;
}
// Get the recipient identity and save it with the itip item.
itipItem.identity = getMsgRecipient();
// 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;
var imipMethod = getMsgImipMethod();
if (imipMethod &&
imipMethod.length != 0 &&
imipMethod.toLowerCase() != "nomethod")
{
itipItem.receivedMethod = imipMethod;
} else {
// There is no METHOD in the content-type header (spec violation).
// Fall back to using the one from the itipItem's ICS.
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);
/**
* Add self to gMessageListeners defined in msgHdrViewOverlay.js
*/
function imipOnLoad() {
var listener = {};
listener.onStartHeaders = onImipStartHeaders;
listener.onEndHeaders = onImipEndHeaders;
gMessageListeners.push(listener);
// Set up our observers
var observerSvc = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerSvc.addObserver(onItipItem, "onItipItemCreation", false);
}
function imipOnUnload() {
removeEventListener("messagepane-loaded", imipOnLoad, true);
removeEventListener("messagepane-unloaded", imipOnUnload, true);
var observerSvc = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerSvc.removeObserver(onItipItem, "onItipItemCreation");
gItipItem = null;
gCalItemsArrayFound = [];
}
function onImipStartHeaders() {
var imipBar = document.getElementById("imip-bar");
imipBar.setAttribute("collapsed", "true");
hideElement("imip-button1");
hideElement("imip-button2");
hideElement("imip-button3");
// A new message is starting.
// Clear our iMIP/iTIP stuff so it doesn't contain stale information.
imipMethod = "";
gItipItem = null;
}
/**
* Required by MessageListener. no-op
*/
function onImipEndHeaders() {
// no-op
}
function setupBar(imipMethod) {
// 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");
if (imipMethod.toUpperCase() == "REQUEST") {
// Check if this is an update or initial request and display things accordingly
processRequestMsg();
} else if (imipMethod.toUpperCase() == "REPLY") {
// Check if this is an reply and display things accordingly
processReplyMsg();
} else if (imipMethod.toUpperCase() == "CANCEL") {
// Check if this is an cancel and display things accordingly
processCancelMsg();
} else if (imipMethod.toUpperCase() == "PUBLISH") {
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarRequestText"));
var button = document.getElementById("imip-button1");
showElement(button);
button.setAttribute("label", ltnGetString("lightning", "imipAddToCalendar.label"));
button.setAttribute("oncommand", "setAttendeeResponse('PUBLISH', '');");
} else {
// Bug xxxx TBD: Something went wrong or we found a message we don't
// support yet. We can show a "This method is not supported in this
// version" or simply hide the iMIP bar at this point
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUnsupportedText"));
Components.utils.reportError("Unknown imipMethod: " + imipMethod);
}
}
function processCancelMsg() {
var imipBar = document.getElementById("imip-bar");
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarCancelText"));
var compCal = createItipCompositeCalendar();
// Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
// same ID, this simplifies our searching, we can just look for Item[0].id
var itemList = gItipItem.getItemList({});
var onFindItemListener = {
onOperationComplete: function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
if (gCalItemsArrayFound.length > 0) {
displayCancel();
}
},
onGetResult: function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
for each (var item in aItems) {
gCalItemsArrayFound.push(aItems[0]);
}
}
}
gCalItemsArrayFound = [];
// Search for item:
compCal.getItem(itemList[0].id, onFindItemListener);
}
function processReplyMsg() {
var imipBar = document.getElementById("imip-bar");
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarReplyText"));
var compCal = createItipCompositeCalendar();
// Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
// same ID, this simplifies our searching, we can just look for Item[0].id
var itemList = gItipItem.getItemList({});
var itipItemDate = itemList[0].stampTime;
// check if ITIP DTSTAMP is in the future
var nowDate = jsDateToDateTime(new Date());
if (itipItemDate.compare(nowDate) > 0) {
itipItemDate = nowDate;
}
var onFindItemListener = {
onOperationComplete: function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
if (gCalItemsArrayFound.length > 0) {
displayReply();
}
},
onGetResult: function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
for each (var item in aItems) {
if (aCalendar.getProperty("itip.disableRevisionChecks") ||
itipItemDate.compare(item.stampTime) > 0) {
gCalItemsArrayFound.push(aItems[0]);
}
}
}
};
gCalItemsArrayFound = [];
// Search for item:
compCal.getItem(itemList[0].id, onFindItemListener);
}
function displayCancel() {
var button = document.getElementById("imip-button1");
showElement(button);
button.setAttribute("label", ltnGetString("lightning", "imipCancelInvitation.label"));
button.setAttribute("oncommand", "deleteItemFromCancel()");
}
function displayReply() {
var button = document.getElementById("imip-button1");
showElement(button);
button.setAttribute("label", ltnGetString("lightning", "imipUpdateInvitation.label"));
button.setAttribute("oncommand", "updateItemStatusFromReply()");
}
function deleteItemFromCancel() {
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) {
}
};
var itemArray = gItipItem.getItemList({});
for each (var calItemFound in gCalItemsArrayFound) {
calItemFound.calendar.deleteItem(calItemFound, operationListener);
}
return true;
}
function updateItemStatusFromReply() {
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) {
}
};
// Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
// same ID, this simplifies our searching, we can just look for Item[0].id
var itemArray = gItipItem.getItemList({});
var itipItem = itemArray[0];
for each (var calItemFound in gCalItemsArrayFound) {
var newItem = calItemFound.clone();
for each (var itipAttendee in itipItem.getAttendees({})) {
var att = newItem.getAttendeeById(itipAttendee.id);
if (att) {
var newAtt = att.clone();
newItem.removeAttendee(att);
newAtt.participationStatus = itipAttendee.participationStatus;
newItem.addAttendee(newAtt);
}
}
newItem.calendar.modifyItem(newItem, calItemFound, operationListener);
}
return true;
}
function getMsgImipMethod() {
return messenger.msgHdrFromURI(GetLoadedMessage()).getStringProperty("imip_method");
}
function getMsgRecipient() {
var imipRecipient = "";
function ltnGetMsgRecipient() {
var msgURI = GetLoadedMessage();
var msgHdr = messenger.msgHdrFromURI(msgURI);
if (!msgHdr) {
return null;
}
var acctmgr = Components.classes["@mozilla.org/messenger/account-manager;1"]
.getService(Components.interfaces.nsIMsgAccountManager);
var identities;
if (msgHdr.accountKey) {
// First, check if the message has an account key. If so, we can use the
// account identities to find the correct recipient
identities = acctmgr.getAccount(msgHdr.accountKey).identities;
identities = getAccountManager().getAccount(msgHdr.accountKey).identities;
} else {
// Without an account key, we have to revert back to using the server
identities = acctmgr.GetIdentitiesForServer(msgHdr.folder.server);
identities = getAccountManager().GetIdentitiesForServer(msgHdr.folder.server);
}
var emailMap = {};
if (identities.Count() == 0) {
// If we were not able to retrieve identities above, then we have no
// choice but to revert to the default identity
var acctMgr = Components.classes["@mozilla.org/messenger/account-manager;1"]
.getService(Components.interfaces.nsIMsgAccountManager);
var identity = acctMgr.defaultAccount.defaultIdentity;
var identity = getAccountManager().defaultAccount.defaultIdentity;
if (!identity) {
// If there isn't a default identity (i.e Local Folders is your
// default identity), then go ahead and use the first available
// identity.
var allIdentities = acctMgr.allIdentities;
var allIdentities = getAccountManager().allIdentities;
if (allIdentities.Count() > 0) {
identity = allIdentities.GetElementAt(0)
.QueryInterface(Components.interfaces.nsIMsgIdentity);
@ -406,35 +117,281 @@ function getMsgRecipient() {
return null;
}
function ltnIsSchedulingCalendar(cal) {
return (isCalendarWritable(cal) &&
cal.getProperty("organizerId") &&
cal.getProperty("itip.transport"));
}
const ltnOnItipItem = {
observe: function ltnOnItipItem_observe(subject, topic, state) {
if (topic == "onItipItemCreation") {
let itipItem = null;
try {
if (!subject) {
let 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.
}
if (!itipItem) {
return;
}
// Get the recipient identity and save it with the itip item.
itipItem.identity = ltnGetMsgRecipient();
// 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;
let imipMethod = messenger.msgHdrFromURI(GetLoadedMessage()).getStringProperty("imip_method");
if (imipMethod && imipMethod.length != 0 && imipMethod.toLowerCase() != "nomethod") {
itipItem.receivedMethod = imipMethod.toUpperCase();
} else { // There is no METHOD in the content-type header (spec violation).
// Fall back to using the one from the itipItem's ICS.
imipMethod = itipItem.receivedMethod;
}
cal.LOG("iTIP method: " + imipMethod);
let writableCalendars = getCalendarManager().getCalendars({}).filter(ltnIsSchedulingCalendar);
if (writableCalendars.length > 0) {
let compCal = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
.createInstance(Components.interfaces.calICompositeCalendar);
writableCalendars.forEach(compCal.addCalendar, compCal);
itipItem.targetCalendar = compCal;
let imipBar = document.getElementById("imip-bar");
imipBar.setAttribute("collapsed", "false");
switch (itipItem.receivedMethod) {
case "REFRESH":
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarRefreshText"));
break;
case "REQUEST":
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarRequestText"));
break;
case "PUBLISH":
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarPublishText"));
break;
case "CANCEL":
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarCancelText"));
break;
case "REPLY":
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarReplyText"));
break;
default:
// Bug xxxx TBD: Something went wrong or we found a message we don't
// support yet. We can show a "This method is not supported in this
// version" or simply hide the iMIP bar at this point
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUnsupportedText"));
cal.ERROR("Unknown iTIP method: " + itipItem.receivedMethod);
return;
}
cal.itip.processItipItem(itipItem, ltnItipOptions);
}
}
}
};
/**
* Call the calendar picker
* Add self to gMessageListeners defined in msgHdrViewOverlay.js
*/
function getTargetCalendar() {
function filterCalendars(c) {
// Only consider calendars that are writable and have a transport.
return isCalendarWritable(c) &&
c.getProperty("itip.transport") != null;
function ltnImipOnLoad() {
let listener = {
onStartHeaders: function onImipStartHeaders() {
var imipBar = document.getElementById("imip-bar");
imipBar.setAttribute("collapsed", "true");
hideElement("imip-button1");
hideElement("imip-button2");
hideElement("imip-button3");
// A new message is starting.
// Clear our iMIP/iTIP stuff so it doesn't contain stale information.
gItipItem = null;
},
onEndHeaders: function onImipEndHeaders() {
}
};
gMessageListeners.push(listener);
// Set up our observers
cal.getObserverService().addObserver(ltnOnItipItem, "onItipItemCreation", false);
}
function ltnImipOnUnload() {
removeEventListener("messagepane-loaded", ltnImipOnLoad, true);
removeEventListener("messagepane-unloaded", ltnImipOnUnload, true);
cal.getObserverService().removeObserver(ltnOnItipItem, "onItipItemCreation");
gItipItem = null;
gCalItemsArrayFound = [];
}
addEventListener("messagepane-loaded", ltnImipOnLoad, true);
addEventListener("messagepane-unloaded", ltnImipOnUnload, true);
var gItipItem = null;
var gActionFunc = null;
function ltnExecAction(partStat) {
switch (gActionFunc.method) {
// methods that don't require the calendar chooser:
case "REFRESH":
case "PUBLISH:UPDATE":
case "REPLY":
case "CANCEL":
break;
default: {
let cal = ltnGetTargetCalendar(gItipItem);
if (!cal) {
return true; // cancelled
}
gItipItem.targetCalendar = cal;
break;
}
}
var calendarToReturn;
var calendars = getCalendarManager().getCalendars({}).filter(filterCalendars);
// XXXNeed an error message if there is no writable calendar
// hide the buttons now, to disable pressing them twice...
hideElement("imip-button1");
hideElement("imip-button2");
hideElement("imip-button3");
// try to further limit down the list to those calendars that are configured to a matching attendee;
var item = gItipItem.getItemList({})[0];
var matchingCals = calendars.filter(
function(cal) {
var identity = cal.getProperty("imip.identity");
if (identity !== null) {
identity = identity.QueryInterface(Components.interfaces.nsIMsgIdentity).email;
return ((gItipItem.identity && (identity.toLowerCase() == gItipItem.identity.toLowerCase())) ||
item.getAttendeeById("mailto:" + identity));
let opListener = {
onOperationComplete: function ltnItipActionListener_onOperationComplete(aCalendar,
aStatus,
aOperationType,
aId,
aDetail) {
// For now, we just state the status for the user something very simple
let imipBar = document.getElementById("imip-bar");
if (Components.isSuccessCode(aStatus)) {
switch (aOperationType) {
case Components.interfaces.calIOperationListener.ADD:
imipBar.setAttribute("label", ltnGetString("lightning", "imipAddedItemToCal"));
break;
case Components.interfaces.calIOperationListener.MODIFY:
imipBar.setAttribute("label", ltnGetString("lightning", "imipUpdatedItem"));
break;
case Components.interfaces.calIOperationListener.DELETE:
imipBar.setAttribute("label", ltnGetString("lightning", "imipCanceledItem"));
break;
}
} else {
let msg = ltnGetString("lightning", "imipBarProcessingFailed", [aStatus.toString(16)]);
imipBar.setAttribute("label", msg);
showError(msg);
}
return false;
});
// if there's none, we will show the whole list of calendars:
if (matchingCals.length > 0) {
calendars = matchingCals;
},
onGetResult: function ltnItipActionListener_onGetResult(aCalendar,
aStatus,
aItemType,
aDetail,
aCount,
aItems) {
}
};
try {
gActionFunc(opListener, partStat);
} catch (exc) {
Components.utils.reportError(exc);
}
return true;
}
function ltnItipOptions(itipItem, rc, actionFunc) {
var imipBar = document.getElementById("imip-bar");
if (Components.isSuccessCode(rc)) {
if (!actionFunc) {
// This case, they clicked on an old message that has already been
// added/updated, we want to tell them that.
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarAlreadyProcessedText"));
return;
}
gItipItem = itipItem;
gActionFunc = actionFunc;
let button1 = document.getElementById("imip-button1");
let button2 = document.getElementById("imip-button2");
let button3 = document.getElementById("imip-button3");
cal.LOG("iTIP options on: " + actionFunc.method);
switch (actionFunc.method) {
case "REPLY":
// fall-thru intended
case "PUBLISH:UPDATE":
case "REQUEST:UPDATE-MINOR":
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUpdateText"));
button1.setAttribute("label", ltnGetString("lightning", "imipUpdate.label"));
button1.setAttribute("oncommand", "return ltnExecAction();");
showElement(button1);
break;
case "PUBLISH":
button1.setAttribute("label", ltnGetString("lightning", "imipAddToCalendar.label"));
button1.setAttribute("oncommand", "return ltnExecAction();");
showElement(button1);
break;
case "REQUEST:UPDATE":
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUpdateText"));
// fall-thru intended
case "REQUEST": {
button1.setAttribute("label", ltnGetString("lightning", "imipAcceptInvitation.label"));
button1.setAttribute("oncommand", "return ltnExecAction('ACCEPTED');");
button2.setAttribute("label", ltnGetString("lightning", "imipDeclineInvitation.label"));
button2.setAttribute("oncommand", "return ltnExecAction('DECLINED');");
button3.setAttribute("label", ltnGetString("lightning", "imipAcceptTentativeInvitation.label"));
button3.setAttribute("oncommand", "return ltnExecAction('TENTATIVE');");
showElement(button1);
showElement(button2);
showElement(button3);
break;
}
case "CANCEL": {
button1.setAttribute("label", ltnGetString("lightning", "imipCancelInvitation.label"));
button1.setAttribute("oncommand", "return ltnExecAction();");
showElement(button1);
break;
}
case "REFRESH": {
button1.setAttribute("label", ltnGetString("lightning", "imipSend.label"));
button1.setAttribute("oncommand", "return ltnExecAction();");
showElement(button1);
break;
}
default:
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUnsupportedText"));
break;
}
} else {
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUnsupportedText"));
}
}
function ltnGetTargetCalendar(itipItem) {
let calendarToReturn = null;
let calendars = getCalendarManager().getCalendars({}).filter(ltnIsSchedulingCalendar);
// XXXNeed an error message if there is no calendar
if (itipItem.receivedMethod == "REQUEST") {
// try to further limit down the list to those calendars that are configured to a matching attendee;
let item = itipItem.getItemList({})[0];
let matchingCals = calendars.filter(
function(calendar) {
return (cal.getInvitedAttendee(item, calendar) != null);
});
// if there's none, we will show the whole list of calendars:
if (matchingCals.length > 0) {
calendars = matchingCals;
}
}
if (calendars.length == 1) {
@ -443,8 +400,7 @@ function getTargetCalendar() {
calendarToReturn = calendars[0];
} else {
// Ask what calendar to import into
var args = new Object();
var aCal;
var args = {};
args.calendars = calendars;
args.onOk = function selectCalendar(aCal) { calendarToReturn = aCal; };
args.promptText = calGetString("calendar", "importPrompt");
@ -452,286 +408,5 @@ function getTargetCalendar() {
"_blank", "chrome,titlebar,modal,resizable", args);
}
if (calendarToReturn) {
// assure gItipItem.identity is set to the configured email address:
var identity = calendarToReturn.getProperty("imip.identity");
if (identity) {
gItipItem.identity = identity.QueryInterface(Components.interfaces.nsIMsgIdentity).email;
}
}
return calendarToReturn;
}
/**
* Type is type of response
* event_status is an optional directive to set the Event STATUS property
*/
function setAttendeeResponse(type, eventStatus) {
if (type && gItipItem) {
// Some methods need a target calendar. Prompt for it first.
switch (type) {
case "ACCEPTED":
case "TENTATIVE":
case "REPLY":
case "PUBLISH":
gItipItem.targetCalendar = getTargetCalendar();
if (!gItipItem.targetCalendar) {
// The dialog was canceled, we are done.
return;
}
}
// Now set the attendee status and perform the iTIP action. If the
// method is not mentioned here, no further action will be taken.
switch (type) {
case "ACCEPTED":
case "TENTATIVE":
case "DECLINED": {
var attId = null;
var attCN = null;
if (gItipItem.targetCalendar) {
var identity = gItipItem.targetCalendar.getProperty("imip.identity");
if (identity) { // configured email supersedes found msg recipient:
identity = identity.QueryInterface(Components.interfaces.nsIMsgIdentity);
attId = ("mailto:" + identity.email);
attCN = identity.fullName;
}
}
if (!attId && gItipItem.identity) {
attId = ("mailto:" + gItipItem.identity);
}
if (!attId) {
// Bug 420516 -- we don't support delegation yet TODO: Localize this?
throw new Error("setAttendeeResponse: " +
"You are not on the list of invited attendees, delegation " +
"is not supported yet. See bug 420516 for details.");
}
for each (var item in gItipItem.getItemList({})) {
if (!item.getAttendeeById(attId)) { // add if not existing, e.g. on mailing list REQUEST
var att = Components.classes["@mozilla.org/calendar/attendee;1"]
.createInstance(Components.interfaces.calIAttendee);
att.id = attId;
att.commonName = attCN;
item.addAttendee(att);
}
}
gItipItem.setAttendeeStatus(attId, type); // workaround for bug 351589 (fixing RSVP)
// Fall through
}
case "REPLY":
case "PUBLISH":
doResponse(eventStatus);
break;
}
}
}
/**
* doResponse performs the iTIP action for the current ItipItem that we
* parsed from the email.
* @param aLocalStatus optional parameter to set the event STATUS property.
* aLocalStatus can be empty, "TENTATIVE", "CONFIRMED", or "CANCELLED"
*/
function doResponse(aLocalStatus) {
// 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.
if (aLocalStatus != null) {
gItipItem.localStatus = aLocalStatus;
}
var itipProc = Components.classes["@mozilla.org/calendar/itip-processor;1"]
.getService(Components.interfaces.calIItipProcessor);
itipProc.processItipItem(gItipItem, operationListener);
}
/**
* Bug 348666 (complete iTIP support) - This gives the user an indication
* that the Action occurred.
*
* 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(aOperationType, aStatus, aDetail) {
// For now, we just state the status for the user something very simple
var imipBar = document.getElementById("imip-bar");
if (Components.isSuccessCode(aStatus)) {
if (aOperationType == Components.interfaces.calIOperationListener.ADD) {
imipBar.setAttribute("label", ltnGetString("lightning", "imipAddedItemToCal"));
} else if (aOperationType == Components.interfaces.calIOperationListener.MODIFY) {
imipBar.setAttribute("label", ltnGetString("lightning", "imipUpdatedItem"));
} else if (aOperationType == Components.interfaces.calIOperationListener.DELETE) {
imipBar.setAttribute("label", ltnGetString("lightning", "imipCanceledItem"));
}
hideElement("imip-button1");
hideElement("imip-button2");
hideElement("imip-button3");
} else {
// Bug 348666: When we handle more iTIP methods, we need to create
// more sophisticated error handling.
// TODO L10N localize
imipBar.setAttribute("collapsed", "true");
var msg = "Invitation could not be processed. Status: " + aStatus;
if (aDetail) {
msg += "\nDetails: " + aDetail;
}
showError(msg);
}
}
/**
* Walks through the list of events in the iTipItem and discovers whether or not
* these events already exist on a calendar. Calls displayRequestMethod.
*/
function processRequestMsg() {
// According to the specification, we have to determine if the event ID
// already exists on the calendar of the user - that means we have to search
// them all. :-(
var existingItemSequence = -1;
var compCal = createItipCompositeCalendar();
// Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
// same ID, this simplifies our searching, we can just look for Item[0].id
var itemList = gItipItem.getItemList({ });
var newSequence = itemList[0].getProperty("SEQUENCE");
// Make sure we don't have a pre Outlook 2007 appointment, but if we do
// use Microsoft's Sequence number. I <3 MS
if ((newSequence == "0") &&
itemList[0].hasProperty("X-MICROSOFT-CDO-APPT-SEQUENCE")) {
newSequence = itemList[0].getProperty("X-MICROSOFT-CDO-APPT-SEQUENCE");
}
var onFindItemListener = {
onOperationComplete:
function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
if (!this.processedId){
// Then the ID doesn't exist, don't call us twice
this.processedId = true;
displayRequestMethod(newSequence, -1);
}
},
onGetResult:
function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
if (aCount && aItems[0] && !this.processedId) {
this.processedId = true;
var existingSequence = aItems[0].getProperty("SEQUENCE");
// Handle the microsoftism foolishness
if ((existingSequence == "0") &&
itemList[0].hasProperty("X-MICROSOFT-CDO-APPT-SEQUENCE")) {
existingSequence = aItems[0].getProperty("X-MICROSOFT-CDO-APPT-SEQUENCE");
}
if (aCalendar.getProperty("itip.disableRevisionChecks")) {
displayRequestMethod(1, 0); // force to be an update
} else {
displayRequestMethod(newSequence, existingSequence);
}
}
}
};
// Search
compCal.getItem(itemList[0].id, onFindItemListener);
}
function displayRequestMethod(newItemSequence, existingItemSequence) {
// Three states here:
// 0 = the new event does not exist on the calendar (therefore, this is an add)
// (Item does not exist yet: existingItemSequence == -1)
// 1 = the event does exist and contains a proper update (this is an update)
// (Item has been updated: newSequence > existingSequence)
// 2 = the event clicked on is an old update and should NOT be applied
// (Item is an old message that has already been added/updated: new <= existing)
var updateValue = 0;
if (existingItemSequence == -1) {
updateValue = 0;
} else if (newItemSequence > existingItemSequence) {
updateValue = 1;
} else {
updateValue = 2;
}
// now display the proper message for this update type:
var imipBar = document.getElementById("imip-bar");
if (updateValue) {
// This is a message updating existing event(s). But updateValue could
// indicate that this update has already been applied, check that first.
if (updateValue == 2) {
// This case, they clicked on an old message that has already been
// added/updated, we want to tell them that.
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarAlreadyAddedText"));
hideElement("imip-button1");
hideElement("imip-button2");
hideElement("imip-button3");
} else {
// Legitimate update, let's offer the update path
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUpdateText"));
var button = document.getElementById("imip-button1");
showElement(button);
button.setAttribute("label", ltnGetString("lightning", "imipUpdateInvitation.label"));
button.setAttribute("oncommand", "setAttendeeResponse('ACCEPTED', 'CONFIRMED');");
// Create a DECLINE button (user chooses not to attend the updated event)
button = document.getElementById("imip-button2");
showElement(button);
button.setAttribute("label", ltnGetString("lightning", "imipDeclineInvitation.label"));
button.setAttribute("oncommand", "setAttendeeResponse('DECLINED', 'CONFIRMED');");
// Create a ACCEPT TENTATIVE button
button = document.getElementById("imip-button3");
showElement(button);
button.setAttribute("label", ltnGetString("lightning", "imipAcceptTentativeInvitation.label"));
button.setAttribute("oncommand", "setAttendeeResponse('TENTATIVE', 'CONFIRMED');");
}
} else {
imipBar.setAttribute("label", ltnGetString("lightning", "imipBarRequestText"));
var button = document.getElementById("imip-button1");
showElement(button);
button.setAttribute("label", ltnGetString("lightning", "imipAcceptInvitation.label"));
button.setAttribute("oncommand", "setAttendeeResponse('ACCEPTED', 'CONFIRMED');");
// Create a DECLINE button
button = document.getElementById("imip-button2");
showElement(button);
button.setAttribute("label", ltnGetString("lightning", "imipDeclineInvitation.label"));
button.setAttribute("oncommand", "setAttendeeResponse('DECLINED', 'CONFIRMED');");
// Create a ACCEPT TENTATIVE button
button = document.getElementById("imip-button3");
showElement(button);
button.setAttribute("label", ltnGetString("lightning", "imipAcceptTentativeInvitation.label"));
button.setAttribute("oncommand", "setAttendeeResponse('TENTATIVE', 'CONFIRMED');");
}
}

Просмотреть файл

@ -38,6 +38,8 @@
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://calendar/modules/calUtils.jsm");
/**
* Gets the value of a string in a .properties file from the lightning bundle
*
@ -47,26 +49,7 @@
* @param aParams optional array of parameters to format the string
*/
function ltnGetString(aBundleName, aStringName, aParams) {
if (ltnGetString.mSBS === undefined) {
ltnGetString.mSBS = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
}
try {
var propName = "chrome://lightning/locale/"+aBundleName+".properties";
var props = ltnGetString.mSBS.createBundle(propName);
if (aParams && aParams.length) {
return props.formatStringFromName(aStringName, aParams, aParams.length);
} else {
return props.GetStringFromName(aStringName);
}
} catch (ex) {
var s = "Failed to read '" + aStringName + "' from " +
"'chrome://lightning/locale/" + aBundleName + ".properties'.";
Components.utils.reportError(s + " Error: " + ex);
return s;
}
return cal.calGetString(aBundleName, aStringName, aParams, "lightning");
}
// shared by lightning-calendar-properties.js and lightning-calendar-creation.js:

Просмотреть файл

@ -85,6 +85,9 @@ pref("calendar.itip.compatSendMode", 0);
// whether "notify" is checked by default when creating new events/todos with attendees
pref("calendar.itip.notify", true);
// whether the organizer propagates replies of attendees to all attendees
pref("calendar.itip.notify-replies", false);
// whether CalDAV (experimental) scheduling is enabled or not.
pref("calendar.caldav.sched.enabled", false);

Просмотреть файл

@ -158,7 +158,7 @@ filterOutlookCsv=Outlook Comma Separated Values (%1$S)
filterWav=Waveform Audio (%1$S)
# Remote calendar errors
errorTitle=Error getting calendar
errorTitle=An error has occurred.
httpPutError=Publishing the calendar file failed.\nStatus code: %1$S: %2$S
otherPutError=Publishing the calendar file failed.\nStatus code: 0x%1$S
readOnlyMode=There has been an error reading data for calendar: %1$S. It has been placed in read-only mode, since changes to this calendar will likely result in data-loss. You may change this setting by choosing 'Edit Calendar'.

Просмотреть файл

@ -57,18 +57,22 @@ imipAddedItemToCal=Event Added to Calendar
imipCanceledItem=Event has been deleted
imipUpdatedItem=Event has been updated
imipBarCancelText=This message contains an event cancellation.
imipBarRefreshText=This message contains an event inquery.
imipBarPublishText=This message contains an event.
imipBarRequestText=This message contains an invitation to an event.
imipBarUpdateText=This message contains an update to an existing event.
imipBarAlreadyAddedText=This message contains an event that has already been added to your calendar.
imipBarAlreadyProcessedText=This message contains an event that has already been processed.
imipBarReplyText=This message contains a reply to an invitation.
imipBarUnsupportedText=This message contains an event that this version of Lightning cannot process.
imipBarProcessingFailed=Processing message failed. Status: %1$S.
imipAcceptInvitation.label=Accept
imipCancelInvitation.label=Delete
imipDeclineInvitation.label=Decline
imipUpdateInvitation.label=Update
imipUpdate.label=Update
imipAcceptTentativeInvitation.label=Tentative
imipSendMailTitle=Notify Attendees
imipSendMail=Would you like to send out notification E-Mails now?
imipSend.label=Send
imipSendMailTitle=E-Mail Notification
imipSendMail=Would you like to send out notification E-Mail now?
imipSendMailOutlook2000CompatMode=Support Outlook 2000 and Outlook 2002/XP
imipNoIdentity=None

Просмотреть файл

@ -293,8 +293,10 @@ calProviderBase.prototype = {
// nsIVariant getProperty(in AUTF8String aName);
getProperty: function cPB_getProperty(aName) {
switch (aName) {
case "itip.transport": // itip/imip default:
case "itip.transport": // iTIP/iMIP default:
return calGetImipTransport(this);
case "itip.notify-replies": // iTIP/iMIP default:
return getPrefSafe("calendar.itip.notify-replies", false);
// temporary hack to get the uncached calendar instance:
case "cache.uncachedCalendar":
return this;

Просмотреть файл

@ -456,7 +456,8 @@ calDavCalendar.prototype = {
return;
}
var locationPath = this.getItemLocationPath(aItem);
let parentItem = aItem.parentItem;
var locationPath = this.getItemLocationPath(parentItem);
var itemUri = this.makeUri(locationPath);
LOG("CalDAV: itemUri.spec = " + itemUri.spec);
@ -486,7 +487,7 @@ calDavCalendar.prototype = {
// for instance) so we'd best re-fetch in order to know
// the current state of the item
// Observers will be notified in getUpdatedItem()
thisCalendar.getUpdatedItem(aItem, aListener);
thisCalendar.getUpdatedItem(parentItem, aListener);
} else {
if (status > 999) {
status = "0x" + status.toString(16);
@ -496,8 +497,7 @@ calDavCalendar.prototype = {
}
};
aItem.calendar = this.superCalendar;
aItem.generation = 1;
parentItem.calendar = this.superCalendar;
var httpchannel = calPrepHttpChannel(itemUri,
this.getSerializedItem(aItem),

Просмотреть файл

@ -152,10 +152,11 @@ calMemoryCalendar.prototype = {
}
}
aItem.calendar = this.superCalendar;
let parentItem = aItem.parentItem
parentItem.calendar = this.superCalendar;
aItem.makeImmutable();
this.mItems[aItem.id] = aItem;
parentItem.makeImmutable();
this.mItems[aItem.id] = parentItem;
// notify the listener
this.notifyOperationComplete(aListener,

Просмотреть файл

@ -408,13 +408,6 @@ calStorageCalendar.prototype = {
"Calendar is readonly");
return;
}
// Ensure that we're looking at the base item
// if we were given an occurrence. Later we can
// optimize this.
if (aItem.parentItem != aItem) {
aItem.parentItem.recurrenceInfo.modifyException(aItem, false);
}
aItem = aItem.parentItem;
if (aItem.id == null) {
// is this an error? Or should we generate an IID?
@ -436,10 +429,11 @@ calStorageCalendar.prototype = {
}
}
aItem.calendar = this.superCalendar;
aItem.makeImmutable();
let parentItem = aItem.parentItem;
parentItem.calendar = this.superCalendar;
parentItem.makeImmutable();
this.flushItem (aItem, null);
this.flushItem(parentItem, null);
// notify the listener
this.notifyOperationComplete(aListener,
@ -2259,9 +2253,10 @@ calStorageCalendar.prototype = {
ip.priority = item.getProperty("PRIORITY");
ip.privacy = item.getProperty("CLASS");
ip.ical_status = item.getProperty("STATUS");
if (!item.parentItem)
ip.event_stamp = item.stampTime.nativeTime;
tmp = item.stampTime;
if (tmp) {
ip.event_stamp = tmp.nativeTime;
}
if (item.alarmOffset) {
ip.alarm_offset = item.alarmOffset.inSeconds;

Просмотреть файл

@ -69,6 +69,12 @@ pref("calendar.previousweeks.inview", 0);
// default transparency of allday items; could be switched to e.g. "OPAQUE":
pref("calendar.allday.defaultTransparency", "TRANSPARENT");
// whether "notify" is checked by default when creating new events/todos with attendees
pref("calendar.itip.notify", true);
// whether the organizer propagates replies of attendees
pref("calendar.itip.notify-replies", false);
// whether CalDAV (experimental) scheduling is enabled or not.
pref("calendar.caldav.sched.enabled", false);