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]))
88 - (NSString *) davContentType
90 return @"text/calendar";
93 - (NSString *) componentTag
95 [self subclassResponsibility: _cmd];
100 - (void) _filterComponent: (iCalEntityObject *) component
102 [component setSummary: @""];
103 [component setComment: @""];
104 [component setUserComment: @""];
105 [component setLocation: @""];
106 [component setCategories: @""];
107 [component setUrl: @""];
108 [component removeAllAttendees];
109 [component removeAllAlarms];
112 - (NSString *) contentAsString
115 iCalCalendar *tmpCalendar;
116 iCalRepeatableEntityObject *tmpComponent;
121 uid = [[context activeUser] login];
122 roles = [self aclsForUser: uid];
123 if ([roles containsObject: SOGoCalendarRole_Organizer]
124 || [roles containsObject: SOGoCalendarRole_Participant]
125 || [roles containsObject: SOGoCalendarRole_ComponentViewer])
127 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];
148 - (NSException *) saveContentString: (NSString *) contentString
149 baseVersion: (unsigned int) baseVersion
153 result = [super saveContentString: contentString
154 baseVersion: baseVersion];
155 if (!result && calContent)
157 [calContent release];
164 - (iCalCalendar *) calendar: (BOOL) create
166 NSString *iCalString, *componentTag;
167 CardGroup *newComponent;
171 iCalString = [super contentAsString];
172 if ([iCalString length] > 0)
173 calendar = [iCalCalendar parseSingleFromSource: iCalString];
178 calendar = [iCalCalendar groupWithTag: @"vcalendar"];
179 [calendar setVersion: @"2.0"];
180 [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
181 componentTag = [[self componentTag] uppercaseString];
182 newComponent = [[calendar classForTag: componentTag]
183 groupWithTag: componentTag];
184 [calendar addChild: newComponent];
194 - (iCalRepeatableEntityObject *) component: (BOOL) create
197 (iCalRepeatableEntityObject *) [[self calendar: create]
198 firstChildWithTag: [self componentTag]];
203 - (NSException *) primarySaveContentString: (NSString *) _iCalString
205 return [super saveContentString: _iCalString];
208 - (NSException *) primaryDelete
210 return [super delete];
213 - (NSException *) deleteWithBaseSequence: (int) a
215 [self subclassResponsibility: _cmd];
220 - (NSException *) delete
222 return [self deleteWithBaseSequence:0];
225 /* EMail Notifications */
226 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
230 NSArray *traversalObjects;
232 /* generate URL from traversal stack */
233 traversalObjects = [context objectTraversalStack];
234 if ([traversalObjects count] > 0)
235 baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
238 baseURL = @"http://localhost/";
239 [self warnWithFormat:@"Unable to create baseURL from context!"];
241 uid = [[LDAPUserManager sharedUserManager]
242 getUIDForEmail: [_person rfc822Email]];
245 ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
249 - (NSException *) changeParticipationStatus: (NSString *) _status
251 iCalRepeatableEntityObject *component;
253 NSString *newContent;
259 component = [self component: NO];
262 myEMail = [[context activeUser] primaryEmail];
263 person = [self findParticipantWithUID: owner];
266 // TODO: send iMIP reply mails?
267 [person setPartStat: _status];
268 newContent = [[component parent] versitString];
271 ex = [self saveContentString: newContent];
273 // TODO: why is the exception wrapped?
275 ex = [NSException exceptionWithHTTPStatus: 500
276 reason: [ex reason]];
280 = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
281 reason: @"Could not generate iCalendar data ..."];
284 ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
285 reason: @"user does not participate in this "
286 @"calendar component"];
289 ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
290 reason: @"unable to parse component record"];
295 - (BOOL) sendEMailNotifications
297 return sendEMailNotifications;
300 - (NSTimeZone *) timeZoneForUser: (NSString *) email
304 uid = [[LDAPUserManager sharedUserManager] getUIDForEmail: email];
306 return [[SOGoUser userWithLogin: uid roles: nil] timeZone];
309 - (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
310 forOldObject: (iCalRepeatableEntityObject *) _oldObject
311 andNewObject: (iCalRepeatableEntityObject *) _newObject
312 toAttendees: (NSArray *) _attendees
315 iCalPerson *organizer;
316 NSString *cn, *email, *sender, *iCalString;
317 NGSendMail *sendmail;
320 iCalPerson *attendee;
322 SOGoAptMailNotification *p;
323 NSString *subject, *text, *header;
324 NGMutableHashMap *headerMap;
326 NGMimeBodyPart *bodyPart;
327 NGMimeMultipartBody *body;
329 if ([_attendees count])
333 organizer = [_newObject organizer];
334 cn = [organizer cnWithoutQuotes];
336 sender = [NSString stringWithFormat:@"%@ <%@>",
338 [organizer rfc822Email]];
340 sender = [organizer rfc822Email];
342 /* generate iCalString once */
343 iCalString = [[_newObject parent] versitString];
345 /* get sendmail object */
346 sendmail = [NGSendMail sharedSendMail];
348 /* get WOApplication instance */
349 app = [WOApplication application];
351 /* generate dynamic message content */
353 count = [_attendees count];
354 for (i = 0; i < count; i++)
356 attendee = [_attendees objectAtIndex:i];
358 /* construct recipient */
360 email = [attendee rfc822Email];
362 recipient = [NSString stringWithFormat: @"%@ <%@>",
367 #warning this could be optimized in a class hierarchy common with the \
368 SOGoObject's acl notification mechanism
369 /* create page name */
370 // TODO: select user's default language?
371 pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
372 mailTemplateDefaultLanguage,
374 /* construct message content */
375 p = [app pageWithName: pageName inContext: context];
376 [p setNewApt: _newObject];
377 [p setOldApt: _oldObject];
378 [p setHomePageURL: [self homePageURLForPerson: attendee]];
379 [p setViewTZ: [self timeZoneForUser: email]];
380 subject = [p getSubject];
383 /* construct message */
384 headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
386 /* NOTE: multipart/alternative seems like the correct choice but
387 * unfortunately Thunderbird doesn't offer the rich content alternative
388 * at all. Mail.app shows the rich content alternative _only_
389 * so we'll stick with multipart/mixed for the time being.
391 [headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
392 [headerMap setObject: sender forKey: @"From"];
393 [headerMap setObject: recipient forKey: @"To"];
394 [headerMap setObject: [NSCalendarDate date] forKey: @"date"];
395 [headerMap setObject: subject forKey: @"Subject"];
396 msg = [NGMimeMessage messageWithHeader: headerMap];
399 body = [[NGMimeMultipartBody alloc] initWithPart: msg];
402 headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
403 [headerMap setObject: @"text/plain; charset=utf-8"
404 forKey: @"content-type"];
405 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
406 [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
408 /* attach text part to multipart body */
409 [body addBodyPart: bodyPart];
412 header = [NSString stringWithFormat: @"text/calendar; method=%@;"
414 [(iCalCalendar *) [_newObject parent] method]];
415 headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
416 [headerMap setObject:header forKey: @"content-type"];
417 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
418 [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
420 /* attach calendar part to multipart body */
421 [body addBodyPart: bodyPart];
423 /* attach multipart body to message */
427 /* send the damn thing */
428 [sendmail sendMimePart: msg
429 toRecipients: [NSArray arrayWithObject: email]
430 sender: [organizer rfc822Email]];
435 // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
437 // BOOL isOrganizerOrOwner;
438 // iCalRepeatableEntityObject *component;
439 // NSString *organizerEmail;
441 // component = [self component: NO];
442 // organizerEmail = [[component organizer] rfc822Email];
443 // if (component && [organizerEmail length] > 0)
444 // isOrganizerOrOwner = [user hasEmail: organizerEmail];
446 // isOrganizerOrOwner
447 // = [[container ownerInContext: context] isEqualToString: [user login]];
449 // return isOrganizerOrOwner;
452 - (iCalPerson *) findParticipantWithUID: (NSString *) uid
456 user = [SOGoUser userWithLogin: uid roles: nil];
458 return [self findParticipant: user];
461 - (iCalPerson *) findParticipant: (SOGoUser *) user
463 iCalPerson *participant, *currentParticipant;
464 iCalEntityObject *component;
465 NSEnumerator *participants;
468 component = [self component: NO];
471 participants = [[component participants] objectEnumerator];
472 currentParticipant = [participants nextObject];
473 while (currentParticipant && !participant)
474 if ([user hasEmail: [currentParticipant rfc822Email]])
475 participant = currentParticipant;
477 currentParticipant = [participants nextObject];
483 - (iCalPerson *) iCalPersonWithUID: (NSString *) uid
487 NSDictionary *contactInfos;
489 um = [LDAPUserManager sharedUserManager];
490 contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
492 person = [iCalPerson new];
493 [person autorelease];
494 [person setCn: [contactInfos objectForKey: @"cn"]];
495 [person setEmail: [contactInfos objectForKey: @"c_email"]];
500 - (NSString *) getUIDForICalPerson: (iCalPerson *) person
504 um = [LDAPUserManager sharedUserManager];
506 return [um getUIDForEmail: [person rfc822Email]];
509 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
511 iCalPerson *currentPerson;
512 NSEnumerator *persons;
513 NSMutableArray *uids;
517 uids = [NSMutableArray array];
519 um = [LDAPUserManager sharedUserManager];
520 persons = [iCalPersons objectEnumerator];
521 currentPerson = [persons nextObject];
522 while (currentPerson)
524 uid = [um getUIDForEmail: [currentPerson rfc822Email]];
526 [uids addObject: uid];
527 currentPerson = [persons nextObject];
533 - (NSString *) _roleOfOwner: (iCalRepeatableEntityObject *) component
536 iCalPerson *organizer;
541 organizer = [component organizer];
542 if ([[organizer rfc822Email] length] > 0)
544 ownerUser = [SOGoUser userWithLogin: owner roles: nil];
545 if ([component userIsOrganizer: ownerUser])
546 role = SOGoCalendarRole_Organizer;
547 else if ([component userIsParticipant: ownerUser])
548 role = SOGoCalendarRole_Participant;
550 role = SOGoRole_None;
553 role = SOGoCalendarRole_Organizer;
556 role = SOGoCalendarRole_Organizer;
561 - (NSString *) _compiledRoleForOwner: (NSString *) ownerRole
562 andUser: (NSString *) userRole
566 if ([userRole isEqualToString: SOGoCalendarRole_ComponentModifier]
567 || ([userRole isEqualToString: SOGoCalendarRole_ComponentResponder]
568 && [ownerRole isEqualToString: SOGoCalendarRole_Participant]))
571 role = SOGoRole_None;
576 - (NSArray *) aclsForUser: (NSString *) uid
578 NSMutableArray *roles;
580 iCalRepeatableEntityObject *component;
581 NSString *accessRole, *ownerRole;
583 roles = [NSMutableArray array];
584 superAcls = [super aclsForUser: uid];
585 if ([superAcls count] > 0)
586 [roles addObjectsFromArray: superAcls];
588 component = [self component: NO];
589 ownerRole = [self _roleOfOwner: component];
590 if ([owner isEqualToString: uid])
591 [roles addObject: ownerRole];
596 accessRole = [container roleForComponentsWithAccessClass:
597 [component symbolicAccessClass]
599 if ([accessRole length] > 0)
601 [roles addObject: accessRole];
602 [roles addObject: [self _compiledRoleForOwner: ownerRole
603 andUser: accessRole]];
606 else if ([roles containsObject: SOGoRole_ObjectCreator])
607 [roles addObject: SOGoCalendarRole_Organizer];