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/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>
47 #import "SOGoAptMailNotification.h"
48 #import "iCalEntityObject+SOGo.h"
49 #import "SOGoCalendarComponent.h"
51 static BOOL sendEMailNotifications = NO;
53 @implementation SOGoCalendarComponent
58 static BOOL didInit = NO;
64 ud = [NSUserDefaults standardUserDefaults];
65 sendEMailNotifications
66 = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
72 if ((self = [super init]))
90 - (NSString *) davContentType
92 return @"text/calendar";
95 - (NSString *) componentTag
97 [self subclassResponsibility: _cmd];
102 - (void) _filterComponent: (iCalEntityObject *) component
104 [component setSummary: @""];
105 [component setComment: @""];
106 [component setUserComment: @""];
107 [component setLocation: @""];
108 [component setCategories: @""];
109 [component setUrl: @""];
110 [component removeAllAttendees];
111 [component removeAllAlarms];
114 - (NSString *) contentAsString
116 iCalCalendar *tmpCalendar;
117 iCalRepeatableEntityObject *tmpComponent;
120 SoSecurityManager *sm;
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])
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];
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])
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];
165 - (void) setContentString: (NSString *) newContent
167 [super setContentString: newContent];
168 ASSIGN (calendar, nil);
169 ASSIGN (calContent, nil);
172 // - (NSException *) saveContentString: (NSString *) contentString
173 // baseVersion: (unsigned int) baseVersion
175 // NSException *result;
177 // result = [super saveContentString: contentString
178 // baseVersion: baseVersion];
179 // if (!result && calContent)
181 // [calContent release];
188 - (iCalCalendar *) calendar: (BOOL) create
190 NSString *iCalString, *componentTag;
191 CardGroup *newComponent;
195 iCalString = [super contentAsString];
196 if ([iCalString length] > 0)
197 calendar = [iCalCalendar parseSingleFromSource: iCalString];
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];
218 - (iCalRepeatableEntityObject *) component: (BOOL) create
221 (iCalRepeatableEntityObject *) [[self calendar: create]
222 firstChildWithTag: [self componentTag]];
227 - (NSException *) primarySaveContentString: (NSString *) _iCalString
229 return [super saveContentString: _iCalString];
232 - (NSException *) primaryDelete
234 return [super delete];
237 - (NSException *) deleteWithBaseSequence: (int) a
239 [self subclassResponsibility: _cmd];
244 - (NSException *) delete
246 return [self deleteWithBaseSequence: 0];
249 /* EMail Notifications */
250 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
254 NSArray *traversalObjects;
256 /* generate URL from traversal stack */
257 traversalObjects = [context objectTraversalStack];
258 if ([traversalObjects count] > 0)
259 baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
262 baseURL = @"http://localhost/";
263 [self warnWithFormat:@"Unable to create baseURL from context!"];
265 uid = [[LDAPUserManager sharedUserManager]
266 getUIDForEmail: [_person rfc822Email]];
269 ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
273 - (NSException *) changeParticipationStatus: (NSString *) _status
275 iCalRepeatableEntityObject *component;
277 NSString *newContent;
282 component = [self component: NO];
285 person = [self findParticipantWithUID: owner];
288 // TODO: send iMIP reply mails?
289 [person setPartStat: _status];
290 newContent = [[component parent] versitString];
293 ex = [self saveContentString: newContent];
295 // TODO: why is the exception wrapped?
297 ex = [NSException exceptionWithHTTPStatus: 500
298 reason: [ex reason]];
302 = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
303 reason: @"Could not generate iCalendar data ..."];
306 ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
307 reason: @"user does not participate in this "
308 @"calendar component"];
311 ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
312 reason: @"unable to parse component record"];
317 - (BOOL) sendEMailNotifications
319 return sendEMailNotifications;
322 - (NSTimeZone *) timeZoneForUser: (NSString *) email
326 uid = [[LDAPUserManager sharedUserManager] getUIDForEmail: email];
328 return [[SOGoUser userWithLogin: uid roles: nil] timeZone];
331 - (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
332 forOldObject: (iCalRepeatableEntityObject *) _oldObject
333 andNewObject: (iCalRepeatableEntityObject *) _newObject
334 toAttendees: (NSArray *) _attendees
337 iCalPerson *organizer;
338 NSString *cn, *email, *sender, *iCalString;
341 iCalPerson *attendee;
342 NSString *recipient, *language;
343 SOGoAptMailNotification *p;
344 NSString *mailDate, *subject, *text, *header;
345 NGMutableHashMap *headerMap;
347 NGMimeBodyPart *bodyPart;
348 NGMimeMultipartBody *body;
350 if ([_attendees count])
354 organizer = [_newObject organizer];
355 cn = [organizer cnWithoutQuotes];
357 sender = [NSString stringWithFormat:@"%@ <%@>",
359 [organizer rfc822Email]];
361 sender = [organizer rfc822Email];
363 /* generate iCalString once */
364 iCalString = [[_newObject parent] versitString];
366 /* get WOApplication instance */
367 app = [WOApplication application];
369 /* generate dynamic message content */
371 count = [_attendees count];
372 for (i = 0; i < count; i++)
374 attendee = [_attendees objectAtIndex:i];
376 /* construct recipient */
378 email = [attendee rfc822Email];
380 recipient = [NSString stringWithFormat: @"%@ <%@>",
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%@%@",
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];
402 /* construct message */
403 headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
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.
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];
419 body = [[NGMimeMultipartBody alloc] initWithPart: msg];
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]];
428 /* attach text part to multipart body */
429 [body addBodyPart: bodyPart];
432 header = [NSString stringWithFormat: @"text/calendar; method=%@;"
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]];
440 /* attach calendar part to multipart body */
441 [body addBodyPart: bodyPart];
443 /* attach multipart body to message */
447 /* send the damn thing */
448 [[SOGoMailer sharedMailer]
450 toRecipients: [NSArray arrayWithObject: email]
451 sender: [organizer rfc822Email]];
456 - (void) sendResponseToOrganizer
458 #warning THIS IS A STUB
461 // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
463 // BOOL isOrganizerOrOwner;
464 // iCalRepeatableEntityObject *component;
465 // NSString *organizerEmail;
467 // component = [self component: NO];
468 // organizerEmail = [[component organizer] rfc822Email];
469 // if (component && [organizerEmail length] > 0)
470 // isOrganizerOrOwner = [user hasEmail: organizerEmail];
472 // isOrganizerOrOwner
473 // = [[container ownerInContext: context] isEqualToString: [user login]];
475 // return isOrganizerOrOwner;
478 - (iCalPerson *) findParticipantWithUID: (NSString *) uid
480 iCalEntityObject *component;
483 user = [SOGoUser userWithLogin: uid roles: nil];
484 component = [self component: NO];
486 return [component findParticipant: user];
489 - (iCalPerson *) iCalPersonWithUID: (NSString *) uid
493 NSDictionary *contactInfos;
495 um = [LDAPUserManager sharedUserManager];
496 contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
498 person = [iCalPerson new];
499 [person autorelease];
500 [person setCn: [contactInfos objectForKey: @"cn"]];
501 [person setEmail: [contactInfos objectForKey: @"c_email"]];
506 - (NSString *) getUIDForICalPerson: (iCalPerson *) person
510 um = [LDAPUserManager sharedUserManager];
512 return [um getUIDForEmail: [person rfc822Email]];
515 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
517 iCalPerson *currentPerson;
518 NSEnumerator *persons;
519 NSMutableArray *uids;
523 uids = [NSMutableArray array];
525 um = [LDAPUserManager sharedUserManager];
526 persons = [iCalPersons objectEnumerator];
527 currentPerson = [persons nextObject];
528 while (currentPerson)
530 uid = [um getUIDForEmail: [currentPerson rfc822Email]];
532 [uids addObject: uid];
533 currentPerson = [persons nextObject];
539 - (NSString *) _roleOfOwner: (iCalRepeatableEntityObject *) component
542 iCalPerson *organizer;
547 organizer = [component organizer];
548 if ([[organizer rfc822Email] length] > 0)
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;
556 role = SOGoRole_None;
559 role = SOGoCalendarRole_Organizer;
562 role = SOGoCalendarRole_Organizer;
567 - (NSString *) _compiledRoleForOwner: (NSString *) ownerRole
568 andUser: (NSString *) userRole
572 if ([userRole isEqualToString: SOGoCalendarRole_ComponentModifier]
573 || ([userRole isEqualToString: SOGoCalendarRole_ComponentResponder]
574 && [ownerRole isEqualToString: SOGoCalendarRole_Participant]))
577 role = SOGoRole_None;
582 - (NSArray *) aclsForUser: (NSString *) uid
584 NSMutableArray *roles;
586 iCalRepeatableEntityObject *component;
587 NSString *accessRole, *ownerRole;
589 roles = [NSMutableArray array];
590 superAcls = [super aclsForUser: uid];
591 if ([superAcls count] > 0)
592 [roles addObjectsFromArray: superAcls];
594 component = [self component: NO];
595 ownerRole = [self _roleOfOwner: component];
596 if ([owner isEqualToString: uid])
597 [roles addObject: ownerRole];
602 accessRole = [container roleForComponentsWithAccessClass:
603 [component symbolicAccessClass]
605 if ([accessRole length] > 0)
607 [roles addObject: accessRole];
608 [roles addObject: [self _compiledRoleForOwner: ownerRole
609 andUser: accessRole]];
612 else if ([roles containsObject: SOGoRole_ObjectCreator])
613 [roles addObject: SOGoCalendarRole_Organizer];