Bug 457203 - iTIP overhaul. r=philipp
--HG-- rename : calendar/base/src/calUtils.jsm => calendar/base/modules/calUtils.jsm
This commit is contained in:
Родитель
c37f6bbb94
Коммит
e178c7de30
|
@ -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,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);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче