Monotone-Parent: 463195ab0268a4a769eab22f23b6aecf0c87ad79

Monotone-Revision: 9abbb51cbabcad645190865841814453369fa85f

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2007-11-18T10:16:25
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Wolfgang Sourdeau 2007-11-18 10:16:25 +00:00
parent e3cdb8ecbf
commit 73bfada6bf
50 changed files with 1751 additions and 1265 deletions

104
ChangeLog
View File

@ -1,5 +1,109 @@
2007-11-18 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/Scheduler/UIxTaskEditor.m ([UIxTaskEditor -saveAction]):
invoke saveComponent:.
* UI/Scheduler/UIxComponentEditor.m ([UIxComponentEditor -hasOrganizer])
([UIxComponentEditor -organizerName]): new template accessor
related to displaying the event's organizer.
([-containsConflict:_component]): removed method.
([UIxComponentEditor -takeValuesFromRequest:_rqinContext:_ctx]):
set RSVP to "TRUE" on each attendee.
* UI/Scheduler/UIxAppointmentEditor.m ([UIxAppointmentEditor
-saveAction]): invoke saveComponent:.
* UI/MailPartViewers/UIxMailPartICalViewer.m
([UIxMailPartICalViewer -isLoggedInUserTheOrganizer]): make use of
the -userIsOrganizer: category method.
([-isLoggedInUserAnAttendee]): make use of -userIsParticipant:.
([UIxMailPartICalViewer -hasSenderStatusChanged]): new template
accessor that determines whether the "Update" button should be
displayed.
* UI/MailPartViewers/UIxMailPartICalActions.m
([UIxMailPartICalActions -deleteFromCalendarAction]): implemented
action.
([UIxMailPartICalActions -updateUserStatusAction]): implemented
action.
* UI/Common/UIxPageFrame.m ([UIxPageFrame
-setCssFiles:newCSSFiles]): new accessor that enables the
sub-templates to specify extra CSS files to load.
* SoObjects/SOGo/SOGoUser.m ([SOGoUser
-homeFolderInContext:context]): cache the home folder of the user
object instead of the current user.
* SoObjects/SOGo/SOGoGCSFolder.m ([SOGoGCSFolder
-deleteEntriesWithIds:ids]): invokes the "prepareDelete" optional
method if the child object implements it.
* SoObjects/SOGo/SOGoContentObject.m ([-setContentString:])
removed method.
* SoObjects/SOGo/LDAPSource.m ([LDAPSource
-setBaseDN:newBaseDNIDField:newIDFieldCNField:newCNFieldUIDField:newUIDFieldmailFields:newMailFieldsandBindFields:newBindFields]):
take a new "mailFields" parameter defining an array of fields
where to look at when searching the user's emails. It defaults to
the standard "mail" LDAP field.
* SoObjects/Appointments/SOGoAptMailICalReply.[hm]: new
SoComponent implementing a template for ITIP replies.
* SoObjects/Appointments/iCalPerson+SOGo.m ([iCalPerson
-mailAddress]): new method that returns a properly formatted email
address for the specified person entry.
([iCalPerson -uid]): new method that tests whether the user is
known to the system and if so, returns its user id.
* SoObjects/Appointments/iCalPerson+SOGo.[hm]: new category module.
* SoObjects/Appointments/iCalEventChanges+SOGo.m
([iCalEventChanges -sequenceShouldBeIncreased]): determine whether
the changes involved need a sequence inscrease, based on the
RFC2446 (ITIP).
* SoObjects/Appointments/iCalEventChanges+SOGo.[hm]: new category
module.
* SoObjects/Appointments/iCalEvent+SOGo.m ([iCalEvent
-isStillRelevant]): new overriden method determining the relevance
of the current event based on its end date.
* SoObjects/Appointments/iCalEvent+SOGo.[hm]: new category module.
* SoObjects/Appointments/iCalEntityObject+SOGo.m
([iCalEntityObject -attendeeUIDs]): new category methods that
returns an array containing the uids of the system-know attendees.
([iCalEntityObject -isStillRelevant]): new template method.
([iCalEntityObject -itipEntryWithMethod:method]): clone the
current entry calendar with the specified ITIP method.
([iCalEntityObject -attendeesWithoutUser:user]): returns an array
of attendees while making sure the specified user is not listed.
* SoObjects/Appointments/SOGoCalendarComponent.m
([SOGoCalendarComponent -calendar:create:secure]): new name for
-calendar:. Added a "secure" parameter that specifies whether a
stripped calendar instance is needed or not. Also, we no longer
cache the content to simplify handling of new data.
([SOGoCalendarComponent -component:create:secure]): same as above.
([SOGoCalendarComponent
-sendEMailUsingTemplateNamed:_pageNameforOldObject:_oldObjectandNewObject:_newObjecttoAttendees:_attendees]):
test whether the component is "still relevant" before sending an
email...
([SOGoCalendarComponent -sendResponseToOrganizer]): new method for
sending ITIP replies.
([SOGoCalendarComponent -getUIDsForICalPerson:iCalPerson]):
removed method. Replaced with -[iCalPerson uid] category method.
* SoObjects/Appointments/SOGoAppointmentObject.[hm]: rewrote
class. No longer override saveContentString:,
saveContentString:baseSequence:, .... Implemented the
saveComponent: and the prepareDelete methods instead. Those
methods are called only from the web methods. This avoids the
risks related to email sending and changes propagation.
* UI/Common/UIxTabItem.m: removed useless class module.
* UI/Common/UIxTabView.[hm]: removed useless class module.

View File

@ -10,6 +10,9 @@ Appointments_OBJC_FILES = \
Product.m \
NSArray+Appointments.m \
iCalEntityObject+SOGo.m \
iCalEvent+SOGo.m \
iCalEventChanges+SOGo.m \
iCalPerson+SOGo.m \
\
SOGoCalendarComponent.m \
SOGoAppointmentObject.m \
@ -24,6 +27,7 @@ Appointments_OBJC_FILES = \
SOGoAptMailUpdate.m \
SOGoAptMailRemoval.m \
SOGoAptMailDeletion.m \
SOGoAptMailICalReply.m \
Appointments_RESOURCE_FILES += \
Version \
@ -31,14 +35,17 @@ Appointments_RESOURCE_FILES += \
Appointments_COMPONENTS += \
SOGoAptMailEnglishInvitation.wo \
SOGoAptMailEnglishICalReply.wo \
SOGoAptMailEnglishUpdate.wo \
SOGoAptMailEnglishRemoval.wo \
SOGoAptMailEnglishDeletion.wo \
SOGoAptMailFrenchInvitation.wo \
SOGoAptMailFrenchICalReply.wo \
SOGoAptMailFrenchUpdate.wo \
SOGoAptMailFrenchRemoval.wo \
SOGoAptMailFrenchDeletion.wo \
SOGoAptMailGermanInvitation.wo \
SOGoAptMailGermanICalReply.wo \
SOGoAptMailGermanUpdate.wo \
SOGoAptMailGermanRemoval.wo \
SOGoAptMailGermanDeletion.wo \

View File

@ -46,12 +46,14 @@
@interface SOGoAppointmentObject : SOGoCalendarComponent
- (NSException *) changeParticipationStatus: (NSString *) _status;
/* "iCal multifolder saves" */
- (NSException *) saveContentString: (NSString *) _iCal
baseSequence: (int) _v;
- (NSException *) deleteWithBaseSequence: (int) _v;
- (NSException *) saveContentString: (NSString *) _iCalString;
// - (NSException *) saveContentString: (NSString *) _iCal
// baseSequence: (int) _v;
// - (NSException *) deleteWithBaseSequence: (int) _v;
// - (NSException *) saveContentString: (NSString *) _iCalString;
@end

View File

@ -22,7 +22,7 @@
#import <Foundation/NSCalendarDate.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGCards/iCalCalendar.h>
@ -30,14 +30,19 @@
#import <NGCards/iCalEventChanges.h>
#import <NGCards/iCalPerson.h>
#import <SoObjects/SOGo/iCalEntityObject+Utilities.h>
#import <SoObjects/SOGo/LDAPUserManager.h>
#import <SoObjects/SOGo/NSArray+Utilities.h>
#import <SoObjects/SOGo/SOGoObject.h>
#import <SoObjects/SOGo/SOGoPermissions.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/WORequest+SOGo.h>
#import "NSArray+Appointments.h"
#import "SOGoAppointmentFolder.h"
#import "iCalEventChanges+SOGo.h"
#import "iCalEntityObject+SOGo.h"
#import "iCalPerson+SOGo.h"
#import "SOGoAppointmentObject.h"
@ -48,387 +53,325 @@
return @"vevent";
}
/* iCal handling */
- (NSArray *) attendeeUIDsFromAppointment: (iCalEvent *) _apt
- (SOGoAppointmentObject *) _lookupEvent: (NSString *) eventUID
forUID: (NSString *) uid
{
SOGoAppointmentFolder *folder;
SOGoAppointmentObject *object;
NSString *possibleName;
folder = [container lookupCalendarFolderForUID: uid];
object = [folder lookupName: nameInContainer
inContext: context acquire: NO];
if ([object isKindOfClass: [NSException class]])
{
possibleName = [folder resourceNameForEventUID: eventUID];
if (possibleName)
{
object = [folder lookupName: nameInContainer
inContext: context acquire: NO];
if ([object isKindOfClass: [NSException class]])
object = nil;
}
}
if (!object)
object = [SOGoAppointmentObject objectWithName: nameInContainer
inContainer: folder];
return object;
}
- (void) _addOrUpdateEvent: (iCalEvent *) event
forUID: (NSString *) uid
{
SOGoAppointmentObject *object;
NSString *iCalString, *userLogin;
userLogin = [[context activeUser] login];
if (![uid isEqualToString: userLogin])
{
object = [self _lookupEvent: [event uid] forUID: uid];
iCalString = [[event parent] versitString];
[object saveContentString: iCalString];
}
}
- (void) _removeEventFromUID: (NSString *) uid
{
SOGoAppointmentFolder *folder;
SOGoAppointmentObject *object;
NSString *userLogin;
userLogin = [[context activeUser] login];
if (![uid isEqualToString: userLogin])
{
folder = [container lookupCalendarFolderForUID: uid];
object = [folder lookupName: nameInContainer
inContext: context acquire: NO];
if (![object isKindOfClass: [NSException class]])
[object delete];
}
}
- (void) _handleRemovedUsers: (NSArray *) attendees
{
NSEnumerator *enumerator;
iCalPerson *currentAttendee;
NSString *currentUID;
enumerator = [attendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
if (currentUID)
[self _removeEventFromUID: currentUID];
}
}
- (void) _requireResponseFromAttendees: (NSArray *) attendees
{
NSEnumerator *enumerator;
iCalPerson *currentAttendee;
enumerator = [attendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
{
[currentAttendee setRsvp: @"TRUE"];
[currentAttendee setParticipationStatus: iCalPersonPartStatNeedsAction];
}
}
- (void) _handleSequenceUpdateInEvent: (iCalEvent *) newEvent
ignoringAttendees: (NSArray *) attendees
fromOldEvent: (iCalEvent *) oldEvent
{
NSMutableArray *updateAttendees, *updateUIDs;
NSEnumerator *enumerator;
iCalPerson *currentAttendee;
NSString *currentUID;
updateAttendees = [NSMutableArray arrayWithArray: [newEvent attendees]];
[updateAttendees removeObjectsInArray: attendees];
updateUIDs = [NSMutableArray arrayWithCapacity: [updateAttendees count]];
enumerator = [updateAttendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
if (currentUID)
[self _addOrUpdateEvent: newEvent
forUID: currentUID];
}
[self sendEMailUsingTemplateNamed: @"Update"
forOldObject: oldEvent
andNewObject: [newEvent itipEntryWithMethod: @"request"]
toAttendees: updateAttendees];
}
- (void) _handleAddedUsers: (NSArray *) attendees
fromEvent: (iCalEvent *) newEvent
{
NSEnumerator *enumerator;
iCalPerson *currentAttendee;
NSString *currentUID;
enumerator = [attendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
if (currentUID)
[self _addOrUpdateEvent: newEvent
forUID: currentUID];
}
}
- (void) _handleUpdatedEvent: (iCalEvent *) newEvent
fromOldEvent: (iCalEvent *) oldEvent
{
LDAPUserManager *um;
NSMutableArray *uids;
NSArray *attendees;
unsigned i, count;
NSString *email, *uid;
if (![_apt isNotNull])
return nil;
if ((attendees = [_apt attendees]) == nil)
return nil;
count = [attendees count];
uids = [NSMutableArray arrayWithCapacity:count + 1];
um = [LDAPUserManager sharedUserManager];
/* add organizer */
email = [[_apt organizer] rfc822Email];
if ([email isNotNull]) {
uid = [um getUIDForEmail: email];
if ([uid isNotNull]) {
[uids addObject:uid];
}
else
[self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
}
/* add attendees */
for (i = 0; i < count; i++)
{
iCalPerson *person;
person = [attendees objectAtIndex:i];
email = [person rfc822Email];
if (![email isNotNull]) continue;
uid = [um getUIDForEmail:email];
if (![uid isNotNull]) {
[self logWithFormat:@"Note: got no uid for email: '%@'", email];
continue;
}
if (![uids containsObject:uid])
[uids addObject:uid];
}
return uids;
}
/* store in all the other folders */
- (NSException *) saveContentString: (NSString *) _iCal
inUIDs: (NSArray *) _uids
{
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
NSException *error;
SOGoAppointmentObject *apt;
e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
objectEnumerator];
while ((folder = [e nextObject]))
{
apt = [SOGoAppointmentObject objectWithName: nameInContainer
inContainer: folder];
error = [apt primarySaveContentString:_iCal];
if (error)
{
[self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
// TODO: make compound
allErrors = error;
}
}
return allErrors;
}
- (NSException *) deleteInUIDs: (NSArray *) _uids
{
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
NSException *error;
SOGoAppointmentObject *apt;
e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
objectEnumerator];
while ((folder = [e nextObject]))
{
apt = [folder lookupName: [self nameInContainer]
inContext: context
acquire:NO];
if ([apt isKindOfClass: [NSException class]]) {
[self logWithFormat: @"%@", [(NSException *) apt reason]];
continue;
}
if ((error = [apt primaryDelete]) != nil) {
[self logWithFormat:@"Note: failed to delete in folder: %@", folder];
// TODO: make compound
allErrors = error;
}
}
return allErrors;
}
/* "iCal multifolder saves" */
- (BOOL) _aptIsStillRelevant: (iCalEvent *) appointment
{
NSCalendarDate *now;
now = [NSCalendarDate calendarDate];
return ([[appointment endDate] earlierDate: now] == now);
}
- (NSException *) saveContentString: (NSString *) _iCal
baseSequence: (int) _v
{
/*
Note: we need to delete in all participants folders and send iMIP messages
for all external accounts.
Steps:
- fetch stored content
- parse old content
- check if sequence matches (or if 0=ignore)
- extract old attendee list + organizer (make unique)
- parse new content (ensure that sequence is increased!)
- extract new attendee list + organizer (make unique)
- make a diff => new, same, removed
- write to new, same
- delete in removed folders
- send iMIP mail for all folders not found
*/
LDAPUserManager *um;
iCalEvent *oldApt, *newApt;
iCalEventChanges *changes;
iCalPerson *organizer;
NSString *oldContent, *uid;
NSArray *uids, *props;
NSMutableArray *attendees, *storeUIDs, *removedUIDs;
NSException *storeError, *delError;
BOOL updateForcesReconsider;
if ([[context request] handledByDefaultHandler])
changes = [newEvent getChangesRelativeToEvent: oldEvent];
attendees = [changes deletedAttendees];
if ([attendees count])
{
updateForcesReconsider = NO;
[self _handleRemovedUsers: attendees];
[self sendEMailUsingTemplateNamed: @"Deletion"
forOldObject: oldEvent
andNewObject: [newEvent itipEntryWithMethod: @"cancel"]
toAttendees: attendees];
}
if ([_iCal length] == 0)
return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
reason: @"got no iCalendar content to store!"];
attendees = [changes insertedAttendees];
if ([changes sequenceShouldBeIncreased])
{
[newEvent increaseSequence];
[self _requireResponseFromAttendees: [newEvent attendees]];
[self _handleSequenceUpdateInEvent: newEvent
ignoringAttendees: attendees
fromOldEvent: oldEvent];
}
else
[self _requireResponseFromAttendees: attendees];
um = [LDAPUserManager sharedUserManager];
if ([attendees count])
{
[self _handleAddedUsers: attendees fromEvent: newEvent];
[self sendEMailUsingTemplateNamed: @"Invitation"
forOldObject: oldEvent
andNewObject: [newEvent itipEntryWithMethod: @"request"]
toAttendees: attendees];
}
}
/* handle old content */
oldContent = [self contentAsString]; /* if nil, this is a new appointment */
if ([oldContent length] == 0)
{
/* new appointment */
[self debugWithFormat:@"saving new appointment: %@", _iCal];
oldApt = nil;
}
- (void) saveComponent: (iCalEvent *) newEvent
{
iCalEvent *oldEvent;
NSArray *attendees;
[[newEvent parent] setMethod: @""];
if ([newEvent userIsOrganizer: [context activeUser]])
{
oldEvent = [self component: NO secure: NO];
if (oldEvent)
[self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent];
else
oldApt = (iCalEvent *) [self component: NO];
/* compare sequence if requested */
if (_v != 0) {
// TODO
}
/* handle new content */
newApt = (iCalEvent *) [self component: NO];
if (!newApt)
return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
reason: @"could not parse iCalendar content!"];
/* diff */
changes = [iCalEventChanges changesFromEvent: oldApt toEvent: newApt];
uids = [self getUIDsForICalPersons: [changes deletedAttendees]];
removedUIDs = [NSMutableArray arrayWithArray: uids];
uids = [self getUIDsForICalPersons: [newApt attendees]];
storeUIDs = [NSMutableArray arrayWithArray: uids];
props = [changes updatedProperties];
/* detect whether sequence has to be increased */
if ([changes hasChanges])
[newApt increaseSequence];
/* preserve organizer */
organizer = [newApt organizer];
uid = [self getUIDForICalPerson: organizer];
if (!uid)
uid = [self ownerInContext: nil];
if (uid)
{
[storeUIDs addObjectUniquely: uid];
[removedUIDs removeObject: uid];
}
/* organizer might have changed completely */
if (oldApt && ([props containsObject: @"organizer"]))
{
uid = [self getUIDForICalPerson: [oldApt organizer]];
if (uid && ![storeUIDs containsObject: uid])
[removedUIDs addObjectUniquely: uid];
}
[self debugWithFormat: @"UID ops:\n store: %@\n remove: %@",
storeUIDs, removedUIDs];
/* if time did change, all participants have to re-decide ...
* ... exception from that rule: the organizer
*/
if (oldApt
&& ([props containsObject: @"startDate"]
|| [props containsObject: @"endDate"]
|| [props containsObject: @"duration"]))
{
NSArray *ps;
unsigned i, count;
ps = [newApt attendees];
count = [ps count];
for (i = 0; i < count; i++) {
iCalPerson *p;
p = [ps objectAtIndex:i];
if (![p hasSameEmailAddress:organizer])
[p setParticipationStatus:iCalPersonPartStatNeedsAction];
}
_iCal = [[newApt parent] versitString];
updateForcesReconsider = YES;
}
/* perform storing */
storeError = [self saveContentString: _iCal inUIDs: storeUIDs];
delError = [self deleteInUIDs: removedUIDs];
// TODO: make compound
if (storeError != nil) return storeError;
if (delError != nil) return delError;
/* email notifications */
if ([self sendEMailNotifications]
&& [self _aptIsStillRelevant: newApt])
{
iCalEvent *requestApt;
requestApt = [newApt copy];
[(iCalCalendar *) [requestApt parent] setMethod: @"request"];
attendees
= [NSMutableArray arrayWithArray: [changes insertedAttendees]];
[attendees removePerson: organizer];
[self sendEMailUsingTemplateNamed: @"Invitation"
forOldObject: nil
andNewObject: requestApt
toAttendees: attendees];
[requestApt release];
if (updateForcesReconsider)
{
iCalEvent *updatedApt;
updatedApt = [newApt copy];
[(iCalCalendar *) [updatedApt parent] setMethod: @"request"];
attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
[attendees removeObjectsInArray:[changes insertedAttendees]];
[attendees removePerson:organizer];
[self sendEMailUsingTemplateNamed: @"Update"
forOldObject: oldApt
andNewObject: updatedApt
toAttendees: attendees];
[updatedApt release];
}
attendees
= [NSMutableArray arrayWithArray: [changes deletedAttendees]];
[attendees removePerson: organizer];
attendees = [newEvent attendeesWithoutUser: [context activeUser]];
if ([attendees count])
{
iCalEvent *cancelledApt;
cancelledApt = [newApt copy];
[(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
[self sendEMailUsingTemplateNamed: @"Removal"
[self _handleAddedUsers: attendees fromEvent: newEvent];
[self sendEMailUsingTemplateNamed: @"Invitation"
forOldObject: nil
andNewObject: cancelledApt
andNewObject: [newEvent itipEntryWithMethod: @"request"]
toAttendees: attendees];
[cancelledApt release];
}
if (![[newEvent attendees] count])
[[newEvent uniqueChildWithTag: @"organizer"] setValue: 0
to: @""];
}
}
else
[self primarySaveContentString: _iCal];
return nil;
[super saveComponent: newEvent];
}
- (NSException *) deleteWithBaseSequence: (int)_v
- (NSException *) _updateAttendee: (iCalPerson *) attendee
forEventUID: (NSString *) eventUID
withSequence: (NSNumber *) sequence
forUID: (NSString *) uid
{
/*
Note: We need to delete in all participants folders and send iMIP messages
for all external accounts.
Delete is basically identical to save with all attendees and the
organizer being deleted.
Steps:
- fetch stored content
- parse old content
- check if sequence matches (or if 0=ignore)
- extract old attendee list + organizer (make unique)
- delete in removed folders
- send iMIP mail for all folders not found
*/
iCalEvent *apt;
NSMutableArray *attendees, *removedUIDs;
SOGoAppointmentObject *eventObject;
iCalEvent *event;
iCalPerson *otherAttendee;
NSString *iCalString;
NSException *error;
if ([[context request] handledByDefaultHandler])
error = nil;
eventObject = [self _lookupEvent: eventUID forUID: uid];
if (![eventObject isNew])
{
/* load existing content */
apt = (iCalEvent *) [self component: NO];
/* compare sequence if requested */
// if (_v != 0) {
// // TODO
// }
removedUIDs = [NSMutableArray arrayWithArray:
[self attendeeUIDsFromAppointment: apt]];
if (![removedUIDs containsObject: owner])
[removedUIDs addObject: owner];
if ([self sendEMailNotifications]
&& [self _aptIsStillRelevant: apt])
event = [eventObject component: NO secure: NO];
if ([[event sequence] compare: sequence]
== NSOrderedSame)
{
/* send notification email to attendees excluding organizer */
attendees = [NSMutableArray arrayWithArray: [apt attendees]];
[attendees removePerson: [apt organizer]];
/* flag appointment as being cancelled */
[(iCalCalendar *) [apt parent] setMethod: @"cancel"];
[apt increaseSequence];
/* remove all attendees to signal complete removal */
[apt removeAllAttendees];
/* send notification email */
[self sendEMailUsingTemplateNamed: @"Deletion"
forOldObject: nil
andNewObject: apt
toAttendees: attendees];
otherAttendee = [event findParticipant: [context activeUser]];
[otherAttendee setPartStat: [attendee partStat]];
iCalString = [[event parent] versitString];
error = [eventObject saveContentString: iCalString];
}
error = [self deleteInUIDs: removedUIDs];
}
else
error = [self primaryDelete];
return error;
}
- (NSException *) saveContentString: (NSString *) _iCalString
- (NSException *) _handleAttendee: (iCalPerson *) attendee
statusChange: (NSString *) newStatus
inEvent: (iCalEvent *) event
{
return [self saveContentString: _iCalString baseSequence: 0];
NSString *newContent, *currentStatus, *organizerUID;
NSException *ex;
ex = nil;
currentStatus = [attendee partStat];
if ([currentStatus caseInsensitiveCompare: newStatus]
!= NSOrderedSame)
{
[attendee setPartStat: newStatus];
newContent = [[event parent] versitString];
ex = [self saveContentString: newContent];
if (!(ex || [event userIsOrganizer: [context activeUser]]))
{
if ([[attendee rsvp] isEqualToString: @"true"])
[self sendResponseToOrganizer];
organizerUID = [[event organizer] uid];
if (organizerUID)
ex = [self _updateAttendee: attendee
forEventUID: [event uid]
withSequence: [event sequence]
forUID: organizerUID];
}
}
return ex;
}
- (NSException *) changeParticipationStatus: (NSString *) _status
{
iCalEvent *event;
iCalPerson *attendee;
NSException *ex;
ex = nil;
event = [self component: NO secure: NO];
if (event)
{
attendee = [event findParticipant: [context activeUser]];
if (attendee)
ex = [self _handleAttendee: attendee statusChange: _status
inEvent: event];
else
ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
reason: @"user does not participate in this "
@"calendar event"];
}
else
ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"unable to parse event record"];
return ex;
}
- (void) prepareDelete
{
iCalEvent *event;
SOGoUser *currentUser;
NSArray *attendees;
if ([[context request] handledByDefaultHandler])
{
currentUser = [context activeUser];
event = [self component: NO secure: NO];
if ([event userIsOrganizer: currentUser])
{
attendees = [event attendeesWithoutUser: currentUser];
if ([attendees count])
{
[self _handleRemovedUsers: attendees];
[self sendEMailUsingTemplateNamed: @"Deletion"
forOldObject: nil
andNewObject: [event itipEntryWithMethod: @"cancel"]
toAttendees: attendees];
}
}
else if ([event userIsParticipant: currentUser])
[self changeParticipationStatus: @"DECLINED"];
}
}
/* message type */

View File

@ -0,0 +1,4 @@
<#IsSubject>Re: rendez-vous le <#AptStartDate/> à <#AptStartTime/></#IsSubject>
<#IsBody>
<#Attendee/> has <#HasAccepted>accepted</#HasAccepted><#HasDeclined>declined</#HasDeclined> your invitation.
</#IsBody>

View File

@ -0,0 +1,33 @@
AptStartDate: WOString {
value = startDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
AptStartTime: WOString {
value = startDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Attendee: WOString {
value = attendee.cnWithoutQuotes;
escapeHTML = NO;
}
HasAccepted: WOConditional {
condition = hasAccepted;
}
HasDeclined: WOConditional {
condition = hasDeclined;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}

View File

@ -0,0 +1,4 @@
<#IsSubject>Re: rendez-vous le <#AptStartDate/> à <#AptStartTime/></#IsSubject>
<#IsBody>
<#Attendee/> a <#HasAccepted>accepté</#HasAccepted><#HasDeclined>décliné</#HasDeclined> votre invitation.
</#IsBody>

View File

@ -0,0 +1,33 @@
AptStartDate: WOString {
value = startDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
AptStartTime: WOString {
value = startDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Attendee: WOString {
value = attendee.cnWithoutQuotes;
escapeHTML = NO;
}
HasAccepted: WOConditional {
condition = hasAccepted;
}
HasDeclined: WOConditional {
condition = hasDeclined;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}

View File

@ -0,0 +1,4 @@
<#IsSubject>Re: rendez-vous le <#AptStartDate/> à <#AptStartTime/></#IsSubject>
<#IsBody>
<#Attendee/> has <#HasAccepted>accepted</#HasAccepted><#HasDeclined>declined</#HasDeclined> your invitation.
</#IsBody>

View File

@ -0,0 +1,33 @@
AptStartDate: WOString {
value = startDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
AptStartTime: WOString {
value = startDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Attendee: WOString {
value = attendee.cnWithoutQuotes;
escapeHTML = NO;
}
HasAccepted: WOConditional {
condition = hasAccepted;
}
HasDeclined: WOConditional {
condition = hasDeclined;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}

View File

@ -0,0 +1,59 @@
/* SOGoAptMailICalReply.h - 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.
*/
#ifndef SOGOAPTMAILICALREPLY_H
#define SOGOAPTMAILICALREPLY_H
#import <NGObjWeb/SoComponent.h>
@class NSString;
@class NSCalendarDate;
@class iCalPerson;
@class iCalEntityObject;
/*
* NOTE: We inherit from SoComponent in order to get the correct
* resourceManager required for this product
*/
@interface SOGoAptMailICalReply : SoComponent
{
iCalEntityObject *apt;
iCalPerson *attendee;
NSString *homePageURL;
BOOL isSubject;
}
- (void) setApt: (iCalEntityObject *) newApt;
- (iCalEntityObject *) apt;
- (void) setAttendee: (iCalPerson *) newAttendee;
- (iCalPerson *) attendee;
/* Content Generation */
- (NSString *) getSubject;
- (NSString *) getBody;
@end
#endif /* SOGOAPTMAILICALREPLY_H */

View File

@ -0,0 +1,178 @@
/* SOGoAptMailICalReply - 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.
*/
#import <Foundation/NSCharacterSet.h>
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSTimeZone.h>
#import <NGObjWeb/WOActionResults.h>
#import <NGObjWeb/WOMessage.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGCards/iCalEntityObject.h>
#import <NGCards/iCalPerson.h>
#import <SoObjects/SOGo/NSString+Utilities.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import "SOGoAptMailICalReply.h"
@interface SOGoAptMailICalReply (PrivateAPI)
- (BOOL) isSubject;
@end
@implementation SOGoAptMailICalReply
static NSCharacterSet *wsSet = nil;
+ (void) initialize
{
static BOOL didInit = NO;
if (!didInit)
{
didInit = YES;
wsSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];
}
}
- (id) init
{
if ((self = [super init]))
{
apt = nil;
attendee = nil;
}
return self;
}
- (void) dealloc
{
[apt release];
[attendee release];
[super dealloc];
}
- (void) setApt: (iCalEntityObject *) newApt
{
ASSIGN (apt, newApt);
}
- (iCalEntityObject *) apt
{
return apt;
}
- (void) setAttendee: (iCalPerson *) newAttendee
{
ASSIGN (attendee, newAttendee);
}
- (iCalPerson *) attendee
{
return attendee;
}
- (BOOL) hasAccepted
{
NSString *partStat;
partStat = [[attendee partStat] lowercaseString];
return [partStat isEqualToString: @"accepted"];
}
- (BOOL) hasDeclined
{
NSString *partStat;
partStat = [[attendee partStat] lowercaseString];
return [partStat isEqualToString: @"declined"];
}
- (NSCalendarDate *) startDate
{
NSCalendarDate *date;
SOGoUser *user;
date = [apt startDate];
user = [[self context] activeUser];
[date setTimeZone: [user timeZone]];
return date;
}
- (BOOL) isSubject
{
return isSubject;
}
/* Generate Response */
- (NSString *) getSubject
{
NSString *subject;
isSubject = YES;
subject = [[[self generateResponse] contentAsString]
stringByTrimmingCharactersInSet: wsSet];
if (!subject)
{
[self errorWithFormat:@"Failed to properly generate subject! Please check "
@"template for component '%@'!",
[self name]];
subject = @"ERROR: missing subject!";
}
return [subject asQPSubjectString: @"utf-8"];
}
- (NSString *) getBody
{
isSubject = NO;
return [[self generateResponse] contentAsString];
}
@end
@interface SOGoAptMailEnglishICalReply : SOGoAptMailICalReply
@end
@implementation SOGoAptMailEnglishICalReply
@end
@interface SOGoAptMailFrenchICalReply : SOGoAptMailICalReply
@end
@implementation SOGoAptMailFrenchICalReply
@end
@interface SOGoAptMailGermanICalReply : SOGoAptMailICalReply
@end
@implementation SOGoAptMailGermanICalReply
@end

View File

@ -35,21 +35,19 @@
@class SOGoUser;
@interface SOGoCalendarComponent : SOGoContentObject
{
iCalCalendar *calendar;
NSString *calContent;
}
- (NSString *) componentTag;
- (iCalCalendar *) calendar: (BOOL) create;
- (iCalRepeatableEntityObject *) component: (BOOL) create;
- (NSException *) primarySaveContentString: (NSString *) _iCalString;
- (NSException *) primaryDelete;
- (iCalCalendar *) calendar: (BOOL) create
secure: (BOOL) secure;
- (id) component: (BOOL) create secure: (BOOL) secure;
- (NSException *) delete;
// - (NSException *) primarySaveContentString: (NSString *) _iCalString;
// - (NSException *) primaryDelete;
- (NSException *) changeParticipationStatus: (NSString *) _status;
// - (NSException *) delete;
- (void) saveComponent: (iCalRepeatableEntityObject *) newObject;
/* mail notifications */
- (BOOL) sendEMailNotifications;
@ -64,7 +62,6 @@
- (iCalPerson *) findParticipantWithUID: (NSString *) uid;
- (iCalPerson *) iCalPersonWithUID: (NSString *) uid;
- (NSString *) getUIDForICalPerson: (iCalPerson *) person;
- (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons;
@end

View File

@ -30,6 +30,7 @@
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NGHashMap.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalRepeatableEntityObject.h>
#import <NGMime/NGMimeBodyPart.h>
@ -42,10 +43,13 @@
#import <SoObjects/SOGo/SOGoMailer.h>
#import <SoObjects/SOGo/SOGoPermissions.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/WORequest+SOGo.h>
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
#import "SOGoAptMailICalReply.h"
#import "SOGoAptMailNotification.h"
#import "iCalEntityObject+SOGo.h"
#import "iCalPerson+SOGo.h"
#import "SOGoCalendarComponent.h"
static BOOL sendEMailNotifications = NO;
@ -67,26 +71,6 @@ static BOOL sendEMailNotifications = NO;
}
}
- (id) init
{
if ((self = [super init]))
{
calendar = nil;
calContent = nil;
}
return self;
}
- (void) dealloc
{
if (calendar)
[calendar release];
if (calContent)
[calContent release];
[super dealloc];
}
- (NSString *) davContentType
{
return @"text/calendar";
@ -111,16 +95,15 @@ static BOOL sendEMailNotifications = NO;
[component removeAllAlarms];
}
- (NSString *) contentAsString
- (NSString *) secureContentAsString
{
iCalCalendar *tmpCalendar;
iCalRepeatableEntityObject *tmpComponent;
// NSArray *roles;
// NSString *uid;
SoSecurityManager *sm;
NSString *iCalString;
if (!calContent)
{
// uid = [[context activeUser] login];
// roles = [self aclsForUser: uid];
// if ([roles containsObject: SOGoCalendarRole_Organizer]
@ -139,113 +122,76 @@ static BOOL sendEMailNotifications = NO;
// else
// calContent = nil;
sm = [SoSecurityManager sharedSecurityManager];
if (![sm validatePermission: SOGoCalendarPerm_ViewAllComponent
onObject: self inContext: context])
calContent = content;
else if (![sm validatePermission: SOGoCalendarPerm_ViewDAndT
onObject: self inContext: context])
sm = [SoSecurityManager sharedSecurityManager];
if (![sm validatePermission: SOGoCalendarPerm_ViewAllComponent
onObject: self inContext: context])
iCalString = content;
else if (![sm validatePermission: SOGoCalendarPerm_ViewDAndT
onObject: self inContext: context])
{
tmpCalendar = [[self calendar: NO secure: NO] copy];
tmpComponent = (iCalRepeatableEntityObject *)
[tmpCalendar firstChildWithTag: [self componentTag]];
[self _filterComponent: tmpComponent];
iCalString = [tmpCalendar versitString];
[tmpCalendar release];
}
else
iCalString = nil;
return iCalString;
}
- (iCalCalendar *) calendar: (BOOL) create secure: (BOOL) secure
{
NSString *componentTag;
CardGroup *newComponent;
iCalCalendar *calendar;
NSString *iCalString;
if (secure)
iCalString = [self secureContentAsString];
else
iCalString = content;
if ([iCalString length] > 0)
calendar = [iCalCalendar parseSingleFromSource: iCalString];
else
{
if (create)
{
tmpCalendar = [[self calendar: NO] copy];
tmpComponent = (iCalRepeatableEntityObject *)
[tmpCalendar firstChildWithTag: [self componentTag]];
[self _filterComponent: tmpComponent];
calContent = [tmpCalendar versitString];
[tmpCalendar release];
calendar = [iCalCalendar groupWithTag: @"vcalendar"];
[calendar setVersion: @"2.0"];
[calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
componentTag = [[self componentTag] uppercaseString];
newComponent = [[calendar classForTag: componentTag]
groupWithTag: componentTag];
[calendar addChild: newComponent];
}
else
calContent = nil;
[calContent retain];
}
return calContent;
}
- (void) setContentString: (NSString *) newContent
{
[super setContentString: newContent];
ASSIGN (calendar, nil);
ASSIGN (calContent, nil);
}
// - (NSException *) saveContentString: (NSString *) contentString
// baseVersion: (unsigned int) baseVersion
// {
// NSException *result;
// result = [super saveContentString: contentString
// baseVersion: baseVersion];
// if (!result && calContent)
// {
// [calContent release];
// calContent = nil;
// }
// return result;
// }
- (iCalCalendar *) calendar: (BOOL) create
{
NSString *iCalString, *componentTag;
CardGroup *newComponent;
if (!calendar)
{
iCalString = [super contentAsString];
if ([iCalString length] > 0)
calendar = [iCalCalendar parseSingleFromSource: iCalString];
else
{
if (create)
{
calendar = [iCalCalendar groupWithTag: @"vcalendar"];
[calendar setVersion: @"2.0"];
[calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
componentTag = [[self componentTag] uppercaseString];
newComponent = [[calendar classForTag: componentTag]
groupWithTag: componentTag];
[calendar addChild: newComponent];
}
}
if (calendar)
[calendar retain];
calendar = nil;
}
return calendar;
}
- (iCalRepeatableEntityObject *) component: (BOOL) create
- (id) component: (BOOL) create secure: (BOOL) secure
{
return
(iCalRepeatableEntityObject *) [[self calendar: create]
firstChildWithTag: [self componentTag]];
return [[self calendar: create secure: secure]
firstChildWithTag: [self componentTag]];
}
- (void) saveComponent: (iCalRepeatableEntityObject *) newObject
{
NSString *newiCalString;
newiCalString = [[newObject parent] versitString];
[self saveContentString: newiCalString];
}
/* raw saving */
- (NSException *) primarySaveContentString: (NSString *) _iCalString
{
return [super saveContentString: _iCalString];
}
- (NSException *) primaryDelete
{
return [super delete];
}
- (NSException *) deleteWithBaseSequence: (int) a
{
[self subclassResponsibility: _cmd];
return nil;
}
- (NSException *) delete
{
return [self deleteWithBaseSequence: 0];
}
/* EMail Notifications */
- (NSString *) homePageURLForPerson: (iCalPerson *) _person
{
@ -262,58 +208,13 @@ static BOOL sendEMailNotifications = NO;
baseURL = @"http://localhost/";
[self warnWithFormat:@"Unable to create baseURL from context!"];
}
uid = [[LDAPUserManager sharedUserManager]
getUIDForEmail: [_person rfc822Email]];
uid = [_person uid];
return ((uid)
? [NSString stringWithFormat:@"%@%@", baseURL, uid]
: nil);
}
- (NSException *) changeParticipationStatus: (NSString *) _status
{
iCalRepeatableEntityObject *component;
iCalPerson *person;
NSString *newContent;
NSException *ex;
ex = nil;
component = [self component: NO];
if (component)
{
person = [self findParticipantWithUID: owner];
if (person)
{
// TODO: send iMIP reply mails?
[person setPartStat: _status];
newContent = [[component parent] versitString];
if (newContent)
{
ex = [self saveContentString: newContent];
if (ex)
// TODO: why is the exception wrapped?
/* Server Error */
ex = [NSException exceptionWithHTTPStatus: 500
reason: [ex reason]];
}
else
ex
= [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"Could not generate iCalendar data ..."];
}
else
ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
reason: @"user does not participate in this "
@"calendar component"];
}
else
ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"unable to parse component record"];
return ex;
}
- (BOOL) sendEMailNotifications
{
return sendEMailNotifications;
@ -335,7 +236,7 @@ static BOOL sendEMailNotifications = NO;
{
NSString *pageName;
iCalPerson *organizer;
NSString *cn, *email, *sender, *iCalString;
NSString *email, *sender, *iCalString;
WOApplication *app;
unsigned i, count;
iCalPerson *attendee;
@ -347,115 +248,196 @@ static BOOL sendEMailNotifications = NO;
NGMimeBodyPart *bodyPart;
NGMimeMultipartBody *body;
if ([_attendees count])
if (sendEMailNotifications
&& [_newObject isStillRelevant])
{
/* sender */
organizer = [_newObject organizer];
cn = [organizer cnWithoutQuotes];
if (cn)
sender = [NSString stringWithFormat:@"%@ <%@>",
cn,
[organizer rfc822Email]];
else
sender = [organizer rfc822Email];
/* generate iCalString once */
iCalString = [[_newObject parent] versitString];
/* get WOApplication instance */
app = [WOApplication application];
/* generate dynamic message content */
count = [_attendees count];
for (i = 0; i < count; i++)
{
attendee = [_attendees objectAtIndex:i];
if (count)
{
/* sender */
organizer = [_newObject organizer];
sender = [organizer mailAddress];
/* construct recipient */
cn = [attendee cn];
email = [attendee rfc822Email];
if (cn)
recipient = [NSString stringWithFormat: @"%@ <%@>",
cn, email];
else
recipient = email;
NSLog (@"sending '%@' from %@",
[(iCalCalendar *) [_newObject parent] method], organizer);
language = [[context activeUser] language];
/* generate iCalString once */
iCalString = [[_newObject parent] versitString];
/* get WOApplication instance */
app = [WOApplication application];
/* generate dynamic message content */
for (i = 0; i < count; i++)
{
attendee = [_attendees objectAtIndex: i];
if (![[attendee uid] isEqualToString: owner])
{
/* construct recipient */
recipient = [attendee mailAddress];
email = [attendee rfc822Email];
NSLog (@"recipient: %@", recipient);
language = [[context activeUser] language];
#warning this could be optimized in a class hierarchy common with the \
SOGoObject acl notification mechanism
/* create page name */
// TODO: select user's default language?
pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
language,
_pageName];
/* construct message content */
p = [app pageWithName: pageName inContext: context];
[p setNewApt: _newObject];
[p setOldApt: _oldObject];
[p setHomePageURL: [self homePageURLForPerson: attendee]];
[p setViewTZ: [self timeZoneForUser: email]];
subject = [p getSubject];
text = [p getBody];
SOGoObject acl notification mechanism
/* create page name */
pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
language, _pageName];
/* construct message content */
p = [app pageWithName: pageName inContext: context];
[p setNewApt: _newObject];
[p setOldApt: _oldObject];
[p setHomePageURL: [self homePageURLForPerson: attendee]];
[p setViewTZ: [self timeZoneForUser: email]];
subject = [p getSubject];
text = [p getBody];
/* construct message */
headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
/* construct message */
headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
/* NOTE: multipart/alternative seems like the correct choice but
* unfortunately Thunderbird doesn't offer the rich content alternative
* at all. Mail.app shows the rich content alternative _only_
* so we'll stick with multipart/mixed for the time being.
*/
[headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
[headerMap setObject: sender forKey: @"From"];
[headerMap setObject: recipient forKey: @"To"];
mailDate = [[NSCalendarDate date] rfc822DateString];
[headerMap setObject: mailDate forKey: @"date"];
[headerMap setObject: subject forKey: @"Subject"];
msg = [NGMimeMessage messageWithHeader: headerMap];
/* NOTE: multipart/alternative seems like the correct choice but
* unfortunately Thunderbird doesn't offer the rich content alternative
* at all. Mail.app shows the rich content alternative _only_
* so we'll stick with multipart/mixed for the time being.
*/
[headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
[headerMap setObject: sender forKey: @"From"];
[headerMap setObject: recipient forKey: @"To"];
mailDate = [[NSCalendarDate date] rfc822DateString];
[headerMap setObject: mailDate forKey: @"date"];
[headerMap setObject: subject forKey: @"Subject"];
msg = [NGMimeMessage messageWithHeader: headerMap];
/* multipart body */
body = [[NGMimeMultipartBody alloc] initWithPart: msg];
/* multipart body */
body = [[NGMimeMultipartBody alloc] initWithPart: msg];
/* text part */
headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
[headerMap setObject: @"text/plain; charset=utf-8"
forKey: @"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
[bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
/* attach text part to multipart body */
[body addBodyPart: bodyPart];
/* text part */
headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
[headerMap setObject: @"text/plain; charset=utf-8"
forKey: @"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
[bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
/* calendar part */
header = [NSString stringWithFormat: @"text/calendar; method=%@;"
@" charset=utf-8",
[(iCalCalendar *) [_newObject parent] method]];
headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
[headerMap setObject:header forKey: @"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
[bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
/* attach text part to multipart body */
[body addBodyPart: bodyPart];
/* attach calendar part to multipart body */
[body addBodyPart: bodyPart];
/* calendar part */
header = [NSString stringWithFormat: @"text/calendar; method=%@;"
@" charset=utf-8",
[(iCalCalendar *) [_newObject parent] method]];
headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
[headerMap setObject:header forKey: @"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
[bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
/* attach multipart body to message */
[msg setBody: body];
[body release];
/* attach calendar part to multipart body */
[body addBodyPart: bodyPart];
/* attach multipart body to message */
[msg setBody: body];
[body release];
/* send the damn thing */
[[SOGoMailer sharedMailer]
sendMimePart: msg
toRecipients: [NSArray arrayWithObject: email]
sender: [organizer rfc822Email]];
}
/* send the damn thing */
[[SOGoMailer sharedMailer]
sendMimePart: msg
toRecipients: [NSArray arrayWithObject: email]
sender: [organizer rfc822Email]];
}
}
}
}
}
- (void) sendResponseToOrganizer
{
#warning THIS IS A STUB
NSString *pageName, *language, *mailDate, *email;
WOApplication *app;
iCalPerson *organizer, *attendee;
NSString *iCalString;
iCalEvent *event;
SOGoAptMailICalReply *p;
NGMutableHashMap *headerMap;
NGMimeMessage *msg;
NGMimeBodyPart *bodyPart;
NGMimeMultipartBody *body;
NSData *bodyData;
event = [[self component: NO secure: NO] itipEntryWithMethod: @"reply"];
if (![event userIsOrganizer: [context activeUser]])
{
organizer = [event organizer];
attendee = [event findParticipant: [context activeUser]];
[event setAttendees: [NSArray arrayWithObject: attendee]];
/* get WOApplication instance */
app = [WOApplication application];
language = [[context activeUser] language];
/* create page name */
pageName
= [NSString stringWithFormat: @"SOGoAptMail%@ICalReply", language];
/* construct message content */
p = [app pageWithName: pageName inContext: context];
[p setApt: event];
[p setAttendee: attendee];
/* construct message */
headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
/* NOTE: multipart/alternative seems like the correct choice but
* unfortunately Thunderbird doesn't offer the rich content alternative
* at all. Mail.app shows the rich content alternative _only_
* so we'll stick with multipart/mixed for the time being.
*/
[headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
[headerMap setObject: [attendee mailAddress] forKey: @"From"];
[headerMap setObject: [organizer mailAddress] forKey: @"To"];
mailDate = [[NSCalendarDate date] rfc822DateString];
[headerMap setObject: mailDate forKey: @"date"];
[headerMap setObject: [p getSubject] forKey: @"Subject"];
msg = [NGMimeMessage messageWithHeader: headerMap];
NSLog (@"sending 'REPLY' from %@ to %@",
[attendee mailAddress], [organizer mailAddress]);
/* multipart body */
body = [[NGMimeMultipartBody alloc] initWithPart: msg];
/* text part */
headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
[headerMap setObject: @"text/plain; charset=utf-8"
forKey: @"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
bodyData = [[p getBody] dataUsingEncoding: NSUTF8StringEncoding];
[bodyPart setBody: bodyData];
/* attach text part to multipart body */
[body addBodyPart: bodyPart];
/* calendar part */
headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
[headerMap setObject: @"text/calendar; method=REPLY; charset=utf-8"
forKey: @"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
iCalString = [[event parent] versitString];
[bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
/* attach calendar part to multipart body */
[body addBodyPart: bodyPart];
/* attach multipart body to message */
[msg setBody: body];
[body release];
/* send the damn thing */
email = [organizer rfc822Email];
[[SOGoMailer sharedMailer]
sendMimePart: msg
toRecipients: [NSArray arrayWithObject: email]
sender: [attendee rfc822Email]];
}
}
// - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
@ -481,7 +463,7 @@ static BOOL sendEMailNotifications = NO;
SOGoUser *user;
user = [SOGoUser userWithLogin: uid roles: nil];
component = [self component: NO];
component = [self component: NO secure: NO];
return [component findParticipant: user];
}
@ -503,31 +485,20 @@ static BOOL sendEMailNotifications = NO;
return person;
}
- (NSString *) getUIDForICalPerson: (iCalPerson *) person
{
LDAPUserManager *um;
um = [LDAPUserManager sharedUserManager];
return [um getUIDForEmail: [person rfc822Email]];
}
- (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
{
iCalPerson *currentPerson;
NSEnumerator *persons;
NSMutableArray *uids;
NSString *uid;
LDAPUserManager *um;
uids = [NSMutableArray array];
um = [LDAPUserManager sharedUserManager];
persons = [iCalPersons objectEnumerator];
currentPerson = [persons nextObject];
while (currentPerson)
{
uid = [um getUIDForEmail: [currentPerson rfc822Email]];
uid = [currentPerson uid];
if (uid)
[uids addObject: uid];
currentPerson = [persons nextObject];
@ -591,7 +562,7 @@ static BOOL sendEMailNotifications = NO;
if ([superAcls count] > 0)
[roles addObjectsFromArray: superAcls];
component = [self component: NO];
component = [self component: NO secure: NO];
ownerRole = [self _roleOfOwner: component];
if ([owner isEqualToString: uid])
[roles addObject: ownerRole];

View File

@ -44,13 +44,6 @@
@interface SOGoTaskObject : SOGoCalendarComponent
/* "iCal multifolder saves" */
- (NSException *) saveContentString: (NSString *) _iCal
baseSequence: (int) _v;
- (NSException *) deleteWithBaseSequence: (int) _v;
- (NSException *) saveContentString: (NSString *) _iCalString;
@end
#endif /* __Appointmentss_SOGoTaskObject_H__ */

View File

@ -38,415 +38,17 @@
#import "SOGoTaskObject.h"
@interface SOGoTaskObject (PrivateAPI)
- (NSString *) homePageURLForPerson: (iCalPerson *) _person;
@end
@implementation SOGoTaskObject
static NSString *mailTemplateDefaultLanguage = nil;
+ (void)initialize {
NSUserDefaults *ud;
static BOOL didInit = NO;
if (didInit) return;
didInit = YES;
ud = [NSUserDefaults standardUserDefaults];
mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
retain];
if (!mailTemplateDefaultLanguage)
mailTemplateDefaultLanguage = @"French";
}
- (NSString *) componentTag
{
return @"vtodo";
}
/* iCal handling */
- (NSArray *)attendeeUIDsFromTask:(iCalToDo *)_task {
LDAPUserManager *um;
NSMutableArray *uids;
NSArray *attendees;
unsigned i, count;
NSString *email, *uid;
if (![_task isNotNull])
return nil;
if ((attendees = [_task attendees]) == nil)
return nil;
count = [attendees count];
uids = [NSMutableArray arrayWithCapacity:count + 1];
um = [LDAPUserManager sharedUserManager];
/* add organizer */
email = [[_task organizer] rfc822Email];
if ([email isNotNull]) {
uid = [um getUIDForEmail:email];
if ([uid isNotNull]) {
[uids addObject:uid];
}
else
[self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
}
/* add attendees */
for (i = 0; i < count; i++) {
iCalPerson *person;
person = [attendees objectAtIndex:i];
email = [person rfc822Email];
if (![email isNotNull]) continue;
uid = [um getUIDForEmail:email];
if (![uid isNotNull]) {
[self logWithFormat:@"Note: got no uid for email: '%@'", email];
continue;
}
if (![uids containsObject:uid])
[uids addObject:uid];
}
return uids;
}
/* store in all the other folders */
- (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
e = [[container lookupCalendarFoldersForUIDs: _uids inContext: context]
objectEnumerator];
while ((folder = [e nextObject]) != nil) {
NSException *error;
SOGoTaskObject *task;
if (![folder isNotNull]) /* no folder was found for given UID */
continue;
task = [folder lookupName:[self nameInContainer] inContext: context
acquire:NO];
if ([task isKindOfClass: [NSException class]])
{
[self logWithFormat:@"Note: an exception occured finding '%@' in folder: %@",
[self nameInContainer], folder];
[self logWithFormat:@"the exception reason was: %@",
[(NSException *) task reason]];
continue;
}
if (![task isNotNull]) {
[self logWithFormat:@"Note: did not find '%@' in folder: %@",
[self nameInContainer], folder];
continue;
}
if ((error = [task primarySaveContentString:_iCal]) != nil) {
[self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
// TODO: make compound
allErrors = error;
}
}
return allErrors;
}
// - (NSException *)deleteInUIDs:(NSArray *)_uids {
// NSEnumerator *e;
// id folder;
// NSException *allErrors = nil;
// e = [[container lookupCalendarFoldersForUIDs: _uids inContext: context]
// objectEnumerator];
// while ((folder = [e nextObject])) {
// NSException *error;
// SOGoTaskObject *task;
// task = [folder lookupName: [self nameInContainer]
// inContext: context
// acquire: NO];
// if (![task isNotNull]) {
// [self logWithFormat:@"Note: did not find '%@' in folder: %@",
// [self nameInContainer], folder];
// continue;
// }
// if ([task isKindOfClass: [NSException class]]) {
// [self logWithFormat:@"Exception: %@", [(NSException *) task reason]];
// continue;
// }
// if ((error = [task primaryDelete]) != nil) {
// [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
// // TODO: make compound
// allErrors = error;
// }
// }
// return allErrors;
// }
/* "iCal multifolder saves" */
- (NSException *) saveContentString: (NSString *) _iCal
baseSequence: (int) _v
{
/*
Note: we need to delete in all participants folders and send iMIP messages
for all external accounts.
Steps:
- fetch stored content
- parse old content
- check if sequence matches (or if 0=ignore)
- extract old attendee list + organizer (make unique)
- parse new content (ensure that sequence is increased!)
- extract new attendee list + organizer (make unique)
- make a diff => new, same, removed
- write to new, same
- delete in removed folders
- send iMIP mail for all folders not found
*/
// LDAPUserManager *um;
// iCalCalendar *calendar;
// iCalToDo *oldApt, *newApt;
// // iCalToDoChanges *changes;
// iCalPerson *organizer;
// NSString *oldContent, *uid;
// NSArray *uids, *props;
// NSMutableArray *attendees, *storeUIDs, *removedUIDs;
NSException *storeError, *delError;
// BOOL updateForcesReconsider;
// updateForcesReconsider = NO;
// if ([_iCal length] == 0) {
// return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
// reason:@"got no iCalendar content to store!"];
// }
// um = [LDAPUserManager sharedUserManager];
// /* handle old content */
// oldContent = [self contentAsString]; /* if nil, this is a new task */
// if ([oldContent length] == 0)
// {
// /* new task */
// [self debugWithFormat:@"saving new task: %@", _iCal];
// oldApt = nil;
// }
// else
// {
// calendar = [iCalCalendar parseSingleFromSource: oldContent];
// oldApt = [self firstTaskFromCalendar: calendar];
// }
// /* compare sequence if requested */
// if (_v != 0) {
// // TODO
// }
// /* handle new content */
// calendar = [iCalCalendar parseSingleFromSource: _iCal];
// newApt = [self firstTaskFromCalendar: calendar];
// if (newApt == nil) {
// return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
// reason:@"could not parse iCalendar content!"];
// }
// /* diff */
// changes = [iCalToDoChanges changesFromEvent: oldApt
// toEvent: newApt];
// uids = [um getUIDsForICalPersons:[changes deletedAttendees]
// applyStrictMapping:NO];
// removedUIDs = [NSMutableArray arrayWithArray:uids];
// uids = [um getUIDsForICalPersons:[newApt attendees]
// applyStrictMapping:NO];
// storeUIDs = [NSMutableArray arrayWithArray:uids];
// props = [changes updatedProperties];
// /* detect whether sequence has to be increased */
// if ([changes hasChanges])
// [newApt increaseSequence];
// /* preserve organizer */
// organizer = [newApt organizer];
// uid = [um getUIDForICalPerson:organizer];
// if (uid) {
// if (![storeUIDs containsObject:uid])
// [storeUIDs addObject:uid];
// [removedUIDs removeObject:uid];
// }
// /* organizer might have changed completely */
// if (oldApt && ([props containsObject: @"organizer"])) {
// uid = [um getUIDForICalPerson:[oldApt organizer]];
// if (uid) {
// if (![storeUIDs containsObject:uid]) {
// if (![removedUIDs containsObject:uid]) {
// [removedUIDs addObject:uid];
// }
// }
// }
// }
// [self debugWithFormat:@"UID ops:\n store: %@\n remove: %@",
// storeUIDs, removedUIDs];
// /* if time did change, all participants have to re-decide ...
// * ... exception from that rule: the organizer
// */
// if (oldApt != nil &&
// ([props containsObject:@"startDate"] ||
// [props containsObject:@"endDate"] ||
// [props containsObject:@"duration"]))
// {
// NSArray *ps;
// unsigned i, count;
// ps = [newApt attendees];
// count = [ps count];
// for (i = 0; i < count; i++) {
// iCalPerson *p;
// p = [ps objectAtIndex:i];
// if (![p hasSameEmailAddress:organizer])
// [p setParticipationStatus:iCalPersonPartStatNeedsAction];
// }
// _iCal = [[newApt parent] versitString];
// updateForcesReconsider = YES;
// }
// /* perform storing */
storeError = [self primarySaveContentString: _iCal];
// storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
// delError = [self deleteInUIDs:removedUIDs];
// TODO: make compound
if (storeError != nil) return storeError;
// if (delError != nil) return delError;
/* email notifications */
// if ([self sendEMailNotifications])
// {
// attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
// [attendees removePerson:organizer];
// [self sendInvitationEMailForTask:newApt
// toAttendees:attendees];
// if (updateForcesReconsider) {
// attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
// [attendees removeObjectsInArray:[changes insertedAttendees]];
// [attendees removePerson:organizer];
// [self sendEMailUsingTemplateNamed: @"Update"
// forOldObject: oldApt
// andNewObject: newApt
// toAttendees: attendees];
// }
// attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
// [attendees removePerson: organizer];
// if ([attendees count]) {
// iCalToDo *cancelledApt;
// cancelledApt = [newApt copy];
// [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
// [self sendEMailUsingTemplateNamed: @"Removal"
// forOldObject: nil
// andNewObject: cancelledApt
// toAttendees: attendees];
// [cancelledApt release];
// }
// }
return nil;
}
- (NSException *)deleteWithBaseSequence:(int)_v {
/*
Note: We need to delete in all participants folders and send iMIP messages
for all external accounts.
Delete is basically identical to save with all attendees and the
organizer being deleted.
Steps:
- fetch stored content
- parse old content
- check if sequence matches (or if 0=ignore)
- extract old attendee list + organizer (make unique)
- delete in removed folders
- send iMIP mail for all folders not found
*/
// iCalToDo *task;
// NSArray *removedUIDs;
// NSMutableArray *attendees;
[self primaryDelete];
return nil;
// /* load existing content */
// task = (iCalToDo *) [self component: NO];
// /* compare sequence if requested */
// if (_v != 0) {
// // TODO
// }
// removedUIDs = [self attendeeUIDsFromTask:task];
// if ([self sendEMailNotifications])
// {
// /* send notification email to attendees excluding organizer */
// attendees = [NSMutableArray arrayWithArray:[task attendees]];
// [attendees removePerson:[task organizer]];
// /* flag task as being cancelled */
// [(iCalCalendar *) [task parent] setMethod: @"cancel"];
// [task increaseSequence];
// /* remove all attendees to signal complete removal */
// [task removeAllAttendees];
// /* send notification email */
// [self sendEMailUsingTemplateNamed: @"Deletion"
// forOldObject: nil
// andNewObject: task
// toAttendees: attendees];
// }
// /* perform */
// return [self deleteInUIDs:removedUIDs];
}
- (NSException *)saveContentString:(NSString *)_iCalString {
return [self saveContentString:_iCalString baseSequence:0];
}
/* message type */
- (NSString *)outlookMessageClass {
- (NSString *) outlookMessageClass
{
return @"IPM.Task";
}

View File

@ -25,11 +25,20 @@
#import <NGCards/iCalEntityObject.h>
@class SOGoUser;
@interface iCalEntityObject (SOGoExtensions)
- (BOOL) userIsParticipant: (SOGoUser *) user;
- (BOOL) userIsOrganizer: (SOGoUser *) user;
- (NSArray *) attendeeUIDs;
- (BOOL) isStillRelevant;
- (id) itipEntryWithMethod: (NSString *) method;
- (NSArray *) attendeesWithoutUser: (SOGoUser *) user;
@end
#endif /* ICALENTITYOBJECT_SOGO_H */

View File

@ -23,11 +23,14 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSEnumerator.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalPerson.h>
#import <SoObjects/SOGo/NSArray+Utilities.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import "iCalPerson+SOGo.h"
#import "iCalEntityObject+SOGo.h"
@implementation iCalEntityObject (SOGoExtensions)
@ -52,6 +55,7 @@
return isParticipant;
}
#warning user could be a delegate, we will need to handle that someday
- (BOOL) userIsOrganizer: (SOGoUser *) user
{
NSString *orgMail;
@ -61,4 +65,66 @@
return [user hasEmail: orgMail];
}
- (NSArray *) attendeeUIDs
{
NSEnumerator *attendees;
NSString *uid;
iCalPerson *currentAttendee;
NSMutableArray *uids;
uids = [NSMutableArray array];
attendees = [[self attendees] objectEnumerator];
while ((currentAttendee = [attendees nextObject]))
{
uid = [currentAttendee uid];
if (uid)
[uids addObject: uid];
}
return uids;
}
#warning this method should be implemented in a category of iCalToDo
- (BOOL) isStillRelevant
{
[self subclassResponsibility: _cmd];
return NO;
}
- (id) itipEntryWithMethod: (NSString *) method
{
iCalCalendar *newCalendar;
iCalEntityObject *newEntry;
newCalendar = [parent mutableCopy];
[newCalendar autorelease];
[newCalendar setMethod: method];
newEntry = (iCalEntityObject *) [newCalendar firstChildWithTag: tag];
return newEntry;
}
- (NSArray *) attendeesWithoutUser: (SOGoUser *) user
{
NSMutableArray *newAttendees;
NSArray *oldAttendees;
unsigned int count, max;
iCalPerson *currentAttendee;
NSString *userID;
userID = [user login];
oldAttendees = [self attendees];
max = [oldAttendees count];
newAttendees = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
currentAttendee = [oldAttendees objectAtIndex: count];
if (![[currentAttendee uid] isEqualToString: userID])
[newAttendees addObject: currentAttendee];
}
return newAttendees;
}
@end

View File

@ -0,0 +1,35 @@
/* iCalEvent+SOGo.h - 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.
*/
#ifndef ICALEVENT_SOGO_H
#define ICALEVENT_SOGO_H
#import <NGCards/iCalEvent.h>
@interface iCalEvent (SOGoExtensions)
- (BOOL) isStillRelevant;
@end
#endif /* ICALEVENT_SOGO_H */

View File

@ -0,0 +1,38 @@
/* iCalEvent+SOGo.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.
*/
#import <Foundation/NSCalendarDate.h>
#import "iCalEvent+SOGo.h"
@implementation iCalEvent (SOGoExtensions)
- (BOOL) isStillRelevant
{
NSCalendarDate *now;
now = [NSCalendarDate calendarDate];
return ([[self endDate] earlierDate: now] == now);
}
@end

View File

@ -0,0 +1,34 @@
/* iCalEventChanges+SOGo.h - 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.
*/
#ifndef ICALEVENTCHANGES_SOGO_H
#define ICALEVENTCHANGES_SOGO_H
#import <NGCards/iCalEventChanges.h>
@interface iCalEventChanges (SOGoExtensions)
- (BOOL) sequenceShouldBeIncreased;
@end
#endif /* ICALEVENTCHANGES_SOGO_H */

View File

@ -0,0 +1,52 @@
/* iCalEventChanges+SOGo.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.
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSString.h>
#import "iCalEventChanges+SOGo.h"
@implementation iCalEventChanges (SOGoExtensions)
- (BOOL) sequenceShouldBeIncreased
{
NSString *properties[] = {@"organizer", @"startDate", @"endDate", /* vtask:
@"due" */
@"rdate", @"rrule", @"exdate", @"exrule",
@"status", @"location", nil};
NSString **currentProperty;
BOOL updateRequired;
updateRequired = NO;
currentProperty = properties;
while (!updateRequired && *currentProperty)
if ([updatedProperties containsObject: *currentProperty])
updateRequired = YES;
else
currentProperty++;
return updateRequired;
}
@end

View File

@ -0,0 +1,39 @@
/* iCalPerson+SOGo.h - 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.
*/
#ifndef ICALPERSON_SOGO_H
#define ICALPERSON_SOGO_H
#import <NGCards/iCalPerson.h>
#import <SoObjects/SOGo/LDAPUserManager.h>
@class NSString;
@interface iCalPerson (SOGoExtension)
- (NSString *) mailAddress;
- (NSString *) uid;
@end
#endif /* ICALPERSON_SOGO_H */

View File

@ -0,0 +1,53 @@
/* iCalPerson+SOGo.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.
*/
#import <Foundation/NSString.h>
#import "iCalPerson+SOGo.h"
static LDAPUserManager *um = nil;
@implementation iCalPerson (SOGoExtension)
- (NSString *) mailAddress
{
NSString *cn, *email, *mailAddress;
cn = [self cnWithoutQuotes];
email = [self rfc822Email];
if ([cn length])
mailAddress = [NSString stringWithFormat:@"%@ <%@>", cn, email];
else
mailAddress = email;
return mailAddress;
}
- (NSString *) uid
{
if (!um)
um = [LDAPUserManager sharedUserManager];
return [um getUIDForEmail: [self rfc822Email]];
}
@end

View File

@ -41,6 +41,7 @@
NSString *IDField; /* the first part of a user DN */
NSString *CNField;
NSString *UIDField;
NSArray *mailFields;
NSString *bindFields;
NGLdapConnection *ldapConnection;
@ -59,6 +60,7 @@
IDField: (NSString *) newIDField
CNField: (NSString *) newCNField
UIDField: (NSString *) newUIDField
mailFields: (NSArray *) newMailFields
andBindFields: (NSString *) newBindFields;
- (BOOL) checkLogin: (NSString *) login

View File

@ -146,6 +146,8 @@ static int sizeLimit;
IDField = @"cn"; /* the first part of a user DN */
CNField = @"cn";
UIDField = @"uid";
mailFields = [NSArray arrayWithObject: @"mail"];
[mailFields retain];
bindFields = nil;
ldapConnection = nil;
@ -164,6 +166,7 @@ static int sizeLimit;
[IDField release];
[CNField release];
[UIDField release];
[mailFields release];
[bindFields release];
[ldapConnection release];
[sourceID release];
@ -183,7 +186,8 @@ static int sizeLimit;
[self setBaseDN: [udSource objectForKey: @"baseDN"]
IDField: [udSource objectForKey: @"IDFieldName"]
CNField: [udSource objectForKey: @"CNFieldName"]
UIDField: [udSource objectForKey: @"UIDFieldName"]
UIDField: [udSource objectForKey: @"UIDFieldName"]
mailFields: [udSource objectForKey: @"MailFieldNames"]
andBindFields: [udSource objectForKey: @"bindFields"]];
return self;
@ -205,6 +209,7 @@ static int sizeLimit;
IDField: (NSString *) newIDField
CNField: (NSString *) newCNField
UIDField: (NSString *) newUIDField
mailFields: (NSArray *) newMailFields
andBindFields: (NSString *) newBindFields
{
ASSIGN (baseDN, newBaseDN);
@ -214,6 +219,8 @@ static int sizeLimit;
ASSIGN (CNField, newCNField);
if (UIDField)
ASSIGN (UIDField, newUIDField);
if (newMailFields)
ASSIGN (mailFields, newMailFields);
if (newBindFields)
ASSIGN (bindFields, newBindFields);
}
@ -346,8 +353,6 @@ static int sizeLimit;
- (NSArray *) _searchAttributes
{
NSArray *attrs;
if (!searchAttributes)
{
searchAttributes = [NSMutableArray new];
@ -355,15 +360,10 @@ static int sizeLimit;
[searchAttributes addObject: CNField];
if (UIDField)
[searchAttributes addObject: UIDField];
[searchAttributes addObjectsFromArray: mailFields];
[searchAttributes addObjectsFromArray: commonSearchFields];
}
// We also include our MailFieldNames in the search
if ((attrs = [[[LDAPUserManager sharedUserManager] metadataForSourceID: sourceID] objectForKey: @"MailFieldNames"]))
{
[searchAttributes addObjectsFromArray: attrs];
}
return searchAttributes;
}
@ -397,6 +397,25 @@ static int sizeLimit;
return ids;
}
- (void) _fillEmailsOfEntry: (NGLdapEntry *) ldapEntry
intoContactEntry: (NSMutableDictionary *) contactEntry
{
NSEnumerator *emailFields;
NSString *currentFieldName, *value;
NSMutableArray *emails;
emails = [NSMutableArray new];
emailFields = [mailFields objectEnumerator];
while ((currentFieldName = [emailFields nextObject]))
{
value = [[ldapEntry attributeWithName: currentFieldName] stringValueAtIndex: 0];
if (value)
[emails addObject: value];
}
[emails autorelease];
[contactEntry setObject: emails forKey: @"c_emails"];
}
- (NSDictionary *) _convertLDAPEntryToContact: (NGLdapEntry *) ldapEntry
{
NSMutableDictionary *contactEntry;
@ -426,6 +445,7 @@ static int sizeLimit;
if (!value)
value = @"";
[contactEntry setObject: value forKey: @"c_cn"];
[self _fillEmailsOfEntry: ldapEntry intoContactEntry: contactEntry];
return contactEntry;
}

View File

@ -27,6 +27,7 @@
#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSValue.h>
#import "NSArray+Utilities.h"
#import "LDAPSource.h"
#import "LDAPUserManager.h"
@ -289,9 +290,7 @@ static NSString *defaultMailDomain = nil;
emails = [contact objectForKey: @"emails"];
uid = [contact objectForKey: @"c_uid"];
systemEmail = [NSString stringWithFormat: @"%@@%@", uid, defaultMailDomain];
if ([emails containsObject: systemEmail])
[emails removeObject: systemEmail];
[emails addObject: systemEmail];
[emails addObjectUniquely: systemEmail];
[contact setObject: [emails objectAtIndex: 0] forKey: @"c_email"];
}
@ -302,8 +301,8 @@ static NSString *defaultMailDomain = nil;
NSDictionary *userEntry;
NSEnumerator *ldapSources;
LDAPSource *currentSource;
NSString *cn, *email, *c_uid;
NSArray *attrs;
NSString *cn, *c_uid;
NSArray *c_emails;
emails = [NSMutableArray array];
cn = nil;
@ -320,18 +319,9 @@ static NSString *defaultMailDomain = nil;
cn = [userEntry objectForKey: @"c_cn"];
if (!c_uid)
c_uid = [userEntry objectForKey: @"c_uid"];
if ((attrs = [[sourcesMetadata objectForKey: [currentSource sourceID]] objectForKey: @"MailFieldNames"]))
{
int i;
for (i = 0; i < [attrs count]; i++)
{
email = [userEntry objectForKey: [attrs objectAtIndex: i]];
if (email && ![emails containsObject: email])
[emails addObject: email];
}
}
c_emails = [userEntry objectForKey: @"c_emails"];
if ([c_emails count])
[emails addObjectsFromArray: c_emails];
}
currentSource = [ldapSources nextObject];
}

View File

@ -48,7 +48,6 @@
/* content */
- (BOOL) isNew;
- (void) setContentString: (NSString *) newContent;
- (NSString *) contentAsString;
- (NSException *) saveContentString: (NSString *) _str
baseVersion: (unsigned int) _baseVersion;
@ -65,4 +64,10 @@
@end
@interface SOGoContentObject (OptionalMethods)
- (void) prepareDelete;
@end
#endif /* __SOGo_SOGoContentObject_H__ */

View File

@ -138,11 +138,6 @@
return content;
}
- (void) setContentString: (NSString *) newContent
{
ASSIGN (content, newContent);
}
- (NSException *) saveContentString: (NSString *) newContent
baseVersion: (unsigned int) newBaseVersion
{
@ -152,11 +147,12 @@
ex = nil;
[self setContentString: newContent];
ASSIGN (content, newContent);
folder = [container ocsFolder];
if (folder)
{
ex = [folder writeContent: content toName: nameInContainer
ex = [folder writeContent: newContent toName: nameInContainer
baseVersion: newBaseVersion];
if (ex)
[self errorWithFormat:@"write failed: %@", ex];

View File

@ -350,7 +350,11 @@ static NSString *defaultUserID = @"<default>";
deleteObject = [self lookupName: currentID
inContext: context acquire: NO];
if (![deleteObject isKindOfClass: [NSException class]])
[deleteObject delete];
{
if ([deleteObject respondsToSelector: @selector (prepareDelete)])
[deleteObject prepareDelete];
[deleteObject delete];
}
}
}

View File

@ -34,17 +34,19 @@
context.activeUser
*/
@class NSString;
@class NSArray;
@class NSDictionary;
@class NSString;
@class NSTimeZone;
@class NSURL;
@class NSUserDefaults;
@class NSTimeZone;
@class WOContext;
@class SOGoAppointmentFolder;
@class SOGoAppointmentFolders;
@class SOGoDateFormatter;
@class WOContext;
@class SOGoUserFolder;
extern NSString *SOGoWeekStartHideWeekNumbers;
extern NSString *SOGoWeekStartJanuary1;
@ -68,6 +70,7 @@ extern NSString *SOGoWeekStartFirstFullWeek;
NSTimeZone *userTimeZone;
SOGoDateFormatter *dateFormatter;
NSMutableArray *mailAccounts;
SOGoUserFolder *homeFolder;
}
+ (NSString *) language;
@ -119,7 +122,7 @@ extern NSString *SOGoWeekStartFirstFullWeek;
/* folders */
- (id) homeFolderInContext: (id) _ctx;
- (SOGoUserFolder *) homeFolderInContext: (id) context;
- (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context;
- (SOGoAppointmentFolder *)

View File

@ -142,6 +142,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
language = nil;
currentPassword = nil;
dateFormatter = nil;
homeFolder = nil;
}
return self;
@ -176,6 +177,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
[allEmails release];
[language release];
[dateFormatter release];
[homeFolder release];
[super dealloc];
}
@ -528,27 +530,17 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
// TODO: those methods should check whether the traversal stack in the context
// already contains proper folders to improve caching behaviour
- (id) homeFolderInContext: (id) _ctx
- (SOGoUserFolder *) homeFolderInContext: (id) context
{
/* Note: watch out for cyclic references */
// TODO: maybe we should add an [activeUser reset] method to SOPE
id folder;
folder = [(WOContext *)_ctx objectForKey:@"ActiveUserHomeFolder"];
if (folder != nil)
return [folder isNotNull] ? folder : nil;
folder = [[WOApplication application] lookupName: [self login]
inContext: _ctx
acquire: NO];
if ([folder isKindOfClass:[NSException class]])
return folder;
[(WOContext *)_ctx setObject: ((folder)
? folder
: (id)[NSNull null])
forKey: @"ActiveUserHomeFolder"];
return folder;
if (!homeFolder)
{
homeFolder = [[WOApplication application] lookupName: [self login]
inContext: context
acquire: NO];
[homeFolder retain];
}
return homeFolder;
}
- (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context

View File

@ -11,16 +11,12 @@ CommonUI_LANGUAGES = English French German
CommonUI_OBJC_FILES += \
CommonUIProduct.m \
UIxPageFrame.m \
UIxPrintPageFrame.m \
UIxAppNavView.m \
\
UIxAclEditor.m \
UIxObjectActions.m \
UIxFolderActions.m \
UIxParentFolderActions.m \
UIxElemBuilder.m \
UIxTabView.m \
UIxTabItem.m \
UIxUserRightsEditor.m \
\
UIxToolbar.m \

View File

@ -40,6 +40,7 @@
NSString *toolbar;
id item;
BOOL isPopup;
NSMutableArray *additionalCSSFiles;
NSMutableArray *additionalJSFiles;
}

View File

@ -241,6 +241,29 @@
return ([[self productJavaScriptURL] length] > 0);
}
- (void) setCssFiles: (NSString *) newCSSFiles
{
NSEnumerator *cssFiles;
NSString *currentFile, *filename;
[additionalCSSFiles release];
additionalCSSFiles = [NSMutableArray new];
cssFiles
= [[newCSSFiles componentsSeparatedByString: @","] objectEnumerator];
while ((currentFile = [cssFiles nextObject]))
{
filename = [self urlForResourceFilename:
[currentFile stringByTrimmingSpaces]];
[additionalCSSFiles addObject: filename];
}
}
- (NSArray *) additionalCSSFiles
{
return additionalCSSFiles;
}
- (void) setJsFiles: (NSString *) newJSFiles
{
NSEnumerator *jsFiles;

View File

@ -19,6 +19,7 @@ Attendees = "Attendees";
request_info = "invites you to participate in a meeting.";
"Add to calendar" = "Add to calendar";
"Delete from calendar" = "Delete from calendar";
"Update status" = "Update status";
Accept = "Accept";
Decline = "Decline";
Tentative = "Tentative";

View File

@ -19,6 +19,7 @@ Attendees = "Invités";
request_info = "vous invite à une réunion.";
"Add to calendar" = "Ajouter à l'agenda";
"Delete from calendar" = "Effacer de l'agenda";
"Update status" = "Intégrer les modifications";
Accept = "Accepter";
Decline = "Decliner";
Tentative = "Tentative";

View File

@ -19,6 +19,7 @@ Attendees = "Attendees";
request_info = "invites you to participate in a meeting.";
"Add to calendar" = "Add to calendar";
"Delete from calendar" = "Delete from calendar";
"Update status" = "Update status";
Accept = "Accept";
Decline = "Decline";
Tentative = "Tentative";

View File

@ -26,12 +26,17 @@
#import <NGObjWeb/WOResponse.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalPerson.h>
#import <UI/Common/WODirectAction+SOGo.h>
#import <NGImap4/NGImap4EnvelopeAddress.h>
#import <SoObjects/Appointments/iCalPerson+SOGo.h>
#import <SoObjects/Appointments/SOGoAppointmentObject.h>
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
#import <SoObjects/Mailer/SOGoMailObject.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/iCalEntityObject+Utilities.h>
#import <SoObjects/Mailer/SOGoMailBodyPart.h>
@ -58,12 +63,12 @@
}
- (SOGoAppointmentObject *) _eventObjectWithUID: (NSString *) uid
forUser: (SOGoUser *) user
{
SOGoAppointmentFolder *personalFolder;
SOGoAppointmentObject *eventObject;
personalFolder
= [[context activeUser] personalCalendarFolderInContext: context];
personalFolder = [user personalCalendarFolderInContext: context];
eventObject = [personalFolder lookupName: uid
inContext: context acquire: NO];
if (![eventObject isKindOfClass: [SOGoAppointmentObject class]])
@ -73,6 +78,11 @@
return eventObject;
}
- (SOGoAppointmentObject *) _eventObjectWithUID: (NSString *) uid
{
return [self _eventObjectWithUID: uid forUser: [context activeUser]];
}
- (iCalEvent *)
_setupChosenEventAndEventObject: (SOGoAppointmentObject **) eventObject
{
@ -86,7 +96,7 @@
chosenEvent = emailEvent;
else
{
calendarEvent = (iCalEvent *) [*eventObject component: NO];
calendarEvent = (iCalEvent *) [*eventObject component: NO secure: NO];
if ([calendarEvent compare: emailEvent] == NSOrderedAscending)
chosenEvent = emailEvent;
else
@ -99,14 +109,42 @@
return chosenEvent;
}
#warning this is code copied from SOGoAppointmentObject...
- (void) _updateAttendee: (iCalPerson *) attendee
withSequence: (NSNumber *) sequence
andCalUID: (NSString *) calUID
forUID: (NSString *) uid
{
SOGoAppointmentObject *eventObject;
iCalEvent *event;
iCalPerson *otherAttendee;
NSString *iCalString;
eventObject = [self _eventObjectWithUID: calUID
forUser: [SOGoUser userWithLogin: uid roles: nil]];
if (![eventObject isNew])
{
event = [eventObject component: NO secure: NO];
if ([[event sequence] compare: sequence]
== NSOrderedSame)
{
otherAttendee
= [event findParticipantWithEmail: [attendee rfc822Email]];
[otherAttendee setPartStat: [attendee partStat]];
iCalString = [[event parent] versitString];
[eventObject saveContentString: iCalString];
}
}
}
- (WOResponse *) _changePartStatusAction: (NSString *) newStatus
{
WOResponse *response;
SOGoAppointmentObject *eventObject;
iCalEvent *chosenEvent;
iCalPerson *user;
iCalCalendar *calendar;
NSString *rsvp, *method;
iCalCalendar *emailCalendar, *calendar;
NSString *rsvp, *method, *organizerUID;
chosenEvent = [self _setupChosenEventAndEventObject: &eventObject];
if (chosenEvent)
@ -114,7 +152,8 @@
user = [chosenEvent findParticipant: [context activeUser]];
[user setPartStat: newStatus];
calendar = [chosenEvent parent];
method = [[calendar method] lowercaseString];
emailCalendar = [[self _emailEvent] parent];
method = [[emailCalendar method] lowercaseString];
if ([method isEqualToString: @"request"])
{
[calendar setMethod: @""];
@ -123,8 +162,12 @@
else
rsvp = nil;
[eventObject saveContentString: [calendar versitString]];
if (rsvp && [rsvp isEqualToString: @"true"])
if ([rsvp isEqualToString: @"true"])
[eventObject sendResponseToOrganizer];
organizerUID = [[chosenEvent organizer] uid];
if (organizerUID)
[self _updateAttendee: user withSequence: [chosenEvent sequence]
andCalUID: [chosenEvent uid] forUID: organizerUID];
response = [self responseWith204];
}
else
@ -146,6 +189,97 @@
return [self _changePartStatusAction: @"DECLINED"];
}
- (WOResponse *) deleteFromCalendarAction
{
iCalEvent *emailEvent;
SOGoAppointmentObject *eventObject;
WOResponse *response;
emailEvent = [self _emailEvent];
if (emailEvent)
{
eventObject = [self _eventObjectWithUID: [emailEvent uid]];
response = [self responseWith204];
}
else
{
response = [context response];
[response setStatus: 409];
}
return response;
}
- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
{
NSString *emailFrom;
SOGoMailObject *mailObject;
NGImap4EnvelopeAddress *address;
mailObject = [[self clientObject] mailObject];
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
emailFrom = [address baseEMail];
return [event findParticipantWithEmail: emailFrom];
}
- (BOOL) _updateParticipantStatusInEvent: (iCalEvent *) calendarEvent
fromEvent: (iCalEvent *) emailEvent
inObject: (SOGoAppointmentObject *) eventObject
{
iCalPerson *calendarParticipant, *mailParticipant;
NSString *partStat;
BOOL result;
calendarParticipant = [self _emailParticipantWithEvent: calendarEvent];
mailParticipant = [self _emailParticipantWithEvent: emailEvent];
if (calendarParticipant && mailParticipant)
{
result = YES;
partStat = [mailParticipant partStat];
if ([partStat caseInsensitiveCompare: [calendarParticipant partStat]]
!= NSOrderedSame)
{
[calendarParticipant setPartStat: [partStat uppercaseString]];
[eventObject saveComponent: calendarEvent];
}
}
else
result = NO;
return result;
}
- (WOResponse *) updateUserStatusAction
{
iCalEvent *emailEvent, *calendarEvent;
SOGoAppointmentObject *eventObject;
WOResponse *response;
response = nil;
emailEvent = [self _emailEvent];
if (emailEvent)
{
eventObject = [self _eventObjectWithUID: [emailEvent uid]];
calendarEvent = [eventObject component: NO secure: NO];
if (([[emailEvent sequence] compare: [calendarEvent sequence]]
!= NSOrderedDescending)
&& ([self _updateParticipantStatusInEvent: calendarEvent
fromEvent: emailEvent
inObject: eventObject]))
response = [self responseWith204];
}
if (!response)
{
response = [context response];
[response setStatus: 409];
}
return response;
}
// - (WOResponse *) markTentativeAction
// {
// return [self _changePartStatusAction: @"TENTATIVE"];

View File

@ -24,10 +24,12 @@
#import "UIxMailPartViewer.h"
@class SOGoDateFormatter;
@class iCalEvent;
@class iCalCalendar;
@class SOGoAppointmentObject;
@class SOGoDateFormatter;
@interface UIxMailPartICalViewer : UIxMailPartViewer
{
iCalCalendar *inCalendar;
@ -35,7 +37,7 @@
id attendee;
SOGoDateFormatter *dateFormatter;
id item;
id storedEventObject;
SOGoAppointmentObject *storedEventObject;
iCalEvent *storedEvent;
}

View File

@ -28,7 +28,6 @@
#import <NGObjWeb/WOResponse.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGImap4/NGImap4EnvelopeAddress.h>
@ -40,9 +39,11 @@
#import <SoObjects/SOGo/SOGoDateFormatter.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/Appointments/iCalEntityObject+SOGo.h>
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
#import <SoObjects/Appointments/SOGoAppointmentObject.h>
#import <SoObjects/Mailer/SOGoMailObject.h>
#import <SoObjects/Mailer/SOGoMailBodyPart.h>
#import "UIxMailPartICalViewer.h"
@ -109,35 +110,32 @@
- (BOOL) couldParseCalendar
{
return [[self inCalendar] isNotNull];
return (([self inCalendar]));
}
- (iCalEvent *) inEvent
{
NSArray *events;
if (inEvent)
return [inEvent isNotNull] ? inEvent : nil;
events = [[self inCalendar] events];
if ([events count] > 0) {
inEvent = [[events objectAtIndex:0] retain];
return inEvent;
}
else {
inEvent = [[NSNull null] retain];
return nil;
}
if (!inEvent)
{
events = [[self inCalendar] events];
if ([events count] > 0)
inEvent = [[events objectAtIndex:0] retain];
}
return inEvent;
}
/* formatters */
- (SOGoDateFormatter *) dateFormatter
{
if (dateFormatter == nil) {
dateFormatter = [[context activeUser] dateFormatterInContext: context];
[dateFormatter retain];
}
if (!dateFormatter)
{
dateFormatter = [[context activeUser] dateFormatterInContext: context];
[dateFormatter retain];
}
return dateFormatter;
}
@ -146,7 +144,7 @@
- (void) setAttendee: (id) _attendee
{
ASSIGN(attendee, _attendee);
ASSIGN (attendee, _attendee);
}
- (id) attendee
@ -220,7 +218,7 @@
/* calendar folder support */
- (id) calendarFolder
- (SOGoAppointmentFolder *) calendarFolder
{
/* return scheduling calendar of currently logged-in user */
SOGoUser *user;
@ -234,49 +232,50 @@
return [folder lookupName: @"personal" inContext: context acquire: NO];
}
- (id) storedEventObject
- (SOGoAppointmentObject *) storedEventObject
{
/* lookup object in the users Calendar */
id calendar;
SOGoAppointmentFolder *calendar;
NSString *filename;
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) {
// 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.
id tmp;
tmp = [calendar lookupName:filename inContext:[self context] acquire:NO];
if ([tmp isNotNull] && ![tmp isKindOfClass:[NSException class]])
storedEventObject = [tmp retain];
if (!storedEventObject)
{
calendar = [self calendarFolder];
if ([calendar isKindOfClass: [NSException class]])
[self errorWithFormat:@"Did not find Calendar folder: %@", calendar];
else
{
filename = [calendar resourceNameForEventUID:[[self inEvent] uid]];
if (filename)
{
storedEventObject = [calendar lookupName: filename
inContext: [self context]
acquire: NO];
if ([storedEventObject isKindOfClass: [NSException class]])
storedEventObject = nil;
else
[storedEventObject retain];
}
}
}
}
if (storedEventObject == nil)
storedEventObject = [[NSNull null] retain];
return storedEventObject;
}
- (BOOL) isEventStoredInCalendar
{
return [[self storedEventObject] isNotNull];
return (([self storedEventObject]));
}
- (iCalEvent *) storedEvent
{
return (iCalEvent *) [(SOGoAppointmentObject *)[self storedEventObject] component: NO];
if (!storedEvent)
{
storedEvent = [[self storedEventObject] component: NO secure: NO];
[storedEvent retain];
}
return storedEvent;
}
/* organizer tracking */
@ -294,34 +293,24 @@
{
iCalEvent *authorativeEvent;
if ([[self storedEvent] compare: [self inEvent]]
== NSOrderedAscending)
[self storedEvent];
if (!storedEvent
|| ([storedEvent compare: [self inEvent]] == NSOrderedAscending))
authorativeEvent = inEvent;
else
authorativeEvent = storedEventObject;
authorativeEvent = [self storedEvent];
return authorativeEvent;
}
- (BOOL) isLoggedInUserTheOrganizer
{
iCalPerson *organizer;
organizer = [[self authorativeEvent] organizer];
return [[context activeUser] hasEmail: [organizer rfc822Email]];
return [[self authorativeEvent] userIsOrganizer: [context activeUser]];
}
- (BOOL) isLoggedInUserAnAttendee
{
NSString *loginEMail;
if ((loginEMail = [self loggedInUserEMail]) == nil) {
[self warnWithFormat:@"Could not determine email of logged in user?"];
return NO;
}
return [[self authorativeEvent] isParticipant:loginEMail];
return [[self authorativeEvent] userIsParticipant: [context activeUser]];
}
/* derived fields */
@ -405,7 +394,34 @@
- (BOOL) isReplySenderAnAttendee
{
return [[self storedReplyAttendee] isNotNull];
return (([self storedReplyAttendee]));
}
- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
{
NSString *emailFrom;
SOGoMailObject *mailObject;
NGImap4EnvelopeAddress *address;
mailObject = [[self clientObject] mailObject];
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
emailFrom = [address baseEMail];
return [event findParticipantWithEmail: emailFrom];
}
- (BOOL) hasSenderStatusChanged
{
iCalPerson *emailParticipant, *calendarParticipant;
[self inEvent];
[self storedEvent];
emailParticipant = [self _emailParticipantWithEvent: inEvent];
calendarParticipant = [self _emailParticipantWithEvent: storedEvent];
return ([[emailParticipant partStat]
caseInsensitiveCompare: [calendarParticipant partStat]]
!= NSOrderedSame);
}
@end /* UIxMailPartICalViewer */

View File

@ -20,6 +20,11 @@
actionClass = "UIxMailPartICalActions";
actionName = "decline";
};
updateUserStatus = {
protectedBy = "View";
actionClass = "UIxMailPartICalActions";
actionName = "updateUserStatus";
};
/* tentative = {
protectedBy = "View";
actionClass = "UIxMailPartICalAction";
@ -29,12 +34,12 @@
protectedBy = "View";
actionClass = "UIxMailPartICalAction";
actionName = "addToCalendar";
};
}; */
deleteFromCalendar = {
protectedBy = "View";
actionClass = "UIxMailPartICalAction";
actionName = "deleteFromCalendar";
}; */
};
};
};
};

View File

@ -264,7 +264,7 @@
unsigned int minutes;
iCalRecurrenceRule *rule;
event = (iCalEvent *) [[self clientObject] component: NO];
event = (iCalEvent *) [[self clientObject] component: NO secure: YES];
if (event)
{
startDate = [event startDate];
@ -352,16 +352,7 @@
- (id <WOActionResults>) saveAction
{
SOGoAppointmentObject *clientObject;
NSString *iCalString;
clientObject = [self clientObject];
NSLog(@"saveAction, clientObject = %@", clientObject);
iCalString = [[clientObject calendar: NO] versitString];
NSLog(@"saveAction, iCalString = %@", iCalString);
[clientObject saveContentString: iCalString];
[[self clientObject] saveComponent: event];
return [self jsCloseWithRefreshMethod: @"refreshEventsAndDisplay()"];
}
@ -385,7 +376,7 @@
iCalRecurrenceRule *rule;
clientObject = [self clientObject];
event = (iCalEvent *) [clientObject component: YES];
event = (iCalEvent *) [clientObject component: YES secure: NO];
[super takeValuesFromRequest: _rq inContext: _ctx];
@ -443,23 +434,18 @@
// TODO: add tentatively
- (id) acceptOrDeclineAction: (BOOL) accept
- (id) acceptAction
{
[[self clientObject] changeParticipationStatus: (accept
? @"ACCEPTED"
: @"DECLINED")];
[[self clientObject] changeParticipationStatus: @"ACCEPTED"];
return self;
}
- (id) acceptAction
{
return [self acceptOrDeclineAction: YES];
}
- (id) declineAction
{
return [self acceptOrDeclineAction: NO];
[[self clientObject] changeParticipationStatus: @"DECLINED"];
return self;
}
@end

View File

@ -41,6 +41,7 @@
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <SoObjects/Appointments/iCalPerson+SOGo.h>
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
#import <SoObjects/Appointments/SOGoAppointmentFolders.h>
#import <SoObjects/Appointments/SOGoAppointmentObject.h>
@ -245,6 +246,16 @@
return url;
}
- (BOOL) hasOrganizer
{
return (![organizer isVoid]);
}
- (NSString *) organizerName
{
return [organizer mailAddress];
}
- (void) setAttendeesNames: (NSString *) newAttendeesNames
{
ASSIGN (attendeesNames, newAttendeesNames);
@ -694,29 +705,8 @@
respondsToSelector: @selector(saveContentString:)];
}
- (BOOL) containsConflict: (id) _component
{
[self subclassResponsibility: _cmd];
return NO;
}
/* access */
#if 0
- (iCalPerson *) getOrganizer
{
iCalPerson *p;
NSString *emailProp;
emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
p = [[[iCalPerson alloc] init] autorelease];
[p setEmail:emailProp];
[p setCn:[self cnForUser]];
return p;
}
#endif
- (BOOL) isMyComponent
{
return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
@ -845,6 +835,7 @@
[currentAttendee setCn: [names objectAtIndex: count]];
[currentAttendee setEmail: currentEmail];
[currentAttendee setRole: @"REQ-PARTICIPANT"];
[currentAttendee setRsvp: @"TRUE"];
[currentAttendee
setParticipationStatus: iCalPersonPartStatNeedsAction];
}

View File

@ -282,7 +282,7 @@
NSString *duration;
unsigned int minutes;
todo = (iCalToDo *) [[self clientObject] component: NO];
todo = (iCalToDo *) [[self clientObject] component: NO secure: YES];
if (todo)
{
startDate = [todo startDate];
@ -345,16 +345,23 @@
- (id <WOActionResults>) saveAction
{
SOGoTaskObject *clientObject;
NSString *iCalString;
clientObject = [self clientObject];
iCalString = [[clientObject calendar: NO] versitString];
[clientObject saveContentString: iCalString];
[[self clientObject] saveComponent: todo];
return [self jsCloseWithRefreshMethod: @"refreshTasks()"];
}
// - (id <WOActionResults>) saveAction
// {
// SOGoTaskObject *clientObject;
// NSString *iCalString;
// clientObject = [self clientObject];
// iCalString = [[clientObject calendar: NO secure: NO] versitString];
// [clientObject saveContentString: iCalString];
// return [self jsCloseWithRefreshMethod: @"refreshTasks()"];
// }
- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
inContext: (WOContext*) context
{
@ -372,7 +379,7 @@
SOGoTaskObject *clientObject;
clientObject = [self clientObject];
todo = (iCalToDo *) [clientObject component: YES];
todo = (iCalToDo *) [clientObject component: YES secure: NO];
[super takeValuesFromRequest: _rq inContext: _ctx];
@ -428,7 +435,7 @@
NSString *newStatus, *iCalString;
clientObject = [self clientObject];
todo = (iCalToDo *) [clientObject component: NO];
todo = (iCalToDo *) [clientObject component: NO secure: NO];
if (todo)
{
newStatus = [self queryParameterForKey: @"status"];
@ -441,7 +448,7 @@
[todo setStatus: @"IN-PROCESS"];
}
iCalString = [[clientObject calendar: NO] versitString];
iCalString = [[clientObject calendar: NO secure: NO] versitString];
[clientObject saveContentString: iCalString];
}

View File

@ -9,6 +9,9 @@
>
<!-- TODO: add iMIP actions -->
<input id="iCalendarAttachment" type="hidden"
var:value="pathToAttachmentObject"/>
<var:if condition="couldParseCalendar" const:negate="1">
<fieldset>
<legend>Parsing Error</legend>
@ -33,23 +36,24 @@
</var:if>
</legend>
<var:if condition="inCalendar.method" const:value="REQUEST">
<var:if condition="inCalendar.method.uppercaseString" 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>
<var:if condition="$storedReplyAttendee.partStatWithDefault"
const:value="NEEDS-ACTION" const:negate="YES">
<p class="uix_ical_toolbar" id="iCalendarToolbar">
<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>
</var:if>
<p>
<var:string label:value="Organizer" />
@ -70,20 +74,20 @@
</var:if>
<var:if condition="inCalendar.method" const:value="REPLY">
<var:if condition="inCalendar.method.uppercaseString" 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>
<var:if condition="hasSenderStatusChanged"
><p class="uix_ical_toolbar">
<input id="iCalendarUpdateUserStatus" class="button"
type="button" label:value="Update status"/>
</p
></var:if>
<!-- TODO: replies to events not in the calendar? -->
<p>
@ -96,7 +100,7 @@
</var:if>
</var:if>
<var:if condition="inCalendar.method" const:value="CANCEL">
<var:if condition="inCalendar.method.uppercaseString" const:value="CANCEL">
<!-- sent to attendees to notify of the attendee being removed or the
event being deleted -->
<var:if condition="isEventStoredInCalendar">
@ -113,13 +117,13 @@
</var:if>
<var:if condition="inCalendar.method" const:value="ADD">
<var:if condition="inCalendar.method.uppercaseString" const:value="ADD">
<!-- TODO -->
<p><var:string label:value="add_info_text" /></p>
</var:if>
<var:if condition="inCalendar.method" const:value="PUBLISH">
<var:if condition="inCalendar.method.uppercaseString" 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>
@ -145,7 +149,6 @@
-->
</var:if>
<!-- the user comment is used in replies -->
<var:if condition="inEvent.userComment.isNotEmpty">
<div class="linked_attachment_meta" style="background-color: white;">

View File

@ -11,6 +11,7 @@
const:popup="YES"
title="name"
var:toolbar="toolbar"
const:cssFiles="UIxComponentEditor.css"
const:jsFiles="skycalendar.js,UIxComponentEditor.js">
<script type="text/javascript">
@ -54,7 +55,11 @@
label:noSelectionString="prio_0"
string="itemPriorityText" selection="priority"/>
</span></span>
<label id="attendeesLabel" style="display: none;"><var:string label:value="Attendees:"
<var:if condition="hasOrganizer"
><label id="organizerLabel"><var:string label:value="Organizer:"
/><span class="content"><var:string value="organizerName"/></span></label>
</var:if>
<label id="attendeesLabel"><var:string label:value="Attendees:"
/><span class="content"
><a href="#" id="attendeesHref"><!-- space --></a></span></label>
<hr />

View File

@ -28,6 +28,10 @@
<var:if condition="hasPageSpecificCSS"
><link type="text/css" rel="stylesheet" var:href="pageCSSURL"
/></var:if>
<var:foreach list="additionalCSSFiles" item="item"
><link type="text/css" rel="stylesheet" var:href="item"
/>
</var:foreach>
<var:if-ie
><link type="text/css" rel="stylesheet" rsrc:href="iefixes.css"
/></var:if-ie>

View File

@ -755,6 +755,7 @@ function configureiCalLinksInMessage() {
var buttons = { "iCalendarAccept": "accept",
"iCalendarDecline": "decline",
"iCalendarTentative": "tentative",
"iCalendarUpdateUserStatus": "updateUserStatus",
"iCalendarAddToCalendar": "addToCalendar",
"iCalendarDeleteFromCalendar": "deleteFromCalendar" };
@ -772,11 +773,13 @@ function onICalendarButtonClick(event) {
var link = $("iCalendarAttachment").value;
if (link) {
var urlstr = link + "/" + this.action;
log ("click: " + urlstr);
triggerAjaxRequest(urlstr, ICalendarButtonCallback,
currentMailbox + "/"
+ currentMessages[currentMailbox]);
window.alert(urlstr);
}
else
log("no link");
}
function ICalendarButtonCallback(http) {

View File

@ -0,0 +1,2 @@
#attendeesLabel
{ display: none; }