Monotone-Parent: a164259525f718f804b625a9f09b4c3b3c9e83f8

Monotone-Revision: 11f4bdac420d36e45b12ca5d1d393ec34f43c609

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2007-11-10T00:02:30
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
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> 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]): * UI/SOGoUI/UIxComponent.m ([UIxComponent -canCreateOrModify]):
new boolean accessor that determines whether someone can create new boolean accessor that determines whether someone can create
(i.e. modify a new entry) or modify an existing entry. (i.e. modify a new entry) or modify an existing entry.

View file

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

View file

@ -34,6 +34,8 @@
#import <NGExtensions/NGBase64Coding.h> #import <NGExtensions/NGBase64Coding.h>
#import <NGImap4/NGImap4Connection.h> #import <NGImap4/NGImap4Connection.h>
#import <SoObjects/SOGo/NSDictionary+Utilities.h>
#import "SOGoMailObject.h" #import "SOGoMailObject.h"
#import "SOGoMailManager.h" #import "SOGoMailManager.h"
@ -305,7 +307,9 @@ static BOOL debugOn = NO;
/* factory */ /* factory */
+ (Class)bodyPartClassForKey:(NSString *)_key inContext:(id)_ctx { + (Class) bodyPartClassForKey: (NSString *) _key
inContext: (id) _ctx
{
NSString *pe; NSString *pe;
pe = [_key pathExtension]; pe = [_key pathExtension];
@ -335,6 +339,36 @@ static BOOL debugOn = NO;
return self; 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 */ /* etag support */
- (id)davEntityTag { - (id)davEntityTag {

View file

@ -735,8 +735,22 @@ static BOOL debugSoParts = NO;
{ {
// TODO: we might want to check for existence prior controller creation // TODO: we might want to check for existence prior controller creation
Class clazz; Class clazz;
NSArray *parts;
clazz = [SOGoMailBodyPart bodyPartClassForKey:_key inContext:_ctx]; 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]; return [clazz objectWithName:_key inContainer: self];
} }
@ -755,7 +769,7 @@ static BOOL debugSoParts = NO;
if ([self isBodyPartKey:_key inContext:_ctx]) { if ([self isBodyPartKey:_key inContext:_ctx]) {
if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil) { if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil) {
if (debugSoParts) if (debugSoParts)
[self logWithFormat: @"mail looked up part %@: %@", _key, obj]; [self logWithFormat: @"mail looked up part %@: %@", _key, obj];
return obj; return obj;
} }

View file

@ -1,30 +1,32 @@
ACCEPTED = "accepted"; ACCEPTED = "accepted";
COMPLETED = "completed"; COMPLETED = "completed";
DECLINED = "declined"; DECLINED = "declined";
DELEGATED = "delegated"; DELEGATED = "delegated";
IN-PROCESS = "in process"; IN-PROCESS = "in process";
NEEDS-ACTION = "needs action"; NEEDS-ACTION = "needs action";
TENTATIVE = "tentative"; TENTATIVE = "tentative";
organized_by_you = "organized by you"; organized_by_you = "organized by you";
you_are_an_attendee = "you are an attendee"; you_are_an_attendee = "you are an attendee";
add_info_text = "iMIP 'ADD' requests are not yet supported by SOGo."; add_info_text = "iMIP 'ADD' requests are not yet supported by SOGo.";
publish_info_text = "The sender informs you of the attached event."; publish_info_text = "The sender informs you of the attached event.";
cancel_info_text = "Your invitation or the whole event was canceled."; 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."; 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"; Organizer = "Organisateur";
Time = "Time"; Time = "Time";
Attendees = "Attendees"; Attendees = "Attendees";
request_info = "invites you to participate in a meeting."; request_info = "invites you to participate in a meeting.";
do_add_to_cal = "add to calendar"; "Add to calendar" = "Add to calendar";
do_del_from_cal = "delete from calendar"; "Delete from calendar" = "Delete from calendar";
do_accept = "accept"; Accept = "Accept";
do_decline = "decline"; Decline = "Decline";
do_tentative = "tentative"; Tentative = "Tentative";
do_update_status = "update status in calendar"; "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_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"; "Untitled" = "Untitled";

View file

@ -1,30 +1,32 @@
ACCEPTED = "Accepté"; ACCEPTED = "Accepté";
COMPLETED = "Terminé"; COMPLETED = "Terminé";
DECLINED = "Refusé"; DECLINED = "Refusé";
DELEGATED = "Délégué"; DELEGATED = "Délégué";
IN-PROCESS = "En cours de traitement"; IN-PROCESS = "En cours de traitement";
NEEDS-ACTION = "Prise de décision nécessaire"; NEEDS-ACTION = "Prise de décision nécessaire";
TENTATIVE = "Proposition"; TENTATIVE = "Proposition";
organized_by_you = "vous êtes l'organisateur"; organized_by_you = "vous êtes l'organisateur";
you_are_an_attendee = "vous êtes invité"; you_are_an_attendee = "vous êtes invité";
add_info_text = "iMIP 'ADD' requests are not yet supported by SOGo."; 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é."; 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é."; 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é."; 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"; Organizer = "Organisateur";
Time = "Date"; Time = "Date";
Attendees = "Invités"; Attendees = "Invités";
request_info = "vous invite à une réunion."; request_info = "vous invite à une réunion.";
do_add_to_cal = "ajouter à l'agenda"; "Add to calendar" = "Ajouter à l'agenda";
do_del_from_cal = "effacer de l'agenda"; "Delete from calendar" = "Effacer de l'agenda";
do_accept = "accepter"; Accept = "Accepter";
do_decline = "decliner"; Decline = "Decliner";
do_tentative = "tentative"; Tentative = "Tentative";
do_update_status = "mettre l'agenda à jour"; "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_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"; "Untitled" = "Sans titre";

View file

@ -17,15 +17,17 @@ Organizer = "Organisateur";
Time = "Time"; Time = "Time";
Attendees = "Attendees"; Attendees = "Attendees";
request_info = "invites you to participate in a meeting."; request_info = "invites you to participate in a meeting.";
do_add_to_cal = "add to calendar"; "Add to calendar" = "Add to calendar";
do_del_from_cal = "delete from calendar"; "Delete from calendar" = "Delete from calendar";
do_accept = "accept"; Accept = "Accept";
do_decline = "decline"; Decline = "Decline";
do_tentative = "tentative"; Tentative = "Tentative";
do_update_status = "update status in calendar"; "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_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"; "Untitled" = "Untitled";
"Size" = "Size"; "Size" = "Size";

View file

@ -1,76 +1,63 @@
/* /* UIxMailPartICalAction.m - this file is part of SOGo
Copyright (C) 2005 SKYRIX Software AG *
* 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. #import <Foundation/NSString.h>
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 <NGObjWeb/WODirectAction.h> #import <NGObjWeb/WODirectAction.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSString+misc.h> #import <UI/Common/WODirectAction+SOGo.h>
@interface UIxMailPartICalAction : WODirectAction @interface UIxMailPartICalAction : WODirectAction
@end @end
@implementation UIxMailPartICalAction @implementation UIxMailPartICalAction
- (id)redirectToViewerWithError:(NSString *)_error { - (WOResponse *) _changePartStatusAction: (NSString *) _newStatus
WOResponse *r; {
NSString *viewURL; return [self responseWithStatus: 404];
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;
} }
- (id)changePartStatusAction:(NSString *)_newStatus { - (WOResponse *) markAcceptedAction
[self logWithFormat:@"TODO: should %@: %@", _newStatus, [self clientObject]]; {
return [self redirectToViewerWithError: return [self _changePartStatusAction: @"ACCEPTED"];
[_newStatus stringByAppendingString:@" not implemented!"]];
} }
- (id)markAcceptedAction { - (WOResponse *) markDeclinedAction
return [self changePartStatusAction:@"ACCEPTED"]; {
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 */ @end /* UIxMailPartICalAction */

View file

@ -10,11 +10,11 @@
OGo is distributed in the hope that it will be useful, but WITHOUT ANY OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or 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. License for more details.
You should have received a copy of the GNU Lesser General Public 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 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. 02111-1307, USA.
*/ */
@ -30,13 +30,13 @@
@interface UIxMailPartICalViewer : UIxMailPartViewer @interface UIxMailPartICalViewer : UIxMailPartViewer
{ {
iCalCalendar *inCalendar; iCalCalendar *inCalendar;
iCalEvent *inEvent; iCalEvent *inEvent;
id attendee; id attendee;
SOGoDateFormatter *dateFormatter; SOGoDateFormatter *dateFormatter;
id item; id item;
id storedEventObject; id storedEventObject;
iCalEvent *storedEvent; iCalEvent *storedEvent;
} }
- (iCalEvent *) authorativeEvent; - (iCalEvent *) authorativeEvent;

View file

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

View file

@ -25,6 +25,16 @@
actionClass = "UIxMailPartICalAction"; actionClass = "UIxMailPartICalAction";
actionName = "markTentative"; 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"?> <?xml version="1.0" standalone="yes"?>
<!DOCTYPE div>
<div xmlns="http://www.w3.org/1999/xhtml" <div xmlns="http://www.w3.org/1999/xhtml"
xmlns:var="http://www.skyrix.com/od/binding" xmlns:var="http://www.skyrix.com/od/binding"
xmlns:label="OGo:label" xmlns:label="OGo:label"
xmlns:const="http://www.skyrix.com/od/constant" xmlns:const="http://www.skyrix.com/od/constant"
xmlns:rsrc="OGo:url" xmlns:rsrc="OGo:url"
class="linked_attachment_frame" class="linked_attachment_frame"
> >
<!-- TODO: add iMIP actions --> <!-- TODO: add iMIP actions -->
<var:if condition="couldParseCalendar" const:negate="1"> <var:if condition="couldParseCalendar" const:negate="1">
<fieldset> <fieldset>
<legend>Parsing Error</legend> <legend>Parsing Error</legend>
The SOGo/SOPE iCalendar parser could not parse the body of this MIME part. The SOGo/SOPE iCalendar parser could not parse the body of this MIME part.
<pre><var:string value="flatContentAsString" /></pre> <pre><var:string value="flatContentAsString" /></pre>
</fieldset> </fieldset>
</var:if> </var:if>
<var:if condition="couldParseCalendar"> <var:if condition="couldParseCalendar">
<fieldset> <fieldset>
<legend> <legend>
<var:string label:value="Appointment"/>: <var:string label:value="Appointment"/>:
<var:string value="inEvent.summary" /> <!-- TODO: shorted title --> <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: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>
<var:if condition="isLoggedInUserAnAttendee"> <var:if condition="isLoggedInUserTheOrganizer" const:negate="1">
(<var:string label:value="you_are_an_attendee"/>) <!--
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> </var:if>
</legend>
<!-- the user comment is used in replies -->
<!-- var:if condition="inCalendar.method" const:value="REQUEST" --> <var:if condition="inEvent.userComment.isNotEmpty">
<!-- sent to attendees to propose or update a meeting --> <div class="linked_attachment_meta" style="background-color: white;">
<var:if condition="isLoggedInUserAnAttendee"> <var:string value="inEvent.userComment" const:insertBR="1" />
<p class="uix_ical_toolbar"> </div>
<a var:href="acceptLink" label:string="do_accept"/> | <br />
<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>
</var:if> </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;"> <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> </div>
<br /> </fieldset>
</var:if> </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" /> <var:string value="appointment" />
<br /> <br />
<br /> <br />
<br /> <br />
--> -->
<pre style="display: none;"><var:string value="flatContentAsString" /></pre> <pre style="display: none;"><var:string value="flatContentAsString" /></pre>
</div> </div>

View file

@ -9,7 +9,7 @@ if (typeof textMailAccounts != 'undefined') {
mailAccounts = new Array(); mailAccounts = new Array();
} }
var currentMessages = new Array(); var currentMessages = {};
var maxCachedMessages = 20; var maxCachedMessages = 20;
var cachedMessages = new Array(); var cachedMessages = new Array();
var currentMailbox = null; var currentMailbox = null;
@ -743,13 +743,56 @@ function configureLinksInMessage() {
if (editDraftButton) if (editDraftButton)
Event.observe(editDraftButton, "click", Event.observe(editDraftButton, "click",
onMessageEditDraft.bindAsEventListener(editDraftButton)); 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() { function resizeMailContent() {
var headerTable = document.getElementsByClassName('mailer_fieldtable')[0]; var headerTable = document.getElementsByClassName('mailer_fieldtable')[0];
var contentDiv = document.getElementsByClassName('mailer_mailcontent')[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) { function onMessageContentMenu(event) {