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 NSString *mailTemplateDefaultLanguage = nil;
51 static BOOL sendEMailNotifications = NO;
53 @implementation SOGoCalendarComponent
58 static BOOL didInit = NO;
64 ud = [NSUserDefaults standardUserDefaults];
65 mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
67 if (!mailTemplateDefaultLanguage)
68 mailTemplateDefaultLanguage = @"English";
70 sendEMailNotifications
71 = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
77 if ((self = [super init]))
95 - (NSString *) davContentType
97 return @"text/calendar";
100 - (NSString *) componentTag
102 [self subclassResponsibility: _cmd];
107 - (void) _filterComponent: (iCalEntityObject *) component
109 [component setSummary: @""];
110 [component setComment: @""];
111 [component setUserComment: @""];
112 [component setLocation: @""];
113 [component setCategories: @""];
114 [component setUrl: @""];
115 [component removeAllAttendees];
116 [component removeAllAlarms];
119 - (NSString *) contentAsString
121 iCalCalendar *tmpCalendar;
122 iCalRepeatableEntityObject *tmpComponent;
125 SoSecurityManager *sm;
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])
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];
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])
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];
170 - (NSException *) saveContentString: (NSString *) contentString
171 baseVersion: (unsigned int) baseVersion
175 result = [super saveContentString: contentString
176 baseVersion: baseVersion];
177 if (!result && calContent)
179 [calContent release];
186 - (iCalCalendar *) calendar: (BOOL) create
188 NSString *iCalString, *componentTag;
189 CardGroup *newComponent;
193 iCalString = [super contentAsString];
194 if ([iCalString length] > 0)
195 calendar = [iCalCalendar parseSingleFromSource: iCalString];
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];
216 - (iCalRepeatableEntityObject *) component: (BOOL) create
219 (iCalRepeatableEntityObject *) [[self calendar: create]
220 firstChildWithTag: [self componentTag]];
225 - (NSException *) primarySaveContentString: (NSString *) _iCalString
227 return [super saveContentString: _iCalString];
230 - (NSException *) primaryDelete
232 return [super delete];
235 - (NSException *) deleteWithBaseSequence: (int) a
237 [self subclassResponsibility: _cmd];
242 - (NSException *) delete
244 return [self deleteWithBaseSequence:0];
247 /* EMail Notifications */
248 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
252 NSArray *traversalObjects;
254 /* generate URL from traversal stack */
255 traversalObjects = [context objectTraversalStack];
256 if ([traversalObjects count] > 0)
257 baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
260 baseURL = @"http://localhost/";
261 [self warnWithFormat:@"Unable to create baseURL from context!"];
263 uid = [[LDAPUserManager sharedUserManager]
264 getUIDForEmail: [_person rfc822Email]];
267 ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
271 - (NSException *) changeParticipationStatus: (NSString *) _status
273 iCalRepeatableEntityObject *component;
275 NSString *newContent;
281 component = [self component: NO];
284 myEMail = [[context activeUser] primaryEmail];
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;
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 #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,
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];
401 /* construct message */
402 headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
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.
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];
418 body = [[NGMimeMultipartBody alloc] initWithPart: msg];
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]];
427 /* attach text part to multipart body */
428 [body addBodyPart: bodyPart];
431 header = [NSString stringWithFormat: @"text/calendar; method=%@;"
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]];
439 /* attach calendar part to multipart body */
440 [body addBodyPart: bodyPart];
442 /* attach multipart body to message */
446 /* send the damn thing */
447 [[SOGoMailer sharedMailer]
449 toRecipients: [NSArray arrayWithObject: email]
450 sender: [organizer rfc822Email]];
455 // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
457 // BOOL isOrganizerOrOwner;
458 // iCalRepeatableEntityObject *component;
459 // NSString *organizerEmail;
461 // component = [self component: NO];
462 // organizerEmail = [[component organizer] rfc822Email];
463 // if (component && [organizerEmail length] > 0)
464 // isOrganizerOrOwner = [user hasEmail: organizerEmail];
466 // isOrganizerOrOwner
467 // = [[container ownerInContext: context] isEqualToString: [user login]];
469 // return isOrganizerOrOwner;
472 - (iCalPerson *) findParticipantWithUID: (NSString *) uid
476 user = [SOGoUser userWithLogin: uid roles: nil];
478 return [self findParticipant: user];
481 - (iCalPerson *) findParticipant: (SOGoUser *) user
483 iCalPerson *participant, *currentParticipant;
484 iCalEntityObject *component;
485 NSEnumerator *participants;
488 component = [self component: NO];
491 participants = [[component participants] objectEnumerator];
492 currentParticipant = [participants nextObject];
493 while (currentParticipant && !participant)
494 if ([user hasEmail: [currentParticipant rfc822Email]])
495 participant = currentParticipant;
497 currentParticipant = [participants nextObject];
503 - (iCalPerson *) iCalPersonWithUID: (NSString *) uid
507 NSDictionary *contactInfos;
509 um = [LDAPUserManager sharedUserManager];
510 contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
512 person = [iCalPerson new];
513 [person autorelease];
514 [person setCn: [contactInfos objectForKey: @"cn"]];
515 [person setEmail: [contactInfos objectForKey: @"c_email"]];
520 - (NSString *) getUIDForICalPerson: (iCalPerson *) person
524 um = [LDAPUserManager sharedUserManager];
526 return [um getUIDForEmail: [person rfc822Email]];
529 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
531 iCalPerson *currentPerson;
532 NSEnumerator *persons;
533 NSMutableArray *uids;
537 uids = [NSMutableArray array];
539 um = [LDAPUserManager sharedUserManager];
540 persons = [iCalPersons objectEnumerator];
541 currentPerson = [persons nextObject];
542 while (currentPerson)
544 uid = [um getUIDForEmail: [currentPerson rfc822Email]];
546 [uids addObject: uid];
547 currentPerson = [persons nextObject];
553 - (NSString *) _roleOfOwner: (iCalRepeatableEntityObject *) component
556 iCalPerson *organizer;
561 organizer = [component organizer];
562 if ([[organizer rfc822Email] length] > 0)
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;
570 role = SOGoRole_None;
573 role = SOGoCalendarRole_Organizer;
576 role = SOGoCalendarRole_Organizer;
581 - (NSString *) _compiledRoleForOwner: (NSString *) ownerRole
582 andUser: (NSString *) userRole
586 if ([userRole isEqualToString: SOGoCalendarRole_ComponentModifier]
587 || ([userRole isEqualToString: SOGoCalendarRole_ComponentResponder]
588 && [ownerRole isEqualToString: SOGoCalendarRole_Participant]))
591 role = SOGoRole_None;
596 - (NSArray *) aclsForUser: (NSString *) uid
598 NSMutableArray *roles;
600 iCalRepeatableEntityObject *component;
601 NSString *accessRole, *ownerRole;
603 roles = [NSMutableArray array];
604 superAcls = [super aclsForUser: uid];
605 if ([superAcls count] > 0)
606 [roles addObjectsFromArray: superAcls];
608 component = [self component: NO];
609 ownerRole = [self _roleOfOwner: component];
610 if ([owner isEqualToString: uid])
611 [roles addObject: ownerRole];
616 accessRole = [container roleForComponentsWithAccessClass:
617 [component symbolicAccessClass]
619 if ([accessRole length] > 0)
621 [roles addObject: accessRole];
622 [roles addObject: [self _compiledRoleForOwner: ownerRole
623 andUser: accessRole]];
626 else if ([roles containsObject: SOGoRole_ObjectCreator])
627 [roles addObject: SOGoCalendarRole_Organizer];