(fix) can now invite to exceptions only (fixes #2561)

pull/237/head
Ludovic Marcotte 2017-07-19 11:05:16 -04:00
parent 277342dced
commit 032b2fbbd4
10 changed files with 203 additions and 82 deletions

12
NEWS
View File

@ -1,3 +1,15 @@
3.3.0 (2017-XX-XX)
------------------
New features
- [core] can now invite attendees to exceptions only (#2561)
Enhancements
-
Bug fixes
-
3.2.10 (2017-07-05) 3.2.10 (2017-07-05)
------------------- -------------------

View File

@ -1,8 +1,6 @@
/* NSArray+Appointments.h - this file is part of SOGo /* NSArray+Appointments.h - this file is part of SOGo
* *
* Copyright (C) 2006 Inverse inc. * Copyright (C) 2006-2017 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -1,8 +1,6 @@
/* NSArray+Appointments.m - this file is part of SOGo /* NSArray+Appointments.m - this file is part of SOGo
* *
* Copyright (C) 2006 Inverse inc. * Copyright (C) 2006-2017 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -1233,8 +1233,15 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
content = [theRecord objectForKey: @"c_cycleinfo"]; content = [theRecord objectForKey: @"c_cycleinfo"];
if (![content isNotNull]) if (![content isNotNull])
{ {
[self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", // If c_iscycle is set but c_cycleinfo is null, that means we're dealing with a vcalendar that
theRecord]; // contains ONLY one or more vevent with recurrence-id set for each of them. This can happen if
// an organizer invites an attendee only to one or many occurences of a repetitive event.
iCalCalendar *c;
c = [iCalCalendar parseSingleFromSource: [theRecord objectForKey: @"c_content"]];
[theRecords addObjectsFromArray: [self _fixupRecords: [c quickRecordsFromContent: [theRecord objectForKey: @"c_content"]
container: nil
nameInContainer: [theRecord objectForKey: @"c_name"]]]];
return; return;
} }

View File

@ -171,9 +171,13 @@
} }
// //
// This method will *ONLY* add or update event information in attendees' calendars.
// It will NOT touch to the organizer calendar in anyway. This method is meant
// to reflect changes in attendees' calendars when the organizer makes changes
// to the event.
// //
// - (void) _addOrUpdateEvent: (iCalEvent *) newEvent
- (void) _addOrUpdateEvent: (iCalEvent *) theEvent oldEvent: (iCalEvent *) oldEvent
forUID: (NSString *) theUID forUID: (NSString *) theUID
owner: (NSString *) theOwner owner: (NSString *) theOwner
{ {
@ -183,56 +187,84 @@
iCalCalendar *iCalendarToSave; iCalCalendar *iCalendarToSave;
iCalendarToSave = nil; iCalendarToSave = nil;
attendeeObject = [self _lookupEvent: [theEvent uid] forUID: theUID]; attendeeObject = [self _lookupEvent: [newEvent uid] forUID: theUID];
// We must add an occurence to a non-existing event. We have if ([newEvent recurrenceId])
// to handle this with care, as in the postCalDAVEventRequestTo:from:
if ([attendeeObject isNew] && [theEvent recurrenceId])
{ {
iCalEvent *ownerEvent; // We must add an occurence to a non-existing event.
iCalPerson *person; if ([attendeeObject isNew])
SOGoUser *user;
// 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
// status set to "DECLINED".
ownerEvent = [[[theEvent parent] events] objectAtIndex: 0];
user = [SOGoUser userWithLogin: theUID];
if (![ownerEvent userAsAttendee: user])
{ {
// Update the master event in the owner's calendar with the iCalEvent *ownerEvent;
// status of the new attendee set as "DECLINED". SOGoUser *user;
person = [iCalPerson elementWithTag: @"attendee"]; // We check if the attendee that was added to a single occurence is
[person setCn: [user cn]]; // present in the master component. If not, we add it with a participation
[person setEmail: [[user allEmails] objectAtIndex: 0]]; // status set to "DECLINED".
[person setParticipationStatus: iCalPersonPartStatDeclined]; ownerEvent = [[[newEvent parent] events] objectAtIndex: 0];
[person setRsvp: @"TRUE"]; user = [SOGoUser userWithLogin: theUID];
[person setRole: @"REQ-PARTICIPANT"];
[ownerEvent addToAttendees: person]; if (![ownerEvent userAsAttendee: user])
{
iCalendarToSave = [[[newEvent parent] mutableCopy] autorelease];
[iCalendarToSave removeChildren: [iCalendarToSave childrenWithTag: @"vevent"]];
[iCalendarToSave addChild: [[newEvent copy] autorelease]];
}
}
else
{
// Only update this occurrence in attendee's calendar
// TODO : when updating the master event, handle exception dates
// in attendee's calendar (add exception dates and remove matching
// occurrences) -- see _updateRecurrenceIDsWithEvent:
NSCalendarDate *currentId;
NSArray *occurences;
iCalEvent *occurence;
int max, count;
iCalendarToSave = [attendeeObject calendar: NO secure: NO];
// If recurrenceId is defined, remove the occurence from
// the repeating event. If a recurrenceId is defined in the
// new event, let's make sure we don't already have one in
// the calendar alright. If so, also remove it.
if ([oldEvent recurrenceId] || [newEvent recurrenceId])
{
// FIXME: use _eventFromRecurrenceId:...
occurences = [iCalendarToSave events];
max = [occurences count];
count = 0;
while (count < max)
{
occurence = [occurences objectAtIndex: count];
currentId = ([oldEvent recurrenceId] ? [oldEvent recurrenceId]: [newEvent recurrenceId]);
if (currentId && [[occurence recurrenceId] compare: currentId] == NSOrderedSame)
{
[[iCalendarToSave children] removeObject: occurence];
break;
}
count++;
}
}
iCalendarToSave = [ownerEvent parent]; [iCalendarToSave addChild: [[newEvent copy] autorelease]];
} }
} }
else else
{ {
// TODO : if [theEvent recurrenceId], only update this occurrence iCalendarToSave = [newEvent parent];
// in attendee's calendar
// TODO : when updating the master event, handle exception dates
// in attendee's calendar (add exception dates and remove matching
// occurrences) -- see _updateRecurrenceIDsWithEvent:
iCalendarToSave = [theEvent parent];
} }
// Save the event in the attendee's calendar // Save the event in the attendee's calendar
if (iCalendarToSave) if (iCalendarToSave)
[attendeeObject saveCalendar: iCalendarToSave]; [attendeeObject saveCalendar: iCalendarToSave];
} }
} }
// //
// // This method will *ONLY* delete event information in attendees' calendars.
// It will NOT touch to the organizer calendar in anyway. This method is meant
// to reflect changes in attendees' calendars when the organizer makes changes
// to the event.
// //
- (void) _removeEventFromUID: (NSString *) theUID - (void) _removeEventFromUID: (NSString *) theUID
owner: (NSString *) theOwner owner: (NSString *) theOwner
@ -251,14 +283,16 @@
// Invitations are always written to the personal folder; it's not necessay // Invitations are always written to the personal folder; it's not necessay
// to look into all folders of the user // to look into all folders of the user
// FIXME: why look only in the personal calendar here?
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)
[object delete]; [object delete];
else else
{ {
calendar = [object calendar: NO secure: NO]; calendar = [object calendar: NO secure: NO];
@ -267,7 +301,7 @@
// the repeating event. // the repeating event.
occurences = [calendar events]; occurences = [calendar events];
max = [occurences count]; max = [occurences count];
count = 1; count = 0;
while (count < max) while (count < max)
{ {
currentOccurence = [occurences objectAtIndex: count]; currentOccurence = [occurences objectAtIndex: count];
@ -290,6 +324,8 @@
[object saveCalendar: calendar]; [object saveCalendar: calendar];
} }
} }
else
[self errorWithFormat: @"Unable to find event with UID %@ in %@'s calendar - skipping delete operation", nameInContainer, theUID];
} }
} }
@ -310,7 +346,7 @@
if (currentUID) if (currentUID)
[self _removeEventFromUID: currentUID [self _removeEventFromUID: currentUID
owner: owner owner: owner
withRecurrenceId: recurrenceId]; withRecurrenceId: recurrenceId];
} }
} }
@ -436,6 +472,7 @@
currentUID = [currentAttendee uidInContext: context]; currentUID = [currentAttendee uidInContext: context];
if (currentUID) if (currentUID)
[self _addOrUpdateEvent: newEvent [self _addOrUpdateEvent: newEvent
oldEvent: oldEvent
forUID: currentUID forUID: currentUID
owner: owner]; owner: owner];
} }
@ -794,6 +831,7 @@
currentUID = [currentAttendee uidInContext: context]; currentUID = [currentAttendee uidInContext: context];
if (currentUID) if (currentUID)
[self _addOrUpdateEvent: newEvent [self _addOrUpdateEvent: newEvent
oldEvent: nil
forUID: currentUID forUID: currentUID
owner: owner]; owner: owner];
} }
@ -826,12 +864,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
e = [events objectAtIndex: i]; e = [events objectAtIndex: i];
if ([e recurrenceId]) if ([e recurrenceId])
for (j = 0; j < [theAttendees count]; j++) { for (j = 0; j < [theAttendees count]; j++) {
if (shouldAdd) { if (shouldAdd)
[e addToAttendees: [theAttendees objectAtIndex: j]]; [e addToAttendees: [theAttendees objectAtIndex: j]];
} else
else {
[e removeFromAttendees: [theAttendees objectAtIndex: j]]; [e removeFromAttendees: [theAttendees objectAtIndex: j]];
}
} }
} }
} }
@ -924,6 +960,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
currentUID = [currentAttendee uidInContext: context]; currentUID = [currentAttendee uidInContext: context];
if (currentUID) if (currentUID)
[self _addOrUpdateEvent: newEvent [self _addOrUpdateEvent: newEvent
oldEvent: oldEvent
forUID: currentUID forUID: currentUID
owner: owner]; owner: owner];
} }
@ -959,7 +996,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// | | // | |
// [saveComponent:]---> _handleAddedUsers:fromEvent: <-+ | // [saveComponent:]---> _handleAddedUsers:fromEvent: <-+ |
// | | v // | | v
// +------------> _handleUpdatedEvent:fromOldEvent: ---> _addOrUpdateEvent:forUID:owner: <-----------+ // +------------> _handleUpdatedEvent:fromOldEvent: ---> _addOrUpdateEvent:oldEvent:forUID:owner: <-----------+
// | | ^ | // | | ^ |
// v v | | // v v | |
// _handleRemovedUsers:withRecurrenceId: _handleSequenceUpdateInEvent:ignoringAttendees:fromOldEvent: | // _handleRemovedUsers:withRecurrenceId: _handleSequenceUpdateInEvent:ignoringAttendees:fromOldEvent: |
@ -1342,6 +1379,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if (delegatedUID) if (delegatedUID)
// Delegate attendee is a local user; add event to their calendar // Delegate attendee is a local user; add event to their calendar
[self _addOrUpdateEvent: event [self _addOrUpdateEvent: event
oldEvent: nil
forUID: delegatedUID forUID: delegatedUID
owner: [theOwnerUser login]]; owner: [theOwnerUser login]];
@ -1636,15 +1674,29 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else else
// Retrieve this occurence ID. // Retrieve this occurence ID.
recurrenceId = [occurence recurrenceId]; recurrenceId = [occurence recurrenceId];
if ([event userIsOrganizer: ownerUser]) if ([occurence userIsAttendee: ownerUser])
{
// The current user deletes the occurence; let the organizer know that
// the user has declined this occurence.
[self changeParticipationStatus: @"DECLINED"
withDelegate: nil
alarm: nil
forRecurrenceId: recurrenceId];
send_receipt = NO;
}
else
{ {
// The organizer deletes an occurence. // The organizer deletes an occurence.
currentUser = [context activeUser]; currentUser = [context activeUser];
attendees = [occurence attendeesWithoutUser: currentUser];
if (recurrenceId)
attendees = [occurence attendeesWithoutUser: currentUser];
else
attendees = [[event parent] attendeesWithoutUser: currentUser];
if (![attendees count] && event != occurence) //if (![attendees count] && event != occurence)
attendees = [event attendeesWithoutUser: currentUser]; //attendees = [event attendeesWithoutUser: currentUser];
if ([attendees count]) if ([attendees count])
{ {
@ -1661,17 +1713,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
withType: @"calendar:cancellation"]; withType: @"calendar:cancellation"];
} }
} }
else if ([occurence userIsAttendee: ownerUser])
{
// The current user deletes the occurence; let the organizer know that
// the user has declined this occurence.
[self changeParticipationStatus: @"DECLINED"
withDelegate: nil
alarm: nil
forRecurrenceId: recurrenceId];
send_receipt = NO;
}
if (send_receipt) if (send_receipt)
[self sendReceiptEmailForObject: event [self sendReceiptEmailForObject: event
addedAttendees: nil addedAttendees: nil

View File

@ -804,15 +804,17 @@
/* sender */ /* sender */
shortSenderEmail = [[object organizer] rfc822Email]; shortSenderEmail = [[object organizer] rfc822Email];
if ([shortSenderEmail length]) if ([shortSenderEmail length])
{ senderEmail = [[object organizer] mailAddress];
senderEmail = [[object organizer] mailAddress];
}
else else
{ {
shortSenderEmail = [[previousObject organizer] rfc822Email]; shortSenderEmail = [[previousObject organizer] rfc822Email];
senderEmail = [[previousObject organizer] mailAddress]; senderEmail = [[previousObject organizer] mailAddress];
} }
// No organizer, grab the event's owner
if (![senderEmail length])
senderEmail = shortSenderEmail = [[ownerUser defaultIdentity] objectForKey: @"email"];
/* calendar part */ /* calendar part */
eventBodyPart = [self _bodyPartForICalObject: object]; eventBodyPart = [self _bodyPartForICalObject: object];

View File

@ -1,8 +1,6 @@
/* iCalCalendar+SOGo.h - this file is part of SOGo /* iCalCalendar+SOGo.h - this file is part of SOGo
* *
* Copyright (C) 2012 Inverse inc * Copyright (C) 2012-2017 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/* iCalCalendar+SOGo.m - this file is part of SOGo /* iCalCalendar+SOGo.m - this file is part of SOGo
* *
* Copyright (C) 2012-2014 Inverse inc * Copyright (C) 2012-2017 Inverse inc
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -20,12 +20,16 @@
#import <Foundation/NSArray.h> #import <Foundation/NSArray.h>
#import <Foundation/NSCalendarDate.h> #import <Foundation/NSCalendarDate.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSString.h> #import <Foundation/NSString.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalRepeatableEntityObject.h> #import <NGCards/iCalRepeatableEntityObject.h>
#import "iCalCalendar+SOGo.h" #import "iCalCalendar+SOGo.h"
#import "iCalEntityObject+SOGo.h" #import "iCalEntityObject+SOGo.h"
#import "NSArray+Appointments.h"
@implementation iCalCalendar (SOGoExtensions) @implementation iCalCalendar (SOGoExtensions)
@ -45,9 +49,10 @@
/* master occurrence */ /* master occurrence */
component = [components objectAtIndex: 0]; component = [components objectAtIndex: 0];
if ([component hasRecurrenceRules]) if ([component hasRecurrenceRules] || [component recurrenceId])
{ {
count = 1; // skip master event // Skip the master event if required
count = ([component recurrenceId] ? 0 : 1);
while (!occurrence && count < max) while (!occurrence && count < max)
{ {
component = [components objectAtIndex: count]; component = [components objectAtIndex: count];
@ -101,4 +106,63 @@
return [(id)element quickRecordFromContent: theContent container: theContainer nameInContainer: nameInContainer]; return [(id)element quickRecordFromContent: theContent container: theContainer nameInContainer: nameInContainer];
} }
- (NSArray *) quickRecordsFromContent: (NSString *) theContent
container: (id) theContainer
nameInContainer: (NSString *) nameInContainer
{
NSCalendarDate *recurrenceId;
NSMutableDictionary *record;
NSMutableArray *allRecords;
NSNumber *dateSecs;
NSArray *elements;
int i;
allRecords = [NSMutableArray array];
// FIXME: what about tasks?
elements = [self events];
for (i = 0; i < [elements count]; i++)
{
record = [(id)[elements objectAtIndex: i] quickRecordFromContent: theContent container: theContainer nameInContainer: nameInContainer];
recurrenceId = [[elements objectAtIndex: i] recurrenceId];
dateSecs = [NSNumber numberWithDouble: [recurrenceId timeIntervalSince1970]];
[record setObject: nameInContainer forKey: @"c_name"];
[record setObject: dateSecs forKey: @"c_recurrence_id"];
[allRecords addObject: record];
}
return allRecords;
}
- (NSArray *) attendeesWithoutUser: (SOGoUser *) user
{
NSMutableArray *allAttendees;
NSArray *events, *attendees;
iCalPerson *attendee;
iCalEvent *event;
int i, j;
allAttendees = [NSMutableArray array];
events = [self events];
for (i = 0; i < [events count]; i++)
{
event = [events objectAtIndex: i];
attendees = [event attendees];
for (j = 0; j < [attendees count]; j++)
{
attendee = [attendees objectAtIndex: j];
[allAttendees removePerson: attendee];
[allAttendees addObject: attendee];
}
}
return allAttendees;
}
@end @end

View File

@ -126,7 +126,7 @@
boolTmp = ((isAllDay) ? 1 : 0); boolTmp = ((isAllDay) ? 1 : 0);
[row setObject: [NSNumber numberWithInt: boolTmp] [row setObject: [NSNumber numberWithInt: boolTmp]
forKey: @"c_isallday"]; forKey: @"c_isallday"];
boolTmp = (([self isRecurrent]) ? 1 : 0); boolTmp = ((([self isRecurrent] || [self recurrenceId])) ? 1 : 0);
[row setObject: [NSNumber numberWithInt: boolTmp] [row setObject: [NSNumber numberWithInt: boolTmp]
forKey: @"c_iscycle"]; forKey: @"c_iscycle"];
boolTmp = (([self isOpaque]) ? 1 : 0); boolTmp = (([self isOpaque]) ? 1 : 0);

View File

@ -265,8 +265,8 @@ static NSArray *tasksFields = nil;
NSString *aDateField; NSString *aDateField;
int daylightOffset; int daylightOffset;
unsigned int count; unsigned int count;
static NSString *fields[] = { @"startDate", @"c_startdate",
@"endDate", @"c_enddate" }; static NSString *fields[] = { @"startDate", @"c_startdate", @"endDate", @"c_enddate" };
/* WARNING: This condition has been put and removed many times, please leave /* WARNING: This condition has been put and removed many times, please leave
it. Here is the story... it. Here is the story...