]> err.no Git - scalable-opengroupware.org/blob - SoObjects/Appointments/SOGoAppointmentObject.m
42e4f01e5147f504b951b29050a2390febb20b4a
[scalable-opengroupware.org] / SoObjects / Appointments / SOGoAppointmentObject.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #import "SOGoAppointmentObject.h"
23
24 #import <NGCards/iCalCalendar.h>
25 #import <NGCards/iCalEvent.h>
26 #import <NGCards/iCalEventChanges.h>
27 #import <NGCards/iCalPerson.h>
28 #import <NGMime/NGMime.h>
29 #import <NGMail/NGMail.h>
30 #import <NGMail/NGSendMail.h>
31
32 #import <SOGo/AgenorUserManager.h>
33 #import <SOGo/SOGoObject.h>
34
35 #import "SOGoAptMailNotification.h"
36 #import "iCalEntityObject+Agenor.h"
37
38 #import "common.h"
39
40 #import "NSArray+Appointments.h"
41
42 @interface SOGoAppointmentObject (PrivateAPI)
43 - (NSString *) homePageURLForPerson: (iCalPerson *) _person;
44   
45 - (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
46                    forOldAppointment: (iCalEvent *) _newApt
47                    andNewAppointment: (iCalEvent *) _oldApt
48                          toAttendees: (NSArray *) _attendees;
49
50 - (void) sendInvitationEMailForAppointment: (iCalEvent *) _apt
51                                toAttendees: (NSArray *) _attendees;
52 - (void) sendAppointmentUpdateEMailForOldAppointment: (iCalEvent *) _oldApt
53                                       newAppointment: (iCalEvent *) _newApt
54                                          toAttendees: (NSArray *) _attendees;
55 - (void) sendAttendeeRemovalEMailForAppointment: (iCalEvent *) _apt
56                                     toAttendees: (NSArray *) _attendees;
57 - (void) sendAppointmentDeletionEMailForAppointment: (iCalEvent *) _apt
58                                         toAttendees: (NSArray *) _attendees;
59 @end
60
61 @implementation SOGoAppointmentObject
62
63 static NSString *mailTemplateDefaultLanguage = nil;
64 static BOOL sendEMailNotifications = NO;
65
66 + (void) initialize
67 {
68   NSUserDefaults      *ud;
69   static BOOL         didInit = NO;
70   
71   if (!didInit)
72     {
73       didInit = YES;
74   
75       ud = [NSUserDefaults standardUserDefaults];
76       mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
77                                       retain];
78       if (!mailTemplateDefaultLanguage)
79         mailTemplateDefaultLanguage = @"French";
80
81       sendEMailNotifications
82         = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
83     }
84 }
85
86 /* accessors */
87
88 - (iCalEvent *) event
89 {
90   return [self firstEventFromCalendar: [self calendar]];
91 }
92
93 /* iCal handling */
94 - (NSArray *) attendeeUIDsFromAppointment: (iCalEvent *) _apt
95 {
96   AgenorUserManager *um;
97   NSMutableArray *uids;
98   NSArray *attendees;
99   unsigned i, count;
100   NSString *email, *uid;
101   
102   if (![_apt isNotNull])
103     return nil;
104   
105   if ((attendees = [_apt attendees]) == nil)
106     return nil;
107   count = [attendees count];
108   uids = [NSMutableArray arrayWithCapacity:count + 1];
109   
110   um = [AgenorUserManager sharedUserManager];
111   
112   /* add organizer */
113   
114   email = [[_apt organizer] rfc822Email];
115   if ([email isNotNull]) {
116     uid = [um getUIDForEmail:email];
117     if ([uid isNotNull]) {
118       [uids addObject:uid];
119     }
120     else
121       [self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
122   }
123
124   /* add attendees */
125   
126   for (i = 0; i < count; i++) {
127     iCalPerson *person;
128     
129     person = [attendees objectAtIndex:i];
130     email  = [person rfc822Email];
131     if (![email isNotNull]) continue;
132     
133     uid = [um getUIDForEmail:email];
134     if (![uid isNotNull]) {
135       [self logWithFormat:@"Note: got no uid for email: '%@'", email];
136       continue;
137     }
138     if (![uids containsObject:uid])
139       [uids addObject:uid];
140   }
141   
142   return uids;
143 }
144
145 /* folder management */
146
147 - (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
148   // TODO: what does this do? lookup the home of the organizer?
149   return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx];
150 }
151 - (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
152   return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
153 }
154
155 /* store in all the other folders */
156
157 - (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
158   NSEnumerator *e;
159   id folder;
160   NSException *allErrors = nil;
161   id ctx;
162
163   ctx = [[WOApplication application] context];
164   
165   e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
166              objectEnumerator];
167   while ((folder = [e nextObject]) != nil) {
168     NSException           *error;
169     SOGoAppointmentObject *apt;
170     
171     if (![folder isNotNull]) /* no folder was found for given UID */
172       continue;
173
174     apt = [folder lookupName: [self nameInContainer] inContext:ctx
175                   acquire: NO];
176     if ([apt isKindOfClass: [NSException class]])
177       {
178         [self logWithFormat:@"Note: an exception occured finding '%@' in folder: %@",
179               [self nameInContainer], folder];
180         [self logWithFormat:@"the exception reason was: %@",
181               [(NSException *) apt reason]];
182         continue;
183       }
184
185     if (![apt isNotNull]) {
186       [self logWithFormat:@"Note: did not find '%@' in folder: %@",
187               [self nameInContainer], folder];
188       continue;
189     }
190     if ([apt isKindOfClass: [NSException class]]) {
191       [self logWithFormat:@"Exception: %@", [(NSException *) apt reason]];
192       continue;
193     }
194     
195     if ((error = [apt primarySaveContentString:_iCal]) != nil) {
196       [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
197       // TODO: make compound
198       allErrors = error;
199     }
200   }
201
202   return allErrors;
203 }
204
205 - (NSException *)deleteInUIDs:(NSArray *)_uids {
206   NSEnumerator *e;
207   id folder;
208   NSException *allErrors = nil;
209   id ctx;
210   
211   ctx = [[WOApplication application] context];
212   
213   e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
214              objectEnumerator];
215   while ((folder = [e nextObject])) {
216     NSException           *error;
217     SOGoAppointmentObject *apt;
218     
219     apt = [folder lookupName:[self nameInContainer] inContext:ctx
220                   acquire:NO];
221     if ([apt isKindOfClass: [NSException class]]) {
222       [self logWithFormat: @"%@", [(NSException *) apt reason]];
223       continue;
224     }
225     
226     if ((error = [apt primaryDelete]) != nil) {
227       [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
228       // TODO: make compound
229       allErrors = error;
230     }
231   }
232   return allErrors;
233 }
234
235 - (iCalEvent *) firstEventFromCalendar: (iCalCalendar *) aCalendar
236 {
237   iCalEvent *event;
238   NSArray *events;
239
240   events = [aCalendar childrenWithTag: @"vevent"];
241   if ([events count])
242     event = (iCalEvent *) [[events objectAtIndex: 0]
243                             groupWithClass: [iCalEvent class]];
244   else
245     event = nil;
246
247   return event;
248 }
249
250 /* "iCal multifolder saves" */
251
252 - (NSException *) saveContentString: (NSString *) _iCal
253                        baseSequence: (int) _v
254 {
255   /* 
256      Note: we need to delete in all participants folders and send iMIP messages
257            for all external accounts.
258      
259      Steps:
260      - fetch stored content
261      - parse old content
262      - check if sequence matches (or if 0=ignore)
263      - extract old attendee list + organizer (make unique)
264      - parse new content (ensure that sequence is increased!)
265      - extract new attendee list + organizer (make unique)
266      - make a diff => new, same, removed
267      - write to new, same
268      - delete in removed folders
269      - send iMIP mail for all folders not found
270   */
271   AgenorUserManager *um;
272   iCalCalendar *newCalendar;
273   iCalEvent *oldApt, *newApt;
274   iCalEventChanges *changes;
275   iCalPerson *organizer;
276   NSString *oldContent, *uid;
277   NSArray *uids, *props;
278   NSMutableArray *attendees, *storeUIDs, *removedUIDs;
279   NSException *storeError, *delError;
280   BOOL updateForcesReconsider;
281   
282   updateForcesReconsider = NO;
283
284   if ([_iCal length] == 0) {
285     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
286                         reason:@"got no iCalendar content to store!"];
287   }
288
289   um = [AgenorUserManager sharedUserManager];
290
291   /* handle old content */
292   
293   oldContent = [self iCalString]; /* if nil, this is a new appointment */
294   if ([oldContent length] == 0)
295     {
296     /* new appointment */
297       [self debugWithFormat:@"saving new appointment: %@", _iCal];
298       oldApt = nil;
299     }
300   else
301     oldApt = [self firstEventFromCalendar: [self calendar]];
302   
303   /* compare sequence if requested */
304
305   if (_v != 0) {
306     // TODO
307   }
308   
309   
310   /* handle new content */
311   
312   newCalendar = [iCalCalendar parseSingleFromSource: _iCal];
313   newApt = [self firstEventFromCalendar: newCalendar];
314   if (newApt == nil) {
315     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
316                         reason:@"could not parse iCalendar content!"];
317   }
318   
319   /* diff */
320   
321   changes = [iCalEventChanges changesFromEvent: oldApt
322                               toEvent: newApt];
323
324   uids = [um getUIDsForICalPersons:[changes deletedAttendees]
325                     applyStrictMapping:NO];
326   removedUIDs = [NSMutableArray arrayWithArray:uids];
327
328   uids = [um getUIDsForICalPersons:[newApt attendees]
329                     applyStrictMapping:NO];
330   storeUIDs = [NSMutableArray arrayWithArray:uids];
331   props = [changes updatedProperties];
332
333   /* detect whether sequence has to be increased */
334   if ([changes hasChanges])
335     [newApt increaseSequence];
336
337   /* preserve organizer */
338
339   organizer = [newApt organizer];
340   uid = [um getUIDForICalPerson:organizer];
341   if (uid) {
342     if (![storeUIDs containsObject:uid])
343       [storeUIDs addObject:uid];
344     [removedUIDs removeObject:uid];
345   }
346
347   /* organizer might have changed completely */
348
349   if (oldApt && ([props containsObject: @"organizer"])) {
350     uid = [um getUIDForICalPerson:[oldApt organizer]];
351     if (uid) {
352       if (![storeUIDs containsObject:uid]) {
353         if (![removedUIDs containsObject:uid]) {
354           [removedUIDs addObject:uid];
355         }
356       }
357     }
358   }
359
360   [self debugWithFormat:@"UID ops:\n  store: %@\n  remove: %@",
361                         storeUIDs, removedUIDs];
362
363   /* if time did change, all participants have to re-decide ...
364    * ... exception from that rule: the organizer
365    */
366
367   if (oldApt != nil &&
368       ([props containsObject:@"startDate"] ||
369        [props containsObject:@"endDate"]   ||
370        [props containsObject:@"duration"]))
371   {
372     NSArray  *ps;
373     unsigned i, count;
374     
375     ps    = [newApt attendees];
376     count = [ps count];
377     for (i = 0; i < count; i++) {
378       iCalPerson *p;
379       
380       p = [ps objectAtIndex:i];
381       if (![p hasSameEmailAddress:organizer])
382         [p setParticipationStatus:iCalPersonPartStatNeedsAction];
383     }
384     _iCal = [[newApt parent] versitString];
385     updateForcesReconsider = YES;
386   }
387
388   /* perform storing */
389
390   storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
391   delError = [self deleteInUIDs:removedUIDs];
392
393   // TODO: make compound
394   if (storeError != nil) return storeError;
395   if (delError   != nil) return delError;
396
397   /* email notifications */
398   if (sendEMailNotifications)
399     {
400       attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
401       [attendees removePerson:organizer];
402       [self sendInvitationEMailForAppointment:newApt
403             toAttendees:attendees];
404
405       if (updateForcesReconsider) {
406         attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
407         [attendees removeObjectsInArray:[changes insertedAttendees]];
408         [attendees removePerson:organizer];
409         [self sendAppointmentUpdateEMailForOldAppointment:oldApt
410               newAppointment:newApt
411               toAttendees:attendees];
412       }
413
414       attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
415       [attendees removePerson: organizer];
416       if ([attendees count])
417         {
418           iCalEvent *canceledApt;
419     
420           canceledApt = [newApt copy];
421           [(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"];
422           [self sendAttendeeRemovalEMailForAppointment:canceledApt
423                 toAttendees: attendees];
424           [canceledApt release];
425         }
426     }
427
428   return nil;
429 }
430
431 - (NSException *)deleteWithBaseSequence:(int)_v {
432   /* 
433      Note: We need to delete in all participants folders and send iMIP messages
434            for all external accounts.
435            Delete is basically identical to save with all attendees and the
436            organizer being deleted.
437
438      Steps:
439      - fetch stored content
440      - parse old content
441      - check if sequence matches (or if 0=ignore)
442      - extract old attendee list + organizer (make unique)
443      - delete in removed folders
444      - send iMIP mail for all folders not found
445   */
446   iCalEvent *apt;
447   NSArray *removedUIDs;
448   NSMutableArray *attendees;
449
450   /* load existing content */
451
452   apt = [self event];  
453   
454   /* compare sequence if requested */
455
456 //   if (_v != 0) {
457 //     // TODO
458 //   }
459   
460   removedUIDs = [self attendeeUIDsFromAppointment:apt];
461
462   if (sendEMailNotifications)
463     {
464       /* send notification email to attendees excluding organizer */
465       attendees = [NSMutableArray arrayWithArray:[apt attendees]];
466       [attendees removePerson:[apt organizer]];
467   
468       /* flag appointment as being canceled */
469       [(iCalCalendar *) [apt parent] setMethod: @"cancel"];
470       [apt increaseSequence];
471
472       /* remove all attendees to signal complete removal */
473       [apt removeAllAttendees];
474
475       /* send notification email */
476       [self sendAppointmentDeletionEMailForAppointment:apt
477             toAttendees:attendees];
478     }
479
480   /* perform */
481
482   return [self deleteInUIDs:removedUIDs];
483 }
484
485 - (NSException *) saveContentString: (NSString *) _iCalString
486 {
487   return [self saveContentString: _iCalString baseSequence: 0];
488 }
489
490 - (NSException *) changeParticipationStatus: (NSString *) _status
491                                   inContext: (id) _ctx
492 {
493   iCalEvent *apt;
494   iCalPerson *p;
495   NSString *newContent;
496   NSException *ex;
497   NSString *myEMail;
498   
499   ex = nil;
500
501   // TODO: do we need to use SOGoAppointment? (prefer iCalEvent?)
502   apt = [self event];
503
504   if (apt)
505     {
506       myEMail = [[_ctx activeUser] email];
507       p = [apt findParticipantWithEmail: myEMail];
508       if (p)
509         {
510   // TODO: send iMIP reply mails?
511   
512           [p setPartStat:_status];
513           newContent = [[apt parent] versitString];
514           if (newContent)
515             {
516               ex = [self saveContentString:newContent];
517               if (ex)
518                 // TODO: why is the exception wrapped?
519                 /* Server Error */
520                 ex = [NSException exceptionWithHTTPStatus: 500
521                                   reason: [ex reason]];
522             }
523           else
524             ex
525               = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
526                              reason: @"Could not generate iCalendar data ..."];
527         }
528       else
529         ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
530                           reason: @"user does not participate in this "
531                           @"appointment"];
532     }
533   else
534     ex = [NSException exceptionWithHTTPStatus:500 /* Server Error */
535                       reason:@"unable to parse appointment record"];
536
537   return ex;
538 }
539
540
541 /* message type */
542
543 - (NSString *) outlookMessageClass
544 {
545   return @"IPM.Appointment";
546 }
547
548 /* EMail Notifications */
549
550 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
551 {
552   NSString *baseURL;
553   NSString *uid;
554   WOContext *ctx;
555   NSArray *traversalObjects;
556
557   /* generate URL from traversal stack */
558   ctx = [[WOApplication application] context];
559   traversalObjects = [ctx objectTraversalStack];
560   if ([traversalObjects count] > 0)
561     baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext:ctx];
562   else
563     {
564       baseURL = @"http://localhost/";
565       [self warnWithFormat:@"Unable to create baseURL from context!"];
566     }
567   uid = [[AgenorUserManager sharedUserManager]
568           getUIDForEmail: [_person rfc822Email]];
569
570   return ((uid)
571           ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
572           : nil);
573 }
574
575 - (NSException *) saveContentString: (NSString *) contentString
576                         baseVersion: (unsigned int) baseVersion
577 {
578   NSString *newContentString, *oldContentString;
579   iCalCalendar *eventCalendar;
580   iCalEvent *event;
581   NSArray *organizers;
582
583   oldContentString = [self iCalString];
584   if (oldContentString)
585     newContentString = contentString;
586   else
587     {
588       eventCalendar = [iCalCalendar parseSingleFromSource: contentString];
589       event = [self firstEventFromCalendar: eventCalendar];
590       organizers = [event childrenWithTag: @"organizer"];
591       if ([organizers count])
592         newContentString = contentString;
593       else
594         {
595           [event setOrganizerWithUid: [[self container] ownerInContext: nil]];
596           newContentString = [eventCalendar versitString];
597         }
598     }
599
600   return [super saveContentString: newContentString
601                 baseVersion: baseVersion];
602 }
603
604 - (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
605                    forOldAppointment: (iCalEvent *) _oldApt
606                    andNewAppointment: (iCalEvent *) _newApt
607                          toAttendees: (NSArray *) _attendees
608 {
609   NSString *pageName;
610   iCalPerson *organizer;
611   NSString *cn, *sender, *iCalString;
612   NGSendMail *sendmail;
613   WOApplication *app;
614   unsigned i, count;
615   iCalPerson *attendee;
616   NSString *recipient;
617   SOGoAptMailNotification *p;
618   NSString *subject, *text, *header;
619   NGMutableHashMap *headerMap;
620   NGMimeMessage *msg;
621   NGMimeBodyPart *bodyPart;
622   NGMimeMultipartBody *body;
623
624   if ([_attendees count])
625     {
626       /* sender */
627
628       organizer = [_newApt organizer];
629       cn = [organizer cnWithoutQuotes];
630       if (cn)
631         sender = [NSString stringWithFormat:@"%@ <%@>",
632                            cn,
633                            [organizer rfc822Email]];
634       else
635         sender = [organizer rfc822Email];
636
637       /* generate iCalString once */
638       iCalString = [[_newApt parent] versitString];
639   
640       /* get sendmail object */
641       sendmail = [NGSendMail sharedSendMail];
642
643       /* get WOApplication instance */
644       app = [WOApplication application];
645
646       /* generate dynamic message content */
647
648       count = [_attendees count];
649       for (i = 0; i < count; i++)
650         {
651           attendee = [_attendees objectAtIndex:i];
652
653           /* construct recipient */
654           cn = [attendee cn];
655           if (cn)
656             recipient = [NSString stringWithFormat: @"%@ <%@>",
657                                   cn,
658                                   [attendee rfc822Email]];
659           else
660             recipient = [attendee rfc822Email];
661
662           /* create page name */
663           // TODO: select user's default language?
664           pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
665                                mailTemplateDefaultLanguage,
666                                _pageName];
667           /* construct message content */
668           p = [app pageWithName: pageName inContext: [WOContext context]];
669           [p setNewApt: _newApt];
670           [p setOldApt: _oldApt];
671           [p setHomePageURL: [self homePageURLForPerson: attendee]];
672           [p setViewTZ: [self userTimeZone: cn]];
673           subject = [p getSubject];
674           text = [p getBody];
675
676           /* construct message */
677           headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
678     
679           /* NOTE: multipart/alternative seems like the correct choice but
680            * unfortunately Thunderbird doesn't offer the rich content alternative
681            * at all. Mail.app shows the rich content alternative _only_
682            * so we'll stick with multipart/mixed for the time being.
683            */
684           [headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
685           [headerMap setObject: sender forKey: @"From"];
686           [headerMap setObject: recipient forKey: @"To"];
687           [headerMap setObject: [NSCalendarDate date] forKey: @"date"];
688           [headerMap setObject: subject forKey: @"Subject"];
689           msg = [NGMimeMessage messageWithHeader: headerMap];
690
691           /* multipart body */
692           body = [[NGMimeMultipartBody alloc] initWithPart: msg];
693     
694           /* text part */
695           headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
696           [headerMap setObject: @"text/plain; charset=utf-8"
697                      forKey: @"content-type"];
698           bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
699           [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
700
701           /* attach text part to multipart body */
702           [body addBodyPart: bodyPart];
703     
704           /* calendar part */
705           header = [NSString stringWithFormat: @"text/calendar; method=%@;"
706                              @" charset=utf-8",
707                              [(iCalCalendar *) [_newApt parent] method]];
708           headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
709           [headerMap setObject:header forKey: @"content-type"];
710           bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
711           [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
712
713           /* attach calendar part to multipart body */
714           [body addBodyPart: bodyPart];
715     
716           /* attach multipart body to message */
717           [msg setBody: body];
718           [body release];
719
720           /* send the damn thing */
721           [sendmail sendMimePart: msg
722                     toRecipients: [NSArray arrayWithObject: [attendee rfc822Email]]
723                     sender: [organizer rfc822Email]];
724         }
725     }
726 }
727
728 - (void) sendInvitationEMailForAppointment: (iCalEvent *) _apt
729                                toAttendees: (NSArray *) _attendees
730 {
731   if ([_attendees count])
732     [self sendEMailUsingTemplateNamed: @"Invitation"
733           forOldAppointment: nil
734           andNewAppointment: _apt
735           toAttendees: _attendees];
736 }
737
738 - (void) sendAppointmentUpdateEMailForOldAppointment: (iCalEvent *) _oldApt
739                                       newAppointment: (iCalEvent *) _newApt
740                                          toAttendees: (NSArray *) _attendees
741 {
742   if ([_attendees count])
743     [self sendEMailUsingTemplateNamed: @"Update"
744           forOldAppointment: _oldApt
745           andNewAppointment: _newApt
746           toAttendees: _attendees];
747 }
748
749 - (void) sendAttendeeRemovalEMailForAppointment: (iCalEvent *) _apt
750                                     toAttendees: (NSArray *) _attendees
751 {
752   if ([_attendees count])
753     [self sendEMailUsingTemplateNamed: @"Removal"
754           forOldAppointment: nil
755           andNewAppointment: _apt
756           toAttendees: _attendees];
757 }
758
759 - (void) sendAppointmentDeletionEMailForAppointment: (iCalEvent *) _apt
760                                         toAttendees: (NSArray *) _attendees
761 {
762   if ([_attendees count])
763     [self sendEMailUsingTemplateNamed: @"Deletion"
764           forOldAppointment: nil
765           andNewAppointment: _apt
766           toAttendees: _attendees];
767 }
768
769 - (NSString *) davContentType
770 {
771   return @"text/calendar";
772 }
773
774 - (NSString *) roleOfUser: (NSString *) login
775                 inContext: (WOContext *) context
776 {
777   AgenorUserManager *um;
778   iCalEvent *event;
779   NSString *role, *email;
780
781   um = [AgenorUserManager sharedUserManager];
782   email = [um getEmailForUID: login];
783
784   event = [self event];
785   if ([event isOrganizer: email])
786     role = @"Organizer";
787   else if ([event isParticipant: email])
788     role = @"Participant";
789   else
790     role = nil;
791
792   return role;
793 }
794
795 @end /* SOGoAppointmentObject */
796
797
798