]> err.no Git - scalable-opengroupware.org/blob - SoObjects/Appointments/SOGoCalendarComponent.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1091 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / Appointments / SOGoCalendarComponent.m
1 /* SOGoCalendarComponent.m - this file is part of SOGo
2  *
3  * Copyright (C) 2006 Inverse groupe conseil
4  *
5  * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
6  *
7  * This file is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This file is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; see the file COPYING.  If not, write to
19  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #import <Foundation/NSString.h>
24
25 #import <NGCards/iCalCalendar.h>
26 #import <NGCards/iCalPerson.h>
27 #import <NGCards/iCalRepeatableEntityObject.h>
28 #import <NGMime/NGMime.h>
29 #import <NGMail/NGMail.h>
30 #import <NGMail/NGSendMail.h>
31
32 #import <SoObjects/SOGo/LDAPUserManager.h>
33 #import <SoObjects/SOGo/SOGoPermissions.h>
34 #import <SoObjects/SOGo/SOGoUser.h>
35 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
36
37 #import "common.h"
38
39 #import "SOGoAptMailNotification.h"
40 #import "iCalEntityObject+SOGo.h"
41 #import "SOGoCalendarComponent.h"
42
43 static NSString *mailTemplateDefaultLanguage = nil;
44 static BOOL sendEMailNotifications = NO;
45
46 @implementation SOGoCalendarComponent
47
48 + (void) initialize
49 {
50   NSUserDefaults      *ud;
51   static BOOL         didInit = NO;
52   
53   if (!didInit)
54     {
55       didInit = YES;
56   
57       ud = [NSUserDefaults standardUserDefaults];
58       mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
59                                       retain];
60       if (!mailTemplateDefaultLanguage)
61         mailTemplateDefaultLanguage = @"English";
62
63       sendEMailNotifications
64         = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
65     }
66 }
67
68 - (id) init
69 {
70   if ((self = [super init]))
71     {
72       calendar = nil;
73       calContent = nil;
74     }
75
76   return self;
77 }
78
79 - (void) dealloc
80 {
81   if (calendar)
82     [calendar release];
83   if (calContent)
84     [calContent release];
85   [super dealloc];
86 }
87
88 - (NSString *) davContentType
89 {
90   return @"text/calendar";
91 }
92
93 - (NSString *) componentTag
94 {
95   [self subclassResponsibility: _cmd];
96
97   return nil;
98 }
99
100 - (void) _filterComponent: (iCalEntityObject *) component
101 {
102   [component setSummary: @""];
103   [component setComment: @""];
104   [component setUserComment: @""];
105   [component setLocation: @""];
106   [component setCategories: @""];
107   [component setUrl: @""];
108   [component removeAllAttendees];
109   [component removeAllAlarms];
110 }
111
112 - (NSString *) contentAsString
113 {
114   NSString *uid;
115   iCalCalendar *tmpCalendar;
116   iCalRepeatableEntityObject *tmpComponent;
117   NSArray *roles;
118
119   if (!calContent)
120     {
121       uid = [[context activeUser] login];
122       roles = [self aclsForUser: uid];
123       if ([roles containsObject: SOGoCalendarRole_Organizer]
124           || [roles containsObject: SOGoCalendarRole_Participant]
125           || [roles containsObject: SOGoCalendarRole_ComponentViewer])
126         {
127           calContent = content;
128           [calContent retain];
129         }
130       else if ([roles containsObject: SOGoCalendarRole_ComponentDAndTViewer])
131         {
132           tmpCalendar = [[self calendar: NO] copy];
133           tmpComponent = (iCalRepeatableEntityObject *)
134             [tmpCalendar firstChildWithTag: [self componentTag]];
135           [self _filterComponent: tmpComponent];
136           calContent = [tmpCalendar versitString];
137           [tmpCalendar release];
138         }
139       else
140         calContent = nil;
141
142       [calContent retain];
143     }
144
145   return calContent;
146 }
147
148 - (NSException *) saveContentString: (NSString *) contentString
149                         baseVersion: (unsigned int) baseVersion
150 {
151   NSException *result;
152
153   result = [super saveContentString: contentString
154                   baseVersion: baseVersion];
155   if (!result && calContent)
156     {
157       [calContent release];
158       calContent = nil;
159     }
160
161   return result;
162 }
163
164 - (iCalCalendar *) calendar: (BOOL) create
165 {
166   NSString *iCalString, *componentTag;
167   CardGroup *newComponent;
168
169   if (!calendar)
170     {
171       iCalString = [super contentAsString];
172       if ([iCalString length] > 0)
173         calendar = [iCalCalendar parseSingleFromSource: iCalString];
174       else
175         {
176           if (create)
177             {
178               calendar = [iCalCalendar groupWithTag: @"vcalendar"];
179               [calendar setVersion: @"2.0"];
180               [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
181               componentTag = [[self componentTag] uppercaseString];
182               newComponent = [[calendar classForTag: componentTag]
183                                groupWithTag: componentTag];
184               [calendar addChild: newComponent];
185             }
186         }
187       if (calendar)
188         [calendar retain];
189     }
190
191   return calendar;
192 }
193
194 - (iCalRepeatableEntityObject *) component: (BOOL) create
195 {
196   return
197     (iCalRepeatableEntityObject *) [[self calendar: create]
198                                      firstChildWithTag: [self componentTag]];
199 }
200
201 /* raw saving */
202
203 - (NSException *) primarySaveContentString: (NSString *) _iCalString
204 {
205   return [super saveContentString: _iCalString];
206 }
207
208 - (NSException *) primaryDelete
209 {
210   return [super delete];
211 }
212
213 - (NSException *) deleteWithBaseSequence: (int) a
214 {
215   [self subclassResponsibility: _cmd];
216
217   return nil;
218 }
219
220 - (NSException *) delete
221 {
222   return [self deleteWithBaseSequence:0];
223 }
224
225 /* EMail Notifications */
226 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
227 {
228   NSString *baseURL;
229   NSString *uid;
230   NSArray *traversalObjects;
231
232   /* generate URL from traversal stack */
233   traversalObjects = [context objectTraversalStack];
234   if ([traversalObjects count] > 0)
235     baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
236   else
237     {
238       baseURL = @"http://localhost/";
239       [self warnWithFormat:@"Unable to create baseURL from context!"];
240     }
241   uid = [[LDAPUserManager sharedUserManager]
242           getUIDForEmail: [_person rfc822Email]];
243
244   return ((uid)
245           ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
246           : nil);
247 }
248
249 - (NSException *) changeParticipationStatus: (NSString *) _status
250 {
251   iCalRepeatableEntityObject *component;
252   iCalPerson *person;
253   NSString *newContent;
254   NSException *ex;
255   NSString *myEMail;
256   
257   ex = nil;
258
259   component = [self component: NO];
260   if (component)
261     {
262       myEMail = [[context activeUser] primaryEmail];
263       person = [self findParticipantWithUID: owner];
264       if (person)
265         {
266           // TODO: send iMIP reply mails?
267           [person setPartStat: _status];
268           newContent = [[component parent] versitString];
269           if (newContent)
270             {
271               ex = [self saveContentString: newContent];
272               if (ex)
273                 // TODO: why is the exception wrapped?
274                 /* Server Error */
275                 ex = [NSException exceptionWithHTTPStatus: 500
276                                   reason: [ex reason]];
277             }
278           else
279             ex
280               = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
281                              reason: @"Could not generate iCalendar data ..."];
282         }
283       else
284         ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
285                           reason: @"user does not participate in this "
286                           @"calendar component"];
287     }
288   else
289     ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
290                       reason: @"unable to parse component record"];
291
292   return ex;
293 }
294
295 - (BOOL) sendEMailNotifications
296 {
297   return sendEMailNotifications;
298 }
299
300 - (NSTimeZone *) timeZoneForUser: (NSString *) email
301 {
302   NSString *uid;
303
304   uid = [[LDAPUserManager sharedUserManager] getUIDForEmail: email];
305
306   return [[SOGoUser userWithLogin: uid roles: nil] timeZone];
307 }
308
309 - (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
310                         forOldObject: (iCalRepeatableEntityObject *) _oldObject
311                         andNewObject: (iCalRepeatableEntityObject *) _newObject
312                          toAttendees: (NSArray *) _attendees
313 {
314   NSString *pageName;
315   iCalPerson *organizer;
316   NSString *cn, *email, *sender, *iCalString;
317   NGSendMail *sendmail;
318   WOApplication *app;
319   unsigned i, count;
320   iCalPerson *attendee;
321   NSString *recipient;
322   SOGoAptMailNotification *p;
323   NSString *subject, *text, *header;
324   NGMutableHashMap *headerMap;
325   NGMimeMessage *msg;
326   NGMimeBodyPart *bodyPart;
327   NGMimeMultipartBody *body;
328
329   if ([_attendees count])
330     {
331       /* sender */
332
333       organizer = [_newObject organizer];
334       cn = [organizer cnWithoutQuotes];
335       if (cn)
336         sender = [NSString stringWithFormat:@"%@ <%@>",
337                            cn,
338                            [organizer rfc822Email]];
339       else
340         sender = [organizer rfc822Email];
341
342       /* generate iCalString once */
343       iCalString = [[_newObject parent] versitString];
344   
345       /* get sendmail object */
346       sendmail = [NGSendMail sharedSendMail];
347
348       /* get WOApplication instance */
349       app = [WOApplication application];
350
351       /* generate dynamic message content */
352
353       count = [_attendees count];
354       for (i = 0; i < count; i++)
355         {
356           attendee = [_attendees objectAtIndex:i];
357
358           /* construct recipient */
359           cn = [attendee cn];
360           email = [attendee rfc822Email];
361           if (cn)
362             recipient = [NSString stringWithFormat: @"%@ <%@>",
363                                   cn, email];
364           else
365             recipient = email;
366
367 #warning this could be optimized in a class hierarchy common with the \
368           SOGoObject's acl notification mechanism
369           /* create page name */
370           // TODO: select user's default language?
371           pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
372                                mailTemplateDefaultLanguage,
373                                _pageName];
374           /* construct message content */
375           p = [app pageWithName: pageName inContext: context];
376           [p setNewApt: _newObject];
377           [p setOldApt: _oldObject];
378           [p setHomePageURL: [self homePageURLForPerson: attendee]];
379           [p setViewTZ: [self timeZoneForUser: email]];
380           subject = [p getSubject];
381           text = [p getBody];
382
383           /* construct message */
384           headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
385           
386           /* NOTE: multipart/alternative seems like the correct choice but
387            * unfortunately Thunderbird doesn't offer the rich content alternative
388            * at all. Mail.app shows the rich content alternative _only_
389            * so we'll stick with multipart/mixed for the time being.
390            */
391           [headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
392           [headerMap setObject: sender forKey: @"From"];
393           [headerMap setObject: recipient forKey: @"To"];
394           [headerMap setObject: [NSCalendarDate date] forKey: @"date"];
395           [headerMap setObject: subject forKey: @"Subject"];
396           msg = [NGMimeMessage messageWithHeader: headerMap];
397
398           /* multipart body */
399           body = [[NGMimeMultipartBody alloc] initWithPart: msg];
400     
401           /* text part */
402           headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
403           [headerMap setObject: @"text/plain; charset=utf-8"
404                      forKey: @"content-type"];
405           bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
406           [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
407
408           /* attach text part to multipart body */
409           [body addBodyPart: bodyPart];
410     
411           /* calendar part */
412           header = [NSString stringWithFormat: @"text/calendar; method=%@;"
413                              @" charset=utf-8",
414                              [(iCalCalendar *) [_newObject parent] method]];
415           headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
416           [headerMap setObject:header forKey: @"content-type"];
417           bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
418           [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
419
420           /* attach calendar part to multipart body */
421           [body addBodyPart: bodyPart];
422     
423           /* attach multipart body to message */
424           [msg setBody: body];
425           [body release];
426
427           /* send the damn thing */
428           [sendmail sendMimePart: msg
429                     toRecipients: [NSArray arrayWithObject: email]
430                     sender: [organizer rfc822Email]];
431         }
432     }
433 }
434
435 // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
436 // {
437 //   BOOL isOrganizerOrOwner;
438 //   iCalRepeatableEntityObject *component;
439 //   NSString *organizerEmail;
440
441 //   component = [self component: NO];
442 //   organizerEmail = [[component organizer] rfc822Email];
443 //   if (component && [organizerEmail length] > 0)
444 //     isOrganizerOrOwner = [user hasEmail: organizerEmail];
445 //   else
446 //     isOrganizerOrOwner
447 //       = [[container ownerInContext: context] isEqualToString: [user login]];
448
449 //   return isOrganizerOrOwner;
450 // }
451
452 - (iCalPerson *) findParticipantWithUID: (NSString *) uid
453 {
454   SOGoUser *user;
455
456   user = [SOGoUser userWithLogin: uid roles: nil];
457
458   return [self findParticipant: user];
459 }
460
461 - (iCalPerson *) findParticipant: (SOGoUser *) user
462 {
463   iCalPerson *participant, *currentParticipant;
464   iCalEntityObject *component;
465   NSEnumerator *participants;
466
467   participant = nil;
468   component = [self component: NO];
469   if (component)
470     {
471       participants = [[component participants] objectEnumerator];
472       currentParticipant = [participants nextObject];
473       while (currentParticipant && !participant)
474         if ([user hasEmail: [currentParticipant rfc822Email]])
475           participant = currentParticipant;
476         else
477           currentParticipant = [participants nextObject];
478     }
479
480   return participant;
481 }
482
483 - (iCalPerson *) iCalPersonWithUID: (NSString *) uid
484 {
485   iCalPerson *person;
486   LDAPUserManager *um;
487   NSDictionary *contactInfos;
488
489   um = [LDAPUserManager sharedUserManager];
490   contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
491
492   person = [iCalPerson new];
493   [person autorelease];
494   [person setCn: [contactInfos objectForKey: @"cn"]];
495   [person setEmail: [contactInfos objectForKey: @"c_email"]];
496
497   return person;
498 }
499
500 - (NSString *) getUIDForICalPerson: (iCalPerson *) person
501 {
502   LDAPUserManager *um;
503
504   um = [LDAPUserManager sharedUserManager];
505
506   return [um getUIDForEmail: [person rfc822Email]];
507 }
508
509 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
510 {
511   iCalPerson *currentPerson;
512   NSEnumerator *persons;
513   NSMutableArray *uids;
514   NSString *uid;
515   LDAPUserManager *um;
516
517   uids = [NSMutableArray array];
518
519   um = [LDAPUserManager sharedUserManager];
520   persons = [iCalPersons objectEnumerator];
521   currentPerson = [persons nextObject];
522   while (currentPerson)
523     {
524       uid = [um getUIDForEmail: [currentPerson rfc822Email]];
525       if (uid)
526         [uids addObject: uid];
527       currentPerson = [persons nextObject];
528     }
529
530   return uids;
531 }
532
533 - (NSString *) _roleOfOwner: (iCalRepeatableEntityObject *) component
534 {
535   NSString *role;
536   iCalPerson *organizer;
537   SOGoUser *ownerUser;
538
539   if (component)
540     {
541       organizer = [component organizer];
542       if ([[organizer rfc822Email] length] > 0)
543         {
544           ownerUser = [SOGoUser userWithLogin: owner roles: nil];
545           if ([component userIsOrganizer: ownerUser])
546             role = SOGoCalendarRole_Organizer;
547           else if ([component userIsParticipant: ownerUser])
548             role = SOGoCalendarRole_Participant;
549           else
550             role = SOGoRole_None;
551         }
552       else
553         role = SOGoCalendarRole_Organizer;
554     }
555   else
556     role = SOGoCalendarRole_Organizer;
557   
558   return role;
559 }
560
561 - (NSString *) _compiledRoleForOwner: (NSString *) ownerRole
562                              andUser: (NSString *) userRole
563 {
564   NSString *role;
565
566   if ([userRole isEqualToString: SOGoCalendarRole_ComponentModifier]
567       || ([userRole isEqualToString: SOGoCalendarRole_ComponentResponder]
568           && [ownerRole isEqualToString: SOGoCalendarRole_Participant]))
569     role = ownerRole;
570   else
571     role = SOGoRole_None;
572
573   return role;
574 }
575
576 - (NSArray *) aclsForUser: (NSString *) uid
577 {
578   NSMutableArray *roles;
579   NSArray *superAcls;
580   iCalRepeatableEntityObject *component;
581   NSString *accessRole, *ownerRole;
582
583   roles = [NSMutableArray array];
584   superAcls = [super aclsForUser: uid];
585   if ([superAcls count] > 0)
586     [roles addObjectsFromArray: superAcls];
587
588   component = [self component: NO];
589   ownerRole = [self _roleOfOwner: component];
590   if ([owner isEqualToString: uid])
591     [roles addObject: ownerRole];
592   else
593     {
594       if (component)
595         {
596           accessRole = [container roleForComponentsWithAccessClass:
597                                     [component symbolicAccessClass]
598                                   forUser: uid];
599           if ([accessRole length] > 0)
600             {
601               [roles addObject: accessRole];
602               [roles addObject: [self _compiledRoleForOwner: ownerRole
603                                       andUser: accessRole]];
604             }
605         }
606       else if ([roles containsObject: SOGoRole_ObjectCreator])
607         [roles addObject: SOGoCalendarRole_Organizer];
608     }
609 //     }
610
611   return roles;
612 }
613
614 @end