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