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