Monotone-Parent: a164259525f718f804b625a9f09b4c3b3c9e83f8

Monotone-Revision: 11f4bdac420d36e45b12ca5d1d393ec34f43c609

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2007-11-10T00:02:30
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Wolfgang Sourdeau 2007-11-10 00:02:30 +00:00
parent aebac17d01
commit 1eda040e84
13 changed files with 563 additions and 416 deletions

View File

@ -1,5 +1,21 @@
2007-11-09 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/MailPartViewers/UIxMailPartICalViewer.m
([UIxMailPartICalViewer -acceptLink])
([UIxMailPartICalViewer -declineLink])
([UIxMailPartICalViewer -tentativeLink]): removed useless methods.
* UI/MailPartViewers/UIxMailPartICalAction.m ([UIxMailPartICalAction -addToCalendarAction])
([UIxMailPartICalAction -deleteFromCalendarAction]): new stub
methods.
* SoObjects/Mailer/SOGoMailObject.m ([SOGoMailObject
-lookupImap4BodyPartKey:]): make use of the new method below.
* SoObjects/Mailer/SOGoMailBodyPart.m ([SOGoMailBodyPart
+bodyPartClassForMimeType:mimeTypeinContext:_ctx]): new method
that returns an appropriate Class depending on a given mime type.
* UI/SOGoUI/UIxComponent.m ([UIxComponent -canCreateOrModify]):
new boolean accessor that determines whether someone can create
(i.e. modify a new entry) or modify an existing entry.

View File

@ -62,6 +62,8 @@
+ (Class) bodyPartClassForKey: (NSString *) _key
inContext: (id) _ctx;
+ (Class) bodyPartClassForMimeType: (NSString *) mimeType
inContext: (id) _ctx;
@end

View File

@ -34,6 +34,8 @@
#import <NGExtensions/NGBase64Coding.h>
#import <NGImap4/NGImap4Connection.h>
#import <SoObjects/SOGo/NSDictionary+Utilities.h>
#import "SOGoMailObject.h"
#import "SOGoMailManager.h"
@ -305,7 +307,9 @@ static BOOL debugOn = NO;
/* factory */
+ (Class)bodyPartClassForKey:(NSString *)_key inContext:(id)_ctx {
+ (Class) bodyPartClassForKey: (NSString *) _key
inContext: (id) _ctx
{
NSString *pe;
pe = [_key pathExtension];
@ -335,6 +339,36 @@ static BOOL debugOn = NO;
return self;
}
+ (Class) bodyPartClassForMimeType: (NSString *) mimeType
inContext: (id) _ctx
{
NSString *classString;
Class klazz;
if ([mimeType isEqualToString: @"image/gif"]
|| [mimeType isEqualToString: @"image/png"]
|| [mimeType isEqualToString: @"image/jpg"])
classString = @"SOGoImageMailBodyPart";
else if ([mimeType isEqualToString: @"text/calendar"])
classString = @"SOGoCalendarMailBodyPart";
else if ([mimeType isEqualToString: @"text/x-vcard"])
classString = @"SOGoVCardMailBodyPart";
else if ([mimeType isEqualToString: @"message/rfc822"])
classString = @"SOGoMessageMailBodyPart";
else
{
NSLog (@"unhandled mime type: '%@'", mimeType);
classString = nil;
}
if (classString)
klazz = NSClassFromString (classString);
else
klazz = Nil;
return klazz;
}
/* etag support */
- (id)davEntityTag {

View File

@ -735,8 +735,22 @@ static BOOL debugSoParts = NO;
{
// TODO: we might want to check for existence prior controller creation
Class clazz;
clazz = [SOGoMailBodyPart bodyPartClassForKey:_key inContext:_ctx];
NSArray *parts;
int part;
NSDictionary *partDesc;
NSString *mimeType;
parts = [[self bodyStructure] objectForKey: @"parts"];
part = [_key intValue] - 1;
if (part > -1 && part < [parts count])
{
partDesc = [parts objectAtIndex: part];
mimeType = [[partDesc keysWithFormat: @"%{type}/%{subtype}"] lowercaseString];
clazz = [SOGoMailBodyPart bodyPartClassForMimeType: mimeType
inContext: _ctx];
}
else
clazz = Nil;
return [clazz objectWithName:_key inContainer: self];
}
@ -755,7 +769,7 @@ static BOOL debugSoParts = NO;
if ([self isBodyPartKey:_key inContext:_ctx]) {
if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil) {
if (debugSoParts)
if (debugSoParts)
[self logWithFormat: @"mail looked up part %@: %@", _key, obj];
return obj;
}

View File

@ -1,30 +1,32 @@
ACCEPTED = "accepted";
COMPLETED = "completed";
DECLINED = "declined";
DELEGATED = "delegated";
IN-PROCESS = "in process";
NEEDS-ACTION = "needs action";
TENTATIVE = "tentative";
organized_by_you = "organized by you";
ACCEPTED = "accepted";
COMPLETED = "completed";
DECLINED = "declined";
DELEGATED = "delegated";
IN-PROCESS = "in process";
NEEDS-ACTION = "needs action";
TENTATIVE = "tentative";
organized_by_you = "organized by you";
you_are_an_attendee = "you are an attendee";
add_info_text = "iMIP 'ADD' requests are not yet supported by SOGo.";
publish_info_text = "The sender informs you of the attached event.";
cancel_info_text = "Your invitation or the whole event was canceled.";
add_info_text = "iMIP 'ADD' requests are not yet supported by SOGo.";
publish_info_text = "The sender informs you of the attached event.";
cancel_info_text = "Your invitation or the whole event was canceled.";
request_info_no_attendee = "is proposing a meeting to the attendees. You receive this mail as a notification, you are not scheduled as a participant.";
Appointment = "Appointment";
Appointment = "Appointment";
Organizer = "Organisateur";
Time = "Time";
Attendees = "Attendees";
request_info = "invites you to participate in a meeting.";
do_add_to_cal = "add to calendar";
do_del_from_cal = "delete from calendar";
do_accept = "accept";
do_decline = "decline";
do_tentative = "tentative";
do_update_status = "update status in calendar";
Organizer = "Organisateur";
Time = "Time";
Attendees = "Attendees";
request_info = "invites you to participate in a meeting.";
"Add to calendar" = "Add to calendar";
"Delete from calendar" = "Delete from calendar";
Accept = "Accept";
Decline = "Decline";
Tentative = "Tentative";
"Update status in calendar" = "Update status in calendar";
reply_info_no_attendee = "You received a reply to a scheduling event but the sender of the reply is not a participant.";
reply_info = "This is a reply to an event invitation done by you.";
reply_info = "This is a reply to an event invitation done by you.";
"to" = "to";
"Untitled" = "Untitled";

View File

@ -1,30 +1,32 @@
ACCEPTED = "Accepté";
COMPLETED = "Terminé";
DECLINED = "Refusé";
DELEGATED = "Délégué";
IN-PROCESS = "En cours de traitement";
ACCEPTED = "Accepté";
COMPLETED = "Terminé";
DECLINED = "Refusé";
DELEGATED = "Délégué";
IN-PROCESS = "En cours de traitement";
NEEDS-ACTION = "Prise de décision nécessaire";
TENTATIVE = "Proposition";
organized_by_you = "vous êtes l'organisateur";
TENTATIVE = "Proposition";
organized_by_you = "vous êtes l'organisateur";
you_are_an_attendee = "vous êtes invité";
add_info_text = "iMIP 'ADD' requests are not yet supported by SOGo.";
publish_info_text = "L'expéditeur vous informe de l'événement attaché.";
cancel_info_text = "Votre invitation ou l'événement au complet a été annulé.";
add_info_text = "iMIP 'ADD' requests are not yet supported by SOGo.";
publish_info_text = "L'expéditeur vous informe de l'événement attaché.";
cancel_info_text = "Votre invitation ou l'événement au complet a été annulé.";
request_info_no_attendee = "propose une réunion entre les invités. Ce message tint seulement lieu d'avis, vous n'êtes pas indiqué comme invité.";
Appointment = "Événement";
Appointment = "Événement";
Organizer = "Organisateur";
Time = "Date";
Attendees = "Invités";
request_info = "vous invite à une réunion.";
do_add_to_cal = "ajouter à l'agenda";
do_del_from_cal = "effacer de l'agenda";
do_accept = "accepter";
do_decline = "decliner";
do_tentative = "tentative";
do_update_status = "mettre l'agenda à jour";
Organizer = "Organisateur";
Time = "Date";
Attendees = "Invités";
request_info = "vous invite à une réunion.";
"Add to calendar" = "Ajouter à l'agenda";
"Delete from calendar" = "Effacer de l'agenda";
Accept = "Accepter";
Decline = "Decliner";
Tentative = "Tentative";
"Update status in calendar" = "Mettre l'agenda à jour";
reply_info_no_attendee = "Vous avez reçu une réponse à un événement mais l'expéditeur n'est pas un invité.";
reply_info = "Ceci est une réponse à un événement que vous avez organisé.";
reply_info = "Ceci est une réponse à un événement que vous avez organisé.";
"to" = "à";
"Untitled" = "Sans titre";

View File

@ -17,15 +17,17 @@ Organizer = "Organisateur";
Time = "Time";
Attendees = "Attendees";
request_info = "invites you to participate in a meeting.";
do_add_to_cal = "add to calendar";
do_del_from_cal = "delete from calendar";
do_accept = "accept";
do_decline = "decline";
do_tentative = "tentative";
do_update_status = "update status in calendar";
"Add to calendar" = "Add to calendar";
"Delete from calendar" = "Delete from calendar";
Accept = "Accept";
Decline = "Decline";
Tentative = "Tentative";
"Update status in calendar" = "Update status in calendar";
reply_info_no_attendee = "You received a reply to a scheduling event but the sender of the reply is not a participant.";
reply_info = "This is a reply to an event invitation done by you.";
"to" = "to";
"Untitled" = "Untitled";
"Size" = "Size";

View File

@ -1,76 +1,63 @@
/*
Copyright (C) 2005 SKYRIX Software AG
/* UIxMailPartICalAction.m - this file is part of SOGo
*
* Copyright (C) 2007 Inverse groupe conseil
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <NGObjWeb/SoObject.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOResponse.h>
#import <Foundation/NSString.h>
#import <NGObjWeb/WODirectAction.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSString+misc.h>
#import <UI/Common/WODirectAction+SOGo.h>
@interface UIxMailPartICalAction : WODirectAction
@end
@implementation UIxMailPartICalAction
- (id)redirectToViewerWithError:(NSString *)_error {
WOResponse *r;
NSString *viewURL;
id mail;
mail = [[self clientObject] valueForKey:@"mailObject"];
[self logWithFormat:@"MAIL: %@", mail];
viewURL = [mail baseURLInContext:[self context]];
[self logWithFormat:@" url: %@", viewURL];
viewURL = [viewURL stringByAppendingString:
[viewURL hasSuffix:@"/"] ? @"view" : @"/view"];
if ([_error isNotNull] && [_error length] > 0) {
viewURL = [viewURL stringByAppendingString:@"?error="];
viewURL = [viewURL stringByAppendingString:
[_error stringByEscapingURL]];
}
r = [[self context] response];
[r setStatus:302 /* moved */];
[r setHeader:viewURL forKey:@"location"];
return r;
- (WOResponse *) _changePartStatusAction: (NSString *) _newStatus
{
return [self responseWithStatus: 404];
}
- (id)changePartStatusAction:(NSString *)_newStatus {
[self logWithFormat:@"TODO: should %@: %@", _newStatus, [self clientObject]];
return [self redirectToViewerWithError:
[_newStatus stringByAppendingString:@" not implemented!"]];
- (WOResponse *) markAcceptedAction
{
return [self _changePartStatusAction: @"ACCEPTED"];
}
- (id)markAcceptedAction {
return [self changePartStatusAction:@"ACCEPTED"];
- (WOResponse *) markDeclinedAction
{
return [self _changePartStatusAction: @"DECLINED"];
}
- (id)markDeclinedAction {
return [self changePartStatusAction:@"DECLINED"];
- (WOResponse *) markTentativeAction
{
return [self _changePartStatusAction: @"TENTATIVE"];
}
- (id)markTentativeAction {
return [self changePartStatusAction:@"TENTATIVE"];
- (WOResponse *) addToCalendarAction
{
return [self responseWithStatus: 404];
}
- (WOResponse *) deleteFromCalendarAction
{
return [self responseWithStatus: 404];
}
@end /* UIxMailPartICalAction */

View File

@ -10,11 +10,11 @@
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
@ -30,13 +30,13 @@
@interface UIxMailPartICalViewer : UIxMailPartViewer
{
iCalCalendar *inCalendar;
iCalEvent *inEvent;
id attendee;
iCalCalendar *inCalendar;
iCalEvent *inEvent;
id attendee;
SOGoDateFormatter *dateFormatter;
id item;
id storedEventObject;
iCalEvent *storedEvent;
id item;
id storedEventObject;
iCalEvent *storedEvent;
}
- (iCalEvent *) authorativeEvent;

View File

@ -1,6 +1,6 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
@ -10,18 +10,18 @@
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
/*
UIxMailPartICalViewer
Show plain/calendar mail parts.
*/
@ -46,44 +46,47 @@
@implementation UIxMailPartICalViewer
- (void)dealloc {
[self->storedEventObject release];
[self->storedEvent release];
[self->attendee release];
[self->item release];
[self->inCalendar release];
[self->inEvent release];
[self->dateFormatter release];
- (void) dealloc
{
[storedEventObject release];
[storedEvent release];
[attendee release];
[item release];
[inCalendar release];
[inEvent release];
[dateFormatter release];
[super dealloc];
}
/* maintain caches */
- (void)resetPathCaches {
- (void) resetPathCaches
{
[super resetPathCaches];
[self->inEvent release]; self->inEvent = nil;
[self->inCalendar release]; self->inCalendar = nil;
[self->storedEventObject release]; self->storedEventObject = nil;
[self->storedEvent release]; self->storedEvent = nil;
[inEvent release]; inEvent = nil;
[inCalendar release]; inCalendar = nil;
[storedEventObject release]; storedEventObject = nil;
[storedEvent release]; storedEvent = nil;
/* not strictly path-related, but useless without it anyway: */
[self->attendee release]; self->attendee = nil;
[self->item release]; self->item = nil;
[attendee release]; attendee = nil;
[item release]; item = nil;
}
/* raw content handling */
- (NSStringEncoding)fallbackStringEncoding {
- (NSStringEncoding) fallbackStringEncoding
{
/*
iCalendar invitations sent by Outlook 2002 have the annoying bug that the
mail states an UTF-8 content encoding but the actual iCalendar content is
encoding in Latin-1 (or Windows Western?).
As a result the content decoding will fail (TODO: always?). In this case we
try to decode with Latin-1.
Note: we could check for the Outlook x-mailer, but it was considered better
to try Latin-1 as a fallback in any case (be tolerant).
to try Latin-1 as a fallback in any case (be tolerant).
*/
return NSISOLatin1StringEncoding;
}
@ -95,59 +98,73 @@
if (!inCalendar)
{
inCalendar
= [iCalCalendar parseSingleFromSource: [self flatContentAsString]];
= [iCalCalendar parseSingleFromSource: [self flatContentAsString]];
[inCalendar retain];
}
return inCalendar;
}
- (BOOL)couldParseCalendar {
- (BOOL) couldParseCalendar
{
return [[self inCalendar] isNotNull];
}
- (iCalEvent *)inEvent {
- (iCalEvent *) inEvent
{
NSArray *events;
if (self->inEvent != nil)
return [self->inEvent isNotNull] ? self->inEvent : nil;
if (inEvent)
return [inEvent isNotNull] ? inEvent : nil;
events = [[self inCalendar] events];
if ([events count] > 0) {
self->inEvent = [[events objectAtIndex:0] retain];
return self->inEvent;
inEvent = [[events objectAtIndex:0] retain];
return inEvent;
}
else {
self->inEvent = [[NSNull null] retain];
inEvent = [[NSNull null] retain];
return nil;
}
}
/* formatters */
- (SOGoDateFormatter *)dateFormatter {
if (self->dateFormatter == nil) {
- (SOGoDateFormatter *) dateFormatter
{
if (dateFormatter == nil) {
dateFormatter = [[context activeUser] dateFormatterInContext: context];
[dateFormatter retain];
}
return self->dateFormatter;
return dateFormatter;
}
/* below is copied from UIxAppointmentView, can we avoid that? */
- (void)setAttendee:(id)_attendee {
ASSIGN(self->attendee, _attendee);
- (void) setAttendee: (id) _attendee
{
ASSIGN(attendee, _attendee);
}
- (id)attendee {
return self->attendee;
- (id) attendee
{
return attendee;
}
- (NSString *) _personForDisplay: (iCalPerson *) person
{
return [NSString stringWithFormat: @"%@ <%@>",
[person cnWithoutQuotes],
[person rfc822Email]];
NSString *fn, *email, *result;
fn = [person cnWithoutQuotes];
email = [person rfc822Email];
if ([fn length])
result = [NSString stringWithFormat: @"%@ <%@>",
fn, email];
else
result = email;
return result;
}
- (NSString *) attendeeForDisplay
@ -155,18 +172,21 @@
return [self _personForDisplay: attendee];
}
- (void)setItem:(id)_item {
ASSIGN(self->item, _item);
- (void) setItem: (id) _item
{
ASSIGN(item, _item);
}
- (id)item {
return self->item;
- (id) item
{
return item;
}
- (NSCalendarDate *) startTime
{
NSCalendarDate *date;
NSTimeZone *timeZone;
date = [[self authorativeEvent] startDate];
timeZone = [[context activeUser] timeZone];
[date setTimeZone: timeZone];
@ -178,7 +198,7 @@
{
NSCalendarDate *date;
NSTimeZone *timeZone;
date = [[self authorativeEvent] endDate];
timeZone = [[context activeUser] timeZone];
[date setTimeZone: timeZone];
@ -186,10 +206,13 @@
return date;
}
- (BOOL)isEndDateOnSameDay {
- (BOOL) isEndDateOnSameDay
{
return [[self startTime] isDateOnSameDay:[self endTime]];
}
- (NSTimeInterval)duration {
- (NSTimeInterval) duration
{
return [[self endTime] timeIntervalSinceDate:[self startTime]];
}
@ -209,45 +232,48 @@
return [folder lookupName: @"personal" inContext: context acquire: NO];
}
- (id)storedEventObject {
- (id) storedEventObject
{
/* lookup object in the users Calendar */
id calendar;
if (self->storedEventObject != nil)
return [self->storedEventObject isNotNull] ? self->storedEventObject : nil;
if (storedEventObject)
return [storedEventObject isNotNull] ? storedEventObject : nil;
calendar = [self calendarFolder];
if ([calendar isKindOfClass:[NSException class]]) {
[self errorWithFormat:@"Did not find Calendar folder: %@", calendar];
}
else {
NSString *filename;
filename = [calendar resourceNameForEventUID:[[self inEvent] uid]];
if (filename != nil) {
if (filename) {
// TODO: When we get an exception, this might be an auth issue meaning
// that the UID indeed exists but that the user has no access to
// the object.
// Of course this is quite unusual for the private calendar though.
// that the UID indeed exists but that the user has no access to
// the object.
// Of course this is quite unusual for the private calendar though.
id tmp;
tmp = [calendar lookupName:filename inContext:[self context] acquire:NO];
if ([tmp isNotNull] && ![tmp isKindOfClass:[NSException class]])
self->storedEventObject = [tmp retain];
storedEventObject = [tmp retain];
}
}
if (self->storedEventObject == nil)
self->storedEventObject = [[NSNull null] retain];
return self->storedEventObject;
if (storedEventObject == nil)
storedEventObject = [[NSNull null] retain];
return storedEventObject;
}
- (BOOL)isEventStoredInCalendar {
- (BOOL) isEventStoredInCalendar
{
return [[self storedEventObject] isNotNull];
}
- (iCalEvent *)storedEvent {
- (iCalEvent *) storedEvent
{
return (iCalEvent *) [(SOGoAppointmentObject *)[self storedEventObject] component: NO];
}
@ -262,27 +288,30 @@
return [identity objectForKey: @"email"];
}
- (iCalEvent *)authorativeEvent {
- (iCalEvent *) authorativeEvent
{
/* DB is considered master, when in DB, ignore mail organizer */
return [self isEventStoredInCalendar]
? [self storedEvent]
: [self inEvent];
}
- (BOOL)isLoggedInUserTheOrganizer {
- (BOOL) isLoggedInUserTheOrganizer
{
NSString *loginEMail;
if ((loginEMail = [self loggedInUserEMail]) == nil) {
[self warnWithFormat:@"Could not determine email of logged in user?"];
return NO;
}
return [[self authorativeEvent] isOrganizer:loginEMail];
}
- (BOOL)isLoggedInUserAnAttendee {
- (BOOL) isLoggedInUserAnAttendee
{
NSString *loginEMail;
if ((loginEMail = [self loggedInUserEMail]) == nil) {
[self warnWithFormat:@"Could not determine email of logged in user?"];
return NO;
@ -309,69 +338,70 @@
/* replies */
- (NGImap4EnvelopeAddress *)replySenderAddress {
- (NGImap4EnvelopeAddress *) replySenderAddress
{
/*
The iMIP reply is the sender of the mail, the 'attendees' are NOT set to
the actual attendees. BUT the attendee field contains the reply-status!
*/
id tmp;
tmp = [[self clientObject] fromEnvelopeAddresses];
if ([tmp count] == 0) return nil;
return [tmp objectAtIndex:0];
}
- (NSString *)replySenderEMail {
- (NSString *) replySenderEMail
{
return [[self replySenderAddress] email];
}
- (NSString *)replySenderBaseEMail {
- (NSString *) replySenderBaseEMail
{
return [[self replySenderAddress] baseEMail];
}
- (iCalPerson *)inReplyAttendee {
- (iCalPerson *) inReplyAttendee
{
NSArray *attendees;
attendees = [[self inEvent] attendees];
if ([attendees count] == 0)
return nil;
if ([attendees count] > 1)
[self warnWithFormat:@"More than one attendee in REPLY: %@", attendees];
return [attendees objectAtIndex:0];
}
- (iCalPerson *)storedReplyAttendee {
- (iCalPerson *) storedReplyAttendee
{
/*
TODO: since an attendee can have multiple email addresses, maybe we
should translate the email to an internal uid and then retrieve
all emails addresses for matching the participant.
should translate the email to an internal uid and then retrieve
all emails addresses for matching the participant.
Note: -findParticipantWithEmail: does not parse the email!
*/
iCalEvent *e;
iCalEvent *e;
iCalPerson *p;
if ((e = [self storedEvent]) == nil)
return nil;
if ((p = [e findParticipantWithEmail:[self replySenderBaseEMail]]))
return p;
if ((p = [e findParticipantWithEmail:[self replySenderEMail]]))
return p;
return nil;
p = nil;
e = [self storedEvent];
if (e)
{
p = [e findParticipantWithEmail: [self replySenderBaseEMail]];
if (!p)
p = [e findParticipantWithEmail:[self replySenderEMail]];
}
return p;
}
- (BOOL)isReplySenderAnAttendee {
- (BOOL) isReplySenderAnAttendee
{
return [[self storedReplyAttendee] isNotNull];
}
/* action URLs */
- (id)acceptLink {
return [[self pathToAttachmentObject] stringByAppendingString:@"/accept"];
}
- (id)declineLink {
return [[self pathToAttachmentObject] stringByAppendingString:@"/decline"];
}
- (id)tentativeLink {
return [[self pathToAttachmentObject] stringByAppendingString:@"/tentative"];
}
@end /* UIxMailPartICalViewer */

View File

@ -25,6 +25,16 @@
actionClass = "UIxMailPartICalAction";
actionName = "markTentative";
};
addToCalendar = {
protectedBy = "View";
actionClass = "UIxMailPartICalAction";
actionName = "addToCalendar";
};
deleteFromCalendar = {
protectedBy = "View";
actionClass = "UIxMailPartICalAction";
actionName = "deleteFromCalendar";
};
};
};
};

View File

@ -1,203 +1,208 @@
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE div>
<div xmlns="http://www.w3.org/1999/xhtml"
xmlns:var="http://www.skyrix.com/od/binding"
xmlns:label="OGo:label"
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:rsrc="OGo:url"
class="linked_attachment_frame"
>
xmlns:var="http://www.skyrix.com/od/binding"
xmlns:label="OGo:label"
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:rsrc="OGo:url"
class="linked_attachment_frame"
>
<!-- TODO: add iMIP actions -->
<var:if condition="couldParseCalendar" const:negate="1">
<fieldset>
<legend>Parsing Error</legend>
The SOGo/SOPE iCalendar parser could not parse the body of this MIME part.
<var:if condition="couldParseCalendar" const:negate="1">
<fieldset>
<legend>Parsing Error</legend>
The SOGo/SOPE iCalendar parser could not parse the body of this MIME part.
<pre><var:string value="flatContentAsString" /></pre>
</fieldset>
</var:if>
<pre><var:string value="flatContentAsString" /></pre>
</fieldset>
</var:if>
<var:if condition="couldParseCalendar">
<fieldset>
<legend>
<var:string label:value="Appointment"/>:
<var:string value="inEvent.summary" /> <!-- TODO: shorted title -->
<var:if condition="couldParseCalendar">
<fieldset>
<legend>
<var:string label:value="Appointment"/>:
<var:string value="inEvent.summary" /> <!-- TODO: shorted title -->
<var:if condition="isLoggedInUserTheOrganizer">
(<var:string label:value="organized_by_you"/>)
</var:if>
<var:if condition="isLoggedInUserAnAttendee">
(<var:string label:value="you_are_an_attendee"/>)
</var:if>
</legend>
<var:if condition="inCalendar.method" const:value="REQUEST">
<!-- sent to attendees to propose or update a meeting -->
<var:if condition="isLoggedInUserAnAttendee">
<p class="uix_ical_toolbar" id="iCalendarToolbar">
<input id="iCalendarAttachment" type="hidden"
var:value="pathToAttachmentObject"/>
<input id="iCalendarAccept" class="button"
type="button" label:value="Accept"/>
<input id="iCalendarDecline" class="button"
type="button" label:value="Decline"/>
<input id="iCalendarTentative" class="button"
type="button" label:value="Tentative"/>
<var:if condition="isEventStoredInCalendar" const:negate="YES">
| <input id="iCalendarAddToCalendar" class="button"
type="button" label:value="Add to calendar"/>
</var:if>
</p>
<p>
<var:string label:value="Organizer" />
<a var:href="inEvent.organizer.email"
><var:string value="organizerDisplayName" /></a>
<var:string label:value="request_info" />
</p>
</var:if>
<var:if condition="isLoggedInUserAnAttendee" const:negate="YES">
<p>
<var:string label:value="Organizer" />
<a var:href="inEvent.organizer.email"
><var:string value="organizerDisplayName" /></a>
<var:string label:value="request_info_no_attendee" />
</p>
</var:if>
</var:if>
<var:if condition="inCalendar.method" const:value="REPLY">
<!-- sent to organizer to update the status of the participant -->
<var:if condition="isReplySenderAnAttendee" const:negate="1">
<p><var:string label:value="reply_info_no_attendee" /></p>
</var:if>
<var:if condition="isReplySenderAnAttendee">
<p class="uix_ical_toolbar">
<a var:href="addStatusReplyLink"
var:_newstat="$inReplyAttendee.partStatWithDefault"
var:_sender="replySenderBaseEMail"
label:string="do_update_status"/>
</p>
<!-- TODO: replies to events not in the calendar? -->
<p>
Status Update:
<var:string label:value="$inReplyAttendee.partStatWithDefault" />,
was:
<var:string
label:value="$storedReplyAttendee.partStatWithDefault" />.
</p>
</var:if>
</var:if>
<var:if condition="inCalendar.method" const:value="CANCEL">
<!-- sent to attendees to notify of the attendee being removed or the
event being deleted -->
<var:if condition="isEventStoredInCalendar">
<p class="uix_ical_toolbar">
<input id="iCalendarDeleteFromCalendar" class="button"
type="button" label:value="Delete from calendar"/>
</p>
</var:if>
<p>
<!-- todo: if there are no attendees, the whole meeting was stopped -->
<var:string label:value="cancel_info_text" />
</p>
</var:if>
<var:if condition="inCalendar.method" const:value="ADD">
<!-- TODO -->
<p><var:string label:value="add_info_text" /></p>
</var:if>
<var:if condition="inCalendar.method" const:value="PUBLISH">
<!-- none-scheduling event sent to someone for adding to the calendar -->
<p><var:string label:value="publish_info_text" /></p>
</var:if>
<var:if condition="isLoggedInUserTheOrganizer">
(<var:string label:value="organized_by_you"/>)
<!--
Possible Status:
REPLY => check whether it matches, if not suggest change, show
comment
REFRESH => add button to resent iCal
COUNTER => show panel to decide on counter
-->
</var:if>
<var:if condition="isLoggedInUserAnAttendee">
(<var:string label:value="you_are_an_attendee"/>)
<var:if condition="isLoggedInUserTheOrganizer" const:negate="1">
<!--
Possible Status:
REQUEST => ACCEPT, TENTATIVELY, DECLINE buttons with comment field
- only show buttons for attendees
PUBLISH => just the 'add to calendar' button, rewrite organizer?
ADD / CANCEL
DECLINE-COUNTER
-->
</var:if>
</legend>
<!-- var:if condition="inCalendar.method" const:value="REQUEST" -->
<!-- sent to attendees to propose or update a meeting -->
<var:if condition="isLoggedInUserAnAttendee">
<p class="uix_ical_toolbar">
<a var:href="acceptLink" label:string="do_accept"/> |
<a var:href="declineLink" label:string="do_decline"/> |
<a var:href="tentativeLink" label:string="do_tentative"/>
<var:if condition="isEventStoredInCalendar" const:negate="1">
| <a var:href="addToCalendarLink" label:string="do_add_to_cal" />
</var:if>
</p>
<p>
<var:string label:value="Organizer" />
<a var:href="inEvent.organizer.email"
><var:string value="organizerDisplayName" /></a>
<var:string label:value="request_info" />
</p>
<!-- the user comment is used in replies -->
<var:if condition="inEvent.userComment.isNotEmpty">
<div class="linked_attachment_meta" style="background-color: white;">
<var:string value="inEvent.userComment" const:insertBR="1" />
</div>
<br />
</var:if>
<var:if condition="isLoggedInUserAnAttendee" const:negate="1">
<p>
<var:string label:value="Organizer" />
<a var:href="inEvent.organizer.email"
><var:string value="organizerDisplayName" /></a>
<var:string label:value="request_info_no_attendee" />
</p>
</var:if>
<!-- var:if -->
<var:if condition="inCalendar.method" const:value="REPLY">
<!-- sent to organizer to update the status of the participant -->
<var:if condition="isReplySenderAnAttendee" const:negate="1">
<p><var:string label:value="reply_info_no_attendee" /></p>
</var:if>
<var:if condition="isReplySenderAnAttendee">
<p class="uix_ical_toolbar">
<a var:href="addStatusReplyLink"
var:_newstat="$inReplyAttendee.partStatWithDefault"
var:_sender="replySenderBaseEMail"
label:string="do_update_status"/>
</p>
<!-- TODO: replies to events not in the calendar? -->
<p>
Status Update:
<var:string label:value="$inReplyAttendee.partStatWithDefault" />,
was:
<var:string
label:value="$storedReplyAttendee.partStatWithDefault" />.
</p>
</var:if>
</var:if>
<var:if condition="inCalendar.method" const:value="CANCEL">
<!-- sent to attendees to notify of the attendee being removed or the
event being deleted -->
<var:if condition="isEventStoredInCalendar">
<p class="uix_ical_toolbar">
<a var:href="delFromCalendarLink" label:string="do_del_from_cal"/>
</p>
</var:if>
<p>
<!-- todo: if there are no attendees, the whole meeting was stopped -->
<var:string label:value="cancel_info_text" />
</p>
</var:if>
<var:if condition="inCalendar.method" const:value="ADD">
<!-- TODO -->
<p><var:string label:value="add_info_text" /></p>
</var:if>
<var:if condition="inCalendar.method" const:value="PUBLISH">
<!-- none-scheduling event sent to someone for adding to the calendar -->
<p><var:string label:value="publish_info_text" /></p>
</var:if>
<var:if condition="isLoggedInUserTheOrganizer">
<!--
Possible Status:
REPLY => check whether it matches, if not suggest change, show
comment
REFRESH => add button to resent iCal
COUNTER => show panel to decide on counter
-->
</var:if>
<var:if condition="isLoggedInUserTheOrganizer" const:negate="1">
<!--
Possible Status:
REQUEST => ACCEPT, TENTATIVELY, DECLINE buttons with comment field
- only show buttons for attendees
PUBLISH => just the 'add to calendar' button, rewrite organizer?
ADD / CANCEL
DECLINE-COUNTER
-->
</var:if>
<!-- the user comment is used in replies -->
<var:if condition="inEvent.userComment.isNotEmpty">
<div class="linked_attachment_meta" style="background-color: white;">
<var:string value="inEvent.userComment" const:insertBR="1" />
<table border="0" class="linked_attachment_meta">
<tr>
<td><var:string label:value="Time"/>:</td>
<td>
<!-- TODO: we need a better viewer for that -->
<var:string value="startTime" formatter="dateFormatter" />
<var:string label:value="to" />
<var:string value="endTime" formatter="dateFormatter" />
</td>
</tr>
<tr>
<td><var:string label:value="Organizer"/>:</td>
<td>
<a var:href="authorativeEvent.organizer.email"
><var:string value="organizerDisplayName" /></a>
</td>
</tr>
<tr>
<td valign="top"><var:string label:value="Attendees"/>:</td>
<td>
<var:foreach list="authorativeEvent.participants" item="attendee">
<a var:href="attendee.email"><var:string value="attendeeForDisplay"/></a>
(<var:string label:value="$attendee.partStatWithDefault" />)
<br />
</var:foreach>
</td>
</tr>
<var:if condition="inEvent.comment.isNotEmpty">
<tr> <!-- description in iCal -->
<td valign="top"><var:string label:value="Comment"/>:</td>
<td>
<var:string value="authorativeEvent.comment" const:insertBR="1" />
</td>
</tr>
</var:if>
</table>
</div>
<br />
</var:if>
</fieldset>
</var:if><!-- could parse -->
<div class="linked_attachment_meta" style="background-color: white;">
<table border="0" class="linked_attachment_meta">
<tr>
<td><var:string label:value="Time"/>:</td>
<td>
<!-- TODO: we need a better viewer for that -->
<var:string value="startTime" formatter="dateFormatter" />
<var:string label:value="to" />
<var:string value="endTime" formatter="dateFormatter" />
</td>
</tr>
<tr>
<td><var:string label:value="Organizer"/>:</td>
<td>
<a var:href="authorativeEvent.organizer.email"
><var:string value="organizerDisplayName" /></a>
</td>
</tr>
<tr>
<td valign="top"><var:string label:value="Attendees"/>:</td>
<td>
<var:foreach list="authorativeEvent.participants" item="attendee">
<a var:href="attendee.email"><var:string value="attendeeForDisplay"/></a>
(<var:string label:value="$attendee.partStatWithDefault" />)
<br />
</var:foreach>
</td>
</tr>
<var:if condition="inEvent.comment.isNotEmpty">
<tr> <!-- description in iCal -->
<td valign="top"><var:string label:value="Comment"/>:</td>
<td>
<var:string value="authorativeEvent.comment" const:insertBR="1" />
</td>
</tr>
</var:if>
</table>
</div>
</fieldset>
</var:if><!-- could parse -->
<!--
<!--
<var:string value="appointment" />
<br />
<br />
<br />
-->
-->
<pre style="display: none;"><var:string value="flatContentAsString" /></pre>
</div>

View File

@ -9,7 +9,7 @@ if (typeof textMailAccounts != 'undefined') {
mailAccounts = new Array();
}
var currentMessages = new Array();
var currentMessages = {};
var maxCachedMessages = 20;
var cachedMessages = new Array();
var currentMailbox = null;
@ -743,13 +743,56 @@ function configureLinksInMessage() {
if (editDraftButton)
Event.observe(editDraftButton, "click",
onMessageEditDraft.bindAsEventListener(editDraftButton));
configureiCalLinksInMessage();
}
function configureiCalLinksInMessage() {
var buttons = { "iCalendarAccept": "accept",
"iCalendarDecline": "decline",
"iCalendarTentative": "tentative",
"iCalendarAddToCalendar": "addToCalendar",
"iCalendarDeleteFromCalendar": "deleteFromCalendar" };
for (var key in buttons) {
var button = $(key);
if (button) {
button.action = buttons[key];
Event.observe(button, "click",
onICalendarButtonClick.bindAsEventListener(button));
}
}
}
function onICalendarButtonClick(event) {
var link = $("iCalendarAttachment").value;
if (link) {
var urlstr = link + "/" + this.action;
triggerAjaxRequest(urlstr, ICalendarButtonCallback,
currentMailbox + "/"
+ currentMessages[currentMailbox]);
window.alert(urlstr);
}
}
function ICalendarButtonCallback(http) {
if (http.readyState == 4)
if (isHttpStatus204(http.status)) {
var oldMsg = http.callbackData;
var msg = currentMailbox + "/" + currentMessages[currentMailbox];
if (oldMsg == msg) {
deleteCachedMessage(oldMsg);
loadMessage(currentMessages[currentMailbox]);
}
}
}
function resizeMailContent() {
var headerTable = document.getElementsByClassName('mailer_fieldtable')[0];
var contentDiv = document.getElementsByClassName('mailer_mailcontent')[0];
contentDiv.setStyle({ 'top': (Element.getHeight(headerTable) + headerTable.offsetTop) + 'px' });
contentDiv.setStyle({ 'top':
(Element.getHeight(headerTable) + headerTable.offsetTop) + 'px' });
}
function onMessageContentMenu(event) {