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