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>
25 #import <NGCards/iCalCalendar.h>
26 #import <NGCards/iCalPerson.h>
27 #import <NGCards/iCalRepeatableEntityObject.h>
28 #import <NGMime/NGMime.h>
29 #import <NGMail/NGMail.h>
30 #import <NGMail/NGSendMail.h>
32 #import <SoObjects/SOGo/LDAPUserManager.h>
33 #import <SoObjects/SOGo/SOGoPermissions.h>
34 #import <SoObjects/SOGo/SOGoUser.h>
35 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
39 #import "SOGoAptMailNotification.h"
40 #import "iCalEntityObject+SOGo.h"
41 #import "SOGoCalendarComponent.h"
43 static NSString *mailTemplateDefaultLanguage = nil;
44 static BOOL sendEMailNotifications = NO;
46 @implementation SOGoCalendarComponent
51 static BOOL didInit = NO;
57 ud = [NSUserDefaults standardUserDefaults];
58 mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
60 if (!mailTemplateDefaultLanguage)
61 mailTemplateDefaultLanguage = @"English";
63 sendEMailNotifications
64 = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
70 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
116 iCalCalendar *tmpCalendar;
117 iCalRepeatableEntityObject *tmpComponent;
122 uid = [[context activeUser] login];
123 roles = [self aclsForUser: uid];
124 if ([roles containsObject: SOGoCalendarRole_Organizer]
125 || [roles containsObject: SOGoCalendarRole_Participant]
126 || [roles containsObject: SOGoCalendarRole_ComponentViewer])
128 calContent = content;
131 else if ([roles containsObject: SOGoCalendarRole_ComponentDAndTViewer])
133 tmpCalendar = [[self calendar: NO] copy];
134 tmpComponent = (iCalRepeatableEntityObject *)
135 [tmpCalendar firstChildWithTag: [self componentTag]];
136 [self _filterComponent: tmpComponent];
137 calContent = [tmpCalendar versitString];
138 [tmpCalendar release];
149 - (NSException *) saveContentString: (NSString *) contentString
150 baseVersion: (unsigned int) baseVersion
154 result = [super saveContentString: contentString
155 baseVersion: baseVersion];
156 if (!result && calContent)
158 [calContent release];
165 - (iCalCalendar *) calendar: (BOOL) create
167 NSString *iCalString, *componentTag;
168 CardGroup *newComponent;
172 iCalString = [super contentAsString];
173 if ([iCalString length] > 0)
174 calendar = [iCalCalendar parseSingleFromSource: iCalString];
179 calendar = [iCalCalendar groupWithTag: @"vcalendar"];
180 [calendar setVersion: @"2.0"];
181 [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
182 componentTag = [[self componentTag] uppercaseString];
183 newComponent = [[calendar classForTag: componentTag]
184 groupWithTag: componentTag];
185 [calendar addChild: newComponent];
196 - (iCalRepeatableEntityObject *) component: (BOOL) create
199 (iCalRepeatableEntityObject *) [[self calendar: create]
200 firstChildWithTag: [self componentTag]];
210 - (NSException *) primarySaveContentString: (NSString *) _iCalString
212 return [super saveContentString: _iCalString];
215 - (NSException *) primaryDelete
217 return [super delete];
220 - (NSException *) deleteWithBaseSequence: (int) a
222 [self subclassResponsibility: _cmd];
227 - (NSException *) delete
229 return [self deleteWithBaseSequence:0];
232 /* EMail Notifications */
233 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
237 NSArray *traversalObjects;
239 /* generate URL from traversal stack */
240 traversalObjects = [context objectTraversalStack];
241 if ([traversalObjects count] > 0)
242 baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
245 baseURL = @"http://localhost/";
246 [self warnWithFormat:@"Unable to create baseURL from context!"];
248 uid = [[LDAPUserManager sharedUserManager]
249 getUIDForEmail: [_person rfc822Email]];
252 ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
256 - (NSException *) changeParticipationStatus: (NSString *) _status
258 iCalRepeatableEntityObject *component;
260 NSString *newContent;
266 component = [self component: NO];
269 myEMail = [[context activeUser] primaryEmail];
270 person = [self findParticipantWithUID: owner];
273 // TODO: send iMIP reply mails?
274 [person setPartStat: _status];
275 newContent = [[component parent] versitString];
278 ex = [self saveContentString: newContent];
280 // TODO: why is the exception wrapped?
282 ex = [NSException exceptionWithHTTPStatus: 500
283 reason: [ex reason]];
287 = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
288 reason: @"Could not generate iCalendar data ..."];
291 ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
292 reason: @"user does not participate in this "
293 @"calendar component"];
296 ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
297 reason: @"unable to parse component record"];
302 - (BOOL) sendEMailNotifications
304 return sendEMailNotifications;
307 - (NSTimeZone *) timeZoneForUser: (NSString *) email
311 uid = [[LDAPUserManager sharedUserManager] getUIDForEmail: email];
313 return [[SOGoUser userWithLogin: uid roles: nil] timeZone];
316 - (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
317 forOldObject: (iCalRepeatableEntityObject *) _oldObject
318 andNewObject: (iCalRepeatableEntityObject *) _newObject
319 toAttendees: (NSArray *) _attendees
322 iCalPerson *organizer;
323 NSString *cn, *email, *sender, *iCalString;
324 NGSendMail *sendmail;
327 iCalPerson *attendee;
329 SOGoAptMailNotification *p;
330 NSString *subject, *text, *header;
331 NGMutableHashMap *headerMap;
333 NGMimeBodyPart *bodyPart;
334 NGMimeMultipartBody *body;
336 if ([_attendees count])
340 organizer = [_newObject organizer];
341 cn = [organizer cnWithoutQuotes];
343 sender = [NSString stringWithFormat:@"%@ <%@>",
345 [organizer rfc822Email]];
347 sender = [organizer rfc822Email];
349 /* generate iCalString once */
350 iCalString = [[_newObject parent] versitString];
352 /* get sendmail object */
353 sendmail = [NGSendMail sharedSendMail];
355 /* get WOApplication instance */
356 app = [WOApplication application];
358 /* generate dynamic message content */
360 count = [_attendees count];
361 for (i = 0; i < count; i++)
363 attendee = [_attendees objectAtIndex:i];
365 /* construct recipient */
367 email = [attendee rfc822Email];
369 recipient = [NSString stringWithFormat: @"%@ <%@>",
374 #warning this could be optimized in a class hierarchy common with the \
375 SOGoObject's acl notification mechanism
376 /* create page name */
377 // TODO: select user's default language?
378 pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
379 mailTemplateDefaultLanguage,
381 /* construct message content */
382 p = [app pageWithName: pageName inContext: context];
383 [p setNewApt: _newObject];
384 [p setOldApt: _oldObject];
385 [p setHomePageURL: [self homePageURLForPerson: attendee]];
386 [p setViewTZ: [self timeZoneForUser: email]];
387 subject = [p getSubject];
390 /* construct message */
391 headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
393 /* NOTE: multipart/alternative seems like the correct choice but
394 * unfortunately Thunderbird doesn't offer the rich content alternative
395 * at all. Mail.app shows the rich content alternative _only_
396 * so we'll stick with multipart/mixed for the time being.
398 [headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
399 [headerMap setObject: sender forKey: @"From"];
400 [headerMap setObject: recipient forKey: @"To"];
401 [headerMap setObject: [NSCalendarDate date] forKey: @"date"];
402 [headerMap setObject: subject forKey: @"Subject"];
403 msg = [NGMimeMessage messageWithHeader: headerMap];
406 body = [[NGMimeMultipartBody alloc] initWithPart: msg];
409 headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
410 [headerMap setObject: @"text/plain; charset=utf-8"
411 forKey: @"content-type"];
412 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
413 [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
415 /* attach text part to multipart body */
416 [body addBodyPart: bodyPart];
419 header = [NSString stringWithFormat: @"text/calendar; method=%@;"
421 [(iCalCalendar *) [_newObject parent] method]];
422 headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
423 [headerMap setObject:header forKey: @"content-type"];
424 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
425 [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
427 /* attach calendar part to multipart body */
428 [body addBodyPart: bodyPart];
430 /* attach multipart body to message */
434 /* send the damn thing */
435 [sendmail sendMimePart: msg
436 toRecipients: [NSArray arrayWithObject: email]
437 sender: [organizer rfc822Email]];
442 // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
444 // BOOL isOrganizerOrOwner;
445 // iCalRepeatableEntityObject *component;
446 // NSString *organizerEmail;
448 // component = [self component: NO];
449 // organizerEmail = [[component organizer] rfc822Email];
450 // if (component && [organizerEmail length] > 0)
451 // isOrganizerOrOwner = [user hasEmail: organizerEmail];
453 // isOrganizerOrOwner
454 // = [[container ownerInContext: context] isEqualToString: [user login]];
456 // return isOrganizerOrOwner;
459 - (iCalPerson *) findParticipantWithUID: (NSString *) uid
463 user = [SOGoUser userWithLogin: uid roles: nil];
465 return [self findParticipant: user];
468 - (iCalPerson *) findParticipant: (SOGoUser *) user
470 iCalPerson *participant, *currentParticipant;
471 iCalEntityObject *component;
472 NSEnumerator *participants;
475 component = [self component: NO];
478 participants = [[component participants] objectEnumerator];
479 currentParticipant = [participants nextObject];
480 while (currentParticipant && !participant)
481 if ([user hasEmail: [currentParticipant rfc822Email]])
482 participant = currentParticipant;
484 currentParticipant = [participants nextObject];
490 - (iCalPerson *) iCalPersonWithUID: (NSString *) uid
494 NSDictionary *contactInfos;
496 um = [LDAPUserManager sharedUserManager];
497 contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
499 person = [iCalPerson new];
500 [person autorelease];
501 [person setCn: [contactInfos objectForKey: @"cn"]];
502 [person setEmail: [contactInfos objectForKey: @"c_email"]];
507 - (NSString *) getUIDForICalPerson: (iCalPerson *) person
511 um = [LDAPUserManager sharedUserManager];
513 return [um getUIDForEmail: [person rfc822Email]];
516 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
518 iCalPerson *currentPerson;
519 NSEnumerator *persons;
520 NSMutableArray *uids;
524 uids = [NSMutableArray array];
526 um = [LDAPUserManager sharedUserManager];
527 persons = [iCalPersons objectEnumerator];
528 currentPerson = [persons nextObject];
529 while (currentPerson)
531 uid = [um getUIDForEmail: [currentPerson rfc822Email]];
533 [uids addObject: uid];
534 currentPerson = [persons nextObject];
540 - (NSString *) _roleOfOwner: (iCalRepeatableEntityObject *) component
543 iCalPerson *organizer;
548 organizer = [component organizer];
549 if ([[organizer rfc822Email] length] > 0)
551 ownerUser = [SOGoUser userWithLogin: owner roles: nil];
552 if ([component userIsOrganizer: ownerUser])
553 role = SOGoCalendarRole_Organizer;
554 else if ([component userIsParticipant: ownerUser])
555 role = SOGoCalendarRole_Participant;
557 role = SOGoRole_None;
560 role = SOGoCalendarRole_Organizer;
563 role = SOGoCalendarRole_Organizer;
568 - (NSString *) _compiledRoleForOwner: (NSString *) ownerRole
569 andUser: (NSString *) userRole
573 if ([userRole isEqualToString: SOGoCalendarRole_ComponentModifier]
574 || ([userRole isEqualToString: SOGoCalendarRole_ComponentResponder]
575 && [ownerRole isEqualToString: SOGoCalendarRole_Participant]))
578 role = SOGoRole_None;
583 - (NSArray *) aclsForUser: (NSString *) uid
585 NSMutableArray *roles;
587 iCalRepeatableEntityObject *component;
588 NSString *accessRole, *ownerRole;
590 roles = [NSMutableArray array];
591 superAcls = [super aclsForUser: uid];
592 if ([superAcls count] > 0)
593 [roles addObjectsFromArray: superAcls];
595 component = [self component: NO];
596 ownerRole = [self _roleOfOwner: component];
597 if ([owner isEqualToString: uid])
598 [roles addObject: ownerRole];
603 accessRole = [container roleForComponentsWithAccessClass:
604 [component symbolicAccessClass]
606 if ([accessRole length] > 0)
608 [roles addObject: accessRole];
609 [roles addObject: [self _compiledRoleForOwner: ownerRole
610 andUser: accessRole]];
613 else if ([roles containsObject: SOGoRole_ObjectCreator])
614 [roles addObject: SOGoCalendarRole_Organizer];
617 NSLog (@"all roles: %@" , roles);