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