]> err.no Git - scalable-opengroupware.org/commitdiff
Added Mail notifications for various events
authorznek <znek@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Tue, 12 Jul 2005 13:56:19 +0000 (13:56 +0000)
committerznek <znek@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Tue, 12 Jul 2005 13:56:19 +0000 (13:56 +0000)
git-svn-id: http://svn.opengroupware.org/SOGo/trunk@720 d1b88da0-ebda-0310-925b-ed51d893ca5b

21 files changed:
SOGo/SoObjects/Appointments/ChangeLog
SOGo/SoObjects/Appointments/GNUmakefile
SOGo/SoObjects/Appointments/SOGoAppointmentObject.m
SOGo/SoObjects/Appointments/SOGoAptMailInvitation.m [new file with mode: 0644]
SOGo/SoObjects/Appointments/SOGoAptMailInvitation.wo/SOGoAptMailInvitation.html [new file with mode: 0644]
SOGo/SoObjects/Appointments/SOGoAptMailInvitation.wo/SOGoAptMailInvitation.wod [new file with mode: 0644]
SOGo/SoObjects/Appointments/SOGoAptMailNotification.h [new file with mode: 0644]
SOGo/SoObjects/Appointments/SOGoAptMailNotification.m [new file with mode: 0644]
SOGo/SoObjects/Appointments/SOGoAptMailUpdate.m [new file with mode: 0644]
SOGo/SoObjects/Appointments/SOGoAptMailUpdate.wo/SOGoAptMailUpdate.html [new file with mode: 0644]
SOGo/SoObjects/Appointments/SOGoAptMailUpdate.wo/SOGoAptMailUpdate.wod [new file with mode: 0644]
SOGo/SoObjects/Appointments/Version
SOGo/SoObjects/SOGo/AgenorUserManager.h
SOGo/SoObjects/SOGo/AgenorUserManager.m
SOGo/SoObjects/SOGo/ChangeLog
SOGo/SoObjects/SOGo/Version
SOGo/UI/Scheduler/ChangeLog
SOGo/UI/Scheduler/UIxCalView.h
SOGo/UI/Scheduler/Version
SOGo/UI/Templates/ChangeLog
SOGo/UI/Templates/UIxAppointmentEditor.wox

index 9efea09c6b216fac97b190b03f9e6797f834fe49..bde97919dc49d97e5d64ebd45790b16592b45e24 100644 (file)
@@ -1,3 +1,22 @@
+2005-07-12  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * v0.9.37
+
+       * SOGoAptMailNotification.[hm]: new component forming the basis for
+         the mail notification templates
+
+       * SOGoAptMailInvitation.[wo, m]: invitation mail template, sent to all
+         new attendees of an appointment
+
+       * SOGoAptMailUpdate.[wo, m]: update mail template, sent to all
+         attendees when appointment time is changed
+
+       * SOGoAppointmentObject.m: rewritten to use the iCalEventChanges object
+         from NGiCal to discover changes to saved appointments. Additionaly
+         email notifications are emitted on certain occasions (this is not
+         full iMip, yet). In case the appointment time changes, state for
+         all attendees is reset to NEEDS-ACTION.
+
 2005-07-08  Marcus Mueller  <znek@mulle-kybernetik.com>
 
        * SOGoAppointmentFolder.m: added 'partmails' and 'partstates' to
index 0cd04c8e80b2889bdbb2e3a37e76a01a8690b0ff..1cab33833f2a50c1a2f407c3c7e99586305a4f0d 100644 (file)
@@ -13,10 +13,18 @@ Appointments_OBJC_FILES = \
        SOGoAppointmentFolder.m         \
        SOGoGroupAppointmentFolder.m    \
        SOGoFreeBusyObject.m            \
+       \
+       SOGoAptMailNotification.m       \
+       SOGoAptMailInvitation.m         \
+       SOGoAptMailUpdate.m             \
+
+Appointments_RESOURCE_FILES +=         \
+       Version                         \
+       product.plist                   \
 
-Appointments_RESOURCE_FILES += \
-       Version                 \
-       product.plist
+Appointments_COMPONENTS +=             \
+       SOGoAptMailInvitation.wo        \
+       SOGoAptMailUpdate.wo            \
 
 -include GNUmakefile.preamble
 include $(GNUSTEP_MAKEFILES)/bundle.make
index 66f149b858b6b26923c8af9bd8f77e917be9fcae..92b216bb42e29936126c999df58c69c452b238d3 100644 (file)
 #include <SOGo/SOGoAppointment.h>
 #include <SaxObjC/SaxObjC.h>
 #include <NGiCal/NGiCal.h>
+#include <NGMime/NGMime.h>
+#include <NGMail/NGMail.h>
+#include <NGMail/NGSendMail.h>
+#include "SOGoAptMailNotification.h"
 #include "common.h"
 
+@interface NSMutableArray (iCalPersonConvenience)
+- (void)removePerson:(iCalPerson *)_person;
+@end
+
+@interface SOGoAppointmentObject (PrivateAPI)
+- (NSString *)homePageURLForPerson:(iCalPerson *)_person;
+- (NSTimeZone *)viewTimeZoneForPerson:(iCalPerson *)_person;
+  
+- (void)sendEMailUsingTemplateNamed:(NSString *)_pageName
+  forOldAppointment:(SOGoAppointment *)_newApt
+  andNewAppointment:(SOGoAppointment *)_oldApt
+  toAttendees:(NSArray *)_attendees;
+
+- (void)sendInvitationEMailForAppointment:(SOGoAppointment *)_apt
+  toAttendees:(NSArray *)_attendees;
+- (void)sendAppointmentUpdateEMailForOldAppointment:(SOGoAppointment *)_oldApt
+  newAppointment:(SOGoAppointment *)_newApt
+  toAttendees:(NSArray *)_attendees;
+- (void)sendRemovalEMailForAppointment:(SOGoAppointment *)_apt
+  toAttendees:(NSArray *)_attendees;
+@end
+
 @implementation SOGoAppointmentObject
 
 static id<NSObject,SaxXMLReader> parser  = nil;
 static SaxObjectDecoder          *sax    = nil;
 static NGLogger                  *logger = nil;
+static NSTimeZone                *MET    = nil;
 
 + (void)initialize {
   NGLoggerManager     *lm;
@@ -54,6 +81,8 @@ static NGLogger                  *logger = nil;
   
   [parser setContentHandler:sax];
   [parser setErrorHandler:sax];
+
+  MET = [[NSTimeZone timeZoneWithAbbreviation:@"MET"] retain];
 }
 
 - (void)dealloc {
@@ -240,18 +269,25 @@ static NGLogger                  *logger = nil;
      - delete in removed folders
      - send iMIP mail for all folders not found
   */
-  SOGoAppointment *oldApt, *newApt;
-  NSString        *oldContent;
-  NSArray         *oldUIDs, *newUIDs;
-  NSMutableArray  *storeUIDs, *removedUIDs;
-  unsigned        i, count;
-  NSException     *storeError, *delError;
+  AgenorUserManager *um;
+  SOGoAppointment   *oldApt, *newApt;
+  iCalEventChanges  *changes;
+  iCalPerson        *organizer;
+  NSString          *oldContent, *uid;
+  NSArray           *uids, *props;
+  NSMutableArray    *attendees, *storeUIDs, *removedUIDs;
+  NSException       *storeError, *delError;
+  BOOL              didChangeAppointmentTime;
   
+  didChangeAppointmentTime = NO;
+
   if ([_iCal length] == 0) {
     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
                        reason:@"got no iCalendar content to store!"];
   }
-  
+
+  um = [AgenorUserManager sharedUserManager];
+
   /* handle old content */
   
   oldContent = [self iCalString]; /* if nil, this is a new appointment */
@@ -271,9 +307,6 @@ static NGLogger                  *logger = nil;
     // TODO
   }
   
-  oldUIDs = [oldApt isNotNull]
-    ? [self attendeeUIDsFromAppointment:oldApt] 
-    : nil;
   
   /* handle new content */
   
@@ -282,50 +315,102 @@ static NGLogger                  *logger = nil;
     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
                        reason:@"could not parse iCalendar content!"];
   }
-  if ((newUIDs = [self attendeeUIDsFromAppointment:newApt]) == nil)
-    [self debugWithFormat:@"got no UIDs from appointment: %@", newApt];
   
   /* diff */
   
-  count       = [oldUIDs count];
-  removedUIDs = [NSMutableArray arrayWithCapacity:count];
-  storeUIDs   = [NSMutableArray arrayWithCapacity:count];
-  for (i = 0; i < count; i++) {
-    NSString *uid;
-    
-    uid = [oldUIDs objectAtIndex:i];
-    if ([newUIDs containsObject:uid])
-      [storeUIDs addObject:uid]; /* old ID is still available */
-    else
-      [removedUIDs addObject:uid]; /* old ID is not available anymore */
+  changes     = [iCalEventChanges changesFromEvent:[oldApt event]
+                                  toEvent:[newApt event]];
+
+  uids        = [um getUIDsForICalPersons:[changes deletedAttendees]
+                    applyStrictMapping:NO];
+  removedUIDs = [NSMutableArray arrayWithArray:uids];
+
+  uids        = [um getUIDsForICalPersons:[newApt attendees]
+                    applyStrictMapping:NO];
+  storeUIDs   = [NSMutableArray arrayWithArray:uids];
+  props       = [changes updatedProperties];
+
+  /* preserve organizer */
+
+  organizer = [[newApt event] organizer];
+  uid       = [um getUIDForICalPerson:organizer];
+  if (uid) {
+    if (![storeUIDs containsObject:uid])
+      [storeUIDs addObject:uid];
+    [removedUIDs removeObject:uid];
   }
-  count = [newUIDs count];
-  for (i = 0; i < count; i++) {
-    NSString *uid;
-    
-    uid = [newUIDs objectAtIndex:i];
-    if ([storeUIDs containsObject:uid]) /* old ID is still available */
-      continue;
-    if ([removedUIDs containsObject:uid]) /* old ID is not available anymore */
-      continue;
+
+  /* organizer might have changed completely */
+
+  if ((oldApt != nil) && ([props containsObject:@"organizer"])) {
+    uid = [um getUIDForICalPerson:[[oldApt event] organizer]];
+    if (uid) {
+      if (![storeUIDs containsObject:uid]) {
+        if (![removedUIDs containsObject:uid]) {
+          [removedUIDs addObject:uid];
+        }
+      }
+    }
+  }
+
+  [self debugWithFormat:@"UID ops:\n  store: %@\n  remove: %@",
+                        storeUIDs, removedUIDs];
+
+  /* if time did change, all participants have to re-decide ...
+   * ... exception from that rule: the organizer
+   */
+
+  if (oldApt != nil                        &&
+      ([props containsObject:@"startDate"] ||
+       [props containsObject:@"endDate"]   ||
+       [props containsObject:@"duration"]))
+  {
+    NSArray  *ps;
+    unsigned i, count;
     
-    /* new ID which is not part of the old set => store a new */
-    [storeUIDs addObject:uid];
+    ps    = [newApt attendees];
+    count = [ps count];
+    for (i = 0; i < count; i++) {
+      iCalPerson *p;
+      
+      p = [ps objectAtIndex:i];
+      if (![p hasSameEmailAddress:organizer])
+        [p setParticipationStatus:iCalPersonPartStatNeedsAction];
+    }
+    _iCal = [newApt iCalString];
+    didChangeAppointmentTime = YES;
   }
-  
-  [self debugWithFormat:
-         @"UID ops:\n  new: %@\n  old: %@\n  store: %@\n  remove: %@",
-         newUIDs, oldUIDs, storeUIDs, removedUIDs];
-  
-  /* perform */
-  
+
+  /* perform storing */
+
   storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
   delError   = [self deleteInUIDs:removedUIDs];
-  
+
   // TODO: make compound
   if (storeError != nil) return storeError;
   if (delError   != nil) return delError;
-  
+
+  /* email notifications */
+
+  attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
+  [attendees removePerson:organizer];
+  [self sendInvitationEMailForAppointment:newApt
+        toAttendees:attendees];
+
+  if (didChangeAppointmentTime) {
+    attendees = [NSMutableArray arrayWithArray:[[newApt event] attendees]];
+    [attendees removeObjectsInArray:[changes insertedAttendees]];
+    [attendees removePerson:organizer];
+    [self sendAppointmentUpdateEMailForOldAppointment:oldApt
+          newAppointment:newApt
+          toAttendees:attendees];
+  }
+
+  attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
+  [attendees removePerson:organizer];
+  [self sendRemovalEMailForAppointment:newApt
+        toAttendees:attendees];
+
   return nil;
 }
 
@@ -379,4 +464,204 @@ static NGLogger                  *logger = nil;
   return @"IPM.Appointment";
 }
 
+/* EMail Notifications */
+
+- (NSString *)homePageURLForPerson:(iCalPerson *)_person {
+  static AgenorUserManager *um      = nil;
+  static NSString          *baseURL = nil;
+  NSString *uid;
+
+  if (!um) {
+    um      = [[AgenorUserManager sharedUserManager] retain];
+    baseURL = @"http://agenor.opengroupware.org/foo/so";
+  }
+  uid = [um getUIDForEmail:[_person rfc822Email]];
+  if (!uid) return nil;
+  return [NSString stringWithFormat:@"%@/%@", baseURL, uid];
+}
+
+- (NSTimeZone *)viewTimeZoneForPerson:(iCalPerson *)_person {
+  /* TODO: get this from user config as soon as this is available and only
+   *       fall back to default timeZone if config data is not available
+   */
+  return MET;
+}
+
+
+- (void)sendEMailUsingTemplateNamed:(NSString *)_pageName
+  forOldAppointment:(SOGoAppointment *)_oldApt
+  andNewAppointment:(SOGoAppointment *)_newApt
+  toAttendees:(NSArray *)_attendees
+{
+  NSString                *pageName;
+  iCalPerson              *organizer;
+  NSString                *cn, *sender, *iCalString;
+  NGSendMail              *sendmail;
+  WOApplication           *app;
+  unsigned                i, count;
+
+  if (![_attendees count]) return; // another job neatly done :-)
+
+  /* sender */
+
+  organizer = [_newApt organizer];
+  cn        = [organizer cnWithoutQuotes];
+  if (cn) {
+    sender = [NSString stringWithFormat:@"%@ <%@>",
+                                        cn,
+                                        [organizer rfc822Email]];
+  }
+  else {
+    sender = [organizer rfc822Email];
+  }
+
+  /* generate iCalString once */
+  iCalString = [_newApt iCalString];
+  
+  /* get sendmail object */
+  sendmail   = [NGSendMail sharedSendMail];
+
+  /* get WOApplication instance */
+  app        = [WOApplication application];
+
+  /* create page name */
+  pageName   = [NSString stringWithFormat:@"SOGoAptMail%@", _pageName];
+
+  /* generate dynamic message content */
+
+  count = [_attendees count];
+  for (i = 0; i < count; i++) {
+    iCalPerson              *attendee;
+    NSString                *recipient;
+    SOGoAptMailNotification *p;
+    NSString                *subject, *text;
+    NGMutableHashMap        *headerMap;
+    NGMimeMessage           *msg;
+    NGMimeBodyPart          *bodyPart;
+    NGMimeMultipartBody     *body;
+    
+    attendee  = [_attendees objectAtIndex:i];
+    
+    /* construct recipient */
+#if 1
+    cn        = [attendee cn];
+    if (cn) {
+      recipient = [NSString stringWithFormat:@"%@ <%@>",
+                                             cn,
+                                             [attendee rfc822Email]];
+    }
+    else {
+      recipient = [attendee rfc822Email];
+    }
+#else
+    recipient = @"Marcus Mueller <mm@skyrix.com>";
+#endif
+
+    /* construct message content */
+    p = [app pageWithName:pageName];
+    [p setNewApt:_newApt];
+    [p setOldApt:_oldApt];
+    [p setHomePageURL:[self homePageURLForPerson:attendee]];
+    [p setViewTZ:[self viewTimeZoneForPerson:attendee]];
+    subject = [p getSubject];
+    text    = [p getBody];
+
+    /* construct message */
+    headerMap = [NGMutableHashMap hashMapWithCapacity:5];
+    
+    /* NOTE: multipart/alternative seems like the correct choice but
+     * unfortunately Thunderbird doesn't offer the rich content alternative
+     * at all. Mail.app shows the rich content alternative _only_
+     * so we'll stick with multipart/mixed for the time being.
+     */
+    [headerMap setObject:@"multipart/mixed"    forKey:@"content-type"];
+    [headerMap setObject:sender                forKey:@"From"];
+    [headerMap setObject:recipient             forKey:@"To"];
+    [headerMap setObject:[NSCalendarDate date] forKey:@"date"];
+    [headerMap setObject:subject               forKey:@"Subject"];
+    msg = [NGMimeMessage messageWithHeader:headerMap];
+    
+    /* multipart body */
+    body = [[NGMimeMultipartBody alloc] initWithPart:msg];
+    
+    /* text part */
+    headerMap = [NGMutableHashMap hashMapWithCapacity:1];
+    [headerMap setObject:@"text/plain" forKey:@"content-type"];
+    bodyPart = [NGMimeBodyPart bodyPartWithHeader:headerMap];
+    [bodyPart setBody:text];
+
+    /* attach text part to multipart body */
+    [body addBodyPart:bodyPart];
+    
+    /* calendar part */
+    headerMap  = [NGMutableHashMap hashMapWithCapacity:1];
+    [headerMap setObject:@"text/calendar" forKey:@"content-type"];
+    bodyPart   = [NGMimeBodyPart bodyPartWithHeader:headerMap];
+    [bodyPart setBody:iCalString];
+    
+    /* attach calendar part to multipart body */
+    [body addBodyPart:bodyPart];
+    
+    /* attach multipart body to message */
+    [msg setBody:body];
+    [body release];
+
+    /* send the damn thing */
+#if 1
+    [sendmail sendMimePart:msg
+              toRecipients:[NSArray arrayWithObject:[attendee rfc822Email]]
+              sender:[organizer rfc822Email]];
+#else
+    [sendmail sendMimePart:msg
+              toRecipients:[NSArray arrayWithObject:@"mm@skyrix.com"]
+              sender:[organizer rfc822Email]];
+#endif
+  }
+}
+
+- (void)sendInvitationEMailForAppointment:(SOGoAppointment *)_apt
+  toAttendees:(NSArray *)_attendees
+{
+  if (![_attendees count]) return; // another job neatly done :-)
+
+  [self sendEMailUsingTemplateNamed:@"Invitation"
+        forOldAppointment:nil
+        andNewAppointment:_apt
+        toAttendees:_attendees];
+}
+
+- (void)sendAppointmentUpdateEMailForOldAppointment:(SOGoAppointment *)_oldApt
+  newAppointment:(SOGoAppointment *)_newApt
+  toAttendees:(NSArray *)_attendees
+{
+  if (![_attendees count]) return;
+  
+  [self sendEMailUsingTemplateNamed:@"Update"
+        forOldAppointment:_oldApt
+        andNewAppointment:_newApt
+        toAttendees:_attendees];
+}
+
+- (void)sendRemovalEMailForAppointment:(SOGoAppointment *)_apt
+  toAttendees:(NSArray *)_attendees
+{
+  if (![_attendees count]) return;
+}
+
 @end /* SOGoAppointmentObject */
+
+@implementation NSMutableArray (iCalPersonConvenience)
+
+- (void)removePerson:(iCalPerson *)_person {
+  int i;
+  
+  for (i = [self count] - 1; i >= 0; i--) {
+    iCalPerson *p;
+    
+    p = [self objectAtIndex:i];
+    if ([p hasSameEmailAddress:_person])
+      [self removeObjectAtIndex:i];
+  }
+}
+
+@end /* NSMutableArray (iCalPersonConvenience) */
diff --git a/SOGo/SoObjects/Appointments/SOGoAptMailInvitation.m b/SOGo/SoObjects/Appointments/SOGoAptMailInvitation.m
new file mode 100644 (file)
index 0000000..4a0b671
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+  Copyright (C) 2000-2005 SKYRIX Software AG
+  This file is part of OpenGroupware.org.
+  OGo is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "SOGoAptMailNotification.h"
+
+@interface SOGoAptMailInvitation : SOGoAptMailNotification
+{
+
+}
+
+@end
+
+@implementation SOGoAptMailInvitation
+
+@end
diff --git a/SOGo/SoObjects/Appointments/SOGoAptMailInvitation.wo/SOGoAptMailInvitation.html b/SOGo/SoObjects/Appointments/SOGoAptMailInvitation.wo/SOGoAptMailInvitation.html
new file mode 100644 (file)
index 0000000..edb87f4
--- /dev/null
@@ -0,0 +1,7 @@
+<#IsSubject>Apt le <#AptStartDate /> at <#AptStartTime /></#IsSubject>
+<#IsBody>
+You're invited by <#Organizer /> to a meeting.
+<#HasHomePageURL>
+Please make a decision for this invitation at <#HomePageURL />.
+</#HasHomePageURL>
+</#IsBody>
\ No newline at end of file
diff --git a/SOGo/SoObjects/Appointments/SOGoAptMailInvitation.wo/SOGoAptMailInvitation.wod b/SOGo/SoObjects/Appointments/SOGoAptMailInvitation.wo/SOGoAptMailInvitation.wod
new file mode 100644 (file)
index 0000000..69c6c1c
--- /dev/null
@@ -0,0 +1,34 @@
+AptStartDate: WOString {
+  value      = newStartDate;
+  dateformat = "%d/%m/%y";
+  escapeHTML = NO;
+}
+
+AptStartTime: WOString {
+  value      = newStartDate;
+  dateformat = "%H:%M";
+  escapeHTML = NO;
+}
+
+Organizer: WOString {
+  value      = newApt.organizer.cnWithoutQuotes;
+  escapeHTML = NO;
+}
+
+HasHomePageURL: WOConditional {
+  condition = homePageURL.length;
+}
+
+HomePageURL: WOString {
+  value      = homePageURL;
+  escapeHTML = NO;
+}
+
+IsSubject: WOConditional {
+  condition = isSubject;
+}
+
+IsBody: WOConditional {
+  condition = isSubject;
+  negate    = YES;
+}
diff --git a/SOGo/SoObjects/Appointments/SOGoAptMailNotification.h b/SOGo/SoObjects/Appointments/SOGoAptMailNotification.h
new file mode 100644 (file)
index 0000000..4829e0a
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+  Copyright (C) 2000-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef        __Appointments_SOGoAptMailNotification_H_
+#define        __Appointments_SOGoAptMailNotification_H_
+
+#include <NGObjWeb/NGObjWeb.h>
+
+@class NSString;
+
+@interface SOGoAptMailNotification : WOComponent
+{
+  id             oldApt;
+  id             newApt;
+  NSString       *homePageURL;
+  NSTimeZone     *viewTZ;
+  NSCalendarDate *oldStartDate;
+  NSCalendarDate *newStartDate;
+  BOOL           isSubject;
+}
+
+- (id)oldApt;
+- (void)setOldApt:(id)_oldApt;
+
+- (id)newApt;
+- (void)setNewApt:(id)_newApt;
+
+- (NSString *)homePageURL;
+- (void)setHomePageURL:(NSString *)_homePageURL;
+
+- (NSTimeZone *)viewTZ;
+- (void)setViewTZ:(NSTimeZone *)_viewTZ;
+
+/* Helpers */
+
+- (NSCalendarDate *)oldStartDate;
+- (NSCalendarDate *)newStartDate;
+
+/* Content Generation */
+
+- (NSString *)getSubject;
+- (NSString *)getBody;
+  
+@end
+
+#endif /* __Appointments_SOGoAptMailNotification_H_ */
diff --git a/SOGo/SoObjects/Appointments/SOGoAptMailNotification.m b/SOGo/SoObjects/Appointments/SOGoAptMailNotification.m
new file mode 100644 (file)
index 0000000..a7a11f2
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+  Copyright (C) 2000-2005 SKYRIX Software AG
+  This file is part of OpenGroupware.org.
+  OGo is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "SOGoAptMailNotification.h"
+#include <SOGo/SOGoAppointment.h>
+#include "common.h"
+
+@interface SOGoAptMailNotification (PrivateAPI)
+- (BOOL)isSubject;
+- (void)setIsSubject:(BOOL)_isSubject;
+@end
+
+@implementation SOGoAptMailNotification
+
+static NSCharacterSet *wsSet  = nil;
+static NSTimeZone     *MET = nil;
+
++ (void)initialize {
+  static BOOL didInit = NO;
+
+  if (didInit) return;
+  didInit = YES;
+
+  wsSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];
+  MET   = [[NSTimeZone timeZoneWithAbbreviation:@"MET"] retain];
+}
+
+- (void)dealloc {
+  [self->oldApt       release];
+  [self->newApt       release];
+  [self->homePageURL  release];
+  [self->viewTZ       release];
+
+  [self->oldStartDate release];
+  [self->newStartDate release];
+  [super dealloc];
+}
+
+- (id)oldApt {
+  return self->oldApt;
+}
+- (void)setOldApt:(id)_oldApt {
+  ASSIGN(self->oldApt, _oldApt);
+}
+
+- (id)newApt {
+  return self->newApt;
+}
+- (void)setNewApt:(id)_newApt {
+  ASSIGN(self->newApt, _newApt);
+}
+
+- (NSString *)homePageURL {
+  return self->homePageURL;
+}
+- (void)setHomePageURL:(NSString *)_homePageURL {
+  ASSIGN(self->homePageURL, _homePageURL);
+}
+
+- (NSTimeZone *)viewTZ {
+  if (self->viewTZ) return self->viewTZ;
+  return MET;
+}
+- (void)setViewTZ:(NSTimeZone *)_viewTZ {
+  ASSIGN(self->viewTZ, _viewTZ);
+}
+
+- (BOOL)isSubject {
+  return self->isSubject;
+}
+- (void)setIsSubject:(BOOL)_isSubject {
+  self->isSubject = _isSubject;
+}
+
+
+/* Helpers */
+
+- (NSCalendarDate *)oldStartDate {
+  if (!self->oldStartDate) {
+    ASSIGN(self->oldStartDate, [[self oldApt] startDate]);
+    [self->oldStartDate setTimeZone:[self viewTZ]];
+  }
+  return self->oldStartDate;
+}
+
+- (NSCalendarDate *)newStartDate {
+  if (!self->newStartDate) {
+    ASSIGN(self->newStartDate, [[self newApt] startDate]);
+    [self->newStartDate setTimeZone:[self viewTZ]];
+  }
+  return self->newStartDate;
+}
+
+/* Generate Response */
+
+- (NSString *)getSubject {
+  [self setIsSubject:YES];
+  return [[[self generateResponse] contentAsString]
+                                   stringByTrimmingCharactersInSet:wsSet];
+}
+
+- (NSString *)getBody {
+  [self setIsSubject:NO];
+  return [[self generateResponse] contentAsString];
+}
+
+@end
diff --git a/SOGo/SoObjects/Appointments/SOGoAptMailUpdate.m b/SOGo/SoObjects/Appointments/SOGoAptMailUpdate.m
new file mode 100644 (file)
index 0000000..fcbfaac
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+  Copyright (C) 2000-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "SOGoAptMailNotification.h"
+
+@interface SOGoAptMailUpdate : SOGoAptMailNotification
+{
+
+}
+
+@end
+
+@implementation SOGoAptMailUpdate
+
+@end
diff --git a/SOGo/SoObjects/Appointments/SOGoAptMailUpdate.wo/SOGoAptMailUpdate.html b/SOGo/SoObjects/Appointments/SOGoAptMailUpdate.wo/SOGoAptMailUpdate.html
new file mode 100644 (file)
index 0000000..8a8544c
--- /dev/null
@@ -0,0 +1,8 @@
+<#IsSubject>Apt for the <#OldAptStartDate /> at <#OldAptStartTime /> changed</#IsSubject>
+<#IsBody>
+This appointment, previously set for <#OldAptStartDate /> 
+at <#OldAptStartTime /> is now scheduled for <#NewAptStartDate /> at <#NewAptStartTime />
+<#HasHomePageURL>
+Please make a decision for these new settings at <#HomePageURL />.
+</#HasHomePageURL>
+</#IsBody>
\ No newline at end of file
diff --git a/SOGo/SoObjects/Appointments/SOGoAptMailUpdate.wo/SOGoAptMailUpdate.wod b/SOGo/SoObjects/Appointments/SOGoAptMailUpdate.wo/SOGoAptMailUpdate.wod
new file mode 100644 (file)
index 0000000..4ca5da7
--- /dev/null
@@ -0,0 +1,46 @@
+OldAptStartDate: WOString {
+  value      = oldStartDate;
+  dateformat = "%d/%m/%y";
+  escapeHTML = NO;
+}
+
+OldAptStartTime: WOString {
+  value      = oldStartDate;
+  dateformat = "%H:%M";
+  escapeHTML = NO;
+}
+
+NewAptStartDate: WOString {
+  value      = newStartDate;
+  dateformat = "%d/%m/%y";
+  escapeHTML = NO;
+}
+
+NewAptStartTime: WOString {
+  value      = newStartDate;
+  dateformat = "%H:%M";
+  escapeHTML = NO;
+}
+
+Organizer: WOString {
+  value = newApt.organizer.cnWithoutQuotes;
+  escapeHTML = NO;
+}
+
+HasHomePageURL: WOConditional {
+  condition = homePageURL.length;
+}
+
+HomePageURL: WOString {
+  value      = homePageURL;
+  escapeHTML = NO;
+}
+
+IsSubject: WOConditional {
+  condition = isSubject;
+}
+
+IsBody: WOConditional {
+  condition = isSubject;
+  negate    = YES;
+}
index b1ee3cc91a66f1af29e13c0b71c330a2985a1ac9..7300dc6ea0c3e2e027677793bc15f96f1bf45d85 100644 (file)
@@ -1,6 +1,6 @@
 # Version file
 
-SUBMINOR_VERSION:=36
+SUBMINOR_VERSION:=37
 
 # v0.9.32 requires libGDLContentStore v4.5.26
 # v0.9.28 requires libNGiCal          v4.5.47
index aa6a3aacf415c4d2b54e2d1f7faaf493174fdb53..421ee1f2d6a67e5922a9c0fff8b2ac3b095dc9fd 100644 (file)
@@ -32,6 +32,7 @@
 
 @class NSString, NSArray, NSURL, NSUserDefaults;
 @class SOGoLRUCache;
+@class iCalPerson;
 
 @interface AgenorUserManager : NSObject
 {
 - (NSString *)getUIDForEmail:(NSString *)_email;
 - (NSString *)getEmailForUID:(NSString *)_uid;
 
+- (NSString *)getUIDForICalPerson:(iCalPerson *)_person;
+/* may insert NSNulls into returned array if _mapStrictly -> YES */
+- (NSArray  *)getUIDsForICalPersons:(NSArray *)_persons
+  applyStrictMapping:(BOOL)_mapStrictly;
+
 /* i.e. BUTTO Hercule, CETE Lyon/DI/ET/TEST */
 - (NSString *)getCNForUID:(NSString *)_uid;
 
index 4bff3b5278017aafcf03a01a43f29cc7aaed5614..082f96606c150b4ae794a0d2f9a905050177be27 100644 (file)
@@ -23,6 +23,7 @@
 #include "AgenorUserDefaults.h"
 #include <NGExtensions/NGExtensions.h>
 #include <NGLdap/NGLdap.h>
+#include <NGiCal/NGiCal.h>
 #include "SOGoLRUCache.h"
 
 @interface AgenorUserManager (PrivateAPI)
@@ -47,6 +48,7 @@ static BOOL     debugOn     = NO;
 static BOOL     useLDAP     = NO;
 static NSString *ldapHost   = nil;
 static NSString *ldapBaseDN = nil;
+static NSNull   *sharedNull = nil;
 static NSString *fallbackIMAP4Server  = nil;
 static NSString *defaultMailDomain    = @"equipement.gouv.fr";
 static NSString *shareLDAPClass       = @"mineqMelBoite";
@@ -85,6 +87,8 @@ static NSArray *fromEMailAttrs = nil;
   fromEMailAttrs = 
     [[NSArray alloc] initWithObjects:mailEmissionAttrName, nil];
 
+  sharedNull = [[NSNull null] retain];
+
   /* profile database URL */
   
   if ((tmp = [ud stringForKey:@"AgenorProfileURL"]) == nil)
@@ -267,6 +271,35 @@ static NSArray *fromEMailAttrs = nil;
   return uid;
 }
 
+- (NSString *)getUIDForICalPerson:(iCalPerson *)_person {
+  return [self getUIDForEmail:[_person rfc822Email]];
+}
+
+  /* may insert NSNulls into returned array */
+- (NSArray  *)getUIDsForICalPersons:(NSArray *)_persons
+  applyStrictMapping:(BOOL)_mapStrictly
+{
+  NSMutableArray *ma;
+  unsigned       i, count;
+
+  count = [_persons count];
+  ma    = [[[NSMutableArray alloc] initWithCapacity:count] autorelease];
+  for (i = 0; i < count; i++) {
+    iCalPerson *p;
+    id         uid;
+
+    p   = [_persons objectAtIndex:i];
+    uid = [self getUIDForICalPerson:p];
+    if (uid) {
+      [ma addObject:uid];
+    }
+    else if (!uid && _mapStrictly) {
+      [ma addObject:sharedNull];
+    }
+  }
+  return ma;
+}
+
 - (NSString *)primaryGetEmailForAgenorUID:(NSString *)_uid {
   NGLdapConnection *conn;
   EOQualifier      *q;
index 89ee183e2942bc0a91683e87257e11843a4c483a..453d72d31e8a1d3d154399febba91a06260a9bb3 100644 (file)
@@ -1,3 +1,8 @@
+2005-07-12  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * AgenorUserManager.[hm]: new API for extracting UIDs from iCalPersons
+         (v0.9.46)
+
 2005-07-12  Helge Hess  <helge.hess@opengroupware.org>
 
        * v0.9.45
@@ -28,6 +33,7 @@
        * added AgenorUserDefaults class (incomplete) as a wrapper for the
          profile data of Agenor users
 
+>>>>>>> .r719
 2005-07-08  Helge Hess  <helge.hess@opengroupware.org>
 
        * v0.9.42
index 2bea963b3a27ed0872886202ac4b82d99eee9df5..09850cc9a414c04a980fd224d306960965fec306 100644 (file)
@@ -1,6 +1,6 @@
 # version file
 
-SUBMINOR_VERSION:=45
+SUBMINOR_VERSION:=46
 
 # v0.9.34 requires libGDLContentStore v4.5.26
 # v0.9.26 requires libOGoContentStore v0.9.13
index 71a3dc462eb3fca42d7fc735df54c8c8eeeae174..1d86f5a5fd1117a627275545315978d66023dc07 100644 (file)
@@ -1,3 +1,8 @@
+2005-07-11  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * UIxCalView.h: added -setAppointments: to the public API
+         (for subclassers) (v0.9.128)
+
 2005-07-08  Marcus Mueller  <znek@mulle-kybernetik.com>
 
        * v0.9.127
index cb3486e1a9a1b70634d0f656a6378a07a0dcb322..f0b1a316b270bddd6c0b7237ef854fdaee494992 100644 (file)
@@ -40,6 +40,8 @@
 /* accessors */
 
 - (NSArray *)appointments;
+- (void)setAppointments:(NSArray *)_apts;
+
 - (NSArray *)allDayApts;
 - (id)appointment;
 - (BOOL)isMyApt;
index 762af8232f410b5b7de800cde36f8fa34bcf5756..b5a7494a0995c61dea6f85e613865761bcf2a4b1 100644 (file)
@@ -1,6 +1,6 @@
 # Version file
 
-SUBMINOR_VERSION:=127
+SUBMINOR_VERSION:=128
 
 # v0.9.123 requires Appointments v0.9.35
 # v0.9.123 requires SOGoUI       v0.9.24
index 6c75b528d66ea2d0d6df6daebcd3375c9410649a..790131407a1d4ee03b5d745c83552adaaa5d8863 100644 (file)
@@ -1,3 +1,8 @@
+2005-07-12  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * UIxAppointmentEditor.wox: moved the 'checkForConflicts' button near
+         the 'save' button as requested
+
 2005-07-08  Marcus Mueller  <znek@mulle-kybernetik.com>
 
        * UIxCalDayOverview.wox, UIxCalWeekOverview.wox,
index a99719bc72939c4e4eafeb162cd18473ed8a069a..ee4d18b3bab91144aa837e7040acd94c2b8268cf 100644 (file)
                   </span>
                 </td>
               </tr>
-              <tr valign="top">
-                <td align="right" width="15%">
-                  <span class="aptview_text">
-                    <var:string label:value="Constraints" />:
-                  </span>
-                </td>
-                <td align="left" bgcolor="#FFFFF0">
-                  <span class="aptview_text">
-                    <input type="checkbox"
-                           var:selection="checkForConflicts"
-                           var:checked="checkForConflicts"
-                    /> <var:string label:value="check for conflicts" />
-                  </span>
-                </td>
-              </tr>
             </table>
           </td>
         </tr>
             </table>
           </td>
         </tr>
+        <tr valign="top">
+          <td>
+            <span class="aptview_text">
+              <input type="checkbox"
+                     var:selection="checkForConflicts"
+                     var:checked="checkForConflicts"
+              /> <var:string label:value="check for conflicts" />
+            </span>
+          </td>
+        </tr>
         <tr>
           <td>
               <input type="submit" label:value="Save" name="save:method" />