1 /* SOGoCalendarComponent.m - this file is part of SOGo
3 * Copyright (C) 2006 Inverse groupe conseil
5 * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
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)
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.
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.
23 #import <Foundation/NSString.h>
24 #import <Foundation/NSUserDefaults.h>
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>
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>
46 #import "SOGoAptMailNotification.h"
47 #import "iCalEntityObject+SOGo.h"
48 #import "SOGoCalendarComponent.h"
50 static BOOL sendEMailNotifications = NO;
52 @implementation SOGoCalendarComponent
57 static BOOL didInit = NO;
63 ud = [NSUserDefaults standardUserDefaults];
64 sendEMailNotifications
65 = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
71 if ((self = [super init]))
89 - (NSString *) davContentType
91 return @"text/calendar";
94 - (NSString *) componentTag
96 [self subclassResponsibility: _cmd];
101 - (void) _filterComponent: (iCalEntityObject *) component
103 [component setSummary: @""];
104 [component setComment: @""];
105 [component setUserComment: @""];
106 [component setLocation: @""];
107 [component setCategories: @""];
108 [component setUrl: @""];
109 [component removeAllAttendees];
110 [component removeAllAlarms];
113 - (NSString *) contentAsString
115 iCalCalendar *tmpCalendar;
116 iCalRepeatableEntityObject *tmpComponent;
119 SoSecurityManager *sm;
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])
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];
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])
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];
164 - (NSException *) saveContentString: (NSString *) contentString
165 baseVersion: (unsigned int) baseVersion
169 result = [super saveContentString: contentString
170 baseVersion: baseVersion];
171 if (!result && calContent)
173 [calContent release];
180 - (iCalCalendar *) calendar: (BOOL) create
182 NSString *iCalString, *componentTag;
183 CardGroup *newComponent;
187 iCalString = [super contentAsString];
188 if ([iCalString length] > 0)
189 calendar = [iCalCalendar parseSingleFromSource: iCalString];
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];
210 - (iCalRepeatableEntityObject *) component: (BOOL) create
213 (iCalRepeatableEntityObject *) [[self calendar: create]
214 firstChildWithTag: [self componentTag]];
219 - (NSException *) primarySaveContentString: (NSString *) _iCalString
221 return [super saveContentString: _iCalString];
224 - (NSException *) primaryDelete
226 return [super delete];
229 - (NSException *) deleteWithBaseSequence: (int) a
231 [self subclassResponsibility: _cmd];
236 - (NSException *) delete
238 return [self deleteWithBaseSequence: 0];
241 /* EMail Notifications */
242 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
246 NSArray *traversalObjects;
248 /* generate URL from traversal stack */
249 traversalObjects = [context objectTraversalStack];
250 if ([traversalObjects count] > 0)
251 baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
254 baseURL = @"http://localhost/";
255 [self warnWithFormat:@"Unable to create baseURL from context!"];
257 uid = [[LDAPUserManager sharedUserManager]
258 getUIDForEmail: [_person rfc822Email]];
261 ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
265 - (NSException *) changeParticipationStatus: (NSString *) _status
267 iCalRepeatableEntityObject *component;
269 NSString *newContent;
274 component = [self component: NO];
277 person = [self findParticipantWithUID: owner];
280 // TODO: send iMIP reply mails?
281 [person setPartStat: _status];
282 newContent = [[component parent] versitString];
285 ex = [self saveContentString: newContent];
287 // TODO: why is the exception wrapped?
289 ex = [NSException exceptionWithHTTPStatus: 500
290 reason: [ex reason]];
294 = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
295 reason: @"Could not generate iCalendar data ..."];
298 ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
299 reason: @"user does not participate in this "
300 @"calendar component"];
303 ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
304 reason: @"unable to parse component record"];
309 - (BOOL) sendEMailNotifications
311 return sendEMailNotifications;
314 - (NSTimeZone *) timeZoneForUser: (NSString *) email
318 uid = [[LDAPUserManager sharedUserManager] getUIDForEmail: email];
320 return [[SOGoUser userWithLogin: uid roles: nil] timeZone];
323 - (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
324 forOldObject: (iCalRepeatableEntityObject *) _oldObject
325 andNewObject: (iCalRepeatableEntityObject *) _newObject
326 toAttendees: (NSArray *) _attendees
329 iCalPerson *organizer;
330 NSString *cn, *email, *sender, *iCalString;
333 iCalPerson *attendee;
334 NSString *recipient, *language;
335 SOGoAptMailNotification *p;
336 NSString *mailDate, *subject, *text, *header;
337 NGMutableHashMap *headerMap;
339 NGMimeBodyPart *bodyPart;
340 NGMimeMultipartBody *body;
342 if ([_attendees count])
346 organizer = [_newObject organizer];
347 cn = [organizer cnWithoutQuotes];
349 sender = [NSString stringWithFormat:@"%@ <%@>",
351 [organizer rfc822Email]];
353 sender = [organizer rfc822Email];
355 /* generate iCalString once */
356 iCalString = [[_newObject parent] versitString];
358 /* get WOApplication instance */
359 app = [WOApplication application];
361 /* generate dynamic message content */
363 count = [_attendees count];
364 for (i = 0; i < count; i++)
366 attendee = [_attendees objectAtIndex:i];
368 /* construct recipient */
370 email = [attendee rfc822Email];
372 recipient = [NSString stringWithFormat: @"%@ <%@>",
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%@%@",
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];
394 /* construct message */
395 headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
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.
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];
411 body = [[NGMimeMultipartBody alloc] initWithPart: msg];
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]];
420 /* attach text part to multipart body */
421 [body addBodyPart: bodyPart];
424 header = [NSString stringWithFormat: @"text/calendar; method=%@;"
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]];
432 /* attach calendar part to multipart body */
433 [body addBodyPart: bodyPart];
435 /* attach multipart body to message */
439 /* send the damn thing */
440 [[SOGoMailer sharedMailer]
442 toRecipients: [NSArray arrayWithObject: email]
443 sender: [organizer rfc822Email]];
448 // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
450 // BOOL isOrganizerOrOwner;
451 // iCalRepeatableEntityObject *component;
452 // NSString *organizerEmail;
454 // component = [self component: NO];
455 // organizerEmail = [[component organizer] rfc822Email];
456 // if (component && [organizerEmail length] > 0)
457 // isOrganizerOrOwner = [user hasEmail: organizerEmail];
459 // isOrganizerOrOwner
460 // = [[container ownerInContext: context] isEqualToString: [user login]];
462 // return isOrganizerOrOwner;
465 - (iCalPerson *) findParticipantWithUID: (NSString *) uid
469 user = [SOGoUser userWithLogin: uid roles: nil];
471 return [self findParticipant: user];
474 - (iCalPerson *) findParticipant: (SOGoUser *) user
476 iCalPerson *participant, *currentParticipant;
477 iCalEntityObject *component;
478 NSEnumerator *participants;
481 component = [self component: NO];
484 participants = [[component participants] objectEnumerator];
485 currentParticipant = [participants nextObject];
486 while (currentParticipant && !participant)
487 if ([user hasEmail: [currentParticipant rfc822Email]])
488 participant = currentParticipant;
490 currentParticipant = [participants nextObject];
496 - (iCalPerson *) iCalPersonWithUID: (NSString *) uid
500 NSDictionary *contactInfos;
502 um = [LDAPUserManager sharedUserManager];
503 contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
505 person = [iCalPerson new];
506 [person autorelease];
507 [person setCn: [contactInfos objectForKey: @"cn"]];
508 [person setEmail: [contactInfos objectForKey: @"c_email"]];
513 - (NSString *) getUIDForICalPerson: (iCalPerson *) person
517 um = [LDAPUserManager sharedUserManager];
519 return [um getUIDForEmail: [person rfc822Email]];
522 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
524 iCalPerson *currentPerson;
525 NSEnumerator *persons;
526 NSMutableArray *uids;
530 uids = [NSMutableArray array];
532 um = [LDAPUserManager sharedUserManager];
533 persons = [iCalPersons objectEnumerator];
534 currentPerson = [persons nextObject];
535 while (currentPerson)
537 uid = [um getUIDForEmail: [currentPerson rfc822Email]];
539 [uids addObject: uid];
540 currentPerson = [persons nextObject];
546 - (NSString *) _roleOfOwner: (iCalRepeatableEntityObject *) component
549 iCalPerson *organizer;
554 organizer = [component organizer];
555 if ([[organizer rfc822Email] length] > 0)
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;
563 role = SOGoRole_None;
566 role = SOGoCalendarRole_Organizer;
569 role = SOGoCalendarRole_Organizer;
574 - (NSString *) _compiledRoleForOwner: (NSString *) ownerRole
575 andUser: (NSString *) userRole
579 if ([userRole isEqualToString: SOGoCalendarRole_ComponentModifier]
580 || ([userRole isEqualToString: SOGoCalendarRole_ComponentResponder]
581 && [ownerRole isEqualToString: SOGoCalendarRole_Participant]))
584 role = SOGoRole_None;
589 - (NSArray *) aclsForUser: (NSString *) uid
591 NSMutableArray *roles;
593 iCalRepeatableEntityObject *component;
594 NSString *accessRole, *ownerRole;
596 roles = [NSMutableArray array];
597 superAcls = [super aclsForUser: uid];
598 if ([superAcls count] > 0)
599 [roles addObjectsFromArray: superAcls];
601 component = [self component: NO];
602 ownerRole = [self _roleOfOwner: component];
603 if ([owner isEqualToString: uid])
604 [roles addObject: ownerRole];
609 accessRole = [container roleForComponentsWithAccessClass:
610 [component symbolicAccessClass]
612 if ([accessRole length] > 0)
614 [roles addObject: accessRole];
615 [roles addObject: [self _compiledRoleForOwner: ownerRole
616 andUser: accessRole]];
619 else if ([roles containsObject: SOGoRole_ObjectCreator])
620 [roles addObject: SOGoCalendarRole_Organizer];