sogo/SoObjects/Appointments/SOGoCalendarComponent.m

1390 lines
41 KiB
Objective-C

/* SOGoCalendarComponent.m - this file is part of SOGo
*
* Copyright (C) 2006-2014 Inverse inc.
*
* 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/NSAutoreleasePool.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoSecurityManager.h>
#import <NGObjWeb/WOApplication.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest+So.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NGHashMap.h>
#import <NGExtensions/NGQuotedPrintableCoding.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalRepeatableEntityObject.h>
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMail/NGMimeMessage.h>
#import <GDLContentStore/GCSFolder.h>
#import <SOGo/NSCalendarDate+SOGo.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSObject+DAV.h>
#import <SOGo/NSObject+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoBuild.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoMailer.h>
#import <SOGo/SOGoGroup.h>
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/SOGoWebDAVAclManager.h>
#import <SOGo/WORequest+SOGo.h>
#import <Appointments/SOGoAppointmentFolder.h>
#import <Mailer/NSString+Mail.h>
#import "SOGoAptMailICalReply.h"
#import "SOGoAptMailNotification.h"
#import "SOGoAptMailReceipt.h"
#import "SOGoEMailAlarmsManager.h"
#import "iCalEntityObject+SOGo.h"
#import "iCalPerson+SOGo.h"
#import "iCalRepeatableEntityObject+SOGo.h"
#import "SOGoCalendarComponent.h"
#import "SOGoComponentOccurence.h"
@implementation SOGoCalendarComponent
+ (SOGoWebDAVAclManager *) webdavAclManager
{
static SOGoWebDAVAclManager *aclManager = nil;
NSString *nsD, *nsI;
if (!aclManager)
{
nsD = @"DAV:";
nsI = @"urn:inverse:params:xml:ns:inverse-dav";
aclManager = [SOGoWebDAVAclManager new];
[aclManager registerDAVPermission: davElement (@"read", nsD)
abstract: NO
withEquivalent: @"SOGoDAVReadPermission" /* hackish */
asChildOf: davElement (@"all", nsD)];
[aclManager registerDAVPermission: davElement (@"view-whole-component", nsI)
abstract: NO
withEquivalent: SOGoCalendarPerm_ViewAllComponent
asChildOf: davElement (@"all", nsD)];
[aclManager registerDAVPermission: davElement (@"view-date-and-time", nsI)
abstract: NO
withEquivalent: SOGoCalendarPerm_ViewDAndT
asChildOf: davElement (@"all", nsD)];
[aclManager registerDAVPermission: davElement (@"read-current-user-privilege-set", nsD)
abstract: NO
withEquivalent: SoPerm_WebDAVAccess
asChildOf: davElement (@"all", nsD)];
[aclManager registerDAVPermission: davElement (@"write", nsD)
abstract: NO
withEquivalent: SOGoCalendarPerm_ModifyComponent
asChildOf: davElement (@"all", nsD)];
[aclManager
registerDAVPermission: davElement (@"write-properties", nsD)
abstract: YES
withEquivalent: SoPerm_ChangePermissions /* hackish */
asChildOf: davElement (@"write", nsD)];
[aclManager
registerDAVPermission: davElement (@"write-content", nsD)
abstract: YES
withEquivalent: nil
asChildOf: davElement (@"write", nsD)];
[aclManager
registerDAVPermission: davElement (@"respond-to-component", nsI)
abstract: NO
withEquivalent: SOGoCalendarPerm_RespondToComponent
asChildOf: davElement (@"write-content", nsD)];
[aclManager registerDAVPermission: davElement (@"admin", nsI)
abstract: YES
withEquivalent: nil
asChildOf: davElement (@"all", nsD)];
[aclManager
registerDAVPermission: davElement (@"read-acl", nsD)
abstract: YES
withEquivalent: SOGoPerm_ReadAcls
asChildOf: davElement (@"admin", nsI)];
[aclManager
registerDAVPermission: davElement (@"write-acl", nsD)
abstract: YES
withEquivalent: nil
asChildOf: davElement (@"admin", nsI)];
}
return aclManager;
}
- (id) init
{
if ((self = [super init]))
{
fullCalendar = nil;
safeCalendar = nil;
originalCalendar = nil;
componentTag = nil;
}
return self;
}
- (void) dealloc
{
[fullCalendar release];
[safeCalendar release];
[originalCalendar release];
[componentTag release];
[super dealloc];
}
- (void) flush
{
DESTROY(fullCalendar);
DESTROY(safeCalendar);
DESTROY(originalCalendar);
}
- (Class *) parsingClass
{
return [iCalCalendar class];
}
- (NSString *) davContentType
{
return @"text/calendar";
}
- (NSString *) componentTag
{
if (!componentTag)
[self subclassResponsibility: _cmd];
return componentTag;
}
- (void) setComponentTag: (NSString *) theTag
{
ASSIGN(componentTag, theTag);
}
- (void) _filterComponent: (iCalEntityObject *) component
{
NSString *type, *summary;
int classification;
type = @"vtodo";
classification = 0;
if ([component isKindOfClass: [iCalEvent class]])
type = @"vevent";
if ([component symbolicAccessClass] == iCalAccessPrivate)
classification = 1;
else if ([component symbolicAccessClass] == iCalAccessConfidential)
classification = 2;
summary = [self labelForKey: [NSString stringWithFormat: @"%@_class%d",
type, classification]
inContext: context];
[component setSummary: summary];
[component setComment: @""];
[component setUserComment: @""];
[component setLocation: @""];
[component setCategories: [NSArray array]];
[component setUrl: @""];
[component removeAllAttendees];
[component removeAllAlarms];
}
- (NSString *) secureContentAsString
{
iCalRepeatableEntityObject *tmpComponent;
iCalCalendar *tmpCalendar;
NSArray *allComponents;
SoSecurityManager *sm;
NSString *iCalString;
int i;
sm = [SoSecurityManager sharedSecurityManager];
if (activeUserIsOwner
|| [[self ownerInContext: context] isEqualToString: [[context activeUser] login]]
|| ![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] mutableCopy];
// We filter all components, in case we have RECURRENCE-ID
allComponents = [tmpCalendar childrenWithTag: [self componentTag]];
for (i = 0; i < [allComponents count]; i++)
{
tmpComponent = (iCalRepeatableEntityObject *)[allComponents objectAtIndex:i];
[self _filterComponent: tmpComponent];
// We add an additional header here to inform clients (if necessary) that
// we churned the content of the calendar.
[tmpComponent addChild: [CardElement simpleElementWithTag: @"X-SOGo-Secure"
value: @"YES"]];
}
iCalString = [tmpCalendar versitString];
[tmpCalendar release];
}
else
iCalString = nil;
return iCalString;
}
- (iCalRepeatableEntityObject *) lookupOccurrence: (NSString *) recID
{
[self subclassResponsibility: _cmd];
return nil;
}
- (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) component
{
[self subclassResponsibility: _cmd];
return nil;
}
- (iCalRepeatableEntityObject *) newOccurenceWithID: (NSString *) recID
{
iCalRepeatableEntityObject *masterOccurence, *newOccurence;
iCalCalendar *calendar;
NSCalendarDate *recDate;
NSTimeZone *timeZone;
iCalPerson *organizer;
recDate = [NSCalendarDate dateWithTimeIntervalSince1970: [recID intValue]];
masterOccurence = [self component: NO secure: NO];
timeZone = [[[context activeUser] userDefaults] timeZone];
[recDate setTimeZone: timeZone];
if ([masterOccurence doesOccurOnDate: recDate])
{
newOccurence = [masterOccurence mutableCopy];
organizer = [masterOccurence organizer];
[newOccurence autorelease];
[newOccurence removeAllRecurrenceRules];
[newOccurence removeAllExceptionRules];
[newOccurence removeAllExceptionDates];
// It is important to set the organizer as some DAV clients (iCal
// and Thunderbird 10/Lightning 1.2) will prompt the event "edition"
// dialog instead of the event "invitation" (for accept/decline/tentative)
// if the organizer isn't found in a specific recurrence
[newOccurence setOrganizer: organizer];
[newOccurence setRecurrenceId: recDate];
calendar = [masterOccurence parent];
[calendar addChild: newOccurence];
}
else
newOccurence = nil;
return newOccurence;
}
- (id) toManyRelationshipKeys
{
return nil;
}
- (id) toOneRelationshipKeys
{
NSMutableArray *keys;
NSArray *occurences;
NSCalendarDate *recID;
unsigned int count, max, seconds;
keys = [NSMutableArray array];
[keys addObject: @"master"];
occurences = [[self calendar: NO secure: NO] allObjects];
max = [occurences count];
for (count = 1; count < max; count++)
{
recID = [[occurences objectAtIndex: count] recurrenceId];
if (recID)
{
seconds = [recID timeIntervalSince1970];
[keys addObject: [NSString stringWithFormat: @"occurence%d",
seconds]];
}
}
return keys;
}
- (id) lookupName: (NSString *) lookupName
inContext: (id) localContext
acquire: (BOOL) acquire
{
id obj;
iCalRepeatableEntityObject *occurence;
NSString *recID;
BOOL isNewOccurence;
obj = [super lookupName: lookupName
inContext: localContext
acquire: acquire];
if (!obj)
{
if ([lookupName isEqualToString: @"master"])
obj = [self occurence: [self component: NO secure: NO]];
else if ([lookupName hasPrefix: @"occurence"])
{
recID = [lookupName substringFromIndex: 9];
occurence = [self lookupOccurrence: recID];
if (occurence)
isNewOccurence = NO;
else
{
occurence = [self newOccurenceWithID: recID];
isNewOccurence = YES;
}
if (occurence)
{
obj = [self occurence: occurence];
if (isNewOccurence)
[obj setIsNew: isNewOccurence];
}
}
}
return obj;
}
- (NSString *) _secureContentWithoutAlarms
{
iCalCalendar *calendar;
NSArray *allComponents;
iCalEntityObject *currentComponent;
NSUInteger count, max;
calendar = [self calendar: NO secure: YES];
allComponents = [calendar childrenWithTag: [self componentTag]];
max = [allComponents count];
for (count = 0; count < max; count++)
{
currentComponent = [allComponents objectAtIndex: count];
[currentComponent removeAllAlarms];
}
return [calendar versitString];
}
- (NSString *) contentAsString
{
NSString *secureContent;
if ([[context request] isSoWebDAVRequest])
{
if ([container showCalendarAlarms])
secureContent = [self secureContentAsString];
else
secureContent = [self _secureContentWithoutAlarms];
}
else
secureContent = [super contentAsString];
return secureContent;
}
- (NSString *) davCalendarData
{
return [self contentAsString];
}
- (iCalCalendar *) calendar: (BOOL) create secure: (BOOL) secure
{
iCalRepeatableEntityObject *newComponent;
iCalCalendar **calendar, *returnedCopy;
NSString *iCalString, *tag, *prodID;
if (secure)
calendar = &safeCalendar;
else
calendar = &fullCalendar;
if (!*calendar)
{
if (secure)
iCalString = [self secureContentAsString];
else
iCalString = content;
if ([iCalString length] > 0)
{
ASSIGN (*calendar, [iCalCalendar parseSingleFromSource: iCalString]);
if (!secure)
originalCalendar = [*calendar copy];
}
else
{
if (create)
{
ASSIGN (*calendar, [iCalCalendar groupWithTag: @"vcalendar"]);
[*calendar setVersion: @"2.0"];
prodID = [NSString stringWithFormat:
@"-//Inverse inc./SOGo %@//EN",
SOGoVersion];
[*calendar setProdID: prodID];
tag = [[self componentTag] uppercaseString];
newComponent = [[*calendar classForTag: tag]
groupWithTag: tag];
[newComponent setUid: [self globallyUniqueObjectId]];
[*calendar addChild: newComponent];
}
}
}
returnedCopy = [*calendar mutableCopy];
[returnedCopy autorelease];
return returnedCopy;
}
- (id) component: (BOOL) create secure: (BOOL) secure
{
return [[self calendar: create secure: secure]
firstChildWithTag: [self componentTag]];
}
//
// Returs "YES" if a a group was decomposed among attendees.
//
// It can also return yes if an attendee was found in the list
// matching the organizer. In which case, it was removed.
//
- (BOOL) expandGroupsInEvent: (iCalEvent *) theEvent
{
NSString *organizerEmail, *domain;
NSMutableArray *allAttendees;
iCalPerson *currentAttendee;
NSEnumerator *enumerator;
NSAutoreleasePool *pool;
SOGoGroup *group;
BOOL eventWasModified;
unsigned int i, j;
domain = [[context activeUser] domain];
organizerEmail = [[theEvent organizer] rfc822Email];
eventWasModified = NO;
allAttendees = [NSMutableArray arrayWithArray: [theEvent attendees]];
enumerator = [[theEvent attendees] objectEnumerator];
j = 0;
pool = [[NSAutoreleasePool alloc] init];
while ((currentAttendee = [enumerator nextObject]))
{
if (j%5 == 0)
{
RELEASE(pool);
pool = [[NSAutoreleasePool alloc] init];
}
group = [SOGoGroup groupWithEmail: [currentAttendee rfc822Email]
inDomain: domain];
if (group)
{
iCalPerson *person;
NSArray *members;
SOGoUser *user;
// We did decompose a group...
[allAttendees removeObject: currentAttendee];
members = [group members];
for (i = 0; i < [members count]; i++)
{
user = [members objectAtIndex: i];
eventWasModified = YES;
// If the organizer is part of the group, we skip it from
// the addition to the attendees' list
if ([user hasEmail: organizerEmail])
continue;
person = [self iCalPersonWithUID: [user login]];
[person setTag: @"ATTENDEE"];
[person setParticipationStatus: [currentAttendee participationStatus]];
[person setRsvp: [currentAttendee rsvp]];
[person setRole: [currentAttendee role]];
if (![allAttendees containsObject: person])
[allAttendees addObject: person];
}
}
else
{
// We remove any attendees matching the organizer. Apple iCal will do that when
// you invite someone. It'll add the organizer in the attendee list, which will
// confuse itself!
if ([[currentAttendee rfc822Email] caseInsensitiveCompare: organizerEmail] == NSOrderedSame)
{
[allAttendees removeObject: currentAttendee];
eventWasModified = YES;
}
}
j++;
} // while (currentAttendee ...
if (eventWasModified)
[theEvent setAttendees: allAttendees];
RELEASE(pool);
return eventWasModified;
}
- (void) _updateRecurrenceIDsWithEvent: (iCalRepeatableEntityObject*) newEvent
{
iCalRepeatableEntityObject *oldMaster, *currentComponent;
iCalDateTime *currentDate;
int deltaSecs;
NSArray *components, *dates;
NSMutableArray *newDates;
unsigned int count, max;
NSCalendarDate *recID, *newDate;
// Compute time interval from previous event definition.
if (!originalCalendar)
{
if (content)
ASSIGN (originalCalendar, [iCalCalendar parseSingleFromSource: content]);
else
[self warnWithFormat: @"content not available, we will crash"];
}
oldMaster = (iCalRepeatableEntityObject *)
[originalCalendar firstChildWithTag: [self componentTag]];
deltaSecs = [[newEvent startDate]
timeIntervalSinceDate: [oldMaster startDate]];
components = [[newEvent parent] events];
max = [components count];
if (max > 0)
{
// Update recurrence-id attribute of occurences.
for (count = 1; count < max; count++)
{
currentComponent = [components objectAtIndex: count];
recID = [[currentComponent recurrenceId] addTimeInterval: deltaSecs];
[currentComponent setRecurrenceId: recID];
}
// Update exception dates in master vEvent.
currentComponent = [components objectAtIndex: 0];
dates = [currentComponent childrenWithTag: @"exdate"];
max = [dates count];
if (max > 0)
{
newDates = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
currentDate = [dates objectAtIndex: count];
newDate = [[currentDate dateTime] addTimeInterval: deltaSecs];
[newDates addObject: newDate];
}
[currentComponent removeAllExceptionDates];
for (count = 0; count < max; count++)
[currentComponent addToExceptionDates: [newDates objectAtIndex: count]];
}
}
}
- (void) updateComponent: (iCalRepeatableEntityObject *) newObject
{
NSString *newUid;
if (!isNew
&& [newObject isRecurrent])
// We update an repeating event -- update exception dates
// and recurrence-ids.
[self _updateRecurrenceIDsWithEvent: newObject];
// As much as we can, we try to use c_name == c_uid in order
// to avoid tricky scenarios with some CalDAV clients. For example,
// if Alice invites Bob (both use SOGo) and Bob accepts the invitation
// using Lightning before having refreshed his calendar, he'll end up
// with a duplicate of the event in his database tables.
if (isNew)
{
newUid = nameInContainer;
if ([newUid hasSuffix: @".ics"])
newUid = [newUid substringToIndex: [newUid length]-4];
[newObject setUid: newUid];
}
if ([[SOGoSystemDefaults sharedSystemDefaults] enableEMailAlarms])
{
SOGoEMailAlarmsManager *eaMgr;
eaMgr = [SOGoEMailAlarmsManager sharedEMailAlarmsManager];
[eaMgr handleAlarmsInCalendar: [newObject parent]
fromComponent: self];
}
}
- (NSException *) saveCalendar: (iCalCalendar *) newCalendar
{
[super saveComponent: newCalendar];
return nil;
}
- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject
{
return [self saveCalendar: [newObject parent]];
}
/* raw saving */
/* EMail Notifications */
- (NSString *) homePageURLForPerson: (iCalPerson *) _person
{
NSString *baseURL;
NSString *uid;
NSArray *traversalObjects;
/* generate URL from traversal stack */
traversalObjects = [context objectTraversalStack];
if ([traversalObjects count] > 0)
baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
else
{
baseURL = @"http://localhost/";
[self warnWithFormat:@"Unable to create baseURL from context!"];
}
uid = [_person uid];
return ((uid)
? [NSString stringWithFormat:@"%@%@", baseURL, uid]
: nil);
}
- (NSTimeZone *) timeZoneForUser: (NSString *) email
{
NSString *uid;
SOGoUserDefaults *ud;
uid = [[SOGoUserManager sharedUserManager] getUIDForEmail: email];
ud = [[SOGoUser userWithLogin: uid] userDefaults];
return [ud timeZone];
}
- (NGMimeBodyPart *) _bodyPartForICalObject: (iCalRepeatableEntityObject *) object
{
NGMimeBodyPart *bodyPart;
NGMutableHashMap *headerMap;
NSString *iCalString, *header, *charset;
NSData *objectData;
iCalCalendar *parent;
parent = [object parent];
iCalString = [NSString stringWithFormat: @"%@\r\n", [parent versitString]];
if ([iCalString canBeConvertedToEncoding: NSISOLatin1StringEncoding])
{
objectData = [iCalString dataUsingEncoding: NSISOLatin1StringEncoding];
charset = @"ISO-8859-1";
}
else
{
objectData = [iCalString dataUsingEncoding: NSUTF8StringEncoding];
charset = @"UTF-8";
}
header = [NSString stringWithFormat: @"text/calendar; method=%@;"
@" charset=\"%@\"",
[(iCalCalendar *) [object parent] method], charset];
headerMap = [NGMutableHashMap hashMapWithCapacity: 3];
[headerMap setObject: @"urn:content-classes:calendarmessage" forKey: @"Content-Class"];
[headerMap setObject: header forKey: @"content-type"];
[headerMap setObject: @"quoted-printable"
forKey: @"content-transfer-encoding"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
[bodyPart setBody: [objectData dataByEncodingQuotedPrintable]];
return bodyPart;
}
//
//
//
- (void) sendEMailUsingTemplateNamed: (NSString *) newPageName
forObject: (iCalRepeatableEntityObject *) object
previousObject: (iCalRepeatableEntityObject *) previousObject
toAttendees: (NSArray *) attendees
withType: (NSString *) msgType
{
NSString *pageName;
NSString *senderEmail, *shortSenderEmail, *email;
WOApplication *app;
unsigned i, count;
iCalPerson *attendee;
NSString *recipient;
SOGoAptMailNotification *p;
NSString *mailDate, *subject, *text;
NGMutableHashMap *headerMap;
NGMimeMessage *msg;
NGMimeBodyPart *bodyPart, *eventBodyPart;
NGMimeMultipartBody *body;
SOGoUser *ownerUser;
SOGoDomainDefaults *dd;
// If defined, we return immediately. When not defined, we send the notifications correctly
if ([object firstChildWithTag: @"X-SOGo-Send-Appointment-Notifications"])
return;
ownerUser = [SOGoUser userWithLogin: owner];
dd = [ownerUser domainDefaults];
if ([dd appointmentSendEMailNotifications] && [object isStillRelevant])
{
count = [attendees count];
if (count)
{
/* sender */
shortSenderEmail = [[object organizer] rfc822Email];
if ([shortSenderEmail length])
{
senderEmail = [[object organizer] mailAddress];
}
else
{
shortSenderEmail = [[previousObject organizer] rfc822Email];
senderEmail = [[previousObject organizer] mailAddress];
}
/* calendar part */
eventBodyPart = [self _bodyPartForICalObject: object];
/* get WOApplication instance */
app = [WOApplication application];
/* generate dynamic message content */
for (i = 0; i < count; i++)
{
attendee = [attendees objectAtIndex: i];
// Don't send a notification to the event organizer nor a deletion
// notification to an attendee who already declined the invitation.
if (![[attendee uid] isEqualToString: owner] &&
!([[attendee partStat] compare: @"DECLINED"] == NSOrderedSame &&
[newPageName compare: @"Deletion"] == NSOrderedSame))
{
/* construct recipient */
recipient = [attendee mailAddress];
email = [attendee rfc822Email];
#warning this could be optimized in a class hierarchy common with the \
SOGoObject acl notification mechanism
/* create page name */
pageName = [NSString stringWithFormat: @"SOGoAptMail%@",
newPageName];
/* construct message content */
p = [app pageWithName: pageName inContext: context];
[p setApt: (iCalEvent *) object];
[p setPreviousApt: (iCalEvent *) previousObject];
if ([[object organizer] cn] && [[[object organizer] cn] length])
{
[p setOrganizerName: [[object organizer] cn]];
}
else
{
[p setOrganizerName: [ownerUser cn]];
}
subject = [[p getSubject] asQPSubjectString: @"UTF-8"];
text = [p getBody];
/* 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.
*/
#warning SOPE is just plain stupid here - if you change the case of keys, it will break the encoding of fields
[headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
[headerMap setObject: @"1.0" forKey: @"MIME-Version"];
[headerMap setObject: senderEmail forKey: @"from"];
[headerMap setObject: recipient forKey: @"to"];
mailDate = [[NSCalendarDate date] rfc822DateString];
[headerMap setObject: mailDate forKey: @"date"];
[headerMap setObject: subject forKey: @"subject"];
[headerMap setObject: [NSString generateMessageID] forKey: @"message-id"];
if ([msgType length] > 0)
[headerMap setObject: msgType forKey: @"x-sogo-message-type"];
msg = [NGMimeMessage messageWithHeader: headerMap];
/* multipart body */
body = [[NGMimeMultipartBody alloc] initWithPart: msg];
/* text part */
headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
[headerMap setObject: @"text/html; charset=utf-8"
forKey: @"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
[bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
/* attach text part to multipart body */
[body addBodyPart: bodyPart];
/* attach calendar part to multipart body */
[body addBodyPart: eventBodyPart];
/* attach multipart body to message */
[msg setBody: body];
[body release];
/* send the damn thing */
[[SOGoMailer mailerWithDomainDefaults: dd]
sendMimePart: msg
toRecipients: [NSArray arrayWithObject: email]
sender: shortSenderEmail
withAuthenticator: [self authenticatorInContext: context]
inContext: context];
}
}
}
}
}
#warning fix this when sendEmailUsing blabla has been cleaned up
- (void) sendIMIPReplyForEvent: (iCalRepeatableEntityObject *) event
from: (SOGoUser *) from
to: (iCalPerson *) recipient
{
NSString *pageName, *mailDate, *email;
WOApplication *app;
iCalPerson *attendee;
SOGoAptMailICalReply *p;
NGMutableHashMap *headerMap;
NGMimeMessage *msg;
NGMimeBodyPart *bodyPart;
NGMimeMultipartBody *body;
NSData *bodyData;
SOGoDomainDefaults *dd;
dd = [from domainDefaults];
if ([dd appointmentSendEMailNotifications] && [event isStillRelevant])
{
/* get WOApplication instance */
app = [WOApplication application];
/* create page name */
pageName = @"SOGoAptMailICalReply";
/* construct message content */
p = [app pageWithName: pageName inContext: context];
[p setApt: (iCalEvent *) event];
attendee = [event userAsAttendee: from];
[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.
*/
#warning SOPE is just plain stupid here - if you change the case of keys, it will break the encoding of fields
[headerMap setObject: [attendee mailAddress] forKey: @"from"];
[headerMap setObject: [recipient mailAddress] forKey: @"to"];
mailDate = [[NSCalendarDate date] rfc822DateString];
[headerMap setObject: mailDate forKey: @"date"];
[headerMap setObject: [[p getSubject] asQPSubjectString: @"UTF-8"]
forKey: @"subject"];
[headerMap setObject: [NSString generateMessageID] forKey: @"message-id"];
[headerMap setObject: @"1.0" forKey: @"MIME-Version"];
[headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
[headerMap setObject: @"calendar:invitation-reply" forKey: @"x-sogo-message-type"];
msg = [NGMimeMessage messageWithHeader: headerMap];
/* multipart body */
body = [[NGMimeMultipartBody alloc] initWithPart: msg];
/* text part */
headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
[headerMap setObject: @"text/html; 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];
/* attach calendar part to multipart body */
[body addBodyPart: [self _bodyPartForICalObject: event]];
/* attach multipart body to message */
[msg setBody: body];
[body release];
/* send the damn thing */
email = [recipient rfc822Email];
[[SOGoMailer mailerWithDomainDefaults: dd]
sendMimePart: msg
toRecipients: [NSArray arrayWithObject: email]
sender: [attendee rfc822Email]
withAuthenticator: [self authenticatorInContext: context]
inContext: context];
}
}
//
//
//
- (void) sendResponseToOrganizer: (iCalRepeatableEntityObject *) newComponent
from: (SOGoUser *) from
{
iCalPerson *organizer, *attendee;
iCalEvent *event;
SOGoUser *ownerUser;
event = [newComponent itipEntryWithMethod: @"reply"];
ownerUser = [SOGoUser userWithLogin: owner];
if (![event userIsOrganizer: ownerUser])
{
organizer = [event organizer];
attendee = [event userAsAttendee: ownerUser];
[event setAttendees: [NSArray arrayWithObject: attendee]];
[self sendIMIPReplyForEvent: event from: from to: organizer];
}
}
//
//
//
- (void) sendReceiptEmailForObject: (iCalRepeatableEntityObject *) object
addedAttendees: (NSArray *) theAddedAttendees
deletedAttendees: (NSArray *) theDeletedAttendees
updatedAttendees: (NSArray *) theUpdatedAttendees
operation: (SOGoEventOperation) theOperation
{
NSString *calendarName, *mailDate, *mailText, *fullSenderEmail, *senderEmail, *fullRecipientEmail, *recipientEmail;
NSDictionary *senderIdentity, *recipientIdentity;
id <SOGoAuthenticator> authenticator;
SOGoUser *currentUser, *ownerUser;
NGMutableHashMap *headerMap;
SOGoAptMailReceipt *page;
SOGoDomainDefaults *dd;
NGMimeMessage *msg;
calendarName = [[self container] displayName];
// We must handle three cases here:
// - Receive a mail when I modify my calendar
// - Receive a mail when someone else modifies my calendar
// - When I modify my calendar, send a mail to: <foo@bar.com>
// We first built the template that we'll slightly adjust for our three use-cases
page = [[WOApplication application] pageWithName: @"SOGoAptMailReceipt"
inContext: context];
[page setApt: (iCalEvent *) object];
[page setAddedAttendees: theAddedAttendees];
[page setDeletedAttendees: theDeletedAttendees];
[page setUpdatedAttendees: theUpdatedAttendees];
[page setOperation: theOperation];
[page setCalendarName: calendarName];
currentUser = [context activeUser];
senderIdentity = [currentUser primaryIdentity];
dd = [currentUser domainDefaults];
#warning SOPE is just plain stupid here - if you change the case of keys, it will break the encoding of fields
headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
// Sender can vary, base on whom modifies the actual event (owner vs. someone else)
senderEmail = [senderIdentity objectForKey: @"email"];
fullSenderEmail = [senderIdentity keysWithFormat: @"%{fullName} <%{email}>"];
[headerMap setObject: fullSenderEmail forKey: @"from"];
// Recipient is fixed, which is the calendar owner
ownerUser = [SOGoUser userWithLogin: self->owner];
recipientIdentity = [ownerUser primaryIdentity];
// Safety net for broken configurations
if (!recipientIdentity)
return;
recipientEmail = [recipientIdentity objectForKey: @"email"];
fullRecipientEmail = [recipientIdentity keysWithFormat: @"%{fullName} <%{email}>"];
[headerMap setObject: fullRecipientEmail forKey: @"to"];
mailDate = [[NSCalendarDate date] rfc822DateString];
[headerMap setObject: mailDate forKey: @"date"];
[headerMap setObject: [page getSubject] forKey: @"subject"];
[headerMap setObject: [NSString generateMessageID] forKey: @"message-id"];
[headerMap setObject: @"1.0" forKey: @"MIME-Version"];
[headerMap setObject: @"text/html; charset=utf-8"
forKey: @"content-type"];
msg = [NGMimeMessage messageWithHeader: headerMap];
/* text part */
mailText = [page getBody];
[msg setBody: [mailText dataUsingEncoding: NSUTF8StringEncoding]];
authenticator = [self authenticatorInContext: context];
if ([self->owner isEqualToString: [currentUser login]])
{
if ([[self container] notifyOnPersonalModifications])
{
[[SOGoMailer mailerWithDomainDefaults: dd]
sendMimePart: msg
toRecipients: [NSArray arrayWithObject: recipientEmail]
sender: senderEmail
withAuthenticator: authenticator
inContext: context];
}
if ([[self container] notifyUserOnPersonalModifications])
{
id o;
o = [headerMap objectForKey: @"to"];
recipientEmail = [[self container] notifiedUserOnPersonalModifications];
[headerMap setObject: recipientEmail forKey: @"to"];
[[SOGoMailer mailerWithDomainDefaults: dd]
sendMimePart: msg
toRecipients: [NSArray arrayWithObject: recipientEmail]
sender: senderEmail
withAuthenticator: authenticator
inContext: context];
[headerMap setObject: o forKey: @"to"];
}
}
if ([[self container] notifyOnExternalModifications] &&
![self->owner isEqualToString: [currentUser login]])
{
[[SOGoMailer mailerWithDomainDefaults: dd]
sendMimePart: msg
toRecipients: [NSArray arrayWithObject: recipientEmail]
sender: senderEmail
withAuthenticator: authenticator
inContext: context];
}
}
//
//
//
- (iCalPerson *) findParticipantWithUID: (NSString *) uid
{
iCalEntityObject *component;
SOGoUser *user;
user = [SOGoUser userWithLogin: uid];
component = [self component: NO secure: NO];
return [component userAsAttendee: user];
}
//
//
//
- (iCalPerson *) iCalPersonWithUID: (NSString *) uid
{
iCalPerson *person;
SOGoUserManager *um;
NSDictionary *contactInfos;
um = [SOGoUserManager sharedUserManager];
contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
person = [iCalPerson new];
[person autorelease];
[person setCn: [contactInfos objectForKey: @"cn"]];
[person setEmail: [contactInfos objectForKey: @"c_email"]];
return person;
}
//
//
//
- (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
{
iCalPerson *currentPerson;
NSEnumerator *persons;
NSMutableArray *uids;
NSString *uid;
uids = [NSMutableArray array];
persons = [iCalPersons objectEnumerator];
while ((currentPerson = [persons nextObject]))
{
uid = [currentPerson uid];
if (uid)
[uids addObject: uid];
}
return uids;
}
- (NSException *) copyToFolder: (SOGoGCSFolder *) newFolder
{
return [self copyComponent: [self calendar: NO secure: NO]
toFolder: newFolder];
}
- (NSException *) copyComponent: (iCalCalendar *) calendar
toFolder: (SOGoGCSFolder *) newFolder
{
NSArray *elements;
NSString *newUID;
unsigned int count, max;
SOGoCalendarComponent *newComponent;
newUID = [self globallyUniqueObjectId];
elements = [calendar allObjects];
max = [elements count];
for (count = 0; count < max; count++)
[[elements objectAtIndex: count] setUid: newUID];
newComponent = [[self class] objectWithName:
[NSString stringWithFormat: @"%@.ics", newUID]
inContainer: newFolder];
return [newComponent saveCalendar: calendar];
}
- (NSException *) moveToFolder: (SOGoGCSFolder *) newFolder
{
NSException *ex;
ex = [self copyToFolder: newFolder];
if (!ex)
ex = [self delete];
return ex;
}
#warning Should we not remove the concept of Organizer and Participant roles?
- (NSString *) _roleOfOwner: (iCalRepeatableEntityObject *) component
{
NSString *role;
iCalPerson *organizer;
SOGoUser *ownerUser;
if (isNew)
role = SOGoCalendarRole_Organizer;
else
{
organizer = [component organizer];
if ([[organizer rfc822Email] length] > 0)
{
ownerUser = [SOGoUser userWithLogin: owner];
if ([component userIsOrganizer: ownerUser])
role = SOGoCalendarRole_Organizer;
else if ([component userIsAttendee: ownerUser])
role = SOGoCalendarRole_Participant;
else
role = SOGoRole_None;
}
else
role = SOGoCalendarRole_Organizer;
}
return role;
}
- (NSString *) _compiledRoleForOwner: (NSString *) ownerRole
andUser: (NSString *) userRole
{
NSString *role;
if ([userRole isEqualToString: SOGoCalendarRole_ComponentModifier]
|| ([userRole isEqualToString: SOGoCalendarRole_ComponentResponder]
&& [ownerRole isEqualToString: SOGoCalendarRole_Participant]))
role = ownerRole;
else
role = SOGoRole_None;
return role;
}
- (NSArray *) aclsForUser: (NSString *) uid
{
NSMutableArray *roles;
NSArray *superAcls;
iCalRepeatableEntityObject *component;
NSString *accessRole, *ownerRole;
SOGoUser *aclUser;
roles = [NSMutableArray array];
superAcls = [super aclsForUser: uid];
if ([superAcls count] > 0)
[roles addObjectsFromArray: superAcls];
component = [self component: NO secure: NO];
ownerRole = [self _roleOfOwner: component];
if ([owner isEqualToString: uid])
[roles addObject: ownerRole];
else
{
if (isNew)
{
if ([roles containsObject: SOGoRole_ObjectCreator])
[roles addObject: SOGoCalendarRole_Organizer];
}
else
{
if (component)
{
aclUser = [SOGoUser userWithLogin: uid];
if ([component userIsOrganizer: aclUser])
[roles addObject: SOGoCalendarRole_Organizer];
else if ([component userIsAttendee: aclUser])
[roles addObject: SOGoCalendarRole_Participant];
accessRole
= [container roleForComponentsWithAccessClass: [component symbolicAccessClass]
forUser: uid];
if ([accessRole length] > 0)
{
[roles addObject: accessRole];
[roles addObject: [self _compiledRoleForOwner: ownerRole
andUser: accessRole]];
}
}
}
}
return roles;
}
- (void) snoozeAlarm: (unsigned int) minutes
{
NSDictionary *quickFields;
GCSFolder *folder;
unsigned int nextAlarm;
folder = [[self container] ocsFolder];
if (!folder)
{
[self errorWithFormat:@"(%s): missing folder for update!",
__PRETTY_FUNCTION__];
return;
}
nextAlarm = [[NSCalendarDate calendarDate] timeIntervalSince1970] + minutes * 60;
quickFields = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: nextAlarm]
forKey: @"c_nextalarm"];
[folder updateQuickFields: quickFields
whereColumn: @"c_name"
isEqualTo: nameInContainer];
}
/* SOGoComponentOccurence protocol */
- (iCalRepeatableEntityObject *) occurence
{
return [self component: YES secure: NO];
}
#warning alarms: we do not handle occurrences
- (NSException *) prepareDelete
{
if ([[SOGoSystemDefaults sharedSystemDefaults] enableEMailAlarms])
{
SOGoEMailAlarmsManager *eaMgr;
eaMgr = [SOGoEMailAlarmsManager sharedEMailAlarmsManager];
[eaMgr deleteAlarmsFromComponent: self];
}
return nil;
}
- (id) PUTAction: (WOContext *) localContext
{
WORequest *rq;
iCalCalendar *putCalendar;
rq = [localContext request];
putCalendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]];
if ([[SOGoSystemDefaults sharedSystemDefaults] enableEMailAlarms])
{
SOGoEMailAlarmsManager *eaMgr;
eaMgr = [SOGoEMailAlarmsManager sharedEMailAlarmsManager];
[eaMgr handleAlarmsInCalendar: putCalendar
fromComponent: self];
}
return [super PUTAction: localContext];
}
// /* Overriding this method dramatically speeds up PROPFIND request, but may
// otherwise be a bad idea... Wait and see. */
// - (NSDictionary*) valuesForKeys: (NSArray*)keys
// {
// NSMutableDictionary *values;
// values = [NSMutableDictionary dictionaryWithCapacity: [keys count]];
// [values setObject: [self davCreationDate] forKey: @"davCreationDate"];
// [values setObject: [self davContentLength] forKey: @"davContentLength"];
// [values setObject: [self davLastModified] forKey: @"davLastModified"];
// [values setObject: @"text/calendar" forKey: @"davContentType"];
// [values setObject: [self baseURL] forKey: @"davURL"];
// return values;
// }
@end