]> err.no Git - scalable-opengroupware.org/blob - SoObjects/Appointments/SOGoCalendarComponent.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1146 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 NSString *mailTemplateDefaultLanguage = nil;
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       mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
66                                       retain];
67       if (!mailTemplateDefaultLanguage)
68         mailTemplateDefaultLanguage = @"English";
69
70       sendEMailNotifications
71         = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
72     }
73 }
74
75 - (id) init
76 {
77   if ((self = [super init]))
78     {
79       calendar = nil;
80       calContent = nil;
81     }
82
83   return self;
84 }
85
86 - (void) dealloc
87 {
88   if (calendar)
89     [calendar release];
90   if (calContent)
91     [calContent release];
92   [super dealloc];
93 }
94
95 - (NSString *) davContentType
96 {
97   return @"text/calendar";
98 }
99
100 - (NSString *) componentTag
101 {
102   [self subclassResponsibility: _cmd];
103
104   return nil;
105 }
106
107 - (void) _filterComponent: (iCalEntityObject *) component
108 {
109   [component setSummary: @""];
110   [component setComment: @""];
111   [component setUserComment: @""];
112   [component setLocation: @""];
113   [component setCategories: @""];
114   [component setUrl: @""];
115   [component removeAllAttendees];
116   [component removeAllAlarms];
117 }
118
119 - (NSString *) contentAsString
120 {
121   iCalCalendar *tmpCalendar;
122   iCalRepeatableEntityObject *tmpComponent;
123 //   NSArray *roles;
124 //   NSString *uid;
125   SoSecurityManager *sm;
126
127   if (!calContent)
128     {
129 //       uid = [[context activeUser] login];
130 //       roles = [self aclsForUser: uid];
131 //       if ([roles containsObject: SOGoCalendarRole_Organizer]
132 //        || [roles containsObject: SOGoCalendarRole_Participant]
133 //        || [roles containsObject: SOGoCalendarRole_ComponentViewer])
134 //      calContent = content;
135 //       else if ([roles containsObject: SOGoCalendarRole_ComponentDAndTViewer])
136 //      {
137 //        tmpCalendar = [[self calendar: NO] copy];
138 //        tmpComponent = (iCalRepeatableEntityObject *)
139 //          [tmpCalendar firstChildWithTag: [self componentTag]];
140 //        [self _filterComponent: tmpComponent];
141 //        calContent = [tmpCalendar versitString];
142 //        [tmpCalendar release];
143 //      }
144 //       else
145 //      calContent = nil;
146
147       sm = [SoSecurityManager sharedSecurityManager];
148       if (![sm validatePermission: SOGoCalendarPerm_ViewAllComponent
149                onObject: self inContext: context])
150         calContent = content;
151       else if (![sm validatePermission: SOGoCalendarPerm_ViewDAndT
152                     onObject: self inContext: context])
153         {
154           tmpCalendar = [[self calendar: NO] copy];
155           tmpComponent = (iCalRepeatableEntityObject *)
156             [tmpCalendar firstChildWithTag: [self componentTag]];
157           [self _filterComponent: tmpComponent];
158           calContent = [tmpCalendar versitString];
159           [tmpCalendar release];
160         }
161       else
162         calContent = nil;
163
164       [calContent retain];
165     }
166
167   return calContent;
168 }
169
170 - (NSException *) saveContentString: (NSString *) contentString
171                         baseVersion: (unsigned int) baseVersion
172 {
173   NSException *result;
174
175   result = [super saveContentString: contentString
176                   baseVersion: baseVersion];
177   if (!result && calContent)
178     {
179       [calContent release];
180       calContent = nil;
181     }
182
183   return result;
184 }
185
186 - (iCalCalendar *) calendar: (BOOL) create
187 {
188   NSString *iCalString, *componentTag;
189   CardGroup *newComponent;
190
191   if (!calendar)
192     {
193       iCalString = [super contentAsString];
194       if ([iCalString length] > 0)
195         calendar = [iCalCalendar parseSingleFromSource: iCalString];
196       else
197         {
198           if (create)
199             {
200               calendar = [iCalCalendar groupWithTag: @"vcalendar"];
201               [calendar setVersion: @"2.0"];
202               [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
203               componentTag = [[self componentTag] uppercaseString];
204               newComponent = [[calendar classForTag: componentTag]
205                                groupWithTag: componentTag];
206               [calendar addChild: newComponent];
207             }
208         }
209       if (calendar)
210         [calendar retain];
211     }
212
213   return calendar;
214 }
215
216 - (iCalRepeatableEntityObject *) component: (BOOL) create
217 {
218   return
219     (iCalRepeatableEntityObject *) [[self calendar: create]
220                                      firstChildWithTag: [self componentTag]];
221 }
222
223 /* raw saving */
224
225 - (NSException *) primarySaveContentString: (NSString *) _iCalString
226 {
227   return [super saveContentString: _iCalString];
228 }
229
230 - (NSException *) primaryDelete
231 {
232   return [super delete];
233 }
234
235 - (NSException *) deleteWithBaseSequence: (int) a
236 {
237   [self subclassResponsibility: _cmd];
238
239   return nil;
240 }
241
242 - (NSException *) delete
243 {
244   return [self deleteWithBaseSequence:0];
245 }
246
247 /* EMail Notifications */
248 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
249 {
250   NSString *baseURL;
251   NSString *uid;
252   NSArray *traversalObjects;
253
254   /* generate URL from traversal stack */
255   traversalObjects = [context objectTraversalStack];
256   if ([traversalObjects count] > 0)
257     baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
258   else
259     {
260       baseURL = @"http://localhost/";
261       [self warnWithFormat:@"Unable to create baseURL from context!"];
262     }
263   uid = [[LDAPUserManager sharedUserManager]
264           getUIDForEmail: [_person rfc822Email]];
265
266   return ((uid)
267           ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
268           : nil);
269 }
270
271 - (NSException *) changeParticipationStatus: (NSString *) _status
272 {
273   iCalRepeatableEntityObject *component;
274   iCalPerson *person;
275   NSString *newContent;
276   NSException *ex;
277   NSString *myEMail;
278   
279   ex = nil;
280
281   component = [self component: NO];
282   if (component)
283     {
284       myEMail = [[context activeUser] primaryEmail];
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;
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 #warning this could be optimized in a class hierarchy common with the \
386           SOGoObject's acl notification mechanism
387           /* create page name */
388           // TODO: select user's default language?
389           pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
390                                mailTemplateDefaultLanguage,
391                                _pageName];
392           /* construct message content */
393           p = [app pageWithName: pageName inContext: context];
394           [p setNewApt: _newObject];
395           [p setOldApt: _oldObject];
396           [p setHomePageURL: [self homePageURLForPerson: attendee]];
397           [p setViewTZ: [self timeZoneForUser: email]];
398           subject = [p getSubject];
399           text = [p getBody];
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: sender forKey: @"From"];
411           [headerMap setObject: recipient forKey: @"To"];
412           mailDate = [[NSCalendarDate date] rfc822DateString];
413           [headerMap setObject: mailDate forKey: @"date"];
414           [headerMap setObject: subject forKey: @"Subject"];
415           msg = [NGMimeMessage messageWithHeader: headerMap];
416
417           /* multipart body */
418           body = [[NGMimeMultipartBody alloc] initWithPart: msg];
419     
420           /* text part */
421           headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
422           [headerMap setObject: @"text/plain; charset=utf-8"
423                      forKey: @"content-type"];
424           bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
425           [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
426
427           /* attach text part to multipart body */
428           [body addBodyPart: bodyPart];
429     
430           /* calendar part */
431           header = [NSString stringWithFormat: @"text/calendar; method=%@;"
432                              @" charset=utf-8",
433                              [(iCalCalendar *) [_newObject parent] method]];
434           headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
435           [headerMap setObject:header forKey: @"content-type"];
436           bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
437           [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
438
439           /* attach calendar part to multipart body */
440           [body addBodyPart: bodyPart];
441     
442           /* attach multipart body to message */
443           [msg setBody: body];
444           [body release];
445
446           /* send the damn thing */
447           [[SOGoMailer sharedMailer]
448             sendMimePart: msg
449             toRecipients: [NSArray arrayWithObject: email]
450             sender: [organizer rfc822Email]];
451         }
452     }
453 }
454
455 // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
456 // {
457 //   BOOL isOrganizerOrOwner;
458 //   iCalRepeatableEntityObject *component;
459 //   NSString *organizerEmail;
460
461 //   component = [self component: NO];
462 //   organizerEmail = [[component organizer] rfc822Email];
463 //   if (component && [organizerEmail length] > 0)
464 //     isOrganizerOrOwner = [user hasEmail: organizerEmail];
465 //   else
466 //     isOrganizerOrOwner
467 //       = [[container ownerInContext: context] isEqualToString: [user login]];
468
469 //   return isOrganizerOrOwner;
470 // }
471
472 - (iCalPerson *) findParticipantWithUID: (NSString *) uid
473 {
474   SOGoUser *user;
475
476   user = [SOGoUser userWithLogin: uid roles: nil];
477
478   return [self findParticipant: user];
479 }
480
481 - (iCalPerson *) findParticipant: (SOGoUser *) user
482 {
483   iCalPerson *participant, *currentParticipant;
484   iCalEntityObject *component;
485   NSEnumerator *participants;
486
487   participant = nil;
488   component = [self component: NO];
489   if (component)
490     {
491       participants = [[component participants] objectEnumerator];
492       currentParticipant = [participants nextObject];
493       while (currentParticipant && !participant)
494         if ([user hasEmail: [currentParticipant rfc822Email]])
495           participant = currentParticipant;
496         else
497           currentParticipant = [participants nextObject];
498     }
499
500   return participant;
501 }
502
503 - (iCalPerson *) iCalPersonWithUID: (NSString *) uid
504 {
505   iCalPerson *person;
506   LDAPUserManager *um;
507   NSDictionary *contactInfos;
508
509   um = [LDAPUserManager sharedUserManager];
510   contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
511
512   person = [iCalPerson new];
513   [person autorelease];
514   [person setCn: [contactInfos objectForKey: @"cn"]];
515   [person setEmail: [contactInfos objectForKey: @"c_email"]];
516
517   return person;
518 }
519
520 - (NSString *) getUIDForICalPerson: (iCalPerson *) person
521 {
522   LDAPUserManager *um;
523
524   um = [LDAPUserManager sharedUserManager];
525
526   return [um getUIDForEmail: [person rfc822Email]];
527 }
528
529 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
530 {
531   iCalPerson *currentPerson;
532   NSEnumerator *persons;
533   NSMutableArray *uids;
534   NSString *uid;
535   LDAPUserManager *um;
536
537   uids = [NSMutableArray array];
538
539   um = [LDAPUserManager sharedUserManager];
540   persons = [iCalPersons objectEnumerator];
541   currentPerson = [persons nextObject];
542   while (currentPerson)
543     {
544       uid = [um getUIDForEmail: [currentPerson rfc822Email]];
545       if (uid)
546         [uids addObject: uid];
547       currentPerson = [persons nextObject];
548     }
549
550   return uids;
551 }
552
553 - (NSString *) _roleOfOwner: (iCalRepeatableEntityObject *) component
554 {
555   NSString *role;
556   iCalPerson *organizer;
557   SOGoUser *ownerUser;
558
559   if (component)
560     {
561       organizer = [component organizer];
562       if ([[organizer rfc822Email] length] > 0)
563         {
564           ownerUser = [SOGoUser userWithLogin: owner roles: nil];
565           if ([component userIsOrganizer: ownerUser])
566             role = SOGoCalendarRole_Organizer;
567           else if ([component userIsParticipant: ownerUser])
568             role = SOGoCalendarRole_Participant;
569           else
570             role = SOGoRole_None;
571         }
572       else
573         role = SOGoCalendarRole_Organizer;
574     }
575   else
576     role = SOGoCalendarRole_Organizer;
577   
578   return role;
579 }
580
581 - (NSString *) _compiledRoleForOwner: (NSString *) ownerRole
582                              andUser: (NSString *) userRole
583 {
584   NSString *role;
585
586   if ([userRole isEqualToString: SOGoCalendarRole_ComponentModifier]
587       || ([userRole isEqualToString: SOGoCalendarRole_ComponentResponder]
588           && [ownerRole isEqualToString: SOGoCalendarRole_Participant]))
589     role = ownerRole;
590   else
591     role = SOGoRole_None;
592
593   return role;
594 }
595
596 - (NSArray *) aclsForUser: (NSString *) uid
597 {
598   NSMutableArray *roles;
599   NSArray *superAcls;
600   iCalRepeatableEntityObject *component;
601   NSString *accessRole, *ownerRole;
602
603   roles = [NSMutableArray array];
604   superAcls = [super aclsForUser: uid];
605   if ([superAcls count] > 0)
606     [roles addObjectsFromArray: superAcls];
607
608   component = [self component: NO];
609   ownerRole = [self _roleOfOwner: component];
610   if ([owner isEqualToString: uid])
611     [roles addObject: ownerRole];
612   else
613     {
614       if (component)
615         {
616           accessRole = [container roleForComponentsWithAccessClass:
617                                     [component symbolicAccessClass]
618                                   forUser: uid];
619           if ([accessRole length] > 0)
620             {
621               [roles addObject: accessRole];
622               [roles addObject: [self _compiledRoleForOwner: ownerRole
623                                       andUser: accessRole]];
624             }
625         }
626       else if ([roles containsObject: SOGoRole_ObjectCreator])
627         [roles addObject: SOGoCalendarRole_Organizer];
628     }
629
630   return roles;
631 }
632
633 @end