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 <NGObjWeb/WORequest+So.h>
31 #import <NGExtensions/NSObject+Logs.h>
32 #import <NGExtensions/NGHashMap.h>
33 #import <NGCards/iCalCalendar.h>
34 #import <NGCards/iCalEvent.h>
35 #import <NGCards/iCalPerson.h>
36 #import <NGCards/iCalRepeatableEntityObject.h>
37 #import <NGMime/NGMimeBodyPart.h>
38 #import <NGMime/NGMimeMultipartBody.h>
39 #import <NGMail/NGMimeMessage.h>
41 #import <SoObjects/SOGo/iCalEntityObject+Utilities.h>
42 #import <SoObjects/SOGo/LDAPUserManager.h>
43 #import <SoObjects/SOGo/NSCalendarDate+SOGo.h>
44 #import <SoObjects/SOGo/SOGoMailer.h>
45 #import <SoObjects/SOGo/SOGoPermissions.h>
46 #import <SoObjects/SOGo/SOGoUser.h>
47 #import <SoObjects/SOGo/WORequest+SOGo.h>
48 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
50 #import "SOGoAptMailICalReply.h"
51 #import "SOGoAptMailNotification.h"
52 #import "iCalEntityObject+SOGo.h"
53 #import "iCalPerson+SOGo.h"
54 #import "SOGoCalendarComponent.h"
56 static BOOL sendEMailNotifications = NO;
58 @implementation SOGoCalendarComponent
63 static BOOL didInit = NO;
69 ud = [NSUserDefaults standardUserDefaults];
70 sendEMailNotifications
71 = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
75 - (NSString *) davContentType
77 return @"text/calendar";
80 - (NSString *) componentTag
82 [self subclassResponsibility: _cmd];
87 - (void) _filterComponent: (iCalEntityObject *) component
89 [component setSummary: @""];
90 [component setComment: @""];
91 [component setUserComment: @""];
92 [component setLocation: @""];
93 [component setCategories: @""];
94 [component setUrl: @""];
95 [component removeAllAttendees];
96 [component removeAllAlarms];
99 - (NSString *) secureContentAsString
101 iCalCalendar *tmpCalendar;
102 iCalRepeatableEntityObject *tmpComponent;
105 SoSecurityManager *sm;
106 NSString *iCalString;
108 // uid = [[context activeUser] login];
109 // roles = [self aclsForUser: uid];
110 // if ([roles containsObject: SOGoCalendarRole_Organizer]
111 // || [roles containsObject: SOGoCalendarRole_Participant]
112 // || [roles containsObject: SOGoCalendarRole_ComponentViewer])
113 // calContent = content;
114 // else if ([roles containsObject: SOGoCalendarRole_ComponentDAndTViewer])
116 // tmpCalendar = [[self calendar: NO] copy];
117 // tmpComponent = (iCalRepeatableEntityObject *)
118 // [tmpCalendar firstChildWithTag: [self componentTag]];
119 // [self _filterComponent: tmpComponent];
120 // calContent = [tmpCalendar versitString];
121 // [tmpCalendar release];
126 sm = [SoSecurityManager sharedSecurityManager];
127 if (![sm validatePermission: SOGoCalendarPerm_ViewAllComponent
128 onObject: self inContext: context])
129 iCalString = content;
130 else if (![sm validatePermission: SOGoCalendarPerm_ViewDAndT
131 onObject: self inContext: context])
133 tmpCalendar = [[self calendar: NO secure: NO] copy];
134 tmpComponent = (iCalRepeatableEntityObject *)
135 [tmpCalendar firstChildWithTag: [self componentTag]];
136 [self _filterComponent: tmpComponent];
137 iCalString = [tmpCalendar versitString];
138 [tmpCalendar release];
146 - (NSString *) contentAsString
148 NSString *secureContent;
150 if ([[context request] isSoWebDAVRequest])
151 secureContent = [self secureContentAsString];
153 secureContent = [super contentAsString];
155 return secureContent;
158 - (iCalCalendar *) calendar: (BOOL) create secure: (BOOL) secure
160 NSString *componentTag;
161 CardGroup *newComponent;
162 iCalCalendar *calendar;
163 NSString *iCalString;
166 iCalString = [self secureContentAsString];
168 iCalString = content;
170 if ([iCalString length] > 0)
171 calendar = [iCalCalendar parseSingleFromSource: iCalString];
176 calendar = [iCalCalendar groupWithTag: @"vcalendar"];
177 [calendar setVersion: @"2.0"];
178 [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
179 componentTag = [[self componentTag] uppercaseString];
180 newComponent = [[calendar classForTag: componentTag]
181 groupWithTag: componentTag];
182 [calendar addChild: newComponent];
191 - (id) component: (BOOL) create secure: (BOOL) secure
193 return [[self calendar: create secure: secure]
194 firstChildWithTag: [self componentTag]];
197 - (void) saveComponent: (iCalRepeatableEntityObject *) newObject
199 NSString *newiCalString;
201 newiCalString = [[newObject parent] versitString];
203 [self saveContentString: newiCalString];
208 /* EMail Notifications */
209 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
213 NSArray *traversalObjects;
215 /* generate URL from traversal stack */
216 traversalObjects = [context objectTraversalStack];
217 if ([traversalObjects count] > 0)
218 baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
221 baseURL = @"http://localhost/";
222 [self warnWithFormat:@"Unable to create baseURL from context!"];
227 ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
231 - (BOOL) sendEMailNotifications
233 return sendEMailNotifications;
236 - (NSTimeZone *) timeZoneForUser: (NSString *) email
240 uid = [[LDAPUserManager sharedUserManager] getUIDForEmail: email];
242 return [[SOGoUser userWithLogin: uid roles: nil] timeZone];
245 - (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
246 forOldObject: (iCalRepeatableEntityObject *) _oldObject
247 andNewObject: (iCalRepeatableEntityObject *) _newObject
248 toAttendees: (NSArray *) _attendees
251 iCalPerson *organizer;
252 NSString *email, *sender, *iCalString;
255 iCalPerson *attendee;
256 NSString *recipient, *language;
257 SOGoAptMailNotification *p;
258 NSString *mailDate, *subject, *text, *header;
259 NGMutableHashMap *headerMap;
261 NGMimeBodyPart *bodyPart;
262 NGMimeMultipartBody *body;
264 if (sendEMailNotifications
265 && [_newObject isStillRelevant])
267 count = [_attendees count];
271 organizer = [_newObject organizer];
272 sender = [organizer mailAddress];
274 NSLog (@"sending '%@' from %@",
275 [(iCalCalendar *) [_newObject parent] method], organizer);
277 /* generate iCalString once */
278 iCalString = [[_newObject parent] versitString];
280 /* get WOApplication instance */
281 app = [WOApplication application];
283 /* generate dynamic message content */
285 for (i = 0; i < count; i++)
287 attendee = [_attendees objectAtIndex: i];
288 if (![[attendee uid] isEqualToString: owner])
290 /* construct recipient */
291 recipient = [attendee mailAddress];
292 email = [attendee rfc822Email];
294 NSLog (@"recipient: %@", recipient);
295 language = [[context activeUser] language];
296 #warning this could be optimized in a class hierarchy common with the \
297 SOGoObject acl notification mechanism
298 /* create page name */
299 pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
300 language, _pageName];
301 /* construct message content */
302 p = [app pageWithName: pageName inContext: context];
303 [p setNewApt: _newObject];
304 [p setOldApt: _oldObject];
305 [p setHomePageURL: [self homePageURLForPerson: attendee]];
306 [p setViewTZ: [self timeZoneForUser: email]];
307 subject = [p getSubject];
310 /* construct message */
311 headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
313 /* NOTE: multipart/alternative seems like the correct choice but
314 * unfortunately Thunderbird doesn't offer the rich content alternative
315 * at all. Mail.app shows the rich content alternative _only_
316 * so we'll stick with multipart/mixed for the time being.
318 [headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
319 [headerMap setObject: sender forKey: @"From"];
320 [headerMap setObject: recipient forKey: @"To"];
321 mailDate = [[NSCalendarDate date] rfc822DateString];
322 [headerMap setObject: mailDate forKey: @"date"];
323 [headerMap setObject: subject forKey: @"Subject"];
324 msg = [NGMimeMessage messageWithHeader: headerMap];
327 body = [[NGMimeMultipartBody alloc] initWithPart: msg];
330 headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
331 [headerMap setObject: @"text/plain; charset=utf-8"
332 forKey: @"content-type"];
333 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
334 [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
336 /* attach text part to multipart body */
337 [body addBodyPart: bodyPart];
340 header = [NSString stringWithFormat: @"text/calendar; method=%@;"
342 [(iCalCalendar *) [_newObject parent] method]];
343 headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
344 [headerMap setObject:header forKey: @"content-type"];
345 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
346 [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
348 /* attach calendar part to multipart body */
349 [body addBodyPart: bodyPart];
351 /* attach multipart body to message */
355 /* send the damn thing */
356 [[SOGoMailer sharedMailer]
358 toRecipients: [NSArray arrayWithObject: email]
359 sender: [organizer rfc822Email]];
366 - (void) sendResponseToOrganizer
368 NSString *pageName, *language, *mailDate, *email;
370 iCalPerson *organizer, *attendee;
371 NSString *iCalString;
373 SOGoAptMailICalReply *p;
374 NGMutableHashMap *headerMap;
376 NGMimeBodyPart *bodyPart;
377 NGMimeMultipartBody *body;
380 if (sendEMailNotifications)
382 event = [[self component: NO secure: NO] itipEntryWithMethod: @"reply"];
383 if (![event userIsOrganizer: [context activeUser]])
385 organizer = [event organizer];
386 attendee = [event findParticipant: [context activeUser]];
387 [event setAttendees: [NSArray arrayWithObject: attendee]];
389 /* get WOApplication instance */
390 app = [WOApplication application];
392 language = [[context activeUser] language];
393 /* create page name */
395 = [NSString stringWithFormat: @"SOGoAptMail%@ICalReply", language];
396 /* construct message content */
397 p = [app pageWithName: pageName inContext: context];
399 [p setAttendee: attendee];
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: [attendee mailAddress] forKey: @"From"];
411 [headerMap setObject: [organizer mailAddress] forKey: @"To"];
412 mailDate = [[NSCalendarDate date] rfc822DateString];
413 [headerMap setObject: mailDate forKey: @"date"];
414 [headerMap setObject: [p getSubject] forKey: @"Subject"];
415 msg = [NGMimeMessage messageWithHeader: headerMap];
417 NSLog (@"sending 'REPLY' from %@ to %@",
418 [attendee mailAddress], [organizer mailAddress]);
421 body = [[NGMimeMultipartBody alloc] initWithPart: msg];
424 headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
425 [headerMap setObject: @"text/plain; charset=utf-8"
426 forKey: @"content-type"];
427 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
428 bodyData = [[p getBody] dataUsingEncoding: NSUTF8StringEncoding];
429 [bodyPart setBody: bodyData];
431 /* attach text part to multipart body */
432 [body addBodyPart: bodyPart];
435 headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
436 [headerMap setObject: @"text/calendar; method=REPLY; charset=utf-8"
437 forKey: @"content-type"];
438 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
439 iCalString = [[event parent] versitString];
440 [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
442 /* attach calendar part to multipart body */
443 [body addBodyPart: bodyPart];
445 /* attach multipart body to message */
449 /* send the damn thing */
450 email = [organizer rfc822Email];
451 [[SOGoMailer sharedMailer]
453 toRecipients: [NSArray arrayWithObject: email]
454 sender: [attendee rfc822Email]];
459 // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
461 // BOOL isOrganizerOrOwner;
462 // iCalRepeatableEntityObject *component;
463 // NSString *organizerEmail;
465 // component = [self component: NO];
466 // organizerEmail = [[component organizer] rfc822Email];
467 // if (component && [organizerEmail length] > 0)
468 // isOrganizerOrOwner = [user hasEmail: organizerEmail];
470 // isOrganizerOrOwner
471 // = [[container ownerInContext: context] isEqualToString: [user login]];
473 // return isOrganizerOrOwner;
476 - (iCalPerson *) findParticipantWithUID: (NSString *) uid
478 iCalEntityObject *component;
481 user = [SOGoUser userWithLogin: uid roles: nil];
482 component = [self component: NO secure: NO];
484 return [component findParticipant: user];
487 - (iCalPerson *) iCalPersonWithUID: (NSString *) uid
491 NSDictionary *contactInfos;
493 um = [LDAPUserManager sharedUserManager];
494 contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
496 person = [iCalPerson new];
497 [person autorelease];
498 [person setCn: [contactInfos objectForKey: @"cn"]];
499 [person setEmail: [contactInfos objectForKey: @"c_email"]];
504 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
506 iCalPerson *currentPerson;
507 NSEnumerator *persons;
508 NSMutableArray *uids;
511 uids = [NSMutableArray array];
513 persons = [iCalPersons objectEnumerator];
514 currentPerson = [persons nextObject];
515 while (currentPerson)
517 uid = [currentPerson uid];
519 [uids addObject: uid];
520 currentPerson = [persons nextObject];
526 - (NSString *) _roleOfOwner: (iCalRepeatableEntityObject *) component
529 iCalPerson *organizer;
534 organizer = [component organizer];
535 if ([[organizer rfc822Email] length] > 0)
537 ownerUser = [SOGoUser userWithLogin: owner roles: nil];
538 if ([component userIsOrganizer: ownerUser])
539 role = SOGoCalendarRole_Organizer;
540 else if ([component userIsParticipant: ownerUser])
541 role = SOGoCalendarRole_Participant;
543 role = SOGoRole_None;
546 role = SOGoCalendarRole_Organizer;
549 role = SOGoCalendarRole_Organizer;
554 - (NSString *) _compiledRoleForOwner: (NSString *) ownerRole
555 andUser: (NSString *) userRole
559 if ([userRole isEqualToString: SOGoCalendarRole_ComponentModifier]
560 || ([userRole isEqualToString: SOGoCalendarRole_ComponentResponder]
561 && [ownerRole isEqualToString: SOGoCalendarRole_Participant]))
564 role = SOGoRole_None;
569 - (NSArray *) aclsForUser: (NSString *) uid
571 NSMutableArray *roles;
573 iCalRepeatableEntityObject *component;
574 NSString *accessRole, *ownerRole;
577 roles = [NSMutableArray array];
578 superAcls = [super aclsForUser: uid];
579 if ([superAcls count] > 0)
580 [roles addObjectsFromArray: superAcls];
582 component = [self component: NO secure: NO];
583 ownerRole = [self _roleOfOwner: component];
584 if ([owner isEqualToString: uid])
585 [roles addObject: ownerRole];
590 aclUser = [SOGoUser userWithLogin: uid roles: nil];
591 if ([component userIsOrganizer: aclUser])
592 [roles addObject: SOGoCalendarRole_Organizer];
593 else if ([component userIsParticipant: aclUser])
594 [roles addObject: SOGoCalendarRole_Participant];
595 accessRole = [container roleForComponentsWithAccessClass:
596 [component symbolicAccessClass]
598 if ([accessRole length] > 0)
600 [roles addObject: accessRole];
601 [roles addObject: [self _compiledRoleForOwner: ownerRole
602 andUser: accessRole]];
605 else if ([roles containsObject: SOGoRole_ObjectCreator])
606 [roles addObject: SOGoCalendarRole_Organizer];