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