propagate from branch 'ca.inverse.sogo.1_3_17' (head f071f53cbc4a39b0b852c58e7e57332f05ef1e0c)

to branch 'ca.inverse.sogo' (head 4117fa474ee2ae428f109a99cb50a33479342fa8)

Monotone-Parent: 4117fa474ee2ae428f109a99cb50a33479342fa8
Monotone-Parent: f071f53cbc4a39b0b852c58e7e57332f05ef1e0c
Monotone-Revision: c4c3d792d4b668f2d69a06c140f8d2223d8dbc7c

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2012-07-13T21:00:00
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Wolfgang Sourdeau 2012-07-13 21:00:00 +00:00
commit 0e276d2acc
12 changed files with 296 additions and 201 deletions

View File

@ -1,3 +1,25 @@
2012-07-13 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/Appointments/SOGoAppointmentObject.m
(-updateContentWithCalendar:fromRequest:): new method spawned from
the previous incarnation of PUTRequest:, designed to centralize
all the processed performed on an iCalendar instance, new or
derived from the original.
* SoObjects/Appointments/SOGoCalendarComponent.m
(-lookupOccurrence:): now a virtual method forcing the use by
subclasses of the new methods below.
(-saveCalendar:): new method that enables the saving of an
iCalendar object.
* SoObjects/Appointments/iCalCalendar+SOGo.m: new category module.
(-eventWithRecurrenceId, -todoWithRecurrenceId): new method that
return as recurrence based on the id passed as parameter.
* Tests/Integration/test-caldav-scheduling.py
(CalDAVSchedulingTest.setUp): use the proper password for
attendee1_delegate.
2012-07-12 Jean Raby <jraby@inverse.ca> 2012-07-12 Jean Raby <jraby@inverse.ca>
* SoObjects/Mailer/SOGoMailForward.m (from, to, cc, reply-to): * SoObjects/Mailer/SOGoMailForward.m (from, to, cc, reply-to):
@ -112,7 +134,6 @@
* UI/Templates/ContactsUI/UIxContactView.wox: * UI/Templates/ContactsUI/UIxContactView.wox:
Show all addresses returned from secondaryEmails. Show all addresses returned from secondaryEmails.
This still need some css tweaks. This still need some css tweaks.
2012-07-01 Wolfgang Sourdeau <wsourdeau@inverse.ca> 2012-07-01 Wolfgang Sourdeau <wsourdeau@inverse.ca>

4
NEWS
View File

@ -5,6 +5,10 @@ New Features
- send and/or receive email notifications when a calendar is modified (new - send and/or receive email notifications when a calendar is modified (new
domain defaults SOGoNotifyOnPersonalModifications and domain defaults SOGoNotifyOnPersonalModifications and
SOGoNotifyOnExternalModifications) SOGoNotifyOnExternalModifications)
- added the SOGoSearchMinimumWordLength domain default which controls the
minimal length required before trigging server-side search operations for
attendee completion, contact searches, etc. The default value is 2, which
means search operations are trigged once the 3rd character is typed.
Enhancements Enhancements
- updated Czech, French translations - updated Czech, French translations

View File

@ -188,8 +188,7 @@ static NSString *sessionsFolderURLString = nil;
queries = [tc specialQueries]; queries = [tc specialQueries];
sql = [NSString stringWithFormat: @"SELECT count(*) FROM %@", sql = [NSString stringWithFormat: @"SELECT count(*) FROM %@", tableName];
[self _storeTableName]];
if ([tc evaluateExpressionX: sql]) if ([tc evaluateExpressionX: sql])
{ {
sql = [queries createSessionsFolderWithName: tableName]; sql = [queries createSessionsFolderWithName: tableName];

View File

@ -9,6 +9,7 @@ Appointments_PRINCIPAL_CLASS = SOGoAppointmentsProduct
Appointments_OBJC_FILES = \ Appointments_OBJC_FILES = \
Product.m \ Product.m \
NSArray+Appointments.m \ NSArray+Appointments.m \
iCalCalendar+SOGo.m \
iCalEntityObject+SOGo.m \ iCalEntityObject+SOGo.m \
iCalRepeatableEntityObject+SOGo.m \ iCalRepeatableEntityObject+SOGo.m \
iCalEvent+SOGo.m \ iCalEvent+SOGo.m \

View File

@ -28,6 +28,8 @@
@class NSException; @class NSException;
@class NSString; @class NSString;
@class WORequest;
@class iCalEvent; @class iCalEvent;
@class iCalCalendar; @class iCalCalendar;
@ -49,6 +51,9 @@
- (NSArray *) postCalDAVEventReplyTo: (NSArray *) recipients from: (NSString *) originator; - (NSArray *) postCalDAVEventReplyTo: (NSArray *) recipients from: (NSString *) originator;
- (NSArray *) postCalDAVEventCancelTo: (NSArray *) recipients from: (NSString *) originator; - (NSArray *) postCalDAVEventCancelTo: (NSArray *) recipients from: (NSString *) originator;
- (NSException *) updateContentWithCalendar: (iCalCalendar *) calendar
fromRequest: (WORequest *) rq;
@end @end
#endif /* __Appointments_SOGoAppointmentObject_H__ */ #endif /* __Appointments_SOGoAppointmentObject_H__ */

View File

@ -28,6 +28,8 @@
#import <NGObjWeb/NSException+HTTP.h> #import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext+SoObjects.h> #import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WOResponse.h>
#import <NGExtensions/NGCalendarDateRange.h>
#import <NGExtensions/NSNull+misc.h> #import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h> #import <NGExtensions/NSObject+Logs.h>
#import <NGCards/iCalCalendar.h> #import <NGCards/iCalCalendar.h>
@ -35,10 +37,11 @@
#import <NGCards/iCalEvent.h> #import <NGCards/iCalEvent.h>
#import <NGCards/iCalEventChanges.h> #import <NGCards/iCalEventChanges.h>
#import <NGCards/iCalPerson.h> #import <NGCards/iCalPerson.h>
#import <NGCards/iCalRecurrenceCalculator.h>
#import <NGCards/NSCalendarDate+NGCards.h> #import <NGCards/NSCalendarDate+NGCards.h>
#import <SaxObjC/XMLNamespaces.h> #import <SaxObjC/XMLNamespaces.h>
#import <SOPE/NGCards/NSString+NGCards.h> #import <NGCards/NSString+NGCards.h>
#import <SOGo/SOGoConstants.h> #import <SOGo/SOGoConstants.h>
#import <SOGo/SOGoUserManager.h> #import <SOGo/SOGoUserManager.h>
@ -53,6 +56,7 @@
#import <SOGo/SOGoWebDAVValue.h> #import <SOGo/SOGoWebDAVValue.h>
#import <SOGo/WORequest+SOGo.h> #import <SOGo/WORequest+SOGo.h>
#import "iCalCalendar+SOGo.h"
#import "iCalEventChanges+SOGo.h" #import "iCalEventChanges+SOGo.h"
#import "iCalEntityObject+SOGo.h" #import "iCalEntityObject+SOGo.h"
#import "iCalPerson+SOGo.h" #import "iCalPerson+SOGo.h"
@ -121,6 +125,12 @@
return newOccurence; return newOccurence;
} }
- (iCalRepeatableEntityObject *) lookupOccurrence: (NSString *) recID
{
return [[self calendar: NO secure: NO] eventWithRecurrenceID: recID];
}
- (SOGoAppointmentObject *) _lookupEvent: (NSString *) eventUID - (SOGoAppointmentObject *) _lookupEvent: (NSString *) eventUID
forUID: (NSString *) uid forUID: (NSString *) uid
{ {
@ -178,54 +188,37 @@
SOGoAppointmentObject *attendeeObject; SOGoAppointmentObject *attendeeObject;
NSString *iCalString; NSString *iCalString;
iCalString = nil;
attendeeObject = [self _lookupEvent: [theEvent uid] forUID: theUID]; attendeeObject = [self _lookupEvent: [theEvent uid] forUID: theUID];
// We must add an occurence to a non-existing event. We have // We must add an occurence to a non-existing event. We have
// to handle this with care, as in the postCalDAVEventRequestTo:from: // to handle this with care, as in the postCalDAVEventRequestTo:from:
if ([attendeeObject isNew] && [theEvent recurrenceId]) if ([attendeeObject isNew] && [theEvent recurrenceId])
{ {
SOGoAppointmentObject *ownerObject;
NSArray *attendees;
iCalEvent *ownerEvent; iCalEvent *ownerEvent;
iCalPerson *person; iCalPerson *person;
SOGoUser *user; SOGoUser *user;
BOOL found;
int i;
// We check if the attendee that was added to a single occurence is // We check if the attendee that was added to a single occurence is
// present in the master component. If not, we add it with a participation // present in the master component. If not, we add it with a participation
// status set to "DECLINED". // status set to "DECLINED".
user = [SOGoUser userWithLogin: theUID];
person = [iCalPerson elementWithTag: @"attendee"];
[person setCn: [user cn]];
[person setEmail: [[user allEmails] objectAtIndex: 0]];
[person setParticipationStatus: iCalPersonPartStatDeclined];
[person setRsvp: @"TRUE"];
[person setRole: @"REQ-PARTICIPANT"];
ownerObject = [self _lookupEvent: [theEvent uid] forUID: theOwner];
ownerEvent = [[[theEvent parent] events] objectAtIndex: 0]; ownerEvent = [[[theEvent parent] events] objectAtIndex: 0];
attendees = [ownerEvent attendees]; user = [SOGoUser userWithLogin: theUID];
found = NO; if (![ownerEvent userAsAttendee: user])
for (i = 0; i < [attendees count]; i++)
{
if ([[attendees objectAtIndex: i] hasSameEmailAddress: person])
{
found = YES;
break;
}
}
if (!found)
{ {
// Update the master event in the owner's calendar with the // Update the master event in the owner's calendar with the
// status of the new attendee set as "DECLINED". // status of the new attendee set as "DECLINED".
person = [iCalPerson elementWithTag: @"attendee"];
[person setCn: [user cn]];
[person setEmail: [[user allEmails] objectAtIndex: 0]];
[person setParticipationStatus: iCalPersonPartStatDeclined];
[person setRsvp: @"TRUE"];
[person setRole: @"REQ-PARTICIPANT"];
[ownerEvent addToAttendees: person]; [ownerEvent addToAttendees: person];
iCalString = [[ownerEvent parent] versitString];
[ownerObject saveContentString: iCalString]; iCalString = [[ownerEvent parent] versitString];
} }
} }
else else
{ {
// TODO : if [theEvent recurrenceId], only update this occurrence // TODO : if [theEvent recurrenceId], only update this occurrence
@ -239,7 +232,8 @@
} }
// Save the event in the attendee's calendar // Save the event in the attendee's calendar
[attendeeObject saveContentString: iCalString]; if (iCalString)
[attendeeObject saveContentString: iCalString];
} }
} }
@ -267,7 +261,7 @@
folder = [[SOGoUser userWithLogin: theUID] folder = [[SOGoUser userWithLogin: theUID]
personalCalendarFolderInContext: context]; personalCalendarFolderInContext: context];
object = [folder lookupName: nameInContainer object = [folder lookupName: nameInContainer
inContext: context acquire: NO]; inContext: context acquire: NO];
if (![object isKindOfClass: [NSException class]]) if (![object isKindOfClass: [NSException class]])
{ {
if (recurrenceId == nil) if (recurrenceId == nil)
@ -448,7 +442,7 @@
NSEnumerator *enumerator; NSEnumerator *enumerator;
NSString *currentUID; NSString *currentUID;
SOGoUser *user; SOGoUser *user;
enumerator = [theAttendees objectEnumerator]; enumerator = [theAttendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject])) while ((currentAttendee = [enumerator nextObject]))
@ -644,7 +638,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[e addToAttendees: [theAttendees objectAtIndex: j]]; [e addToAttendees: [theAttendees objectAtIndex: j]];
else else
[e removeFromAttendees: [theAttendees objectAtIndex: j]]; [e removeFromAttendees: [theAttendees objectAtIndex: j]];
} }
} }
@ -791,7 +785,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
SOGoUser *ownerUser; SOGoUser *ownerUser;
NSArray *attendees; NSArray *attendees;
NSException *ex; NSException *ex;
[[newEvent parent] setMethod: @""]; [[newEvent parent] setMethod: @""];
ownerUser = [SOGoUser userWithLogin: owner]; ownerUser = [SOGoUser userWithLogin: owner];
@ -839,7 +833,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// If recurrenceId is defined, find the specified occurence // If recurrenceId is defined, find the specified occurence
// within the repeating vEvent. // within the repeating vEvent.
recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]]; recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]];
oldEvent = (iCalEvent*)[self lookupOccurence: recurrenceTime]; oldEvent = (iCalEvent*)[self lookupOccurrence: recurrenceTime];
if (oldEvent == nil) if (oldEvent == nil)
// If no occurence found, create one // If no occurence found, create one
oldEvent = (iCalEvent *)[self newOccurenceWithID: recurrenceTime]; oldEvent = (iCalEvent *)[self newOccurenceWithID: recurrenceTime];
@ -906,7 +900,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// If recurrenceId is defined, find the specified occurence // If recurrenceId is defined, find the specified occurence
// within the repeating vEvent. // within the repeating vEvent.
recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]]; recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]];
event = [eventObject lookupOccurence: recurrenceTime]; event = [eventObject lookupOccurrence: recurrenceTime];
if (event == nil) if (event == nil)
// If no occurence found, create one // If no occurence found, create one
@ -1016,7 +1010,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
statusChange: (NSString *) newStatus statusChange: (NSString *) newStatus
inEvent: (iCalEvent *) event inEvent: (iCalEvent *) event
{ {
NSString *newContent, *currentStatus, *organizerUID; NSString *currentStatus, *organizerUID;
SOGoUser *ownerUser, *currentUser; SOGoUser *ownerUser, *currentUser;
NSException *ex; NSException *ex;
@ -1161,14 +1155,18 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
#endif #endif
} }
/*
// We generate the updated iCalendar file and we save it in the database. // We generate the updated iCalendar file and we save it in the database.
// We do this ONLY when using SOGo from the Web interface. Over DAV, it'll // We do this ONLY when using SOGo from the Web interface. Over DAV, it'll
// be handled directly in PUTAction: // be handled directly in PUTAction:
if (![context request] || [[context request] handledByDefaultHandler]) if (![context request] || [[context request] handledByDefaultHandler])
{ {
NSString *newContent;
newContent = [[event parent] versitString]; newContent = [[event parent] versitString];
ex = [self saveContentString: newContent]; ex = [self saveContentString: newContent];
} }
*/
// If the current user isn't the organizer of the event // If the current user isn't the organizer of the event
// that has just been updated, we update the event and // that has just been updated, we update the event and
@ -1340,7 +1338,8 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
ex = nil; ex = nil;
delegatedUser = nil; delegatedUser = nil;
calendar = [self calendar: NO secure: NO]; calendar = [[self calendar: NO secure: NO] mutableCopy];
[calendar autorelease];
if (calendar) if (calendar)
{ {
if (_recurrenceId) if (_recurrenceId)
@ -1348,7 +1347,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// If _recurrenceId is defined, find the specified occurence // If _recurrenceId is defined, find the specified occurence
// within the repeating vEvent. // within the repeating vEvent.
recurrenceTime = [NSString stringWithFormat: @"%f", [_recurrenceId timeIntervalSince1970]]; recurrenceTime = [NSString stringWithFormat: @"%f", [_recurrenceId timeIntervalSince1970]];
event = (iCalEvent*)[self lookupOccurence: recurrenceTime]; event = (iCalEvent*)[self lookupOccurrence: recurrenceTime];
if (event == nil) if (event == nil)
// If no occurence found, create one // If no occurence found, create one
@ -1535,11 +1534,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
return partStats; return partStats;
} }
#warning parseSingleFromSource is invoked far too many times: maybe we should use an additional ivar to store the new iCalendar - (void) _setupResponseInRequestCalendar: (iCalCalendar *) rqCalendar
- (void) _setupResponseCalendarInRequest: (WORequest *) rq
{ {
iCalCalendar *calendar, *putCalendar; iCalCalendar *calendar;
NSData *newContent;
NSArray *keys; NSArray *keys;
NSDictionary *partStats, *newPartStats; NSDictionary *partStats, *newPartStats;
NSString *partStat, *key; NSString *partStat, *key;
@ -1551,8 +1548,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
max = [keys count]; max = [keys count];
if (max > 0) if (max > 0)
{ {
putCalendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]]; newPartStats = [self _partStatsFromCalendar: rqCalendar];
newPartStats = [self _partStatsFromCalendar: putCalendar];
if ([keys isEqualToArray: [newPartStats allKeys]]) if ([keys isEqualToArray: [newPartStats allKeys]])
{ {
for (count = 0; count < max; count++) for (count = 0; count < max; count++)
@ -1563,37 +1559,23 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
} }
} }
} }
newContent = [[calendar versitString]
dataUsingEncoding: [rq contentEncoding]];
[rq setContent: newContent];
} }
- (void) _adjustTransparencyInRequest: (WORequest *) rq - (void) _adjustTransparencyInRequestCalendar: (iCalCalendar *) rqCalendar
{ {
iCalCalendar *calendar;
NSArray *allEvents; NSArray *allEvents;
iCalEvent *event; iCalEvent *event;
int i; int i;
BOOL modified;
calendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]]; allEvents = [rqCalendar events];
allEvents = [calendar events];
modified = NO;
for (i = 0; i < [allEvents count]; i++) for (i = 0; i < [allEvents count]; i++)
{ {
event = [allEvents objectAtIndex: i]; event = [allEvents objectAtIndex: i];
if ([event isAllDay] && [event isOpaque]) if ([event isAllDay] && [event isOpaque])
{ {
[event setTransparency: @"TRANSPARENT"]; [event setTransparency: @"TRANSPARENT"];
modified = YES; }
}
} }
if (modified)
[rq setContent: [[calendar versitString] dataUsingEncoding: [rq contentEncoding]]];
} }
/** /**
@ -1601,17 +1583,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
* Currently only check if the events have an end date or a duration. * Currently only check if the events have an end date or a duration.
* @param rq the HTTP PUT request * @param rq the HTTP PUT request
*/ */
- (void) _adjustEventsInRequest: (WORequest *) rq - (void) _adjustEventsInRequestCalendar: (iCalCalendar *) rqCalendar
{ {
iCalCalendar *calendar;
NSArray *allEvents; NSArray *allEvents;
iCalEvent *event; iCalEvent *event;
NSUInteger i; NSUInteger i;
BOOL modified;
calendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]]; allEvents = [rqCalendar events];
allEvents = [calendar events];
modified = NO;
for (i = 0; i < [allEvents count]; i++) for (i = 0; i < [allEvents count]; i++)
{ {
@ -1624,28 +1602,16 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[event setDuration: @"P1D"]; [event setDuration: @"P1D"];
else else
[event setDuration: @"PT1H"]; [event setDuration: @"PT1H"];
[self warnWithFormat: @"Invalid event: no end date; setting duration to %@", [event duration]];
modified = YES;
[self errorWithFormat: @"Invalid event: no end date; setting duration to %@", [event duration]];
} }
} }
if (modified)
[rq setContent: [[calendar versitString] dataUsingEncoding: [rq contentEncoding]]];
} }
- (void) _decomposeGroupsInRequest: (WORequest *) rq - (void) _decomposeGroupsInRequestCalendar: (iCalCalendar *) rqCalendar
{ {
iCalCalendar *calendar;
NSArray *allEvents; NSArray *allEvents;
iCalEvent *event; iCalEvent *event;
int i; int i;
BOOL modified;
// If we decomposed at least one group, let's rewrite the content
// of the request. Otherwise, leave it as is in case this rewrite
// isn't totaly lossless.
calendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]];
// The algorithm is pretty straightforward: // The algorithm is pretty straightforward:
// //
@ -1654,20 +1620,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// If some are groups, we decompose them // If some are groups, we decompose them
// We regenerate the iCalendar string // We regenerate the iCalendar string
// //
allEvents = [calendar events]; allEvents = [rqCalendar events];
modified = NO;
for (i = 0; i < [allEvents count]; i++) for (i = 0; i < [allEvents count]; i++)
{ {
event = [allEvents objectAtIndex: i]; event = [allEvents objectAtIndex: i];
modified |= [self expandGroupsInEvent: event]; [self expandGroupsInEvent: event];
} }
// If we decomposed at least one group, let's rewrite the content
// of the request. Otherwise, leave it as is in case this rewrite
// isn't totaly lossless.
if (modified)
[rq setContent: [[calendar versitString] dataUsingEncoding: [rq contentEncoding]]];
} }
@ -1731,25 +1689,28 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
} }
// //
// If we see "X-SOGo: NoGroupsDecomposition" in the HTTP headers, we // This method is meant to be the common point of any save operation from web
// simply invoke super's PUTAction. // and DAV requests, as well as from code making use of SOGo as a library
// (OpenChange)
// //
// We also check if we must force transparency on all day events - (NSException *) updateContentWithCalendar: (iCalCalendar *) calendar
// from iPhone clients. fromRequest: (WORequest *) rq
//
- (id) PUTAction: (WOContext *) _ctx
{ {
NSException *ex; NSException *ex;
NSString *etag;
NSArray *roles; NSArray *roles;
WORequest *rq; SOGoUser *ownerUser;
id response;
unsigned int baseVersion; if (calendar == fullCalendar
|| calendar == safeCalendar
|| calendar == originalCalendar)
[NSException raise: NSInvalidArgumentException
format: @"the 'calendar' argument must be a distinct instance"
@" from the original object"];
rq = [_ctx request]; ownerUser = [SOGoUser userWithLogin: owner];
roles = [[context activeUser] rolesForObject: self inContext: context];
roles = [[context activeUser] rolesForObject: self
inContext: context];
// //
// We check if we gave only the "Respond To" right and someone is actually // We check if we gave only the "Respond To" right and someone is actually
// responding to one of our invitation. In this case, _setupResponseCalendarInRequest // responding to one of our invitation. In this case, _setupResponseCalendarInRequest
@ -1757,24 +1718,20 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// //
if ([roles containsObject: @"ComponentResponder"] if ([roles containsObject: @"ComponentResponder"]
&& ![roles containsObject: @"ComponentModifier"]) && ![roles containsObject: @"ComponentModifier"])
[self _setupResponseCalendarInRequest: rq]; [self _setupResponseInRequestCalendar: calendar];
else else
{ {
SOGoUser *user;
user = [SOGoUser userWithLogin: owner];
if (![[rq headersForKey: @"X-SOGo"] if (![[rq headersForKey: @"X-SOGo"]
containsObject: @"NoGroupsDecomposition"]) containsObject: @"NoGroupsDecomposition"])
[self _decomposeGroupsInRequest: rq]; [self _decomposeGroupsInRequestCalendar: calendar];
if ([[user domainDefaults] iPhoneForceAllDayTransparency] if ([[ownerUser domainDefaults] iPhoneForceAllDayTransparency]
&& [rq isIPhone]) && [rq isIPhone])
{ {
[self _adjustTransparencyInRequest: rq]; [self _adjustTransparencyInRequestCalendar: calendar];
} }
[self _adjustEventsInRequest: rq]; [self _adjustEventsInRequestCalendar: calendar];
} }
// //
@ -1782,25 +1739,20 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// //
if ([self isNew]) if ([self isNew])
{ {
iCalCalendar *calendar; iCalEvent *event;
SOGoUser *ownerUser;
iCalEvent *event, *conflictingEvent;
NSArray *attendees; NSArray *attendees;
NSString *eventUID; NSString *eventUID;
BOOL scheduling; BOOL scheduling;
calendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]];
attendees = nil; attendees = nil;
event = [[calendar events] objectAtIndex: 0]; event = [[calendar events] objectAtIndex: 0];
eventUID = [event uid]; eventUID = [event uid];
ownerUser = [SOGoUser userWithLogin: owner];
scheduling = [self _shouldScheduleEvent: [event organizer]]; scheduling = [self _shouldScheduleEvent: [event organizer]];
// make sure eventUID doesn't conflict with an existing event - see bug #1853 // make sure eventUID doesn't conflict with an existing event - see bug #1853
// TODO: send out a no-uid-conflict (DAV:href) xml element (rfc4791 section 5.3.2.1) // TODO: send out a no-uid-conflict (DAV:href) xml element (rfc4791 section 5.3.2.1)
if (conflictingEvent = [container resourceNameForEventUID: eventUID]) if ([container resourceNameForEventUID: eventUID])
{ {
return [NSException exceptionWithHTTPStatus: 403 return [NSException exceptionWithHTTPStatus: 403
reason: [NSString stringWithFormat: @"Event UID already in use. (%s)", eventUID]]; reason: [NSString stringWithFormat: @"Event UID already in use. (%s)", eventUID]];
@ -1852,33 +1804,21 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
} // if ([self isNew]) } // if ([self isNew])
else else
{ {
iCalCalendar *oldCalendar, *newCalendar; iCalCalendar *oldCalendar;
iCalEvent *oldEvent, *newEvent; iCalEvent *oldEvent, *newEvent;
iCalEventChanges *changes; iCalEventChanges *changes;
NSMutableArray *oldEvents, *newEvents; NSArray *oldEvents, *newEvents;
NSCalendarDate *recurrenceId; NSCalendarDate *recurrenceId;
NSException *error;
BOOL master; BOOL master;
int i; int i;
//
// We must check for etag changes prior doing anything since an attendee could
// have changed its participation status and the organizer didn't get the
// copy and is trying to do a modification to the event.
//
error = [self matchesRequestConditionInContext: _ctx];
if (error)
return (WOResponse *)error;
// //
// We check what has changed in the event and react accordingly. // We check what has changed in the event and react accordingly.
// //
newCalendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]]; newEvents = [calendar events];
newEvents = [NSMutableArray arrayWithArray: [newCalendar events]];
oldCalendar = [self calendar: NO secure: NO]; oldCalendar = [self calendar: NO secure: NO];
oldEvents = [NSMutableArray arrayWithArray: [oldCalendar events]]; oldEvents = [oldCalendar events];
recurrenceId = nil; recurrenceId = nil;
master = NO; master = NO;
@ -1911,8 +1851,8 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
} }
else else
{ {
[newEvents removeObject: oldEvent]; [calendar removeChild: oldEvent];
[oldEvents removeObject: newEvent]; [oldCalendar removeChild: newEvent];
} }
} }
@ -1981,13 +1921,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{ {
if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent])) if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent]))
return ex; return ex;
else
{
// We might have auto-accepted resources here. If that's the
// case, let's regenerate the versitstring and replace the
// one from the request.
[rq setContent: [[[newEvent parent] versitString] dataUsingEncoding: [rq contentEncoding]]];
}
} }
// //
// else => attendee is responding // else => attendee is responding
@ -2058,23 +1991,57 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
} }
} // else of if (isNew) ... } // else of if (isNew) ...
unsigned int baseVersion;
// We must NOT invoke [super PUTAction:] here as it'll resave // We must NOT invoke [super PUTAction:] here as it'll resave
// the content string and we could have etag mismatches. // the content string and we could have etag mismatches.
response = [_ctx response];
baseVersion = (isNew ? 0 : version); baseVersion = (isNew ? 0 : version);
ex = [self saveContentString: [rq contentAsString] ex = [self saveContentString: [calendar versitString]
baseVersion: baseVersion]; baseVersion: baseVersion];
return ex;
}
//
// If we see "X-SOGo: NoGroupsDecomposition" in the HTTP headers, we
// simply invoke super's PUTAction.
//
// We also check if we must force transparency on all day events
// from iPhone clients.
//
- (id) PUTAction: (WOContext *) _ctx
{
NSException *ex;
NSString *etag;
WORequest *rq;
WOResponse *response;
iCalCalendar *rqCalendar;
rq = [_ctx request];
rqCalendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]];
if (![self isNew])
{
//
// We must check for etag changes prior doing anything since an attendee could
// have changed its participation status and the organizer didn't get the
// copy and is trying to do a modification to the event.
//
ex = [self matchesRequestConditionInContext: context];
if (ex)
return ex;
}
ex = [self updateContentWithCalendar: rqCalendar fromRequest: rq];
if (ex) if (ex)
response = (WOResponse *) ex; response = (WOResponse *) ex;
else else
{ {
response = [_ctx response];
if (isNew) if (isNew)
[response setStatus: 201 /* Created */]; [response setStatus: 201 /* Created */];
else else
[response setStatus: 204 /* No Content */]; [response setStatus: 204 /* No Content */];
etag = [self davEntityTag]; etag = [self davEntityTag];
if (etag) if (etag)
[response setHeader: etag forKey: @"etag"]; [response setHeader: etag forKey: @"etag"];

View File

@ -24,6 +24,7 @@
#ifndef SOGOCALENDARCOMPONENT_H #ifndef SOGOCALENDARCOMPONENT_H
#define SOGOCALENDARCOMPONENT_H #define SOGOCALENDARCOMPONENT_H
#import <SOGo/SOGoConstants.h>
#import <SOGo/SOGoContentObject.h> #import <SOGo/SOGoContentObject.h>
#import "SOGoComponentOccurence.h" #import "SOGoComponentOccurence.h"
@ -61,6 +62,7 @@
toFolder: (SOGoGCSFolder *) newFolder; toFolder: (SOGoGCSFolder *) newFolder;
- (void) updateComponent: (iCalRepeatableEntityObject *) newObject; - (void) updateComponent: (iCalRepeatableEntityObject *) newObject;
- (NSException *) saveCalendar: (iCalCalendar *) newCalendar;
- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject; - (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject;
/* mail notifications */ /* mail notifications */
@ -78,7 +80,8 @@
- (void) sendReceiptEmailForObject: (iCalRepeatableEntityObject *) object - (void) sendReceiptEmailForObject: (iCalRepeatableEntityObject *) object
addedAttendees: (NSArray *) theAddedAttendees addedAttendees: (NSArray *) theAddedAttendees
deletedAttendees: (NSArray *) theDeletedAttendees deletedAttendees: (NSArray *) theDeletedAttendees
updatedAttendees: (NSArray *) theUpdatedAttendees; updatedAttendees: (NSArray *) theUpdatedAttendees
operation: (SOGoEventOperation) theOperation;
- (iCalPerson *) findParticipantWithUID: (NSString *) uid; - (iCalPerson *) findParticipantWithUID: (NSString *) uid;
@ -86,7 +89,8 @@
- (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons; - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons;
/* recurrences */ /* recurrences */
- (iCalRepeatableEntityObject *) lookupOccurence: (NSString *) recID; /* same as above, but refers to the existing calendar component */
- (iCalRepeatableEntityObject *) lookupOccurrence: (NSString *) recID;
- (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) component; - (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) component;
- (iCalRepeatableEntityObject *) newOccurenceWithID: (NSString *) recID; - (iCalRepeatableEntityObject *) newOccurenceWithID: (NSString *) recID;

View File

@ -269,45 +269,11 @@
return iCalString; return iCalString;
} }
static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence, - (iCalRepeatableEntityObject *) lookupOccurrence: (NSString *) recID
NSString *recID)
{ {
unsigned int seconds, recSeconds; [self subclassResponsibility: _cmd];
seconds = [recID intValue];
recSeconds = [[occurence recurrenceId] timeIntervalSince1970];
return (seconds == recSeconds); return nil;
}
- (iCalRepeatableEntityObject *) lookupOccurence: (NSString *) recID
{
iCalRepeatableEntityObject *component, *occurence, *currentOccurence;
NSArray *occurences;
unsigned int count, max;
occurence = nil;
component = [self component: NO secure: NO];
if ([component hasRecurrenceRules])
{
occurences = [[self calendar: NO secure: NO] allObjects];
max = [occurences count];
count = 1; // skip master event
while (!occurence && count < max)
{
currentOccurence = [occurences objectAtIndex: count];
if (_occurenceHasID (currentOccurence, recID))
occurence = currentOccurence;
else
count++;
}
}
else if (_occurenceHasID (component, recID))
/* The "master" event could be that occurrence. */
occurence = component;
return occurence;
} }
- (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) component - (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) component
@ -397,7 +363,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
else if ([lookupName hasPrefix: @"occurence"]) else if ([lookupName hasPrefix: @"occurence"])
{ {
recID = [lookupName substringFromIndex: 9]; recID = [lookupName substringFromIndex: 9];
occurence = [self lookupOccurence: recID]; occurence = [self lookupOccurrence: recID];
if (occurence) if (occurence)
isNewOccurence = NO; isNewOccurence = NO;
else else
@ -679,17 +645,18 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
} }
} }
- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject - (NSException *) saveCalendar: (iCalCalendar *) newCalendar
{ {
NSString *newiCalString; [self saveContentString: [newCalendar versitString]];
newiCalString = [[newObject parent] versitString];
[self saveContentString: newiCalString];
return nil; return nil;
} }
- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject
{
return [self saveCalendar: [newObject parent]];
}
/* raw saving */ /* raw saving */
/* EMail Notifications */ /* EMail Notifications */

View File

@ -32,6 +32,7 @@
#import <SoObjects/SOGo/SOGoMailer.h> #import <SoObjects/SOGo/SOGoMailer.h>
#import "iCalCalendar+SOGo.h"
#import "NSArray+Appointments.h" #import "NSArray+Appointments.h"
#import "SOGoAptMailNotification.h" #import "SOGoAptMailNotification.h"
#import "SOGoAppointmentFolder.h" #import "SOGoAppointmentFolder.h"
@ -46,6 +47,11 @@
return @"vtodo"; return @"vtodo";
} }
- (iCalRepeatableEntityObject *) lookupOccurrence: (NSString *) recID
{
return [[self calendar: NO secure: NO] todoWithRecurrenceID: recID];
}
- (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) occ - (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) occ
{ {
NSArray *allTodos; NSArray *allTodos;

View File

@ -0,0 +1,39 @@
/* iCalCalendar+SOGo.h - this file is part of SOGo
*
* Copyright (C) 2012 Inverse inc
*
* 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 ICALCALENDAR_SOGO_H
#define ICALCALENDAR_SOGO_H
#import <NGCards/iCalCalendar.h>
@class NSString;
@class iCalEvent;
@class iCalToDo;
@interface iCalCalendar (SOGoExtensions)
- (iCalEvent *) eventWithRecurrenceID: (NSString *) recID;
- (iCalToDo *) todoWithRecurrenceID: (NSString *) recID;
@end
#endif /* ICALCALENDAR_SOGO_H */

View File

@ -0,0 +1,82 @@
/* iCalCalendar+SOGo.m - this file is part of $PROJECT_NAME_HERE$
*
* Copyright (C) 2012 Inverse inc
*
* 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/NSString.h>
#import <NGCards/iCalRepeatableEntityObject.h>
#import "iCalCalendar+SOGo.h"
@implementation iCalCalendar (SOGoExtensions)
- (id) _occurrence: (NSString *) recID
inArray: (NSArray *) components
{
id occurrence;
iCalRepeatableEntityObject *component;
NSUInteger count, max, seconds, recSeconds;
occurrence = nil;
seconds = [recID intValue];
max = [components count];
/* master occurrence */
component = [components objectAtIndex: 0];
if ([component hasRecurrenceRules])
{
count = 1; // skip master event
while (!occurrence && count < max)
{
component = [components objectAtIndex: count];
recSeconds = [[component recurrenceId] timeIntervalSince1970];
if (recSeconds == seconds)
occurrence = component;
else
count++;
}
}
else
{
/* The "master" event could be that occurrence. */
recSeconds = [[component recurrenceId] timeIntervalSince1970];
if (recSeconds == seconds)
occurrence = component;
}
return occurrence;
}
- (iCalEvent *) eventWithRecurrenceID: (NSString *) recID
{
return [self _occurrence: recID inArray: [self events]];
}
- (iCalToDo *) todoWithRecurrenceID: (NSString *) recID;
{
return [self _occurrence: recID inArray: [self todos]];
}
@end

View File

@ -115,7 +115,7 @@ class CalDAVSchedulingTest(unittest.TestCase):
self.attendee1_client = webdavlib.WebDAVClient(hostname, port, self.attendee1_client = webdavlib.WebDAVClient(hostname, port,
attendee1_username, attendee1_password) attendee1_username, attendee1_password)
self.attendee1_delegate_client = webdavlib.WebDAVClient(hostname, port, self.attendee1_delegate_client = webdavlib.WebDAVClient(hostname, port,
attendee1_delegate_username, attendee1_password) attendee1_delegate_username, attendee1_delegate_password)
utility = utilities.TestUtility(self, self.client) utility = utilities.TestUtility(self, self.client)
(self.user_name, self.user_email) = utility.fetchUserInfo(username) (self.user_name, self.user_email) = utility.fetchUserInfo(username)