]> err.no Git - scalable-opengroupware.org/commitdiff
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1265 d1b88da0-ebda-0310...
authorwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Sun, 18 Nov 2007 10:17:54 +0000 (10:17 +0000)
committerwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Sun, 18 Nov 2007 10:17:54 +0000 (10:17 +0000)
98 files changed:
ChangeLog
NEWS
SOPE/NGCards/CardElement.h
SOPE/NGCards/CardElement.m
SOPE/NGCards/CardGroup.h
SOPE/NGCards/CardGroup.m
SOPE/NGCards/ChangeLog
SOPE/NGCards/iCalCalendar.m
SOPE/NGCards/iCalEventChanges.m
SOPE/NGCards/iCalPerson.m
SOPE/sope-patchset-r1546.diff
SoObjects/Appointments/GNUmakefile
SoObjects/Appointments/SOGoAppointmentObject.h
SoObjects/Appointments/SOGoAppointmentObject.m
SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.html [new file with mode: 0644]
SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.wod [new file with mode: 0644]
SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.html [new file with mode: 0644]
SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.wod [new file with mode: 0644]
SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.html [new file with mode: 0644]
SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.wod [new file with mode: 0644]
SoObjects/Appointments/SOGoAptMailICalReply.h [new file with mode: 0644]
SoObjects/Appointments/SOGoAptMailICalReply.m [new file with mode: 0644]
SoObjects/Appointments/SOGoCalendarComponent.h
SoObjects/Appointments/SOGoCalendarComponent.m
SoObjects/Appointments/SOGoTaskObject.h
SoObjects/Appointments/SOGoTaskObject.m
SoObjects/Appointments/iCalEntityObject+SOGo.h
SoObjects/Appointments/iCalEntityObject+SOGo.m
SoObjects/Appointments/iCalEventChanges+SOGo.h [new file with mode: 0644]
SoObjects/Appointments/iCalEventChanges+SOGo.m [new file with mode: 0644]
SoObjects/Appointments/iCalPerson+SOGo.h [new file with mode: 0644]
SoObjects/Appointments/iCalPerson+SOGo.m [new file with mode: 0644]
SoObjects/Mailer/SOGoDraftObject.m
SoObjects/Mailer/SOGoMailBaseObject.m
SoObjects/Mailer/SOGoMailBodyPart.m
SoObjects/Mailer/SOGoMailObject+Draft.m
SoObjects/Mailer/SOGoMailObject.m
SoObjects/SOGo/LDAPSource.h
SoObjects/SOGo/LDAPSource.m
SoObjects/SOGo/LDAPUserManager.m
SoObjects/SOGo/SOGoContentObject.h
SoObjects/SOGo/SOGoContentObject.m
SoObjects/SOGo/SOGoGCSFolder.m
SoObjects/SOGo/SOGoUser.h
SoObjects/SOGo/SOGoUser.m
UI/Common/GNUmakefile
UI/Common/UIxAppNavView.m [deleted file]
UI/Common/UIxPageFrame.h
UI/Common/UIxPageFrame.m
UI/Common/UIxPrintPageFrame.m [deleted file]
UI/Common/UIxTabItem.m [deleted file]
UI/Common/UIxTabView.h [deleted file]
UI/Common/UIxTabView.m [deleted file]
UI/Contacts/UIxContactEditor.m
UI/MailPartViewers/English.lproj/Localizable.strings
UI/MailPartViewers/French.lproj/Localizable.strings
UI/MailPartViewers/German.lproj/Localizable.strings
UI/MailPartViewers/UIxMailPartICalActions.m
UI/MailPartViewers/UIxMailPartICalViewer.h
UI/MailPartViewers/UIxMailPartICalViewer.m
UI/MailPartViewers/UIxMailRenderingContext.m
UI/MailPartViewers/product.plist
UI/MailerUI/Toolbars/SOGoMailObject.toolbar
UI/MailerUI/UIxMailToSelection.m
UI/Scheduler/UIxAppointmentEditor.m
UI/Scheduler/UIxComponentEditor.h
UI/Scheduler/UIxComponentEditor.m
UI/Scheduler/UIxTaskEditor.m
UI/Templates/ContactsUI/UIxContactEditor.wox
UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox
UI/Templates/SchedulerUI/UIxComponentEditor.wox
UI/Templates/UIxAppNavView.wox [deleted file]
UI/Templates/UIxPageFrame.wox
UI/Templates/UIxPrintPageFrame.wox [deleted file]
UI/Templates/UIxToolbar.wox
UI/WebServerResources/ContactsUI.js
UI/WebServerResources/MailerUI.css
UI/WebServerResources/MailerUI.js
UI/WebServerResources/SOGoRootPage.js
UI/WebServerResources/SchedulerUI.css
UI/WebServerResources/SchedulerUI.js
UI/WebServerResources/UIxAclEditor.js
UI/WebServerResources/UIxAppointmentEditor.js
UI/WebServerResources/UIxAttendeesEditor.js
UI/WebServerResources/UIxCalUserRightsEditor.js
UI/WebServerResources/UIxComponentEditor.css
UI/WebServerResources/UIxComponentEditor.js
UI/WebServerResources/UIxContactEditor.js
UI/WebServerResources/UIxContactsUserFolders.js
UI/WebServerResources/UIxContactsUserRightsEditor.js
UI/WebServerResources/UIxMailEditor.css
UI/WebServerResources/UIxMailEditor.js
UI/WebServerResources/UIxMailPopupView.js
UI/WebServerResources/UIxMailToSelection.js
UI/WebServerResources/UIxMailUserRightsEditor.js
UI/WebServerResources/UIxTaskEditor.js
UI/WebServerResources/generic.css
UI/WebServerResources/generic.js

index f77b4a32c9f915f22b66cfbdea81de779c9db249..1f711462f46b8fef09cd80085b55945303c9a4b5 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,169 @@
+2007-11-18  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * UI/Scheduler/UIxTaskEditor.m ([UIxTaskEditor -saveAction]):
+       invoke saveComponent:.
+
+       * UI/Scheduler/UIxComponentEditor.m ([UIxComponentEditor -hasOrganizer])
+       ([UIxComponentEditor -organizerName]): new template accessor
+       related to displaying the event's organizer.
+       ([-containsConflict:_component]): removed method.
+       ([UIxComponentEditor -takeValuesFromRequest:_rqinContext:_ctx]):
+       set RSVP to "TRUE" on each attendee.
+
+       * UI/Scheduler/UIxAppointmentEditor.m ([UIxAppointmentEditor
+       -saveAction]): invoke saveComponent:.
+
+       * UI/MailPartViewers/UIxMailPartICalViewer.m
+       ([UIxMailPartICalViewer -isLoggedInUserTheOrganizer]): make use of
+       the -userIsOrganizer: category method.
+       ([-isLoggedInUserAnAttendee]): make use of -userIsParticipant:.
+       ([UIxMailPartICalViewer -hasSenderStatusChanged]): new template
+       accessor that determines whether the "Update" button should be
+       displayed.
+
+       * UI/MailPartViewers/UIxMailPartICalActions.m
+       ([UIxMailPartICalActions -deleteFromCalendarAction]): implemented
+       action.
+       ([UIxMailPartICalActions -updateUserStatusAction]): implemented
+       action.
+
+       * UI/Common/UIxPageFrame.m ([UIxPageFrame
+       -setCssFiles:newCSSFiles]): new accessor that enables the
+       sub-templates to specify extra CSS files to load.
+
+       * SoObjects/SOGo/SOGoUser.m ([SOGoUser
+       -homeFolderInContext:context]): cache the home folder of the user
+       object instead of the current user.
+
+       * SoObjects/SOGo/SOGoGCSFolder.m ([SOGoGCSFolder
+       -deleteEntriesWithIds:ids]): invokes the "prepareDelete" optional
+       method if the child object implements it.
+
+       * SoObjects/SOGo/SOGoContentObject.m ([-setContentString:])
+       removed method.
+
+       * SoObjects/SOGo/LDAPSource.m ([LDAPSource
+       -setBaseDN:newBaseDNIDField:newIDFieldCNField:newCNFieldUIDField:newUIDFieldmailFields:newMailFieldsandBindFields:newBindFields]):
+       take a new "mailFields" parameter defining an array of fields
+       where to look at when searching the user's emails. It defaults to
+       the standard "mail" LDAP field.
+
+       * SoObjects/Appointments/SOGoAptMailICalReply.[hm]: new
+       SoComponent implementing a template for ITIP replies.
+
+       * SoObjects/Appointments/iCalPerson+SOGo.m ([iCalPerson
+       -mailAddress]): new method that returns a properly formatted email
+       address for the specified person entry.
+       ([iCalPerson -uid]): new method that tests whether the user is
+       known to the system and if so, returns its user id.
+
+       * SoObjects/Appointments/iCalPerson+SOGo.[hm]: new category module.
+
+       * SoObjects/Appointments/iCalEventChanges+SOGo.m
+       ([iCalEventChanges -sequenceShouldBeIncreased]): determine whether
+       the changes involved need a sequence inscrease, based on the
+       RFC2446 (ITIP).
+
+       * SoObjects/Appointments/iCalEventChanges+SOGo.[hm]: new category
+       module.
+
+       * SoObjects/Appointments/iCalEvent+SOGo.m ([iCalEvent
+       -isStillRelevant]): new overriden method determining the relevance
+       of the current event based on its end date.
+
+       * SoObjects/Appointments/iCalEvent+SOGo.[hm]: new category module.
+
+       * SoObjects/Appointments/iCalEntityObject+SOGo.m
+       ([iCalEntityObject -attendeeUIDs]): new category methods that
+       returns an array containing the uids of the system-know attendees.
+       ([iCalEntityObject -isStillRelevant]): new template method.
+       ([iCalEntityObject -itipEntryWithMethod:method]): clone the
+       current entry calendar with the specified ITIP method.
+       ([iCalEntityObject -attendeesWithoutUser:user]): returns an array
+       of attendees while making sure the specified user is not listed.
+
+       * SoObjects/Appointments/SOGoCalendarComponent.m
+       ([SOGoCalendarComponent -calendar:create:secure]): new name for
+       -calendar:. Added a "secure" parameter that specifies whether a
+       stripped calendar instance is needed or not. Also, we no longer
+       cache the content to simplify handling of new data.
+       ([SOGoCalendarComponent -component:create:secure]): same as above.
+       ([SOGoCalendarComponent
+       -sendEMailUsingTemplateNamed:_pageNameforOldObject:_oldObjectandNewObject:_newObjecttoAttendees:_attendees]):
+       test whether the component is "still relevant" before sending an
+       email...
+       ([SOGoCalendarComponent -sendResponseToOrganizer]): new method for
+       sending ITIP replies.
+       ([SOGoCalendarComponent -getUIDsForICalPerson:iCalPerson]):
+       removed method. Replaced with -[iCalPerson uid] category method.
+
+       * SoObjects/Appointments/SOGoAppointmentObject.[hm]: rewrote
+       class. No longer override saveContentString:,
+       saveContentString:baseSequence:, .... Implemented the
+       saveComponent: and the prepareDelete methods instead. Those
+       methods are called only from the web methods. This avoids the
+       risks related to email sending and changes propagation.
+
+       * UI/Common/UIxTabItem.m: removed useless class module.
+
+       * UI/Common/UIxTabView.[hm]: removed useless class module.
+
+       * UI/Common/UIxPrintPageFrame.m: removed useless class module.
+
+       * UI/Common/UIxAppNavView.m: removed useless class module.
+
+2007-11-16  Ludovic Marcotte <ludovic@inverse.ca>
+
+        * SoObjects/Mailer/SOGoMailBaseObject.m
+       Fixed typo.
+
+       * SoObjects/Mailer/SOGoMailBodyPart.m
+       We also grok image/jpeg and return the SOGoMailBodyPart
+       for attachments fetching.
+
+       * SoObjects/Mailer/SOGoMailObject+Draft.m
+       Prevent a crash in case body decoding failed during
+       a reply.
+
+       * SoObjects/Mailer/SOGoMailObject.m
+       Improved body decoding during a reply to also try
+       latin1 as an encoding.
+
+       * UI/MailPartViewers/UIxMailRenderingContext.m
+       Greatly improved the display mechanisms for emails.
+       Also properly consider the content disposition for
+       most content types.
+
+       * UI/MailerUI/UIxMailToSelection.m
+       Removed worthless code.
+
+       * UI/WebServerResources/MailerUI.css
+       CSS fix for table views.
+
+2007-11-15  Ludovic Marcotte <ludovic@inverse.ca>
+
+        * UI/WebServerResources/MailerUI.js
+        We now check for empty selection and warn the
+        user about it when deleting messages
+        * SoObjects/Mailer/SOGoDraftObject.m
+        Correctly check for the presence of a subject
+        before attempting to forward a message from
+        the Drafts folder.
+
+        * SoObjects/Mailer/SOGoMailObject+Draft.m
+        We no longer use "[Fwd: ]" but simply "Fwd:"
+        when forwarding email messages.
+        * SoObjects/SOGo/SOGoUser.m
+        Modified the default forwarding format to be
+        inline instead of "attachment".
+        * SoObjects/Mailer/SOGoDraftObject.m
+        We now create and use a NGMimeContentDispositionHeaderField
+        in order to avoid encoding the whole Content-Disposition
+        header in case a non-ASCII char is present!
+
 2007-11-13  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
 
        * SoObjects/SOGo/iCalEntityObject+Utilities.m ([iCalEntityObject
diff --git a/NEWS b/NEWS
index 768a45e0814ec3e01216b21d83b84faa945d4113..584836317998e1c992379aeedd3a72747ba23129 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -18,7 +18,7 @@
 - improved support for multiple selection in tables and lists;
 - improved IE7 and Safari support: attendees selector, email file attachments;
 - updated PrototypeJS to version 1.6.0;
-- improved address completion in attendees selector;
+- improved address completion and freebusy timeline in attendees selector;
 - changed look of message composition window to Thunderbird 2.0;
 - countless bugfixes;
 
index ebd89f1e4529d4b5d1773d6adf2471cfc62b17e3..de633d07c4532aa4a493b69f5ea587d94f56859e 100644 (file)
@@ -33,7 +33,7 @@
 
 @class CardGroup;
 
-@interface CardElement : NSObject <NSCopying>
+@interface CardElement : NSObject <NSCopying, NSMutableCopying>
 {
   NSString *tag;
   NSMutableArray *values;
@@ -56,7 +56,7 @@
                values: (NSArray *) someValues;
 
 - (void) setParent: (CardGroup *) aParent;
-- (CardGroup *) parent;
+- (id) parent;
 
 - (void) setTag: (NSString *) aTag;
 
index cc36975564a1f2ddc886e530d33f206e22edbc55..9767359b6e8d1573e162c344111f07eb11d3d83a 100644 (file)
   parent = aParent;
 }
 
-- (CardGroup *) parent
+- (id) parent
 {
   return parent;
 }
   return new;
 }
 
+/* NSMutableCopying */
+- (id) mutableCopyWithZone: (NSZone *) aZone
+{
+  CardElement *new;
+
+  new = [[self class] new];
+  [new setTag: [tag mutableCopyWithZone: aZone]];
+  [new setGroup: [group mutableCopyWithZone: aZone]];
+  [new setParent: parent];
+  [new setValuesAsCopy: [values mutableCopyWithZone: aZone]];
+  [new setAttributesAsCopy: [attributes mutableCopyWithZone: aZone]];
+
+  return new;
+}
+
 @end
index 6800f07693eff300c1832010069f9c9014ff1477..1dfb6c7efe9c43004fd813b9d7abad4520329412 100644 (file)
@@ -29,7 +29,7 @@
 @class NSMutableArray;
 @class NSString;
 
-@interface CardGroup : CardElement <NSCopying>
+@interface CardGroup : CardElement <NSCopying, NSMutableCopying>
 {
   NSMutableArray *children;
 }
index fc45e695ea10a6adb2990a0aba7f89ee240618dd..33063496486bc10ee277ff6c9a0116d31697f88d 100644 (file)
@@ -159,29 +159,32 @@ static NGCardsSaxHandler *sax = nil;
   NSString *childTag;
   CardElement *newChild;
 
-  childTag = [aChild tag];
-  newChild = nil;
-  mappedClass = [self classForTag: [childTag uppercaseString]];
-  if (mappedClass)
+  if (aChild)
     {
-      if (![aChild isKindOfClass: mappedClass])
-        {
-          NSLog (@"warning: new child to entity '%@': '%@' converted to '%@'",
-                 tag, childTag, NSStringFromClass(mappedClass));
-          if ([aChild isKindOfClass: [CardGroup class]])
-            newChild = [(CardGroup *) aChild groupWithClass: mappedClass];
-          else
-            newChild = [aChild elementWithClass: mappedClass];
-        }
+      childTag = [aChild tag];
+      newChild = nil;
+      mappedClass = [self classForTag: [childTag uppercaseString]];
+      if (mappedClass)
+       {
+         if (![aChild isKindOfClass: mappedClass])
+           {
+             NSLog (@"warning: new child to entity '%@': '%@' converted to '%@'",
+                    tag, childTag, NSStringFromClass(mappedClass));
+             if ([aChild isKindOfClass: [CardGroup class]])
+               newChild = [(CardGroup *) aChild groupWithClass: mappedClass];
+             else
+               newChild = [aChild elementWithClass: mappedClass];
+           }
+       }
+      //   else
+      //     NSLog (@"warning: no mapped class for tag '%@'",
+      //            childTag);
+
+      if (!newChild)
+       newChild = aChild;
+      [children addObject: newChild];
+      [newChild setParent: self];
     }
-//   else
-//     NSLog (@"warning: no mapped class for tag '%@'",
-//            childTag);
-
-  if (!newChild)
-    newChild = aChild;
-  [children addObject: newChild];
-  [newChild setParent: self];
 }
 
 - (CardElement *) uniqueChildWithTag: (NSString *) aTag
@@ -214,17 +217,20 @@ static NGCardsSaxHandler *sax = nil;
   NSString *childTag;
   NSEnumerator *existing;
 
-  childTag = [aChild tag];
-  existing = [[self childrenWithTag: childTag] objectEnumerator];
-
-  currentChild = [existing nextObject];
-  while (currentChild)
+  if (aChild)
     {
-      [children removeObject: currentChild];
+      childTag = [aChild tag];
+      existing = [[self childrenWithTag: childTag] objectEnumerator];
+
       currentChild = [existing nextObject];
-    }
+      while (currentChild)
+       {
+         [children removeObject: currentChild];
+         currentChild = [existing nextObject];
+       }
 
-  [self addChild: aChild];
+      [self addChild: aChild];
+    }
 }
 
 - (CardElement *) firstChildWithTag: (NSString *) aTag;
@@ -260,12 +266,8 @@ static NGCardsSaxHandler *sax = nil;
   NSEnumerator *newChildren;
 
   newChildren = [someChildren objectEnumerator];
-  currentChild = [newChildren nextObject];
-  while (currentChild)
-    {
-      [self addChild: currentChild];
-      currentChild = [newChildren nextObject];
-    }
+  while ((currentChild = [newChildren nextObject]))
+    [self addChild: currentChild];
 }
 
 - (NSArray *) children
@@ -343,9 +345,14 @@ static NGCardsSaxHandler *sax = nil;
 
 - (void) setChildrenAsCopy: (NSMutableArray *) someChildren
 {
-  [children release];
-  children = someChildren;
-  [children retain];
+  NSEnumerator *list;
+  CardElement *currentChild;
+
+  ASSIGN (children, someChildren);
+
+  list = [children objectEnumerator];
+  while ((currentChild = [list nextObject]))
+    [currentChild setParent: self];
 }
 
 - (void) addChildWithTag: (NSString *) aTag
@@ -358,12 +365,8 @@ static NGCardsSaxHandler *sax = nil;
 
   newChild = [CardElement simpleElementWithTag: aTag value: aValue];
   types = [someTypes objectEnumerator];
-  type = [types nextObject];
-  while (type)
-    {
-      [newChild addType: type];
-      type = [types nextObject];
-    }
+  while ((type = [types nextObject]))
+    [newChild addType: type];
 
   [self addChild: newChild];
 }
@@ -400,6 +403,8 @@ static NGCardsSaxHandler *sax = nil;
     [children replaceObjectAtIndex: index withObject: newElement];
 }
 
+/* NSCopying */
+
 - (id) copyWithZone: (NSZone *) aZone
 {
   CardGroup *new;
@@ -410,4 +415,16 @@ static NGCardsSaxHandler *sax = nil;
   return new;
 }
 
+/* NSMutableCopying */
+
+- (id) mutableCopyWithZone: (NSZone *) aZone
+{
+  CardGroup *new;
+
+  new = [super mutableCopyWithZone: aZone];
+  [new setChildrenAsCopy: [children mutableCopyWithZone: aZone]];
+
+  return new;
+}
+
 @end
index ec0c0c5427e77f09170df467184e84ce022bd361..55ec998f42445f0bd33c9e1c8a117482f2155f79 100644 (file)
@@ -1,3 +1,21 @@
+2007-11-18  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * iCalPerson.m ([-rsvp]): return lowercase string.
+
+       * iCalCalendar.m ([iCalCalendar -setMethod:_value]): convert
+       method to uppercase before setting value.
+
+       * CardGroup.m ([CardGroup -addChild:aChild]): don't process nil
+       values for aChild.
+       ([CardGroup -setUniqueChild:aChild]): same as above.
+       ([CardGroup -setChildrenAsCopy:someChildren]): reparent the copied
+       children.
+       ([CardGroup -mutableCopyWithZone:aZone]): implemented
+       NSMutableCopying protocol.
+
+       * CardElement.m ([CardElement -mutableCopyWithZone:aZone]):
+       implemented NSMutableCopying protocol.
+
 2007-11-12  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
 
        * iCalTimeZonePeriod.m ([iCalTimeZonePeriod
index 060509ee839a5a7d31e4a5c410c8d155f9933fdf..b478b7f91d465d02cb8a9b75a26513abcc6f7f18 100644 (file)
 
 - (void) setMethod: (NSString *) _value
 {
-  [[self uniqueChildWithTag: @"method"] setValue: 0 to: _value];
+  [[self uniqueChildWithTag: @"method"] setValue: 0
+                                       to: [_value uppercaseString]];
 }
 
 - (NSString *) method
index 4105c180199f5ee96addc664fda5893090d7a3f2..045e8ca0edd4dd5ff3d28ada5c7c18f156f61f41 100644 (file)
 - (id)initWithFromEvent:(iCalEvent *)_from toEvent:(iCalEvent *)_to {
   self = [super init];
   if(self) {
-    self->insertedAttendees = [[NSMutableArray alloc] init];
-    self->deletedAttendees  = [[NSMutableArray alloc] init];
-    self->updatedAttendees  = [[NSMutableArray alloc] init];
-    self->insertedAlarms    = [[NSMutableArray alloc] init];
-    self->deletedAlarms     = [[NSMutableArray alloc] init];
-    self->updatedAlarms     = [[NSMutableArray alloc] init];
-    self->updatedProperties = [[NSMutableArray alloc] init];
+    insertedAttendees = [NSMutableArray new];
+    deletedAttendees  = [NSMutableArray new];
+    updatedAttendees  = [NSMutableArray new];
+    insertedAlarms    = [NSMutableArray new];
+    deletedAlarms     = [NSMutableArray new];
+    updatedAlarms     = [NSMutableArray new];
+    updatedProperties = [NSMutableArray new];
     [self _trackAttendeeChanges:_from :_to];
     [self _trackPropertyChanges:_from :_to];
   }
 }
 
 - (void)dealloc {
-  [self->insertedAttendees release];
-  [self->deletedAttendees  release];
-  [self->updatedAttendees  release];
-  [self->insertedAlarms    release];
-  [self->deletedAlarms     release];
-  [self->updatedAlarms     release];
-  [self->updatedProperties release];
+  [insertedAttendees release];
+  [deletedAttendees  release];
+  [updatedAttendees  release];
+  [insertedAlarms    release];
+  [deletedAlarms     release];
+  [updatedAlarms     release];
+  [updatedProperties release];
   [super dealloc];
 }
 
       if([fp hasSameEmailAddress:tp]) {
         found = YES;
         if(![fp isEqualToPerson:tp]) {
-          [self->updatedAttendees addObject:tp];
+          [updatedAttendees addObject:tp];
         }
         break;
       }
     }
     if(!found) {
-      [self->deletedAttendees addObject:fp];
+      [deletedAttendees addObject:fp];
     }
   }
   for(t = 0; t < tcount; t++) {
       }
     }
     if(!found)
-      [self->insertedAttendees addObject:tp];
+      [insertedAttendees addObject:tp];
   }
 }
 
 
 - (void)_trackPropertyChanges:(iCalEvent *)_from :(iCalEvent *)_to {
   if(!IS_EQUAL([_from startDate], [_to startDate], isEqualToDate:))
-    [self->updatedProperties addObject:@"startDate"];
+    [updatedProperties addObject:@"startDate"];
   if(!IS_EQUAL([_from endDate], [_to endDate], isEqualToDate:))
-    [self->updatedProperties addObject:@"endDate"];
+    [updatedProperties addObject:@"endDate"];
   if(!IS_EQUAL([_from created], [_to created], isEqualToDate:))
-    [self->updatedProperties addObject:@"created"];
+    [updatedProperties addObject:@"created"];
   if(!IS_EQUAL([_from lastModified], [_to lastModified], isEqualToDate:))
-    [self->updatedProperties addObject:@"lastModified"];
+    [updatedProperties addObject:@"lastModified"];
   if(![_from durationAsTimeInterval] == [_to durationAsTimeInterval])
-    [self->updatedProperties addObject:@"duration"];
+    [updatedProperties addObject:@"duration"];
   if(!IS_EQUAL([_from summary], [_to summary], isEqualToString:))
-    [self->updatedProperties addObject:@"summary"];
+    [updatedProperties addObject:@"summary"];
   if(!IS_EQUAL([_from location], [_to location], isEqualToString:))
-    [self->updatedProperties addObject:@"location"];
+    [updatedProperties addObject:@"location"];
   if(!IS_EQUAL([_from comment], [_to comment], isEqualToString:))
-    [self->updatedProperties addObject:@"comment"];
+    [updatedProperties addObject:@"comment"];
   if(!IS_EQUAL([_from priority], [_to priority], isEqualToString:))
-    [self->updatedProperties addObject:@"priority"];
+    [updatedProperties addObject:@"priority"];
   if(!IS_EQUAL([_from status], [_to status], isEqualToString:))
-    [self->updatedProperties addObject:@"status"];
+    [updatedProperties addObject:@"status"];
   if(!IS_EQUAL([_from accessClass], [_to accessClass], isEqualToString:))
-    [self->updatedProperties addObject:@"accessClass"];
+    [updatedProperties addObject:@"accessClass"];
   if(!IS_EQUAL([_from sequence], [_to sequence], isEqualToNumber:))
-    [self->updatedProperties addObject:@"sequence"];
+    [updatedProperties addObject:@"sequence"];
   if(!IS_EQUAL([_from organizer], [_to organizer], isEqual:))
-    [self->updatedProperties addObject:@"organizer"];
+    [updatedProperties addObject:@"organizer"];
 }
 
 - (BOOL)hasChanges {
 }
 
 - (NSArray *)insertedAttendees {
-  return self->insertedAttendees;
+  return insertedAttendees;
 }
 - (NSArray *)deletedAttendees {
-  return self->deletedAttendees;
+  return deletedAttendees;
 }
 - (NSArray *)updatedAttendees {
-  return self->updatedAttendees;
+  return updatedAttendees;
 }
 
 - (NSArray *)insertedAlarms {
-  return self->insertedAlarms;
+  return insertedAlarms;
 }
 - (NSArray *)deletedAlarms {
-  return self->deletedAlarms;
+  return deletedAlarms;
 }
 - (NSArray *)updatedAlarms {
-  return self->updatedAlarms;
+  return updatedAlarms;
 }
 
 - (NSArray *)updatedProperties {
-  return self->updatedProperties;
+  return updatedProperties;
 }
 
 /* descriptions */
   ms = [NSMutableString stringWithCapacity:128];
   [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
   
-  [ms appendFormat:@" updatedProperties=%@", self->updatedProperties];
-  [ms appendFormat:@" insertedAttendees=%@", self->insertedAttendees];
-  [ms appendFormat:@" deletedAttendees=%@", self->deletedAttendees];
-  [ms appendFormat:@" updatedAttendees=%@", self->updatedAttendees];
+  [ms appendFormat:@" updatedProperties=%@", updatedProperties];
+  [ms appendFormat:@" insertedAttendees=%@", insertedAttendees];
+  [ms appendFormat:@" deletedAttendees=%@", deletedAttendees];
+  [ms appendFormat:@" updatedAttendees=%@", updatedAttendees];
   
   [ms appendString:@">"];
   return ms;
index 68e3deef5f3bcd5a494128cc78901f6d8f6fcc5b..11b76507a2ae05b015be0381d47b7df5062a8165 100644 (file)
@@ -85,7 +85,7 @@
 
 - (NSString *) rsvp
 {
-  return [self value: 0 ofAttribute: @"rsvp"];
+  return [[self value: 0 ofAttribute: @"rsvp"] lowercaseString];
 }
 
 // - (void)setXuid:(NSString *)_s {
index e26b5934b4b8313e1226152527779e008694160c..216e640f02da1f09968bc5b63c8b9410e44e2288 100644 (file)
@@ -443,6 +443,72 @@ Index: sope-mime/NGMail/NGSmtpClient.m
      [self->connection flush];
  
      reply = [self receiveReply];
+Index: sope-mime/NGMail/NGMimeMessageGenerator.m
+===================================================================
+--- sope-mime/NGMail/NGMimeMessageGenerator.m  (révision 1546)
++++ sope-mime/NGMail/NGMimeMessageGenerator.m  (copie de travail)
+@@ -86,37 +86,40 @@
+   char         *des    = NULL;
+   unsigned int cnt;
+   BOOL         doEnc;
+-  NSString     *str;
++//   NSString     *str;
+   // TODO: this s***s big time!
++//   NSLog (@"class: '%@'", NSStringFromClass ([_data class]));
++// #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
++//   str = [[NSString alloc] initWithData:_data
++//                           encoding:NSISOLatin1StringEncoding];
++//   str = [str autorelease];
++  
++// #else
++//   str = [[NSString alloc] initWithData:_data
++//                           encoding:NSISOLatin9StringEncoding];
++// #endif
++//   bytes  = [str cString];
++//   length = [str cStringLength];
+-#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
+-  str = [[NSString alloc] initWithData:_data
+-                          encoding:NSISOLatin1StringEncoding];
+-#else
+-  str = [[NSString alloc] initWithData:_data
+-                          encoding:NSISOLatin9StringEncoding];
+-#endif
+-  str = [str autorelease];
+-  
+-  bytes  = [str cString];
+-  length = [str cStringLength];
+-  
++  bytes = [_data bytes];
++  length = [_data length];
++
+   /* check whether we need to encode */
+-  
+-  for (cnt = 0, doEnc = NO; cnt < length; cnt++) {
+-    if ((unsigned char)bytes[cnt] > 127) {
++  cnt = 0;
++  doEnc = NO;
++  while (!doEnc && cnt < length)
++    if ((unsigned char)bytes[cnt] > 127)
+       doEnc = YES;
+-      break;
+-    }
+-  }
+-  
++    else
++      cnt++;
++
+   if (!doEnc)
+     return _data;
+   
+   /* encode quoted printable */
+   {
+-    char        iso[]     = "=?iso-8859-15?q?";
++    char        iso[]     = "=?utf-8?q?";
+     unsigned    isoLen    = 16;
+     char        isoEnd[]  = "?=";
+     unsigned    isoEndLen = 2;
 Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m
 ===================================================================
 --- sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m       (révision 1546)
@@ -557,6 +623,15 @@ Index: sope-mime/NGMime/NGMimePartParser.m
 ===================================================================
 --- sope-mime/NGMime/NGMimePartParser.m        (révision 1546)
 +++ sope-mime/NGMime/NGMimePartParser.m        (copie de travail)
+@@ -227,7 +227,7 @@
+ }
+ + (NSStringEncoding)defaultHeaderFieldEncoding {
+-  return NSISOLatin1StringEncoding;
++  return NSUTF8StringEncoding;
+ }
+ - (id)valueOfHeaderField:(NSString *)_name data:(id)_data {
 @@ -1091,7 +1091,10 @@
    id<NGMimeBodyParser> bodyParser   = nil;
    
@@ -569,6 +644,142 @@ Index: sope-mime/NGMime/NGMimePartParser.m
    contentType = ([ctype isKindOfClass:[NGMimeType class]])
      ? ctype
      : [NGMimeType mimeType:[ctype stringValue]];
+Index: sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m
+===================================================================
+--- sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m    (révision 1546)
++++ sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m    (copie de travail)
+@@ -49,80 +49,70 @@
+   
+   // TODO: move the stuff below to some NSString or NSData category?
+   
+-  data = [NSMutableData dataWithCapacity:64];
++  data = [NSMutableData dataWithCapacity: 64];
+   tmp  = [field type];
+   [data appendBytes:[tmp cString] length:[tmp length]];
+   tmp = [field filename];
+   if (tmp != nil) {
+     [data appendBytes:"; " length:2];
+     [data appendBytes:"filename=\"" length:10];
+-    {
+-      unsigned char *ctmp;
+-      int  cnt, len;
+-      BOOL doEnc;
+-      
+-      // TODO: unicode?
+-      len  = [tmp cStringLength];
+-      ctmp = malloc(len + 3);
+-      [tmp getCString:(char *)ctmp]; ctmp[len] = '\0';
+-      cnt  = 0;
+-      doEnc = NO;
+-      while (cnt < len) {
+-        if ((unsigned char)ctmp[cnt] > 127) {
+-          doEnc = YES;
+-          break;
+-        }
+-        cnt++;
++    
++    NSData *d;
++    unsigned char* bytes;
++    unsigned length;
++    int  cnt;
++    BOOL doEnc;
++
++    //d = [tmp dataUsingEncoding: NSUTF8StringEncoding];
++    //bytes = [d bytes];
++    //length = [d length];
++    bytes = [tmp cStringUsingEncoding: NSUTF8StringEncoding];
++    length = strlen(bytes);
++    
++    cnt = 0;
++    doEnc = NO;
++    while (cnt < length) {
++      if ((unsigned char)bytes[cnt] > 127) {
++      doEnc = YES;
++      break;
+       }
+-      if (doEnc) {
+-        char        iso[]     = "=?iso-8859-15?q?";
+-        unsigned    isoLen    = 16;
+-        char        isoEnd[]  = "?=";
+-        unsigned    isoEndLen = 2;
+-        unsigned    desLen;
+-        char        *des;
+-      
+-        if (ctmp) free(ctmp);
+-        {
+-          NSData *data;
++      cnt++;
++    }
+-#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
+-          data = [tmp dataUsingEncoding:NSISOLatin1StringEncoding];
+-#else
+-          data = [tmp dataUsingEncoding:NSISOLatin9StringEncoding];
+-#endif
+-
+-          len  = [data length];
+-          ctmp =  malloc(len+1);
+-          [data getBytes:ctmp];  ctmp[len] = '\0';
+-        }
+-          
+-        desLen = len * 3 + 20;
+-        des    = calloc(desLen + 10, sizeof(char));
+-      
+-        memcpy(des, ctmp, cnt);
+-        memcpy(des + cnt, iso, isoLen);
+-        desLen =
+-        NGEncodeQuotedPrintableMime((unsigned char *)ctmp + cnt, len - cnt,
+-                                    (unsigned char *)des + cnt + isoLen,
+-                                    desLen - cnt - isoLen);
+-        if ((int)desLen != -1) {
+-          memcpy(des + cnt + isoLen + desLen, isoEnd, isoEndLen);
+-          [data appendBytes:des length:(cnt + isoLen + desLen + isoEndLen)];
+-        }
+-        else {
++    if (doEnc)
++      {
++      char        iso[]     = "=?utf-8?q?";
++      unsigned    isoLen    = 10;
++      char        isoEnd[]  = "?=";
++      unsigned    isoEndLen = 2;
++      int    desLen;
++      char        *des;
++      
++      desLen = length * 3 + 20;
++      
++      des = calloc(desLen + 2, sizeof(char));
++      
++      memcpy(des, iso, isoLen);
++      desLen = NGEncodeQuotedPrintableMime((unsigned char *)bytes, length,
++                                           (unsigned char *)(des + isoLen),
++                                           desLen - isoLen);
++      if (desLen != -1) {
++        memcpy(des + isoLen + desLen, isoEnd, isoEndLen);
++        [data appendBytes:des length:(isoLen + desLen + isoEndLen)];
++      }
++      else {
+           [self logWithFormat:@"WARNING(%s:%i): An error occour during "
+               @"quoted-printable decoding",
+                 __PRETTY_FUNCTION__, __LINE__];
+-        }
+-        if (des) free(des);
++        if (des != NULL) free(des);
++      }
+       }
+-      else {
+-        [data appendBytes:ctmp length:len];
++    else
++      {
++      [data appendBytes:[tmp cString] length:[tmp length]];
+       }
+-    }
+-      //      [data appendBytes:[tmp cString] length:[tmp length]];
+-      [data appendBytes:"\"" length:1];
++
++    [data appendBytes:"\"" length:1];
+   }
+   return data;
+ }
 Index: sope-core/NGExtensions/FdExt.subproj/NSString+Encoding.m
 ===================================================================
 --- sope-core/NGExtensions/FdExt.subproj/NSString+Encoding.m   (révision 1546)
index 99a7b16590e8521b44b5e9e8fd7dca8860263f22..8924bd4bef0f4aa15743398e249f9b0f8c6bc61b 100644 (file)
@@ -10,6 +10,9 @@ Appointments_OBJC_FILES = \
        Product.m                       \
        NSArray+Appointments.m          \
        iCalEntityObject+SOGo.m         \
+       iCalEvent+SOGo.m                \
+       iCalEventChanges+SOGo.m         \
+       iCalPerson+SOGo.m       \
        \
        SOGoCalendarComponent.m         \
        SOGoAppointmentObject.m         \
@@ -24,6 +27,7 @@ Appointments_OBJC_FILES = \
        SOGoAptMailUpdate.m             \
        SOGoAptMailRemoval.m            \
        SOGoAptMailDeletion.m           \
+       SOGoAptMailICalReply.m          \
 
 Appointments_RESOURCE_FILES +=         \
        Version                         \
@@ -31,14 +35,17 @@ Appointments_RESOURCE_FILES +=              \
 
 Appointments_COMPONENTS +=     \
        SOGoAptMailEnglishInvitation.wo \
+       SOGoAptMailEnglishICalReply.wo  \
        SOGoAptMailEnglishUpdate.wo     \
        SOGoAptMailEnglishRemoval.wo    \
        SOGoAptMailEnglishDeletion.wo   \
        SOGoAptMailFrenchInvitation.wo  \
+       SOGoAptMailFrenchICalReply.wo   \
        SOGoAptMailFrenchUpdate.wo      \
        SOGoAptMailFrenchRemoval.wo     \
        SOGoAptMailFrenchDeletion.wo    \
        SOGoAptMailGermanInvitation.wo  \
+       SOGoAptMailGermanICalReply.wo   \
        SOGoAptMailGermanUpdate.wo      \
        SOGoAptMailGermanRemoval.wo     \
        SOGoAptMailGermanDeletion.wo    \
index 8262970f22f9eafa769799a54b89c27eee80ec30..7b3b237952d6149df3d2dbc20081f5e0f94c0def 100644 (file)
 
 @interface SOGoAppointmentObject : SOGoCalendarComponent
 
+- (NSException *) changeParticipationStatus: (NSString *) _status;
+
 /* "iCal multifolder saves" */
 
-- (NSException *) saveContentString: (NSString *) _iCal
-                       baseSequence: (int) _v;
-- (NSException *) deleteWithBaseSequence: (int) _v;
-- (NSException *) saveContentString: (NSString *) _iCalString;
+// - (NSException *) saveContentString: (NSString *) _iCal
+//                        baseSequence: (int) _v;
+// - (NSException *) deleteWithBaseSequence: (int) _v;
+// - (NSException *) saveContentString: (NSString *) _iCalString;
 
 @end
 
index 12f97b38a0e13f33b7df263f62e9f49f95c719c8..58de668a1e0bbde17d5ec09b0ef4f5af7b203c9a 100644 (file)
@@ -22,7 +22,7 @@
 #import <Foundation/NSCalendarDate.h>
 
 #import <NGObjWeb/NSException+HTTP.h>
-#import <NGObjWeb/WOContext.h>
+#import <NGObjWeb/WOContext+SoObjects.h>
 #import <NGExtensions/NSNull+misc.h>
 #import <NGExtensions/NSObject+Logs.h>
 #import <NGCards/iCalCalendar.h>
 #import <NGCards/iCalEventChanges.h>
 #import <NGCards/iCalPerson.h>
 
+#import <SoObjects/SOGo/iCalEntityObject+Utilities.h>
 #import <SoObjects/SOGo/LDAPUserManager.h>
 #import <SoObjects/SOGo/NSArray+Utilities.h>
 #import <SoObjects/SOGo/SOGoObject.h>
 #import <SoObjects/SOGo/SOGoPermissions.h>
+#import <SoObjects/SOGo/SOGoUser.h>
 #import <SoObjects/SOGo/WORequest+SOGo.h>
 
 #import "NSArray+Appointments.h"
 #import "SOGoAppointmentFolder.h"
+#import "iCalEventChanges+SOGo.h"
+#import "iCalEntityObject+SOGo.h"
+#import "iCalPerson+SOGo.h"
 
 #import "SOGoAppointmentObject.h"
 
   return @"vevent";
 }
 
-/* iCal handling */
-- (NSArray *) attendeeUIDsFromAppointment: (iCalEvent *) _apt
+- (SOGoAppointmentObject *) _lookupEvent: (NSString *) eventUID
+                                 forUID: (NSString *) uid
 {
-  LDAPUserManager *um;
-  NSMutableArray *uids;
-  NSArray *attendees;
-  unsigned i, count;
-  NSString *email, *uid;
-  
-  if (![_apt isNotNull])
-    return nil;
-  
-  if ((attendees = [_apt attendees]) == nil)
-    return nil;
-  count = [attendees count];
-  uids = [NSMutableArray arrayWithCapacity:count + 1];
-  
-  um = [LDAPUserManager sharedUserManager];
-  
-  /* add organizer */
-  
-  email = [[_apt organizer] rfc822Email];
-  if ([email isNotNull]) {
-    uid = [um getUIDForEmail: email];
-    if ([uid isNotNull]) {
-      [uids addObject:uid];
-    }
-    else
-      [self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
-  }
-
-  /* add attendees */
-  
-  for (i = 0; i < count; i++)
+  SOGoAppointmentFolder *folder;
+  SOGoAppointmentObject *object;
+  NSString *possibleName;
+
+  folder = [container lookupCalendarFolderForUID: uid];
+  object = [folder lookupName: nameInContainer
+                  inContext: context acquire: NO];
+  if ([object isKindOfClass: [NSException class]])
     {
-      iCalPerson *person;
-    
-      person = [attendees objectAtIndex:i];
-      email  = [person rfc822Email];
-      if (![email isNotNull]) continue;
-    
-      uid = [um getUIDForEmail:email];
-      if (![uid isNotNull]) {
-        [self logWithFormat:@"Note: got no uid for email: '%@'", email];
-        continue;
-      }
-      if (![uids containsObject:uid])
-        [uids addObject:uid];
+      possibleName = [folder resourceNameForEventUID: eventUID];
+      if (possibleName)
+       {
+         object = [folder lookupName: nameInContainer
+                          inContext: context acquire: NO];
+         if ([object isKindOfClass: [NSException class]])
+           object = nil;
+       }
     }
 
-  return uids;
-}
+  if (!object)
+    object = [SOGoAppointmentObject objectWithName: nameInContainer
+                                   inContainer: folder];
 
-/* store in all the other folders */
+  return object;
+}
 
-- (NSException *) saveContentString: (NSString *) _iCal
-                            inUIDs: (NSArray *) _uids
+- (void) _addOrUpdateEvent: (iCalEvent *) event
+                   forUID: (NSString *) uid
 {
-  NSEnumerator *e;
-  id folder;
-  NSException *allErrors = nil;
-  NSException           *error;
-  SOGoAppointmentObject *apt;
-
-  e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
-       objectEnumerator];
-  while ((folder = [e nextObject]))
+  SOGoAppointmentObject *object;
+  NSString *iCalString, *userLogin;
+
+  userLogin = [[context activeUser] login];
+  if (![uid isEqualToString: userLogin])
     {
-      apt = [SOGoAppointmentObject objectWithName: nameInContainer
-                                  inContainer: folder];
-      error = [apt primarySaveContentString:_iCal];
-      if (error)
-       {
-         [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
-         // TODO: make compound
-         allErrors = error;
-       }
+      object = [self _lookupEvent: [event uid] forUID: uid];
+      iCalString = [[event parent] versitString];
+      [object saveContentString: iCalString];
     }
-
-  return allErrors;
 }
 
-- (NSException *) deleteInUIDs: (NSArray *) _uids
+- (void) _removeEventFromUID: (NSString *) uid
 {
-  NSEnumerator *e;
-  id folder;
-  NSException *allErrors = nil;
-  NSException           *error;
-  SOGoAppointmentObject *apt;
-  
-  e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
-       objectEnumerator];
-  while ((folder = [e nextObject]))
+  SOGoAppointmentFolder *folder;
+  SOGoAppointmentObject *object;
+  NSString *userLogin;
+
+  userLogin = [[context activeUser] login];
+  if (![uid isEqualToString: userLogin])
     {
-      apt = [folder lookupName: [self nameInContainer]
-                   inContext: context
-                   acquire:NO];
-      if ([apt isKindOfClass: [NSException class]]) {
-       [self logWithFormat: @"%@", [(NSException *) apt reason]];
-       continue;
-      }
-    
-      if ((error = [apt primaryDelete]) != nil) {
-       [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
-       // TODO: make compound
-       allErrors = error;
-      }
+      folder = [container lookupCalendarFolderForUID: uid];
+      object = [folder lookupName: nameInContainer
+                      inContext: context acquire: NO];
+      if (![object isKindOfClass: [NSException class]])
+       [object delete];
     }
-
-  return allErrors;
 }
 
-/* "iCal multifolder saves" */
-- (BOOL) _aptIsStillRelevant: (iCalEvent *) appointment
+- (void) _handleRemovedUsers: (NSArray *) attendees
 {
-  NSCalendarDate *now;
-
-  now = [NSCalendarDate calendarDate];
+  NSEnumerator *enumerator;
+  iCalPerson *currentAttendee;
+  NSString *currentUID;
 
-  return ([[appointment endDate] earlierDate: now] == now);
+  enumerator = [attendees objectEnumerator];
+  while ((currentAttendee = [enumerator nextObject]))
+    {
+      currentUID = [currentAttendee uid];
+      if (currentUID)
+       [self _removeEventFromUID: currentUID];
+    }
 }
 
-- (NSException *) saveContentString: (NSString *) _iCal
-                       baseSequence: (int) _v
+- (void) _requireResponseFromAttendees: (NSArray *) attendees
 {
-  /* 
-     Note: we need to delete in all participants folders and send iMIP messages
-           for all external accounts.
-     
-     Steps:
-     - fetch stored content
-     - parse old content
-     - check if sequence matches (or if 0=ignore)
-     - extract old attendee list + organizer (make unique)
-     - parse new content (ensure that sequence is increased!)
-     - extract new attendee list + organizer (make unique)
-     - make a diff => new, same, removed
-     - write to new, same
-     - delete in removed folders
-     - send iMIP mail for all folders not found
-  */
-  LDAPUserManager *um;
-  iCalEvent *oldApt, *newApt;
-  iCalEventChanges *changes;
-  iCalPerson *organizer;
-  NSString *oldContent, *uid;
-  NSArray *uids, *props;
-  NSMutableArray *attendees, *storeUIDs, *removedUIDs;
-  NSException *storeError, *delError;
-  BOOL updateForcesReconsider;
-  
-  if ([[context request] handledByDefaultHandler])
-    {
-      updateForcesReconsider = NO;
-
-      if ([_iCal length] == 0)
-       return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
-                           reason: @"got no iCalendar content to store!"];
+  NSEnumerator *enumerator;
+  iCalPerson *currentAttendee;
 
-      um = [LDAPUserManager sharedUserManager];
-
-      /* handle old content */
-  
-      oldContent = [self contentAsString]; /* if nil, this is a new appointment */
-      if ([oldContent length] == 0)
-       {
-         /* new appointment */
-         [self debugWithFormat:@"saving new appointment: %@", _iCal];
-         oldApt = nil;
-       }
-      else
-       oldApt = (iCalEvent *) [self component: NO];
-  
-      /* compare sequence if requested */
-      if (_v != 0) {
-       // TODO
-      }
-  
-      /* handle new content */
-  
-      newApt = (iCalEvent *) [self component: NO];
-      if (!newApt)
-       return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
-                           reason: @"could not parse iCalendar content!"];
-
-      /* diff */
-  
-      changes = [iCalEventChanges changesFromEvent: oldApt toEvent: newApt];
-      uids = [self getUIDsForICalPersons: [changes deletedAttendees]];
-      removedUIDs = [NSMutableArray arrayWithArray: uids];
-
-      uids = [self getUIDsForICalPersons: [newApt attendees]];
-      storeUIDs = [NSMutableArray arrayWithArray: uids];
-      props = [changes updatedProperties];
+  enumerator = [attendees objectEnumerator];
+  while ((currentAttendee = [enumerator nextObject]))
+    {
+      [currentAttendee setRsvp: @"TRUE"];
+      [currentAttendee setParticipationStatus: iCalPersonPartStatNeedsAction];
+    }
+}
 
-      /* detect whether sequence has to be increased */
-      if ([changes hasChanges])
-       [newApt increaseSequence];
+- (void) _handleSequenceUpdateInEvent: (iCalEvent *) newEvent
+                   ignoringAttendees: (NSArray *) attendees
+                        fromOldEvent: (iCalEvent *) oldEvent
+{
+  NSMutableArray *updateAttendees, *updateUIDs;
+  NSEnumerator *enumerator;
+  iCalPerson *currentAttendee;
+  NSString *currentUID;
 
-      /* preserve organizer */
+  updateAttendees = [NSMutableArray arrayWithArray: [newEvent attendees]];
+  [updateAttendees removeObjectsInArray: attendees];
 
-      organizer = [newApt organizer];
-      uid = [self getUIDForICalPerson: organizer];
-      if (!uid)
-       uid = [self ownerInContext: nil];
-      if (uid)
-       {
-         [storeUIDs addObjectUniquely: uid];
-         [removedUIDs removeObject: uid];
-       }
+  updateUIDs = [NSMutableArray arrayWithCapacity: [updateAttendees count]];
+  enumerator = [updateAttendees objectEnumerator];
+  while ((currentAttendee = [enumerator nextObject]))
+    {
+      currentUID = [currentAttendee uid];
+      if (currentUID)
+       [self _addOrUpdateEvent: newEvent
+             forUID: currentUID];
+    }
 
-      /* organizer might have changed completely */
+  [self sendEMailUsingTemplateNamed: @"Update"
+       forOldObject: oldEvent
+       andNewObject: [newEvent itipEntryWithMethod: @"request"]
+       toAttendees: updateAttendees];
+}
 
-      if (oldApt && ([props containsObject: @"organizer"]))
-       {
-         uid = [self getUIDForICalPerson: [oldApt organizer]];
-         if (uid && ![storeUIDs containsObject: uid])
-           [removedUIDs addObjectUniquely: uid];
-       }
+- (void) _handleAddedUsers: (NSArray *) attendees
+                fromEvent: (iCalEvent *) newEvent
+{
+  NSEnumerator *enumerator;
+  iCalPerson *currentAttendee;
+  NSString *currentUID;
 
-      [self debugWithFormat: @"UID ops:\n  store: %@\n  remove: %@",
-           storeUIDs, removedUIDs];
+  enumerator = [attendees objectEnumerator];
+  while ((currentAttendee = [enumerator nextObject]))
+    {
+      currentUID = [currentAttendee uid];
+      if (currentUID)
+       [self _addOrUpdateEvent: newEvent
+             forUID: currentUID];
+    }
+}
 
-      /* if time did change, all participants have to re-decide ...
-       * ... exception from that rule: the organizer
-       */
+- (void) _handleUpdatedEvent: (iCalEvent *) newEvent
+               fromOldEvent: (iCalEvent *) oldEvent
+{
+  NSArray *attendees;
+  iCalEventChanges *changes;
 
-      if (oldApt
-         && ([props containsObject: @"startDate"]
-             || [props containsObject: @"endDate"]
-             || [props containsObject: @"duration"]))
-       {
-         NSArray  *ps;
-         unsigned i, count;
-    
-         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 parent] versitString];
-         updateForcesReconsider = YES;
-       }
+  changes = [newEvent getChangesRelativeToEvent: oldEvent];
+  attendees = [changes deletedAttendees];
+  if ([attendees count])
+    {
+      [self _handleRemovedUsers: attendees];
+      [self sendEMailUsingTemplateNamed: @"Deletion"
+           forOldObject: oldEvent
+           andNewObject: [newEvent itipEntryWithMethod: @"cancel"]
+           toAttendees: attendees];
+    }
 
-      /* perform storing */
+  attendees = [changes insertedAttendees];
+  if ([changes sequenceShouldBeIncreased])
+    {
+      [newEvent increaseSequence];
+      [self _requireResponseFromAttendees: [newEvent attendees]];
+      [self _handleSequenceUpdateInEvent: newEvent
+           ignoringAttendees: attendees
+           fromOldEvent: oldEvent];
+    }
+  else
+    [self _requireResponseFromAttendees: attendees];
 
-      storeError = [self saveContentString: _iCal inUIDs: storeUIDs];
-      delError = [self deleteInUIDs: removedUIDs];
+  if ([attendees count])
+    {
+      [self _handleAddedUsers: attendees fromEvent: newEvent];
+      [self sendEMailUsingTemplateNamed: @"Invitation"
+           forOldObject: oldEvent
+           andNewObject: [newEvent itipEntryWithMethod: @"request"]
+           toAttendees: attendees];
+    }
+}
 
-      // TODO: make compound
-      if (storeError != nil) return storeError;
-      if (delError   != nil) return delError;
+- (void) saveComponent: (iCalEvent *) newEvent
+{
+  iCalEvent *oldEvent;
+  NSArray *attendees;
 
-      /* email notifications */
-      if ([self sendEMailNotifications]
-         && [self _aptIsStillRelevant: newApt])
+  [[newEvent parent] setMethod: @""];
+  if ([newEvent userIsOrganizer: [context activeUser]])
+    {
+      oldEvent = [self component: NO secure: NO];
+      if (oldEvent)
+       [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent];
+      else
        {
-         iCalEvent *requestApt;
-
-         requestApt = [newApt copy];
-         [(iCalCalendar *) [requestApt parent] setMethod: @"request"];
-         attendees
-           = [NSMutableArray arrayWithArray: [changes insertedAttendees]];
-         [attendees removePerson: organizer];
-         [self sendEMailUsingTemplateNamed: @"Invitation"
-               forOldObject: nil
-               andNewObject: requestApt
-               toAttendees: attendees];
-         [requestApt release];
-
-         if (updateForcesReconsider)
-           {
-             iCalEvent *updatedApt;
-    
-             updatedApt = [newApt copy];
-             [(iCalCalendar *) [updatedApt parent] setMethod: @"request"];
-             attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
-             [attendees removeObjectsInArray:[changes insertedAttendees]];
-             [attendees removePerson:organizer];
-             [self sendEMailUsingTemplateNamed: @"Update"
-                   forOldObject: oldApt
-                   andNewObject: updatedApt
-                   toAttendees: attendees];
-             [updatedApt release];
-           }
-
-         attendees
-           = [NSMutableArray arrayWithArray: [changes deletedAttendees]];
-         [attendees removePerson: organizer];
+         attendees = [newEvent attendeesWithoutUser: [context activeUser]];
          if ([attendees count])
            {
-             iCalEvent *cancelledApt;
-    
-             cancelledApt = [newApt copy];
-             [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
-             [self sendEMailUsingTemplateNamed: @"Removal"
+             [self _handleAddedUsers: attendees fromEvent: newEvent];
+             [self sendEMailUsingTemplateNamed: @"Invitation"
                    forOldObject: nil
-                   andNewObject: cancelledApt
+                   andNewObject: [newEvent itipEntryWithMethod: @"request"]
                    toAttendees: attendees];
-             [cancelledApt release];
            }
+
+         if (![[newEvent attendees] count])
+           [[newEvent uniqueChildWithTag: @"organizer"] setValue: 0
+                                                        to: @""];
        }
     }
-  else
-    [self primarySaveContentString: _iCal];
 
-  return nil;
+  [super saveComponent: newEvent];
 }
 
-- (NSException *) deleteWithBaseSequence: (int)_v
+- (NSException *) _updateAttendee: (iCalPerson *) attendee
+                     forEventUID: (NSString *) eventUID
+                    withSequence: (NSNumber *) sequence
+                          forUID: (NSString *) uid
 {
-  /* 
-     Note: We need to delete in all participants folders and send iMIP messages
-           for all external accounts.
-          Delete is basically identical to save with all attendees and the
-          organizer being deleted.
-
-     Steps:
-     - fetch stored content
-     - parse old content
-     - check if sequence matches (or if 0=ignore)
-     - extract old attendee list + organizer (make unique)
-     - delete in removed folders
-     - send iMIP mail for all folders not found
-  */
-  iCalEvent *apt;
-  NSMutableArray *attendees, *removedUIDs;
+  SOGoAppointmentObject *eventObject;
+  iCalEvent *event;
+  iCalPerson *otherAttendee;
+  NSString *iCalString;
   NSException *error;
 
-  if ([[context request] handledByDefaultHandler])
+  error = nil;
+
+  eventObject = [self _lookupEvent: eventUID forUID: uid];
+  if (![eventObject isNew])
     {
-  /* load existing content */
-      apt = (iCalEvent *) [self component: NO];
-  
-  /* compare sequence if requested */
+      event = [eventObject component: NO secure: NO];
+      if ([[event sequence] compare: sequence]
+         == NSOrderedSame)
+       {
+         otherAttendee = [event findParticipant: [context activeUser]];
+         [otherAttendee setPartStat: [attendee partStat]];
+         iCalString = [[event parent] versitString];
+         error = [eventObject saveContentString: iCalString];
+       }
+    }
 
-//   if (_v != 0) {
-//     // TODO
-//   }
-  
-      removedUIDs = [NSMutableArray arrayWithArray:
-                                     [self attendeeUIDsFromAppointment: apt]];
-      if (![removedUIDs containsObject: owner])
-       [removedUIDs addObject: owner];
+  return error;
+}
+
+- (NSException *) _handleAttendee: (iCalPerson *) attendee
+                    statusChange: (NSString *) newStatus
+                         inEvent: (iCalEvent *) event
+{
+  NSString *newContent, *currentStatus, *organizerUID;
+  NSException *ex;
 
-      if ([self sendEMailNotifications]
-         && [self _aptIsStillRelevant: apt])
+  ex = nil;
+
+  currentStatus = [attendee partStat];
+  if ([currentStatus caseInsensitiveCompare: newStatus]
+      != NSOrderedSame)
+    {
+      [attendee setPartStat: newStatus];
+      newContent = [[event parent] versitString];
+      ex = [self saveContentString: newContent];
+      if (!(ex || [event userIsOrganizer: [context activeUser]]))
        {
-         /* send notification email to attendees excluding organizer */
-         attendees = [NSMutableArray arrayWithArray: [apt attendees]];
-         [attendees removePerson: [apt organizer]];
-  
-         /* flag appointment as being cancelled */
-         [(iCalCalendar *) [apt parent] setMethod: @"cancel"];
-         [apt increaseSequence];
-
-         /* remove all attendees to signal complete removal */
-         [apt removeAllAttendees];
-
-         /* send notification email */
-         [self sendEMailUsingTemplateNamed: @"Deletion"
-               forOldObject: nil
-               andNewObject: apt
-               toAttendees: attendees];
+         if ([[attendee rsvp] isEqualToString: @"true"])
+           [self sendResponseToOrganizer];
+         organizerUID = [[event organizer] uid];
+         if (organizerUID)
+           ex = [self _updateAttendee: attendee
+                      forEventUID: [event uid]
+                      withSequence: [event sequence]
+                      forUID: organizerUID];
        }
+    }
+
+  return ex;
+}
 
-      error = [self deleteInUIDs: removedUIDs];
+- (NSException *) changeParticipationStatus: (NSString *) _status
+{
+  iCalEvent *event;
+  iCalPerson *attendee;
+  NSException *ex;
+  
+  ex = nil;
+
+  event = [self component: NO secure: NO];
+  if (event)
+    {
+      attendee = [event findParticipant: [context activeUser]];
+      if (attendee)
+       ex = [self _handleAttendee: attendee statusChange: _status
+                  inEvent: event];
+      else
+        ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
+                          reason: @"user does not participate in this "
+                          @"calendar event"];
     }
   else
-    error = [self primaryDelete];
+    ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
+                      reason: @"unable to parse event record"];
 
-  return error;
+  return ex;
 }
 
-- (NSException *) saveContentString: (NSString *) _iCalString
+- (void) prepareDelete
 {
-  return [self saveContentString: _iCalString baseSequence: 0];
+  iCalEvent *event;
+  SOGoUser *currentUser;
+  NSArray *attendees;
+
+  if ([[context request] handledByDefaultHandler])
+    {
+      currentUser = [context activeUser];
+      event = [self component: NO secure: NO];
+      if ([event userIsOrganizer: currentUser])
+       {
+         attendees = [event attendeesWithoutUser: currentUser];
+         if ([attendees count])
+           {
+             [self _handleRemovedUsers: attendees];
+             [self sendEMailUsingTemplateNamed: @"Deletion"
+                   forOldObject: nil
+                   andNewObject: [event itipEntryWithMethod: @"cancel"]
+                   toAttendees: attendees];
+           }
+       }
+      else if ([event userIsParticipant: currentUser])
+       [self changeParticipationStatus: @"DECLINED"];
+    }
 }
 
 /* message type */
diff --git a/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.html b/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.html
new file mode 100644 (file)
index 0000000..c861c27
--- /dev/null
@@ -0,0 +1,4 @@
+<#IsSubject>Re: rendez-vous le <#AptStartDate/> Ã  <#AptStartTime/></#IsSubject>
+<#IsBody>
+<#Attendee/> has <#HasAccepted>accepted</#HasAccepted><#HasDeclined>declined</#HasDeclined> your invitation.
+</#IsBody>
diff --git a/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.wod b/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.wod
new file mode 100644 (file)
index 0000000..71a9546
--- /dev/null
@@ -0,0 +1,33 @@
+AptStartDate: WOString {
+  value = startDate;
+  dateformat = "%d/%m/%y";
+  escapeHTML = NO;
+}
+
+AptStartTime: WOString {
+  value = startDate;
+  dateformat = "%H:%M";
+  escapeHTML = NO;
+}
+
+Attendee: WOString {
+  value = attendee.cnWithoutQuotes;
+  escapeHTML = NO;
+}
+
+HasAccepted: WOConditional {
+  condition = hasAccepted;
+}
+
+HasDeclined: WOConditional {
+  condition = hasDeclined;
+}
+
+IsSubject: WOConditional {
+  condition = isSubject;
+}
+
+IsBody: WOConditional {
+  condition = isSubject;
+  negate    = YES;
+}
diff --git a/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.html b/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.html
new file mode 100644 (file)
index 0000000..b12b499
--- /dev/null
@@ -0,0 +1,4 @@
+<#IsSubject>Re: rendez-vous le <#AptStartDate/> Ã  <#AptStartTime/></#IsSubject>
+<#IsBody>
+<#Attendee/> a <#HasAccepted>accepté</#HasAccepted><#HasDeclined>décliné</#HasDeclined> votre invitation.
+</#IsBody>
diff --git a/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.wod b/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.wod
new file mode 100644 (file)
index 0000000..71a9546
--- /dev/null
@@ -0,0 +1,33 @@
+AptStartDate: WOString {
+  value = startDate;
+  dateformat = "%d/%m/%y";
+  escapeHTML = NO;
+}
+
+AptStartTime: WOString {
+  value = startDate;
+  dateformat = "%H:%M";
+  escapeHTML = NO;
+}
+
+Attendee: WOString {
+  value = attendee.cnWithoutQuotes;
+  escapeHTML = NO;
+}
+
+HasAccepted: WOConditional {
+  condition = hasAccepted;
+}
+
+HasDeclined: WOConditional {
+  condition = hasDeclined;
+}
+
+IsSubject: WOConditional {
+  condition = isSubject;
+}
+
+IsBody: WOConditional {
+  condition = isSubject;
+  negate    = YES;
+}
diff --git a/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.html b/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.html
new file mode 100644 (file)
index 0000000..c861c27
--- /dev/null
@@ -0,0 +1,4 @@
+<#IsSubject>Re: rendez-vous le <#AptStartDate/> Ã  <#AptStartTime/></#IsSubject>
+<#IsBody>
+<#Attendee/> has <#HasAccepted>accepted</#HasAccepted><#HasDeclined>declined</#HasDeclined> your invitation.
+</#IsBody>
diff --git a/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.wod b/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.wod
new file mode 100644 (file)
index 0000000..71a9546
--- /dev/null
@@ -0,0 +1,33 @@
+AptStartDate: WOString {
+  value = startDate;
+  dateformat = "%d/%m/%y";
+  escapeHTML = NO;
+}
+
+AptStartTime: WOString {
+  value = startDate;
+  dateformat = "%H:%M";
+  escapeHTML = NO;
+}
+
+Attendee: WOString {
+  value = attendee.cnWithoutQuotes;
+  escapeHTML = NO;
+}
+
+HasAccepted: WOConditional {
+  condition = hasAccepted;
+}
+
+HasDeclined: WOConditional {
+  condition = hasDeclined;
+}
+
+IsSubject: WOConditional {
+  condition = isSubject;
+}
+
+IsBody: WOConditional {
+  condition = isSubject;
+  negate    = YES;
+}
diff --git a/SoObjects/Appointments/SOGoAptMailICalReply.h b/SoObjects/Appointments/SOGoAptMailICalReply.h
new file mode 100644 (file)
index 0000000..3b53089
--- /dev/null
@@ -0,0 +1,59 @@
+/* SOGoAptMailICalReply.h - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SOGOAPTMAILICALREPLY_H
+#define SOGOAPTMAILICALREPLY_H
+
+#import <NGObjWeb/SoComponent.h>
+
+@class NSString;
+@class NSCalendarDate;
+
+@class iCalPerson;
+@class iCalEntityObject;
+
+/*
+ * NOTE: We inherit from SoComponent in order to get the correct
+ *       resourceManager required for this product
+ */
+@interface SOGoAptMailICalReply : SoComponent
+{
+  iCalEntityObject *apt;
+  iCalPerson *attendee;
+  NSString *homePageURL;
+  BOOL isSubject;
+}
+
+- (void) setApt: (iCalEntityObject *) newApt;
+- (iCalEntityObject *) apt;
+
+- (void) setAttendee: (iCalPerson *) newAttendee;
+- (iCalPerson *) attendee;
+
+/* Content Generation */
+
+- (NSString *) getSubject;
+- (NSString *) getBody;
+  
+@end
+
+#endif /* SOGOAPTMAILICALREPLY_H */
diff --git a/SoObjects/Appointments/SOGoAptMailICalReply.m b/SoObjects/Appointments/SOGoAptMailICalReply.m
new file mode 100644 (file)
index 0000000..02a366e
--- /dev/null
@@ -0,0 +1,178 @@
+/* SOGoAptMailICalReply - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#import <Foundation/NSCharacterSet.h>
+#import <Foundation/NSCalendarDate.h>
+#import <Foundation/NSTimeZone.h>
+
+#import <NGObjWeb/WOActionResults.h>
+#import <NGObjWeb/WOMessage.h>
+#import <NGObjWeb/WOContext+SoObjects.h>
+#import <NGExtensions/NSObject+Logs.h>
+
+#import <NGCards/iCalEntityObject.h>
+#import <NGCards/iCalPerson.h>
+
+#import <SoObjects/SOGo/NSString+Utilities.h>
+#import <SoObjects/SOGo/SOGoUser.h>
+
+#import "SOGoAptMailICalReply.h"
+
+@interface SOGoAptMailICalReply (PrivateAPI)
+
+- (BOOL) isSubject;
+
+@end
+
+@implementation SOGoAptMailICalReply
+
+static NSCharacterSet *wsSet  = nil;
+
++ (void) initialize
+{
+  static BOOL didInit = NO;
+
+  if (!didInit)
+    {
+      didInit = YES;
+      wsSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];
+    }
+}
+
+- (id) init
+{
+  if ((self = [super init]))
+    {
+      apt = nil;
+      attendee = nil;
+    }
+
+  return self;
+}
+
+- (void) dealloc
+{
+  [apt release];
+  [attendee release];
+  [super dealloc];
+}
+
+- (void) setApt: (iCalEntityObject *) newApt
+{
+  ASSIGN (apt, newApt);
+}
+
+- (iCalEntityObject *) apt
+{
+  return apt;
+}
+
+- (void) setAttendee: (iCalPerson *) newAttendee
+{
+  ASSIGN (attendee, newAttendee);
+}
+
+- (iCalPerson *) attendee
+{
+  return attendee;
+}
+
+- (BOOL) hasAccepted
+{
+  NSString *partStat;
+
+  partStat = [[attendee partStat] lowercaseString];
+
+  return [partStat isEqualToString: @"accepted"];
+}
+
+- (BOOL) hasDeclined
+{
+  NSString *partStat;
+
+  partStat = [[attendee partStat] lowercaseString];
+
+  return [partStat isEqualToString: @"declined"];
+}
+
+- (NSCalendarDate *) startDate
+{
+  NSCalendarDate *date;
+  SOGoUser *user;
+
+  date = [apt startDate];
+  user = [[self context] activeUser];
+  [date setTimeZone: [user timeZone]];
+
+  return date;
+}
+
+- (BOOL) isSubject
+{
+  return isSubject;
+}
+
+/* Generate Response */
+
+- (NSString *) getSubject
+{
+  NSString *subject;
+
+  isSubject = YES;
+  subject = [[[self generateResponse] contentAsString]
+             stringByTrimmingCharactersInSet: wsSet];
+  if (!subject)
+    {
+      [self errorWithFormat:@"Failed to properly generate subject! Please check "
+           @"template for component '%@'!",
+           [self name]];
+      subject = @"ERROR: missing subject!";
+    }
+
+  return [subject asQPSubjectString: @"utf-8"];
+}
+
+- (NSString *) getBody
+{
+  isSubject = NO;
+  return [[self generateResponse] contentAsString];
+}
+
+@end
+
+@interface SOGoAptMailEnglishICalReply : SOGoAptMailICalReply
+@end
+
+@implementation SOGoAptMailEnglishICalReply
+@end
+
+@interface SOGoAptMailFrenchICalReply : SOGoAptMailICalReply
+@end
+
+@implementation SOGoAptMailFrenchICalReply
+@end
+
+@interface SOGoAptMailGermanICalReply : SOGoAptMailICalReply
+@end
+
+@implementation SOGoAptMailGermanICalReply
+@end
index 4bb746a3ac2b2261b8998a59bf20285d95008fc6..a0973e08fcc5de57f77024d5d9aedf75cbeee31b 100644 (file)
 @class SOGoUser;
 
 @interface SOGoCalendarComponent : SOGoContentObject
-{
-  iCalCalendar *calendar;
-  NSString *calContent;
-}
 
 - (NSString *) componentTag;
-- (iCalCalendar *) calendar: (BOOL) create;
-- (iCalRepeatableEntityObject *) component: (BOOL) create;
 
-- (NSException *) primarySaveContentString: (NSString *) _iCalString;
-- (NSException *) primaryDelete;
+- (iCalCalendar *) calendar: (BOOL) create
+                    secure: (BOOL) secure;
+- (id) component: (BOOL) create secure: (BOOL) secure;
 
-- (NSException *) delete;
+// - (NSException *) primarySaveContentString: (NSString *) _iCalString;
+// - (NSException *) primaryDelete;
 
-- (NSException *) changeParticipationStatus: (NSString *) _status;
+// - (NSException *) delete;
+
+- (void) saveComponent: (iCalRepeatableEntityObject *) newObject;
 
 /* mail notifications */
 - (BOOL) sendEMailNotifications;
@@ -64,7 +62,6 @@
 - (iCalPerson *) findParticipantWithUID: (NSString *) uid;
 
 - (iCalPerson *) iCalPersonWithUID: (NSString *) uid;
-- (NSString *) getUIDForICalPerson: (iCalPerson *) person;
 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons;
 
 @end
index 72cb73c3b8da8ce6b8cc974b9348855b32fed441..102749871a7e51df97cde3f3c9c50e6dfb880280 100644 (file)
@@ -30,6 +30,7 @@
 #import <NGExtensions/NSObject+Logs.h>
 #import <NGExtensions/NGHashMap.h>
 #import <NGCards/iCalCalendar.h>
+#import <NGCards/iCalEvent.h>
 #import <NGCards/iCalPerson.h>
 #import <NGCards/iCalRepeatableEntityObject.h>
 #import <NGMime/NGMimeBodyPart.h>
 #import <SoObjects/SOGo/SOGoMailer.h>
 #import <SoObjects/SOGo/SOGoPermissions.h>
 #import <SoObjects/SOGo/SOGoUser.h>
+#import <SoObjects/SOGo/WORequest+SOGo.h>
 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
 
+#import "SOGoAptMailICalReply.h"
 #import "SOGoAptMailNotification.h"
 #import "iCalEntityObject+SOGo.h"
+#import "iCalPerson+SOGo.h"
 #import "SOGoCalendarComponent.h"
 
 static BOOL sendEMailNotifications = NO;
@@ -67,26 +71,6 @@ static BOOL sendEMailNotifications = NO;
     }
 }
 
-- (id) init
-{
-  if ((self = [super init]))
-    {
-      calendar = nil;
-      calContent = nil;
-    }
-
-  return self;
-}
-
-- (void) dealloc
-{
-  if (calendar)
-    [calendar release];
-  if (calContent)
-    [calContent release];
-  [super dealloc];
-}
-
 - (NSString *) davContentType
 {
   return @"text/calendar";
@@ -111,16 +95,15 @@ static BOOL sendEMailNotifications = NO;
   [component removeAllAlarms];
 }
 
-- (NSString *) contentAsString
+- (NSString *) secureContentAsString
 {
   iCalCalendar *tmpCalendar;
   iCalRepeatableEntityObject *tmpComponent;
 //   NSArray *roles;
 //   NSString *uid;
   SoSecurityManager *sm;
+  NSString *iCalString;
 
-  if (!calContent)
-    {
 //       uid = [[context activeUser] login];
 //       roles = [self aclsForUser: uid];
 //       if ([roles containsObject: SOGoCalendarRole_Organizer]
@@ -139,112 +122,75 @@ static BOOL sendEMailNotifications = NO;
 //       else
 //     calContent = nil;
 
-      sm = [SoSecurityManager sharedSecurityManager];
-      if (![sm validatePermission: SOGoCalendarPerm_ViewAllComponent
-              onObject: self inContext: context])
-       calContent = content;
-      else if (![sm validatePermission: SOGoCalendarPerm_ViewDAndT
-                   onObject: self inContext: context])
-       {
-         tmpCalendar = [[self calendar: NO] copy];
-         tmpComponent = (iCalRepeatableEntityObject *)
-           [tmpCalendar firstChildWithTag: [self componentTag]];
-         [self _filterComponent: tmpComponent];
-         calContent = [tmpCalendar versitString];
-         [tmpCalendar release];
-       }
-      else
-       calContent = nil;
-
-      [calContent retain];
+  sm = [SoSecurityManager sharedSecurityManager];
+  if (![sm validatePermission: SOGoCalendarPerm_ViewAllComponent
+          onObject: self inContext: context])
+    iCalString = content;
+  else if (![sm validatePermission: SOGoCalendarPerm_ViewDAndT
+               onObject: self inContext: context])
+    {
+      tmpCalendar = [[self calendar: NO secure: NO] copy];
+      tmpComponent = (iCalRepeatableEntityObject *)
+       [tmpCalendar firstChildWithTag: [self componentTag]];
+      [self _filterComponent: tmpComponent];
+      iCalString = [tmpCalendar versitString];
+      [tmpCalendar release];
     }
+  else
+    iCalString = nil;
 
-  return calContent;
-}
-
-- (void) setContentString: (NSString *) newContent
-{
-  [super setContentString: newContent];
-  ASSIGN (calendar, nil);
-  ASSIGN (calContent, nil);
+  return iCalString;
 }
 
-// - (NSException *) saveContentString: (NSString *) contentString
-//                         baseVersion: (unsigned int) baseVersion
-// {
-//   NSException *result;
-
-//   result = [super saveContentString: contentString
-//                   baseVersion: baseVersion];
-//   if (!result && calContent)
-//     {
-//       [calContent release];
-//       calContent = nil;
-//     }
-
-//   return result;
-// }
-
-- (iCalCalendar *) calendar: (BOOL) create
+- (iCalCalendar *) calendar: (BOOL) create secure: (BOOL) secure
 {
-  NSString *iCalString, *componentTag;
+  NSString *componentTag;
   CardGroup *newComponent;
+  iCalCalendar *calendar;
+  NSString *iCalString;
+
+  if (secure)
+    iCalString = [self secureContentAsString];
+  else
+    iCalString = content;
 
-  if (!calendar)
+  if ([iCalString length] > 0)
+    calendar = [iCalCalendar parseSingleFromSource: iCalString];
+  else
     {
-      iCalString = [super contentAsString];
-      if ([iCalString length] > 0)
-        calendar = [iCalCalendar parseSingleFromSource: iCalString];
+      if (create)
+       {
+         calendar = [iCalCalendar groupWithTag: @"vcalendar"];
+         [calendar setVersion: @"2.0"];
+         [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
+         componentTag = [[self componentTag] uppercaseString];
+         newComponent = [[calendar classForTag: componentTag]
+                          groupWithTag: componentTag];
+         [calendar addChild: newComponent];
+       }
       else
-        {
-          if (create)
-            {
-              calendar = [iCalCalendar groupWithTag: @"vcalendar"];
-              [calendar setVersion: @"2.0"];
-              [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
-              componentTag = [[self componentTag] uppercaseString];
-              newComponent = [[calendar classForTag: componentTag]
-                               groupWithTag: componentTag];
-              [calendar addChild: newComponent];
-            }
-        }
-      if (calendar)
-        [calendar retain];
+       calendar = nil;
     }
 
   return calendar;
 }
 
-- (iCalRepeatableEntityObject *) component: (BOOL) create
-{
-  return
-    (iCalRepeatableEntityObject *) [[self calendar: create]
-                                    firstChildWithTag: [self componentTag]];
-}
-
-/* raw saving */
-
-- (NSException *) primarySaveContentString: (NSString *) _iCalString
+- (id) component: (BOOL) create secure: (BOOL) secure
 {
-  return [super saveContentString: _iCalString];
+  return [[self calendar: create secure: secure]
+          firstChildWithTag: [self componentTag]];
 }
 
-- (NSException *) primaryDelete
+- (void) saveComponent: (iCalRepeatableEntityObject *) newObject
 {
-  return [super delete];
-}
+  NSString *newiCalString;
 
-- (NSException *) deleteWithBaseSequence: (int) a
-{
-  [self subclassResponsibility: _cmd];
+  newiCalString = [[newObject parent] versitString];
 
-  return nil;
+  [self saveContentString: newiCalString];
 }
 
-- (NSException *) delete
-{
-  return [self deleteWithBaseSequence: 0];
-}
+/* raw saving */
 
 /* EMail Notifications */
 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
@@ -262,58 +208,13 @@ static BOOL sendEMailNotifications = NO;
       baseURL = @"http://localhost/";
       [self warnWithFormat:@"Unable to create baseURL from context!"];
     }
-  uid = [[LDAPUserManager sharedUserManager]
-          getUIDForEmail: [_person rfc822Email]];
+  uid = [_person uid];
 
   return ((uid)
           ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
           : nil);
 }
 
-- (NSException *) changeParticipationStatus: (NSString *) _status
-{
-  iCalRepeatableEntityObject *component;
-  iCalPerson *person;
-  NSString *newContent;
-  NSException *ex;
-  
-  ex = nil;
-
-  component = [self component: NO];
-  if (component)
-    {
-      person = [self findParticipantWithUID: owner];
-      if (person)
-        {
-         // TODO: send iMIP reply mails?
-          [person setPartStat: _status];
-          newContent = [[component parent] versitString];
-          if (newContent)
-            {
-              ex = [self saveContentString: newContent];
-              if (ex)
-                // TODO: why is the exception wrapped?
-                /* Server Error */
-                ex = [NSException exceptionWithHTTPStatus: 500
-                                  reason: [ex reason]];
-            }
-          else
-            ex
-              = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
-                             reason: @"Could not generate iCalendar data ..."];
-        }
-      else
-        ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
-                          reason: @"user does not participate in this "
-                          @"calendar component"];
-    }
-  else
-    ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
-                      reason: @"unable to parse component record"];
-
-  return ex;
-}
-
 - (BOOL) sendEMailNotifications
 {
   return sendEMailNotifications;
@@ -335,7 +236,7 @@ static BOOL sendEMailNotifications = NO;
 {
   NSString *pageName;
   iCalPerson *organizer;
-  NSString *cn, *email, *sender, *iCalString;
+  NSString *email, *sender, *iCalString;
   WOApplication *app;
   unsigned i, count;
   iCalPerson *attendee;
@@ -347,115 +248,196 @@ static BOOL sendEMailNotifications = NO;
   NGMimeBodyPart *bodyPart;
   NGMimeMultipartBody *body;
 
-  if ([_attendees count])
+  if (sendEMailNotifications 
+      && [_newObject isStillRelevant])
     {
-      /* sender */
-
-      organizer = [_newObject organizer];
-      cn = [organizer cnWithoutQuotes];
-      if (cn)
-        sender = [NSString stringWithFormat:@"%@ <%@>",
-                           cn,
-                           [organizer rfc822Email]];
-      else
-        sender = [organizer rfc822Email];
+      count = [_attendees count];
+      if (count)
+       {
+         /* sender */
+         organizer = [_newObject organizer];
+         sender = [organizer mailAddress];
+
+         NSLog (@"sending '%@' from %@",
+                [(iCalCalendar *) [_newObject parent] method], organizer);
 
-      /* generate iCalString once */
-      iCalString = [[_newObject parent] versitString];
+         /* generate iCalString once */
+         iCalString = [[_newObject parent] versitString];
   
-      /* get WOApplication instance */
-      app = [WOApplication application];
+         /* get WOApplication instance */
+         app = [WOApplication application];
 
-      /* generate dynamic message content */
+         /* generate dynamic message content */
 
-      count = [_attendees count];
-      for (i = 0; i < count; i++)
-        {
-          attendee = [_attendees objectAtIndex:i];
-
-          /* construct recipient */
-          cn = [attendee cn];
-         email = [attendee rfc822Email];
-          if (cn)
-            recipient = [NSString stringWithFormat: @"%@ <%@>",
-                                  cn, email];
-          else
-            recipient = email;
-
-         language = [[context activeUser] language];
+         for (i = 0; i < count; i++)
+           {
+             attendee = [_attendees objectAtIndex: i];
+             if (![[attendee uid] isEqualToString: owner])
+               {
+                 /* construct recipient */
+                 recipient = [attendee mailAddress];
+                 email = [attendee rfc822Email];
+
+                 NSLog (@"recipient: %@", recipient);
+                 language = [[context activeUser] language];
 #warning this could be optimized in a class hierarchy common with the  \
-          SOGoObject acl notification mechanism
-          /* create page name */
-          // TODO: select user's default language?
-          pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
-                               language,
-                               _pageName];
-          /* construct message content */
-          p = [app pageWithName: pageName inContext: context];
-          [p setNewApt: _newObject];
-          [p setOldApt: _oldObject];
-          [p setHomePageURL: [self homePageURLForPerson: attendee]];
-          [p setViewTZ: [self timeZoneForUser: email]];
-          subject = [p getSubject];
-          text = [p getBody];
-
-          /* construct message */
-          headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
+  SOGoObject acl notification mechanism
+                 /* create page name */
+                 pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
+                                      language, _pageName];
+                 /* construct message content */
+                 p = [app pageWithName: pageName inContext: context];
+                 [p setNewApt: _newObject];
+                 [p setOldApt: _oldObject];
+                 [p setHomePageURL: [self homePageURLForPerson: attendee]];
+                 [p setViewTZ: [self timeZoneForUser: email]];
+                 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"];
-         mailDate = [[NSCalendarDate date] rfc822DateString];
-          [headerMap setObject: mailDate 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; charset=utf-8"
-                     forKey: @"content-type"];
-          bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
-          [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
-
-          /* attach text part to multipart body */
-          [body addBodyPart: bodyPart];
+                 /* 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"];
+                 mailDate = [[NSCalendarDate date] rfc822DateString];
+                 [headerMap setObject: mailDate 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; charset=utf-8"
+                            forKey: @"content-type"];
+                 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
+                 [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
+
+                 /* attach text part to multipart body */
+                 [body addBodyPart: bodyPart];
     
-          /* calendar part */
-          header = [NSString stringWithFormat: @"text/calendar; method=%@;"
-                             @" charset=utf-8",
-                             [(iCalCalendar *) [_newObject parent] method]];
-          headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
-          [headerMap setObject:header forKey: @"content-type"];
-          bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
-          [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
-
-          /* attach calendar part to multipart body */
-          [body addBodyPart: bodyPart];
+                 /* calendar part */
+                 header = [NSString stringWithFormat: @"text/calendar; method=%@;"
+                                    @" charset=utf-8",
+                                    [(iCalCalendar *) [_newObject parent] method]];
+                 headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
+                 [headerMap setObject:header forKey: @"content-type"];
+                 bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
+                 [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
+
+                 /* attach calendar part to multipart body */
+                 [body addBodyPart: bodyPart];
     
-          /* attach multipart body to message */
-          [msg setBody: body];
-          [body release];
-
-          /* send the damn thing */
-          [[SOGoMailer sharedMailer]
-           sendMimePart: msg
-           toRecipients: [NSArray arrayWithObject: email]
-           sender: [organizer rfc822Email]];
-        }
+                 /* attach multipart body to message */
+                 [msg setBody: body];
+                 [body release];
+
+                 /* send the damn thing */
+                 [[SOGoMailer sharedMailer]
+                   sendMimePart: msg
+                   toRecipients: [NSArray arrayWithObject: email]
+                   sender: [organizer rfc822Email]];
+               }
+           }
+       }
     }
 }
 
 - (void) sendResponseToOrganizer
 {
-#warning THIS IS A STUB  
+  NSString *pageName, *language, *mailDate, *email;
+  WOApplication *app;
+  iCalPerson *organizer, *attendee;
+  NSString *iCalString;
+  iCalEvent *event;
+  SOGoAptMailICalReply *p;
+  NGMutableHashMap *headerMap;
+  NGMimeMessage *msg;
+  NGMimeBodyPart *bodyPart;
+  NGMimeMultipartBody *body;
+  NSData *bodyData;
+
+  event = [[self component: NO secure: NO] itipEntryWithMethod: @"reply"];
+  if (![event userIsOrganizer: [context activeUser]])
+    {
+      organizer = [event organizer];
+      attendee = [event findParticipant: [context activeUser]];
+      [event setAttendees: [NSArray arrayWithObject: attendee]];
+
+      /* get WOApplication instance */
+      app = [WOApplication application];
+
+      language = [[context activeUser] language];
+      /* create page name */
+      pageName
+       = [NSString stringWithFormat: @"SOGoAptMail%@ICalReply", language];
+      /* construct message content */
+      p = [app pageWithName: pageName inContext: context];
+      [p setApt: event];
+      [p setAttendee: attendee];
+
+      /* 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: [attendee mailAddress] forKey: @"From"];
+      [headerMap setObject: [organizer mailAddress] forKey: @"To"];
+      mailDate = [[NSCalendarDate date] rfc822DateString];
+      [headerMap setObject: mailDate forKey: @"date"];
+      [headerMap setObject: [p getSubject] forKey: @"Subject"];
+      msg = [NGMimeMessage messageWithHeader: headerMap];
+
+      NSLog (@"sending 'REPLY' from %@ to %@",
+            [attendee mailAddress], [organizer mailAddress]);
+
+      /* multipart body */
+      body = [[NGMimeMultipartBody alloc] initWithPart: msg];
+
+      /* text part */
+      headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
+      [headerMap setObject: @"text/plain; charset=utf-8"
+                forKey: @"content-type"];
+      bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
+      bodyData = [[p getBody] dataUsingEncoding: NSUTF8StringEncoding];
+      [bodyPart setBody: bodyData];
+
+      /* attach text part to multipart body */
+      [body addBodyPart: bodyPart];
+
+      /* calendar part */
+      headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
+      [headerMap setObject: @"text/calendar; method=REPLY; charset=utf-8"
+                forKey: @"content-type"];
+      bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
+      iCalString = [[event parent] versitString];
+      [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
+
+      /* attach calendar part to multipart body */
+      [body addBodyPart: bodyPart];
+
+      /* attach multipart body to message */
+      [msg setBody: body];
+      [body release];
+
+      /* send the damn thing */
+      email = [organizer rfc822Email];
+      [[SOGoMailer sharedMailer]
+       sendMimePart: msg
+       toRecipients: [NSArray arrayWithObject: email]
+       sender: [attendee rfc822Email]];
+    }
 }
 
 // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user
@@ -481,7 +463,7 @@ static BOOL sendEMailNotifications = NO;
   SOGoUser *user;
 
   user = [SOGoUser userWithLogin: uid roles: nil];
-  component = [self component: NO];
+  component = [self component: NO secure: NO];
 
   return [component findParticipant: user];
 }
@@ -503,31 +485,20 @@ static BOOL sendEMailNotifications = NO;
   return person;
 }
 
-- (NSString *) getUIDForICalPerson: (iCalPerson *) person
-{
-  LDAPUserManager *um;
-
-  um = [LDAPUserManager sharedUserManager];
-
-  return [um getUIDForEmail: [person rfc822Email]];
-}
-
 - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons
 {
   iCalPerson *currentPerson;
   NSEnumerator *persons;
   NSMutableArray *uids;
   NSString *uid;
-  LDAPUserManager *um;
 
   uids = [NSMutableArray array];
 
-  um = [LDAPUserManager sharedUserManager];
   persons = [iCalPersons objectEnumerator];
   currentPerson = [persons nextObject];
   while (currentPerson)
     {
-      uid = [um getUIDForEmail: [currentPerson rfc822Email]];
+      uid = [currentPerson uid];
       if (uid)
        [uids addObject: uid];
       currentPerson = [persons nextObject];
@@ -591,7 +562,7 @@ static BOOL sendEMailNotifications = NO;
   if ([superAcls count] > 0)
     [roles addObjectsFromArray: superAcls];
 
-  component = [self component: NO];
+  component = [self component: NO secure: NO];
   ownerRole = [self _roleOfOwner: component];
   if ([owner isEqualToString: uid])
     [roles addObject: ownerRole];
index da442022ba0dd9931078e877a7dcb0ac717daa59..c369e6fb10c46413a9c8ddbf7595152aac84385a 100644 (file)
 
 @interface SOGoTaskObject : SOGoCalendarComponent
 
-/* "iCal multifolder saves" */
-
-- (NSException *) saveContentString: (NSString *) _iCal
-                      baseSequence: (int) _v;
-- (NSException *) deleteWithBaseSequence: (int) _v;
-- (NSException *) saveContentString: (NSString *) _iCalString;
-
 @end
 
 #endif /* __Appointmentss_SOGoTaskObject_H__ */
index 5c628fa6635616c4833afab4c105132579927134..16bbb625491c6bc8b0271682eb25d0304bd3c5fa 100644 (file)
 
 #import "SOGoTaskObject.h"
 
-@interface SOGoTaskObject (PrivateAPI)
-
-- (NSString *) homePageURLForPerson: (iCalPerson *) _person;
-  
-@end
-
 @implementation SOGoTaskObject
 
-static NSString                  *mailTemplateDefaultLanguage = nil;
-
-+ (void)initialize {
-  NSUserDefaults      *ud;
-  static BOOL         didInit = NO;
-  
-  if (didInit) return;
-  didInit = YES;
-  
-  ud = [NSUserDefaults standardUserDefaults];
-  mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
-                                     retain];
-  if (!mailTemplateDefaultLanguage)
-    mailTemplateDefaultLanguage = @"French";
-}
-
 - (NSString *) componentTag
 {
   return @"vtodo";
 }
 
-/* iCal handling */
-
-- (NSArray *)attendeeUIDsFromTask:(iCalToDo *)_task {
-  LDAPUserManager *um;
-  NSMutableArray    *uids;
-  NSArray  *attendees;
-  unsigned i, count;
-  NSString *email, *uid;
-  
-  if (![_task isNotNull])
-    return nil;
-  
-  if ((attendees = [_task attendees]) == nil)
-    return nil;
-  count = [attendees count];
-  uids = [NSMutableArray arrayWithCapacity:count + 1];
-  
-  um = [LDAPUserManager sharedUserManager];
-  
-  /* add organizer */
-  
-  email = [[_task organizer] rfc822Email];
-  if ([email isNotNull]) {
-    uid = [um getUIDForEmail:email];
-    if ([uid isNotNull]) {
-      [uids addObject:uid];
-    }
-    else
-      [self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
-  }
-
-  /* add attendees */
-  
-  for (i = 0; i < count; i++) {
-    iCalPerson *person;
-    
-    person = [attendees objectAtIndex:i];
-    email  = [person rfc822Email];
-    if (![email isNotNull]) continue;
-    
-    uid = [um getUIDForEmail:email];
-    if (![uid isNotNull]) {
-      [self logWithFormat:@"Note: got no uid for email: '%@'", email];
-      continue;
-    }
-    if (![uids containsObject:uid])
-      [uids addObject:uid];
-  }
-  
-  return uids;
-}
-
-/* store in all the other folders */
-
-- (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
-  NSEnumerator *e;
-  id           folder;
-  NSException  *allErrors = nil;
-
-  e = [[container lookupCalendarFoldersForUIDs: _uids inContext: context]
-            objectEnumerator];
-  while ((folder = [e nextObject]) != nil) {
-    NSException           *error;
-    SOGoTaskObject *task;
-    
-    if (![folder isNotNull]) /* no folder was found for given UID */
-      continue;
-    
-    task = [folder lookupName:[self nameInContainer] inContext: context
-                 acquire:NO];
-    if ([task isKindOfClass: [NSException class]])
-      {
-        [self logWithFormat:@"Note: an exception occured finding '%@' in folder: %@",
-             [self nameInContainer], folder];
-        [self logWithFormat:@"the exception reason was: %@",
-              [(NSException *) task reason]];
-        continue;
-      }
-
-    if (![task isNotNull]) {
-      [self logWithFormat:@"Note: did not find '%@' in folder: %@",
-             [self nameInContainer], folder];
-      continue;
-    }
-    
-    if ((error = [task primarySaveContentString:_iCal]) != nil) {
-      [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
-      // TODO: make compound
-      allErrors = error;
-    }
-  }
-  return allErrors;
-}
-// - (NSException *)deleteInUIDs:(NSArray *)_uids {
-//   NSEnumerator *e;
-//   id           folder;
-//   NSException  *allErrors = nil;
-  
-//   e = [[container lookupCalendarFoldersForUIDs: _uids inContext: context]
-//          objectEnumerator];
-//   while ((folder = [e nextObject])) {
-//     NSException           *error;
-//     SOGoTaskObject *task;
-    
-//     task = [folder lookupName: [self nameInContainer]
-//                inContext: context
-//                    acquire: NO];
-//     if (![task isNotNull]) {
-//       [self logWithFormat:@"Note: did not find '%@' in folder: %@",
-//           [self nameInContainer], folder];
-//       continue;
-//     }
-//     if ([task isKindOfClass: [NSException class]]) {
-//       [self logWithFormat:@"Exception: %@", [(NSException *) task reason]];
-//       continue;
-//     }
-    
-//     if ((error = [task primaryDelete]) != nil) {
-//       [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
-//       // TODO: make compound
-//       allErrors = error;
-//     }
-//   }
-//   return allErrors;
-// }
-
-/* "iCal multifolder saves" */
-
-- (NSException *) saveContentString: (NSString *) _iCal
-                       baseSequence: (int) _v
-{
-  /* 
-     Note: we need to delete in all participants folders and send iMIP messages
-           for all external accounts.
-     
-     Steps:
-     - fetch stored content
-     - parse old content
-     - check if sequence matches (or if 0=ignore)
-     - extract old attendee list + organizer (make unique)
-     - parse new content (ensure that sequence is increased!)
-     - extract new attendee list + organizer (make unique)
-     - make a diff => new, same, removed
-     - write to new, same
-     - delete in removed folders
-     - send iMIP mail for all folders not found
-  */
-//   LDAPUserManager *um;
-//   iCalCalendar *calendar;
-//   iCalToDo *oldApt, *newApt;
-// //   iCalToDoChanges  *changes;
-//   iCalPerson        *organizer;
-//   NSString          *oldContent, *uid;
-//   NSArray           *uids, *props;
-//   NSMutableArray    *attendees, *storeUIDs, *removedUIDs;
-  NSException       *storeError, *delError;
-//   BOOL              updateForcesReconsider;
-  
-//   updateForcesReconsider = NO;
-
-//   if ([_iCal length] == 0) {
-//     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
-//                     reason:@"got no iCalendar content to store!"];
-//   }
-
-//   um = [LDAPUserManager sharedUserManager];
-
-//   /* handle old content */
-  
-//   oldContent = [self contentAsString]; /* if nil, this is a new task */
-//   if ([oldContent length] == 0)
-//     {
-//     /* new task */
-//       [self debugWithFormat:@"saving new task: %@", _iCal];
-//       oldApt = nil;
-//     }
-//   else
-//     {
-//       calendar = [iCalCalendar parseSingleFromSource: oldContent];
-//       oldApt = [self firstTaskFromCalendar: calendar];
-//     }
-  
-//   /* compare sequence if requested */
-
-//   if (_v != 0) {
-//     // TODO
-//   }
-  
-  
-//   /* handle new content */
-  
-//   calendar = [iCalCalendar parseSingleFromSource: _iCal];
-//   newApt = [self firstTaskFromCalendar: calendar];
-//   if (newApt == nil) {
-//     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
-//                     reason:@"could not parse iCalendar content!"];
-//   }
-  
-//   /* diff */
-  
-//   changes = [iCalToDoChanges changesFromEvent: oldApt
-//                               toEvent: newApt];
-
-//   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];
-
-//   /* detect whether sequence has to be increased */
-//   if ([changes hasChanges])
-//     [newApt increaseSequence];
-
-//   /* preserve organizer */
-
-//   organizer = [newApt organizer];
-//   uid       = [um getUIDForICalPerson:organizer];
-//   if (uid) {
-//     if (![storeUIDs containsObject:uid])
-//       [storeUIDs addObject:uid];
-//     [removedUIDs removeObject:uid];
-//   }
-
-//   /* organizer might have changed completely */
-
-//   if (oldApt && ([props containsObject: @"organizer"])) {
-//     uid = [um getUIDForICalPerson:[oldApt 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;
-    
-//     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 parent] versitString];
-//     updateForcesReconsider = YES;
-//   }
-
-//   /* perform storing */
-
-  storeError = [self primarySaveContentString: _iCal];
-
-//   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 */
-//   if ([self sendEMailNotifications])
-//     {
-//   attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
-//   [attendees removePerson:organizer];
-//   [self sendInvitationEMailForTask:newApt
-//         toAttendees:attendees];
-
-//   if (updateForcesReconsider) {
-//     attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
-//     [attendees removeObjectsInArray:[changes insertedAttendees]];
-//     [attendees removePerson:organizer];
-//       [self sendEMailUsingTemplateNamed: @"Update"
-//             forOldObject: oldApt
-//             andNewObject: newApt
-//             toAttendees: attendees];
-//   }
-
-//   attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
-//   [attendees removePerson: organizer];
-//   if ([attendees count]) {
-//     iCalToDo *cancelledApt;
-    
-//     cancelledApt = [newApt copy];
-//     [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
-//           [self sendEMailUsingTemplateNamed: @"Removal"
-//                 forOldObject: nil
-//                 andNewObject: cancelledApt
-//                 toAttendees: attendees];
-//     [cancelledApt release];
-//   }
-// }
-
-  return nil;
-}
-
-- (NSException *)deleteWithBaseSequence:(int)_v {
-  /* 
-     Note: We need to delete in all participants folders and send iMIP messages
-           for all external accounts.
-          Delete is basically identical to save with all attendees and the
-          organizer being deleted.
-
-     Steps:
-     - fetch stored content
-     - parse old content
-     - check if sequence matches (or if 0=ignore)
-     - extract old attendee list + organizer (make unique)
-     - delete in removed folders
-     - send iMIP mail for all folders not found
-  */
-//   iCalToDo *task;
-//   NSArray         *removedUIDs;
-//   NSMutableArray  *attendees;
-
-  [self primaryDelete];
-
-  return nil;
-//   /* load existing content */
-  
-//   task = (iCalToDo *) [self component: NO];
-  
-//   /* compare sequence if requested */
-
-//   if (_v != 0) {
-//     // TODO
-//   }
-  
-//   removedUIDs = [self attendeeUIDsFromTask:task];
-
-//   if ([self sendEMailNotifications])
-//     {
-//       /* send notification email to attendees excluding organizer */
-//       attendees = [NSMutableArray arrayWithArray:[task attendees]];
-//       [attendees removePerson:[task organizer]];
-  
-//       /* flag task as being cancelled */
-//       [(iCalCalendar *) [task parent] setMethod: @"cancel"];
-//       [task increaseSequence];
-
-//       /* remove all attendees to signal complete removal */
-//       [task removeAllAttendees];
-
-//       /* send notification email */
-//       [self sendEMailUsingTemplateNamed: @"Deletion"
-//             forOldObject: nil
-//             andNewObject: task
-//             toAttendees: attendees];
-//     }
-
-//   /* perform */
-  
-//   return [self deleteInUIDs:removedUIDs];
-}
-
-- (NSException *)saveContentString:(NSString *)_iCalString {
-  return [self saveContentString:_iCalString baseSequence:0];
-}
-
 /* message type */
 
-- (NSString *)outlookMessageClass {
+- (NSString *) outlookMessageClass
+{
   return @"IPM.Task";
 }
 
index 7f8702b9e5c4250c227ea96ff00eb9c6291e43c1..f26ae5a1a398a44ef9cb801844a8b5c2aa4006c8 100644 (file)
 
 #import <NGCards/iCalEntityObject.h>
 
+@class SOGoUser;
+
 @interface iCalEntityObject (SOGoExtensions)
 
 - (BOOL) userIsParticipant: (SOGoUser *) user;
 - (BOOL) userIsOrganizer: (SOGoUser *) user;
 
+- (NSArray *) attendeeUIDs;
+- (BOOL) isStillRelevant;
+
+- (id) itipEntryWithMethod: (NSString *) method;
+
+- (NSArray *) attendeesWithoutUser: (SOGoUser *) user;
+
 @end
 
 #endif /* ICALENTITYOBJECT_SOGO_H */
index 3a441116151ca2302f8db547b4dcb4527d594a56..83cd25a58884d68de8e5f4b83e1567fe9447afb1 100644 (file)
 #import <Foundation/NSArray.h>
 #import <Foundation/NSEnumerator.h>
 
+#import <NGCards/iCalCalendar.h>
 #import <NGCards/iCalPerson.h>
 
 #import <SoObjects/SOGo/NSArray+Utilities.h>
 #import <SoObjects/SOGo/SOGoUser.h>
 
+#import "iCalPerson+SOGo.h"
+
 #import "iCalEntityObject+SOGo.h"
 
 @implementation iCalEntityObject (SOGoExtensions)
@@ -52,6 +55,7 @@
   return isParticipant;
 }
 
+#warning user could be a delegate, we will need to handle that someday
 - (BOOL) userIsOrganizer: (SOGoUser *) user
 {
   NSString *orgMail;
   return [user hasEmail: orgMail];
 }
 
+- (NSArray *) attendeeUIDs
+{
+  NSEnumerator *attendees;
+  NSString *uid;
+  iCalPerson *currentAttendee;
+  NSMutableArray *uids;
+
+  uids = [NSMutableArray array];
+
+  attendees = [[self attendees] objectEnumerator];
+  while ((currentAttendee = [attendees nextObject]))
+    {
+      uid = [currentAttendee uid];
+      if (uid)
+       [uids addObject: uid];
+    }
+
+  return uids;
+}
+
+#warning this method should be implemented in a category of iCalToDo
+- (BOOL) isStillRelevant
+{
+  [self subclassResponsibility: _cmd];
+  return NO;
+}
+
+- (id) itipEntryWithMethod: (NSString *) method
+{
+  iCalCalendar *newCalendar;
+  iCalEntityObject *newEntry;
+
+  newCalendar = [parent mutableCopy];
+  [newCalendar autorelease];
+  [newCalendar setMethod: method];
+  newEntry = (iCalEntityObject *) [newCalendar firstChildWithTag: tag];
+
+  return newEntry;
+}
+
+- (NSArray *) attendeesWithoutUser: (SOGoUser *) user
+{
+  NSMutableArray *newAttendees;
+  NSArray *oldAttendees;
+  unsigned int count, max;
+  iCalPerson *currentAttendee;
+  NSString *userID;
+
+  userID = [user login];
+  oldAttendees = [self attendees];
+  max = [oldAttendees count];
+  newAttendees = [NSMutableArray arrayWithCapacity: max];
+  for (count = 0; count < max; count++)
+    {
+      currentAttendee = [oldAttendees objectAtIndex: count];
+      if (![[currentAttendee uid] isEqualToString: userID])
+       [newAttendees addObject: currentAttendee];
+    }
+
+  return newAttendees;
+}
+
 @end
diff --git a/SoObjects/Appointments/iCalEventChanges+SOGo.h b/SoObjects/Appointments/iCalEventChanges+SOGo.h
new file mode 100644 (file)
index 0000000..1c4ed55
--- /dev/null
@@ -0,0 +1,34 @@
+/* iCalEventChanges+SOGo.h - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ICALEVENTCHANGES_SOGO_H
+#define ICALEVENTCHANGES_SOGO_H
+
+#import <NGCards/iCalEventChanges.h>
+
+@interface iCalEventChanges (SOGoExtensions)
+
+- (BOOL) sequenceShouldBeIncreased;
+
+@end
+
+#endif /* ICALEVENTCHANGES_SOGO_H */
diff --git a/SoObjects/Appointments/iCalEventChanges+SOGo.m b/SoObjects/Appointments/iCalEventChanges+SOGo.m
new file mode 100644 (file)
index 0000000..b1e25c6
--- /dev/null
@@ -0,0 +1,52 @@
+/* iCalEventChanges+SOGo.m - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#import <Foundation/NSArray.h>
+#import <Foundation/NSEnumerator.h>
+#import <Foundation/NSString.h>
+
+#import "iCalEventChanges+SOGo.h"
+
+@implementation iCalEventChanges (SOGoExtensions)
+
+- (BOOL) sequenceShouldBeIncreased
+{
+  NSString *properties[] = {@"organizer", @"startDate", @"endDate", /* vtask:
+                                                                      @"due" */
+                           @"rdate", @"rrule", @"exdate", @"exrule",
+                           @"status", @"location", nil};
+  NSString **currentProperty;
+  BOOL updateRequired;
+
+  updateRequired = NO;
+
+  currentProperty = properties;
+  while (!updateRequired && *currentProperty)
+    if ([updatedProperties containsObject: *currentProperty])
+      updateRequired = YES;
+    else
+      currentProperty++;
+
+  return updateRequired;
+}
+
+@end
diff --git a/SoObjects/Appointments/iCalPerson+SOGo.h b/SoObjects/Appointments/iCalPerson+SOGo.h
new file mode 100644 (file)
index 0000000..06312ac
--- /dev/null
@@ -0,0 +1,39 @@
+/* iCalPerson+SOGo.h - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ICALPERSON_SOGO_H
+#define ICALPERSON_SOGO_H
+
+#import <NGCards/iCalPerson.h>
+
+#import <SoObjects/SOGo/LDAPUserManager.h>
+
+@class NSString;
+
+@interface iCalPerson (SOGoExtension)
+
+- (NSString *) mailAddress;
+- (NSString *) uid;
+
+@end
+
+#endif /* ICALPERSON_SOGO_H */
diff --git a/SoObjects/Appointments/iCalPerson+SOGo.m b/SoObjects/Appointments/iCalPerson+SOGo.m
new file mode 100644 (file)
index 0000000..53d828c
--- /dev/null
@@ -0,0 +1,53 @@
+/* iCalPerson+SOGo.m - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#import <Foundation/NSString.h>
+
+#import "iCalPerson+SOGo.h"
+
+static LDAPUserManager *um = nil;
+
+@implementation iCalPerson (SOGoExtension)
+
+- (NSString *) mailAddress
+{
+  NSString *cn, *email, *mailAddress;
+
+  cn = [self cnWithoutQuotes];
+  email = [self rfc822Email];
+  if ([cn length])
+    mailAddress = [NSString stringWithFormat:@"%@ <%@>", cn, email];
+  else
+    mailAddress = email;
+
+  return mailAddress;
+}
+
+- (NSString *) uid
+{
+  if (!um)
+    um = [LDAPUserManager sharedUserManager];
+
+  return [um getUIDForEmail: [self rfc822Email]];
+}
+
+@end
index e4293472e8619fc1a1921c2055ed8fa8da711da3..f56c1be80340e577a9eabe0dfc0190a67f600b52 100644 (file)
@@ -51,6 +51,7 @@
 #import <NGMime/NGMimeMultipartBody.h>
 #import <NGMime/NGMimeType.h>
 #import <NGMime/NGMimeHeaderFieldGenerator.h>
+#import <NGMime/NGMimeHeaderFields.h>
 
 #import <SoObjects/SOGo/NSArray+Utilities.h>
 #import <SoObjects/SOGo/NSCalendarDate+SOGo.h>
@@ -614,10 +615,14 @@ static BOOL        showTextAttachmentsInline  = NO;
   SOGoUser *currentUser;
 
   [sourceMail fetchCoreInfos];
-
-  info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward]
-                      forKey: @"subject"];
-  [self setHeaders: info];
+  
+  if ([sourceMail subjectForForward])
+    {
+      info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward] 
+                          forKey: @"subject"];
+      [self setHeaders: info];
+    }
+  
   [self setSourceURL: [sourceMail imap4URLString]];
   [self setSourceFlag: @"$Forwarded"];
 
@@ -905,7 +910,7 @@ static BOOL        showTextAttachmentsInline  = NO;
     cdtype = @"inline";
   else
     cdtype = @"attachment";
-  
+
   cd = [cdtype stringByAppendingString: @"; filename=\""];
   cd = [cd stringByAppendingString: _name];
   cd = [cd stringByAppendingString: @"\""];
@@ -950,7 +955,13 @@ static BOOL        showTextAttachmentsInline  = NO;
       is7bit = YES;
   }
   if ((s = [self contentDispositionForAttachmentWithName:_name]))
-    [map setObject:s forKey: @"content-disposition"];
+    {
+      NGMimeContentDispositionHeaderField *o;
+      
+      o = [[NGMimeContentDispositionHeaderField alloc] initWithString: s];
+      [map setObject:o forKey: @"content-disposition"];
+      [o release];
+    }
   
   /* prepare body content */
   
index 6feb888a78a57bbf124e75d7abb20cc1b539236b..d4387d5237a82da2ac50bfa6c4eabc873d2917f0 100644 (file)
@@ -207,7 +207,7 @@ static BOOL debugOn = YES;
 
 - (BOOL)isBodyPartKey:(NSString *)_key inContext:(id)_ctx {
   /*
-    Every key starting with a digit is consider an IMAP4 mime part key, used in
+    Every key starting with a digit is considered an IMAP4 mime part key, used in
     SOGoMailObject and SOGoMailBodyPart.
   */
   if ([_key length] == 0)
index 78be1fb677d67e34a65b660b106bfb017ede49b3..8082912773530475578703a998a025d371377641 100644 (file)
@@ -349,7 +349,8 @@ static BOOL debugOn = NO;
 
   if ([mimeType isEqualToString: @"image/gif"]
       || [mimeType isEqualToString: @"image/png"]
-      || [mimeType isEqualToString: @"image/jpg"])
+      || [mimeType isEqualToString: @"image/jpg"]
+      || [mimeType isEqualToString: @"image/jpeg"])
     classString = @"SOGoImageMailBodyPart";
   else if ([mimeType isEqualToString: @"text/calendar"])
     classString = @"SOGoCalendarMailBodyPart";
@@ -366,7 +367,7 @@ static BOOL debugOn = NO;
   if (classString)
     klazz = NSClassFromString (classString);
   else
-    klazz = Nil;
+    klazz = [SOGoMailBodyPart class];
 
   return klazz;
 }
index 91f3a17a93ee0eb7be84b9559baf29057066f3cf..ee54256137c36151852f61e672a73efb28829084 100644 (file)
@@ -80,6 +80,8 @@
   int index;
   BOOL htmlContent;
 
+  content = @"";
+
   if ([keys count])
     {
       types = [keys objectsForKey: @"mimeType"];
        }
       else
        htmlContent = NO;
-      if (index == NSNotFound)
-       content = @"";
-      else
+      
+      if (index != NSNotFound)
        {
          contentKey = [keys objectAtIndex: index];
          parts = [self fetchPlainTextStrings:
                          [NSArray arrayWithObject: contentKey]];
-         rawPart = [[parts allValues] objectAtIndex: 0];
-         if (htmlContent)
-           content = [rawPart htmlToText];
-         else
-           content = rawPart;
+         if ([parts count] > 0)
+           {
+             rawPart = [[parts allValues] objectAtIndex: 0];
+             if (htmlContent)
+               content = [rawPart htmlToText];
+             else
+               content = rawPart;
+           }
        }
     }
-  else
-    content = @"";
-
+  
   return content;
 }
 
 
   subject = [self decodedSubject];
   if ([subject length] > 0)
-    newSubject = [NSString stringWithFormat: @"[Fwd: %@]", subject];
+    newSubject = [NSString stringWithFormat: @"Fwd: %@", subject];
   else
     newSubject = subject;
 
index f6969e9721fb02db61eebcf26b1bb92f0dc26260..4c44bd993496ca60fd334fee144e377720b55cfd 100644 (file)
@@ -656,8 +656,13 @@ static BOOL debugSoParts       = NO;
          [s autorelease];
        }
       else
-       s = [NSString stringWithData: mailData
-                     usingEncodingNamed: charset];
+       {
+         s = [NSString stringWithData: mailData usingEncodingNamed: charset];
+         
+         // If it has failed, we try at least using UTF-8. Normally, this can NOT fail
+         if (!s)
+           s = [[[NSString alloc] initWithData: mailData encoding: NSISOLatin1StringEncoding] autorelease];
+       }
     }
   else
     s = nil;
index 201e16b8a5719b0e84ab739cf90054eecd15ce90..c2edec3de3d3fdf5662fa3b0dc3d69f6e7f4abdb 100644 (file)
@@ -41,6 +41,7 @@
   NSString *IDField; /* the first part of a user DN */
   NSString *CNField;
   NSString *UIDField;
+  NSArray *mailFields;
   NSString *bindFields;
 
   NGLdapConnection *ldapConnection;
@@ -59,6 +60,7 @@
           IDField: (NSString *) newIDField
           CNField: (NSString *) newCNField
          UIDField: (NSString *) newUIDField
+       mailFields: (NSArray *) newMailFields
      andBindFields: (NSString *) newBindFields;
 
 - (BOOL) checkLogin: (NSString *) login
index 28475ad186212dda342b46c661c00ebf1c54c852..b2cd7ad32c7774df1ce49463360c2532746e0f17 100644 (file)
@@ -146,6 +146,8 @@ static int sizeLimit;
       IDField = @"cn"; /* the first part of a user DN */
       CNField = @"cn";
       UIDField = @"uid";
+      mailFields = [NSArray arrayWithObject: @"mail"];
+      [mailFields retain];
       bindFields = nil;
 
       ldapConnection = nil;
@@ -164,6 +166,7 @@ static int sizeLimit;
   [IDField release];
   [CNField release];
   [UIDField release];
+  [mailFields release];
   [bindFields release];
   [ldapConnection release];
   [sourceID release];
@@ -183,7 +186,8 @@ static int sizeLimit;
   [self setBaseDN: [udSource objectForKey: @"baseDN"]
        IDField: [udSource objectForKey: @"IDFieldName"]
        CNField: [udSource objectForKey: @"CNFieldName"]
-       UIDField:  [udSource objectForKey: @"UIDFieldName"]
+       UIDField: [udSource objectForKey: @"UIDFieldName"]
+       mailFields: [udSource objectForKey: @"MailFieldNames"]
        andBindFields: [udSource objectForKey: @"bindFields"]];
 
   return self;
@@ -205,6 +209,7 @@ static int sizeLimit;
           IDField: (NSString *) newIDField
           CNField: (NSString *) newCNField
          UIDField: (NSString *) newUIDField
+       mailFields: (NSArray *) newMailFields
      andBindFields: (NSString *) newBindFields
 {
   ASSIGN (baseDN, newBaseDN);
@@ -214,6 +219,8 @@ static int sizeLimit;
     ASSIGN (CNField, newCNField);
   if (UIDField)
     ASSIGN (UIDField, newUIDField);
+  if (newMailFields)
+    ASSIGN (mailFields, newMailFields);
   if (newBindFields)
     ASSIGN (bindFields, newBindFields);
 }
@@ -346,8 +353,6 @@ static int sizeLimit;
 
 - (NSArray *) _searchAttributes
 {
-  NSArray *attrs;
-
   if (!searchAttributes)
     {
       searchAttributes = [NSMutableArray new];
@@ -355,15 +360,10 @@ static int sizeLimit;
        [searchAttributes addObject: CNField];
       if (UIDField)
        [searchAttributes addObject: UIDField];
+      [searchAttributes addObjectsFromArray: mailFields];
       [searchAttributes addObjectsFromArray: commonSearchFields];
     }
 
-  // We also include our MailFieldNames in the search
-  if ((attrs = [[[LDAPUserManager sharedUserManager] metadataForSourceID: sourceID] objectForKey: @"MailFieldNames"]))
-    {
-      [searchAttributes addObjectsFromArray: attrs];
-    }
-
   return searchAttributes;
 }
 
@@ -397,6 +397,25 @@ static int sizeLimit;
   return ids;
 }
 
+- (void) _fillEmailsOfEntry: (NGLdapEntry *) ldapEntry
+          intoContactEntry: (NSMutableDictionary *) contactEntry
+{
+  NSEnumerator *emailFields;
+  NSString *currentFieldName, *value;
+  NSMutableArray *emails;
+
+  emails = [NSMutableArray new];
+  emailFields = [mailFields objectEnumerator];
+  while ((currentFieldName = [emailFields nextObject]))
+    {
+      value = [[ldapEntry attributeWithName: currentFieldName] stringValueAtIndex: 0];
+      if (value)
+       [emails addObject: value];
+    }
+  [emails autorelease];
+  [contactEntry setObject: emails forKey: @"c_emails"];
+}
+
 - (NSDictionary *) _convertLDAPEntryToContact: (NGLdapEntry *) ldapEntry
 {
   NSMutableDictionary *contactEntry;
@@ -426,6 +445,7 @@ static int sizeLimit;
   if (!value)
     value = @"";
   [contactEntry setObject: value forKey: @"c_cn"];
+  [self _fillEmailsOfEntry: ldapEntry intoContactEntry: contactEntry];
 
   return contactEntry;
 }
index be9ed79dcff309740b95c773952c384427a19e45..6dcdd3afafaf8c5e0ce3713907ec71d60b68dcdb 100644 (file)
@@ -27,6 +27,7 @@
 #import <Foundation/NSUserDefaults.h>
 #import <Foundation/NSValue.h>
 
+#import "NSArray+Utilities.h"
 #import "LDAPSource.h"
 #import "LDAPUserManager.h"
 
@@ -289,9 +290,7 @@ static NSString *defaultMailDomain = nil;
   emails = [contact objectForKey: @"emails"];
   uid = [contact objectForKey: @"c_uid"];
   systemEmail = [NSString stringWithFormat: @"%@@%@", uid, defaultMailDomain];
-  if ([emails containsObject: systemEmail])
-    [emails removeObject: systemEmail];
-  [emails addObject: systemEmail];
+  [emails addObjectUniquely: systemEmail];
   [contact setObject: [emails objectAtIndex: 0] forKey: @"c_email"];
 }
 
@@ -302,8 +301,8 @@ static NSString *defaultMailDomain = nil;
   NSDictionary *userEntry;
   NSEnumerator *ldapSources;
   LDAPSource *currentSource;
-  NSString *cn, *email, *c_uid;
-  NSArray *attrs;
+  NSString *cn, *c_uid;
+  NSArray *c_emails;
   
   emails = [NSMutableArray array];
   cn = nil;
@@ -320,18 +319,9 @@ static NSString *defaultMailDomain = nil;
            cn = [userEntry objectForKey: @"c_cn"];
          if (!c_uid)
            c_uid = [userEntry objectForKey: @"c_uid"];
-
-         if ((attrs = [[sourcesMetadata objectForKey: [currentSource sourceID]] objectForKey: @"MailFieldNames"]))
-           {
-             int i;
-
-             for (i = 0; i < [attrs count]; i++)
-               {
-                 email = [userEntry objectForKey: [attrs objectAtIndex: i]];
-                 if (email && ![emails containsObject: email])
-                   [emails addObject: email];
-               }
-           }
+         c_emails = [userEntry objectForKey: @"c_emails"];
+         if ([c_emails count])
+           [emails addObjectsFromArray: c_emails];
        }
       currentSource = [ldapSources nextObject];
     }
index 4fc2c7a8bd5ab1787e11f448b81e5936e2cf821f..807b98745437ca19d856a6beed65641da540dd4a 100644 (file)
@@ -48,7 +48,6 @@
 /* content */
 
 - (BOOL) isNew;
-- (void) setContentString: (NSString *) newContent;
 - (NSString *) contentAsString;
 - (NSException *) saveContentString: (NSString *) _str
                         baseVersion: (unsigned int) _baseVersion;
 
 @end
 
+@interface SOGoContentObject (OptionalMethods)
+
+- (void) prepareDelete;
+
+@end
+
 #endif /* __SOGo_SOGoContentObject_H__ */
index 7e43dbbbbf72022a85158696b3d744d5294c4b17..e45b458f7b5d5ed12ae4ae89ca704da3ba1955c2 100644 (file)
   return content;
 }
 
-- (void) setContentString: (NSString *) newContent
-{
-  ASSIGN (content, newContent);
-}
-
 - (NSException *) saveContentString: (NSString *) newContent
                         baseVersion: (unsigned int) newBaseVersion
 {
 
   ex = nil;
 
-  [self setContentString: newContent];
+  ASSIGN (content, newContent);
+
   folder = [container ocsFolder];
   if (folder)
     {
-      ex = [folder writeContent: content toName: nameInContainer
+      ex = [folder writeContent: newContent toName: nameInContainer
                   baseVersion: newBaseVersion];
       if (ex)
        [self errorWithFormat:@"write failed: %@", ex];
index 5b938f0916a7381aff2d8903b0b6e5cedcb6841f..9322f247f15eaed22bd4b60224347cf756230a6d 100644 (file)
@@ -350,7 +350,11 @@ static NSString *defaultUserID = @"<default>";
       deleteObject = [self lookupName: currentID
                           inContext: context acquire: NO];
       if (![deleteObject isKindOfClass: [NSException class]])
-       [deleteObject delete];
+       {
+         if ([deleteObject respondsToSelector: @selector (prepareDelete)])
+           [deleteObject prepareDelete];
+         [deleteObject delete];
+       }
     }
 }
 
index 10b9f0fd45f23a918e23a6219c89b5145813c936..64e1f8a03da52c3ebeb7e09d38b45cf209088adc 100644 (file)
     context.activeUser
 */
 
-@class NSString;
 @class NSArray;
 @class NSDictionary;
+@class NSString;
+@class NSTimeZone;
 @class NSURL;
 @class NSUserDefaults;
-@class NSTimeZone;
+
 @class WOContext;
+
 @class SOGoAppointmentFolder;
 @class SOGoAppointmentFolders;
 @class SOGoDateFormatter;
-@class WOContext;
+@class SOGoUserFolder;
 
 extern NSString *SOGoWeekStartHideWeekNumbers;
 extern NSString *SOGoWeekStartJanuary1;
@@ -68,6 +70,7 @@ extern NSString *SOGoWeekStartFirstFullWeek;
   NSTimeZone *userTimeZone;
   SOGoDateFormatter *dateFormatter;
   NSMutableArray *mailAccounts;
+  SOGoUserFolder *homeFolder;
 }
 
 + (NSString *) language;
@@ -119,7 +122,7 @@ extern NSString *SOGoWeekStartFirstFullWeek;
 
 /* folders */
 
-- (id) homeFolderInContext: (id) _ctx;
+- (SOGoUserFolder *) homeFolderInContext: (id) context;
 
 - (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context;
 - (SOGoAppointmentFolder *)
index 9488c1dd35e59bb3b5e8d81fb30ee80b8b56c81b..95f32ac960cffbb9fb1aaa09ba689cb5100f91ad 100644 (file)
@@ -142,6 +142,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
       language = nil;
       currentPassword = nil;
       dateFormatter = nil;
+      homeFolder = nil;
     }
 
   return self;
@@ -176,6 +177,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
   [allEmails release];
   [language release];
   [dateFormatter release];
+  [homeFolder release];
   [super dealloc];
 }
 
@@ -506,7 +508,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
   messageForwarding
     = [[self userDefaults] stringForKey: @"MessageForwarding"];
   if (![messageForwarding length])
-    messageForwarding = @"attached";
+    messageForwarding = @"inline";
 
   return messageForwarding;
 }
@@ -528,27 +530,17 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
 // TODO: those methods should check whether the traversal stack in the context
 //       already contains proper folders to improve caching behaviour
 
-- (id) homeFolderInContext: (id) _ctx
+- (SOGoUserFolder *) homeFolderInContext: (id) context
 {
-  /* Note: watch out for cyclic references */
-  // TODO: maybe we should add an [activeUser reset] method to SOPE
-  id folder;
-  
-  folder = [(WOContext *)_ctx objectForKey:@"ActiveUserHomeFolder"];
-  if (folder != nil)
-    return [folder isNotNull] ? folder : nil;
-  
-  folder = [[WOApplication application] lookupName: [self login]
-                                       inContext: _ctx
-                                       acquire: NO];
-  if ([folder isKindOfClass:[NSException class]])
-    return folder;
-  
-  [(WOContext *)_ctx setObject: ((folder)
-                                ? folder
-                                : (id)[NSNull null])
-               forKey: @"ActiveUserHomeFolder"];
-  return folder;
+  if (!homeFolder)
+    {
+      homeFolder = [[WOApplication application] lookupName: [self login]
+                                               inContext: context
+                                               acquire: NO];
+      [homeFolder retain];
+    }
+
+  return homeFolder;
 }
 
 - (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context
index 873df3e605c9082fee1cd1a01c8a25544e52976f..d9530e53046ea98770ac84f3c230decc44d55c46 100644 (file)
@@ -11,16 +11,12 @@ CommonUI_LANGUAGES = English French German
 CommonUI_OBJC_FILES +=         \
        CommonUIProduct.m       \
        UIxPageFrame.m          \
-       UIxPrintPageFrame.m     \
-       UIxAppNavView.m         \
        \
        UIxAclEditor.m  \
        UIxObjectActions.m      \
        UIxFolderActions.m      \
        UIxParentFolderActions.m \
        UIxElemBuilder.m        \
-       UIxTabView.m            \
-       UIxTabItem.m            \
        UIxUserRightsEditor.m   \
        \
        UIxToolbar.m            \
diff --git a/UI/Common/UIxAppNavView.m b/UI/Common/UIxAppNavView.m
deleted file mode 100644 (file)
index 87dfc09..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
-  Copyright (C) 2004 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.
-*/
-
-#import <NGObjWeb/SoObject+SoDAV.h>
-#import <NGObjWeb/WOContext+SoObjects.h>
-
-#import <SOGoUI/UIxComponent.h>
-
-@interface UIxAppNavView : UIxComponent
-{
-  id element;
-  id lastElement;
-}
-
-@end
-
-@implementation UIxAppNavView
-
-- (void)dealloc {
-  [element     release];
-  [lastElement release];
-  [super dealloc];
-}
-
-/* accessors */
-
-- (void)setElement:(id)_element {
-  ASSIGN(element, _element);
-}
-- (id)element {
-  return element;
-}
-
-- (void)setLastElement:(id)_element {
-  ASSIGN(lastElement, _element);
-}
-- (id)lastElement {
-  return lastElement;
-}
-
-/* navigation */
-
-- (NSArray *)navPathElements {
-  NSArray        *traversalObjects;
-  NSMutableArray *navPathComponents;
-  unsigned int   i, count;
-  
-  traversalObjects = [[self context] objectTraversalStack];
-  count = ([traversalObjects count] - 1); /* remove SoPageInvocation */
-  
-  navPathComponents = [[NSMutableArray alloc] initWithCapacity:count];
-  for (i = 0; i < count; i++) {
-    NSString *name;
-    id obj;
-    
-    obj = [traversalObjects objectAtIndex:i];
-    
-    name = [obj davDisplayName];
-    if ([name length] == 0)
-      name = NSStringFromClass([obj class]);
-    name = [self labelForKey:name];
-
-    if (![name hasPrefix:@"sogod"]) {
-      NSMutableDictionary *c;
-      NSString *url;
-      
-      url = [obj baseURLInContext:[self context]];
-      if (![url hasSuffix:@"/"])
-       url = [url stringByAppendingString:@"/"];
-      
-      c = [[NSMutableDictionary alloc] initWithCapacity:2];
-      [c setObject:name forKey:@"name"];
-      [c setObject:url forKey:@"url"];
-      [navPathComponents addObject:c];
-      [c release];
-    }
-  }
-
-  [self setLastElement:[navPathComponents lastObject]];
-  return [navPathComponents autorelease];
-}
-
-@end /* UIxAppNavView */
index 1c3094a56d81d4403475d8bccc8dd17e47581574..d87126f2343072b50ba5fd74de16e17ff7d639fb 100644 (file)
@@ -40,6 +40,7 @@
   NSString *toolbar;
   id item;
   BOOL isPopup;
+  NSMutableArray *additionalCSSFiles;
   NSMutableArray *additionalJSFiles;
 }
 
index efda984ef4f5f2ced97dff63dc0d41106ccbe1ad..9aaa5df61b8b5f360a43bc3df6afef4b2aecbdc2 100644 (file)
   return ([[self productJavaScriptURL] length] > 0);
 }
 
+- (void) setCssFiles: (NSString *) newCSSFiles
+{
+  NSEnumerator *cssFiles;
+  NSString *currentFile, *filename;
+
+  [additionalCSSFiles release];
+  additionalCSSFiles = [NSMutableArray new];
+
+  cssFiles
+    = [[newCSSFiles componentsSeparatedByString: @","] objectEnumerator];
+  while ((currentFile = [cssFiles nextObject]))
+    {
+      filename = [self urlForResourceFilename:
+                        [currentFile stringByTrimmingSpaces]];
+      [additionalCSSFiles addObject: filename];
+    }
+}
+
+- (NSArray *) additionalCSSFiles
+{
+  return additionalCSSFiles;
+}
+
 - (void) setJsFiles: (NSString *) newJSFiles
 {
   NSEnumerator *jsFiles;
diff --git a/UI/Common/UIxPrintPageFrame.m b/UI/Common/UIxPrintPageFrame.m
deleted file mode 100644 (file)
index 48bb2da..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-  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.
-*/
-
-#import <NGObjWeb/SoComponent.h>
-
-@class NSString;
-
-@interface UIxPrintPageFrame : SoComponent
-{
-  NSString *title;
-}
-
-@end
-
-@implementation UIxPrintPageFrame
-
-- (void) dealloc
-{
-  [title release];
-  [super dealloc];
-}
-
-/* accessors */
-
-- (void)setTitle: (NSString *) _value
-{
-  ASSIGNCOPY (title, _value);
-}
-
-- (NSString *) title
-{
-  return title;
-}
-
-@end /* UIxPrintPageFrame */
diff --git a/UI/Common/UIxTabItem.m b/UI/Common/UIxTabItem.m
deleted file mode 100644 (file)
index 28f4088..0000000
+++ /dev/null
@@ -1,475 +0,0 @@
-/*
-  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 "UIxTabView.h"
-#include "common.h"
-
-#if DEBUG
-#  define DEBUG_JS 1
-#endif
-
-/* context keys */
-extern NSString *UIxTabView_HEAD;
-extern NSString *UIxTabView_BODY;
-extern NSString *UIxTabView_KEYS;
-extern NSString *UIxTabView_SCRIPT;
-extern NSString *UIxTabView_ACTIVEKEY;
-extern NSString *UIxTabView_COLLECT;
-
-@implementation UIxTabItem
-
-static Class StrClass = Nil;
-
-+ (int)version {
-  return [super version] + 0;
-}
-+ (void)initialize {
-  StrClass = [NSString class];
-}
-
-static NSString *retStrForInt(int i) {
-  switch(i) {
-  case 0:  return @"0";
-  case 1:  return @"1";
-  case 2:  return @"2";
-  case 3:  return @"3";
-  case 4:  return @"4";
-  case 5:  return @"5";
-  case 6:  return @"6";
-  case 7:  return @"7";
-  case 8:  return @"8";
-  case 9:  return @"9";
-  case 10: return @"10";
-    // TODO: find useful count!
-  default:
-    return [[StrClass alloc] initWithFormat:@"%i", i];
-  }
-}
-
-- (id)initWithName:(NSString *)_name
-  associations:(NSDictionary *)_config
-  template:(WOElement *)_subs
-{
-  if ((self = [super initWithName:_name associations:_config template:_subs])) {
-    self->key      = WOExtGetProperty(_config, @"key");
-    self->label    = WOExtGetProperty(_config, @"label");
-
-    self->isScript = WOExtGetProperty(_config, @"isScript");
-    self->href     = WOExtGetProperty(_config, @"href");
-
-    self->icon     = WOExtGetProperty(_config, @"icon");
-    self->action   = WOExtGetProperty(_config, @"action");
-
-    self->tabStyle         = WOExtGetProperty(_config, @"tabStyle");
-    self->selectedTabStyle = WOExtGetProperty(_config, @"selectedTabStyle");
-
-    self->tabIcon         = WOExtGetProperty(_config, @"tabIcon");
-    self->leftTabIcon     = WOExtGetProperty(_config, @"leftTabIcon");
-    self->selectedTabIcon = WOExtGetProperty(_config, @"selectedTabIcon");
-    
-    self->asBackground    = WOExtGetProperty(_config, @"asBackground");
-    self->width           = WOExtGetProperty(_config, @"width");
-    self->height          = WOExtGetProperty(_config, @"height");
-    self->activeBgColor   = WOExtGetProperty(_config, @"activeBgColor");
-    self->inactiveBgColor = WOExtGetProperty(_config, @"inactiveBgColor");
-    
-    self->template = [_subs retain];
-  }
-  return self;
-}
-
-- (void)dealloc {
-  [self->key      release];
-  [self->label    release];
-
-  [self->href   release];
-
-  [self->action   release];
-
-  [self->isScript release];
-  [self->template release];
-
-  [self->tabStyle release];
-  [self->selectedTabStyle release];
-
-  [self->icon     release];
-  [self->leftTabIcon     release];
-  [self->selectedTabIcon release];
-  [self->tabIcon         release];
-
-  [self->asBackground release];
-  [self->width        release];
-  [self->height       release];
-
-  [self->activeBgColor   release];
-  [self->inactiveBgColor release];
-  
-  [super dealloc];
-}
-
-/* responder */
-
-- (void)takeValuesFromRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
-  NSString *activeTabKey;
-  NSString *myTabKey;
-  BOOL     doCheck;
-  
-  if ([_ctx objectForKey:UIxTabView_HEAD]) {
-    /* head clicks */
-    [[_ctx component] debugWithFormat:
-                        @"UIxTabItem: head takes (no) values, eid='%@'",
-                        [_ctx elementID]];
-    return;
-  }
-
-  if ((activeTabKey = [_ctx objectForKey:UIxTabView_BODY]) == nil) {
-    [[_ctx component] debugWithFormat:@"UIxTabItem: invalid state"];
-    [self->template takeValuesFromRequest:_rq inContext:_ctx];
-    return;
-  }
-  
-  myTabKey = [self->key      stringValueInComponent:[_ctx component]];
-  doCheck  = [self->isScript boolValueInComponent:[_ctx component]];
-    
-  if ([activeTabKey isEqualToString:myTabKey] || doCheck) {
-#if ADD_OWN_ELEMENTIDS
-    [_ctx appendElementIDComponent:activeTabKey];
-#endif
-      
-#if DEBUG_TAKEVALUES
-    [[_ctx component] debugWithFormat:
-                          @"UIxTabItem: body takes values, eid='%@'",
-                          [_ctx elementID]];
-#endif
-      
-    [self->template takeValuesFromRequest:_rq inContext:_ctx];
-#if ADD_OWN_ELEMENTIDS
-    [_ctx deleteLastElementIDComponent];
-#endif
-  }
-#if DEBUG_TAKEVALUES
-  else {
-      [[_ctx component] debugWithFormat:
-                          @"UIxTabItem: body takes no values, eid='%@'",
-                          [_ctx elementID]];
-  }
-#endif
-}
-
-- (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
-  id            result;
-  WOAssociation *tmp;
-  NSString      *activeTabKey;
-  
-  if ((tmp = [_ctx objectForKey:UIxTabView_HEAD])) {
-    /* click on tab icon */
-    NSString      *tabkey;
-    
-    tabkey = [_ctx currentElementID];
-    [_ctx consumeElementID];
-    [_ctx appendElementIDComponent:tabkey];
-    
-    if ([tmp isValueSettable])
-      [tmp setValue:tabkey inComponent:[_ctx component]];
-    
-#if 0
-    result = [self->action valueInComponent:[_ctx component]];
-#endif
-
-    [_ctx deleteLastElementIDComponent];
-  }
-  else if ((activeTabKey = [_ctx objectForKey:UIxTabView_BODY])) {
-    /* clicked somewhere in the (active) body */
-    result = [self->template invokeActionForRequest:_req inContext:_ctx];
-  }
-  else {
-    [[_ctx component] logWithFormat:@"UIxTabItem: invalid invoke state"];
-    result = [self->template invokeActionForRequest:_req inContext:_ctx];
-  }
-  
-  return result;
-}
-
-/* info collection */
-
-- (void)_collectInContext:(WOContext *)_ctx key:(NSString *)k {
-  BOOL  isLeft = NO;
-  NSMutableArray *keys;
-  UIxTabItemInfo  *info;
-  WOComponent    *cmp;
-      
-  cmp  = [_ctx component];
-  keys = [_ctx objectForKey:UIxTabView_KEYS];
-  if (keys == nil) {
-    keys = [[[NSMutableArray alloc] init] autorelease];
-    [_ctx setObject:keys forKey:UIxTabView_KEYS];
-    isLeft = YES;
-  }
-      
-  if (k == nil) {
-    /* auto-assign a key */
-    k = retStrForInt([keys count]);
-  }
-  else
-    k = [k retain];
-  [_ctx appendElementIDComponent:k];
-  
-  info = [[UIxTabItemInfo alloc] init];
-  info->key      = [k copy];
-  info->label    = [[self->label stringValueInComponent:cmp] copy];
-  info->icon     = [[self->icon  stringValueInComponent:cmp] copy];
-#if 0
-  info->uri      = [[_ctx componentActionURL] copy];
-#else
-  info->uri      = [[self->href stringValueInComponent:cmp] copy];
-#endif
-  info->isScript = [self->isScript boolValueInComponent:cmp];
-  info->tabIcon  = [[self->tabIcon stringValueInComponent:cmp] copy];
-  info->leftIcon = [[self->leftTabIcon stringValueInComponent:cmp] copy];
-  info->selIcon  = [[self->selectedTabIcon stringValueInComponent:cmp]
-                                           copy];
-  info->tabStyle         = [[self->tabStyle stringValueInComponent:cmp] copy];
-  info->selectedTabStyle = [[self->selectedTabStyle stringValueInComponent:cmp]
-                                                    copy];
-
-  if (self->asBackground == nil)
-    info->asBackground = 0;
-  else {
-    info->asBackground
-      = ([self->asBackground boolValueInComponent:cmp]) ? 1 : -1;
-  }
-  info->width        = [[self->width  stringValueInComponent:cmp] copy];
-  info->height       = [[self->height stringValueInComponent:cmp] copy];
-  info->activeBg     = [[self->activeBgColor stringValueInComponent:cmp]
-                                             copy];
-  info->inactiveBg   = [[self->inactiveBgColor stringValueInComponent:cmp]
-                                               copy];
-      
-  if (info->leftIcon == nil) info->leftIcon = [info->tabIcon copy];
-      
-  [keys addObject:info];
-  [info release];
-  [k release];
-      
-  [_ctx deleteLastElementIDComponent];
-}
-
-/* header generation */
-
-- (void)_appendHeadToResponse:(WOResponse *)_response
-  inContext:(WOContext *)_ctx
-  activeKey:(NSString *)activeKey
-  key:(NSString *)k
-{
-  /* head is currently generated in UIxTabView */
-#if 0
-  // note: some associations can be inherited by UIxTabView !
-  BOOL        doImages;
-  WOComponent *comp;
-  BOOL        doBgIcon;
-  NSString    *label;
-  NSString    *w, *h;
-  
-  doImages = ![[[_ctx request] clientCapabilities] isTextModeBrowser];
-  comp     = [_ctx component];
-  
-  doBgIcon = self->asBackground && doImages
-    ? [self->asBackground boolValueInComponent:comp] ? YES : NO
-    : NO;
-  
-  if ((label = [self->label stringValueInComponent:comp]) == nil)
-    label = k;
-
-  if (doImages) {
-    /* lookup image */
-    NSString *imgName = nil;
-    // ...
-    
-    imgUri = WEUriOfResource(imgName, _ctx);
-    if ([imgUri length] < 1)
-      doImages = NO;
-  }
-  
-  // .... _isActive
-#endif
-}
-
-/* body generation */
-
-- (void)_appendBodyToResponse:(WOResponse *)_response
-  inContext:(WOContext *)_ctx
-  activeKey:(NSString *)tmp
-  key:(NSString *)k
-{
-  BOOL doScript;
-  BOOL isScript_;
-  BOOL isActive;
-
-  doScript  = [[_ctx objectForKey:UIxTabView_SCRIPT] boolValue];
-  isScript_ = [self->isScript boolValueInComponent:[_ctx component]];
-  isActive  = [tmp isEqualToString:k];
-    
-  if (doScript && (isActive || isScript_)) {
-    [_response appendContentString:@"<div id=\""];
-    [_response appendContentString:k];
-    [_response appendContentString:@"TabLayer\" style=\"display: none;\">\n"];
-  }
-  
-  if (isActive || (doScript && isScript_)) {
-    /* content is active or used as layer*/
-#if ADD_OWN_ELEMENTIDS
-    [_ctx appendElementIDComponent:k];
-#endif
-#if DEBUG && 0
-    NSLog(@"TAB: %@", k);
-#endif
-    
-    [self->template appendToResponse:_response inContext:_ctx];
-    
-#if ADD_OWN_ELEMENTIDS
-    [_ctx deleteLastElementIDComponent];
-#endif
-  }
-    
-  if (doScript && (isActive || isScript_)) {
-    NSString *jsout;
-    [_response appendContentString:@"</div>"];
-
-    jsout = [NSString alloc];
-    jsout = [jsout initWithFormat:
-                   @"<script language=\"JavaScript\">\n<!--\n"
-                   @"%@Tab[\"Div\"] = %@TabLayer;\n",
-                   k, k];
-    
-    [_response appendContentString:jsout];
-    [jsout release];
-    
-#if DEBUG_JS
-    jsout = [NSString alloc];
-    jsout = [jsout initWithFormat:
-                     @"if (%@Tab[\"Div\"].style==null) {"
-                     @"alert('missing style in div for tab %@');}",
-                     k, k];
-    
-    [_response appendContentString:jsout];
-    [jsout release];
-#endif
-    
-    if (isActive) {
-      [_response appendContentString:@"showTab("];
-      [_response appendContentString:k];
-      [_response appendContentString:@"Tab);\n"];
-    }
-    [_response appendContentString:@"//-->\n</script>"];
-  }
-}
-
-/* master generation method */
-
-- (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
-  NSString *k;
-  BOOL     doForm;
-  id       tmp;
-  
-  doForm = [_ctx isInForm];
-  k = [self->key stringValueInComponent:[_ctx component]];
-  
-  if ((tmp = [_ctx objectForKey:UIxTabView_HEAD])) {
-    if ([tmp isEqual:UIxTabView_COLLECT]) {
-      [self _collectInContext:_ctx key:k];
-    }
-    else {
-      [self _appendHeadToResponse:_response inContext:_ctx
-            activeKey:tmp key:k];
-    }
-  }
-  else if ((tmp = [_ctx objectForKey:UIxTabView_BODY])) {
-    [self _appendBodyToResponse:_response inContext:_ctx
-          activeKey:tmp key:k];
-  }
-  else {
-    [self warnWithFormat:@"(%s): invalid UIxTabItem state !!!",
-            __PRETTY_FUNCTION__];
-    [_response appendContentString:@"[invalid state]"];
-  }
-}
-
-@end /* UIxTabItem */
-
-@implementation UIxTabItemInfo
-
-- (void)dealloc {
-  [self->uri                release];
-  [self->icon               release];
-  [self->label              release];
-  [self->key                release];
-  [self->tabStyle           release];
-  [self->selectedTabStyle   release];
-  [self->tabIcon            release];
-  [self->selIcon            release];
-  [self->leftIcon           release];
-  [self->width              release];
-  [self->height             release];
-  [self->activeBg           release];
-  [self->inactiveBg         release];
-
-  [super dealloc];
-}
-
-/* accessors */
-
-- (NSString *)key {
-  return self->key;
-}
-- (NSString *)label {
-  return self->label;
-}
-- (NSString *)icon {
-  return self->icon;
-}
-- (NSString *)uri {
-  return self->uri;
-}
-- (BOOL)isScript {
-  return self->isScript;
-}
-
-- (int)asBackground {
-  return self->asBackground;
-}
-
-- (NSString *)width {
-  return self->width;
-}
-
-- (NSString *)height {
-  return self->height;
-}
-
-- (NSString *)activeBg {
-  return self->activeBg;
-}
-
-- (NSString *)inactiveBg {
-  return self->inactiveBg;
-}
-
-@end /* UIxTabItemInfo */
diff --git a/UI/Common/UIxTabView.h b/UI/Common/UIxTabView.h
deleted file mode 100644 (file)
index a2e6a1f..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
-  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 __UIxTabView_H__
-#define __UIxTabView_H__
-
-/*
-  This is a library private header !
-*/
-
-#include <NGObjWeb/WODynamicElement.h>
-
-/*
-  Does not support tab-head-creation from nested components !!!
-
-  hh: Why not ??? -> Because selection is manipulated in sub-elements
-
-  UIxTabView creates element-IDs like
-
-    .h.*.$key.  for the tab-items   (head-mode)
-    .b.$key...  for the tab-content (content-mode) (new, hh)
-
-  !!! UIxTabView JavaScript can't handle duplicate tab-keys !!!
-*/
-
-@interface UIxTabView : WODynamicElement
-{
-  WOAssociation *selection;
-
-  /* config: */
-  WOAssociation *headerStyle;
-  WOAssociation *bodyStyle;
-  WOAssociation *tabStyle;
-  WOAssociation *selectedTabStyle;
-
-  /* old config: */
-  WOAssociation *bgColor;
-  WOAssociation *nonSelectedBgColor;
-  WOAssociation *leftCornerIcon;
-  WOAssociation *rightCornerIcon;
-  
-  WOAssociation *tabIcon;
-  WOAssociation *leftTabIcon;
-  WOAssociation *selectedTabIcon;
-  
-  WOAssociation *asBackground;
-  WOAssociation *width;
-  WOAssociation *height;
-  WOAssociation *activeBgColor;
-  WOAssociation *inactiveBgColor;
-
-  WOAssociation *fontColor;
-  WOAssociation *fontSize;
-  WOAssociation *fontFace;
-
-  id            template;
-}
-
-@end
-
-@interface UIxTabItem : WODynamicElement
-{
-  WOAssociation *key;
-  WOAssociation *label;
-
-  WOAssociation *href;
-  WOAssociation *isScript;
-
-  WOAssociation *action;
-  WOAssociation *icon;
-
-  /* config: */
-  WOAssociation *tabStyle;
-  WOAssociation *selectedTabStyle;
-
-  /* old config */
-  WOAssociation *tabIcon;
-  WOAssociation *leftTabIcon;
-  WOAssociation *selectedTabIcon;
-
-  WOAssociation *asBackground;
-  WOAssociation *width;
-  WOAssociation *height;
-  WOAssociation *activeBgColor;
-  WOAssociation *inactiveBgColor;
-  
-  id            template;
-}
-
-@end
-
-@interface UIxTabItemInfo : NSObject
-{
-@public
-  NSString *label;
-  NSString *icon;
-  NSString *key;
-  NSString *uri;
-  NSString *tabIcon;
-  NSString *leftIcon;
-  NSString *selIcon;
-  NSString *tabStyle;
-  NSString *selectedTabStyle;
-
-  int      asBackground; // 0 -> not set, 1 -> YES, else -> NO
-  NSString *width;
-  NSString *height;
-  NSString *activeBg;
-  NSString *inactiveBg;
-
-  BOOL     isScript;
-}
-@end
-
-#endif /* __UIxTabView_H__ */
diff --git a/UI/Common/UIxTabView.m b/UI/Common/UIxTabView.m
deleted file mode 100644 (file)
index 6bc96ed..0000000
+++ /dev/null
@@ -1,684 +0,0 @@
-/*
-  Copyright (C) 2000-2004 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 "UIxTabView.h"
-#include "common.h"
-#include <NGObjWeb/NGObjWeb.h>
-#include <NGExtensions/NGExtensions.h>
-#include <EOControl/EOControl.h>
-#include <NGObjWeb/WEClientCapabilities.h>
-
-#if DEBUG
-// #  define DEBUG_TAKEVALUES 1
-#  define DEBUG_JS 1
-#endif
-
-/* context keys */
-NSString *UIxTabView_HEAD      = @"UIxTabView_head";
-NSString *UIxTabView_BODY      = @"UIxTabView_body";
-NSString *UIxTabView_KEYS      = @"UIxTabView_keys";
-NSString *UIxTabView_SCRIPT    = @"UIxTabView_script";
-NSString *UIxTabView_ACTIVEKEY = @"UIxTabView_activekey";
-NSString *UIxTabView_COLLECT   = @"~tv~";
-
-@implementation UIxTabView
-
-static NSNumber *YesNumber;
-
-+ (void)initialize {
-  if (YesNumber == nil)
-    YesNumber = [[NSNumber numberWithBool:YES] retain];
-}
-
-+ (int)version {
-  return [super version] + 0;
-}
-
-- (id)initWithName:(NSString *)_name
-  associations:(NSDictionary *)_config
-  template:(WOElement *)_subs
-{
-  if ((self = [super initWithName:_name associations:_config template:_subs])) {
-    self->selection          = WOExtGetProperty(_config, @"selection");
-    
-    self->headerStyle        = WOExtGetProperty(_config, @"headerStyle");
-    self->bodyStyle          = WOExtGetProperty(_config, @"bodyStyle");
-    self->tabStyle           = WOExtGetProperty(_config, @"tabStyle");
-    self->selectedTabStyle   = WOExtGetProperty(_config, @"selectedTabStyle");
-
-    self->bgColor            = WOExtGetProperty(_config, @"bgColor");
-    self->nonSelectedBgColor = WOExtGetProperty(_config, @"nonSelectedBgColor");
-    self->leftCornerIcon     = WOExtGetProperty(_config, @"leftCornerIcon");
-    self->rightCornerIcon    = WOExtGetProperty(_config, @"rightCornerIcon");
-
-    self->tabIcon            = WOExtGetProperty(_config, @"tabIcon");
-    self->leftTabIcon        = WOExtGetProperty(_config, @"leftTabIcon");
-    self->selectedTabIcon    = WOExtGetProperty(_config, @"selectedTabIcon");
-
-    self->asBackground       = WOExtGetProperty(_config, @"asBackground");
-    self->width              = WOExtGetProperty(_config, @"width");
-    self->height             = WOExtGetProperty(_config, @"height");
-    self->activeBgColor      = WOExtGetProperty(_config, @"activeBgColor");
-    self->inactiveBgColor    = WOExtGetProperty(_config, @"inactiveBgColor");
-
-    self->fontColor          = WOExtGetProperty(_config, @"fontColor");
-    self->fontSize           = WOExtGetProperty(_config, @"fontSize");
-    self->fontFace           = WOExtGetProperty(_config, @"fontFace");
-
-    self->template = RETAIN(_subs);
-  }
-  return self;
-}
-
-- (void)dealloc {
-  [self->selection release];
-
-  [self->headerStyle release];
-  [self->bodyStyle release];
-  [self->tabStyle release];
-  [self->selectedTabStyle release];
-
-  RELEASE(self->bgColor);
-  RELEASE(self->nonSelectedBgColor);
-  RELEASE(self->leftCornerIcon);
-  RELEASE(self->rightCornerIcon);
-
-  RELEASE(self->leftTabIcon);
-  RELEASE(self->selectedTabIcon);
-  RELEASE(self->tabIcon);
-
-  RELEASE(self->width);
-  RELEASE(self->height);
-
-  RELEASE(self->activeBgColor);
-  RELEASE(self->inactiveBgColor);
-
-  RELEASE(self->fontColor);
-  RELEASE(self->fontSize);
-  RELEASE(self->fontFace);
-  
-  RELEASE(self->template);
-  [super dealloc];
-}
-
-/* nesting */
-
-- (id)saveNestedStateInContext:(WOContext *)_ctx {
-  return nil;
-}
-- (void)restoreNestedState:(id)_state inContext:(WOContext *)_ctx {
-  if (_state == nil) return;
-}
-
-- (NSArray *)collectKeysInContext:(WOContext *)_ctx {
-  /* collect mode, collects all keys */
-  [_ctx setObject:UIxTabView_COLLECT forKey:UIxTabView_HEAD];
-  
-  [self->template appendToResponse:nil inContext:_ctx];
-  
-  [_ctx removeObjectForKey:UIxTabView_HEAD];
-  return [_ctx objectForKey:UIxTabView_KEYS];
-}
-
-/* responder */
-
-- (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
-  id       nestedState;
-  NSString *activeTabKey;
-  
-  activeTabKey = [self->selection stringValueInComponent:[_ctx component]];
-  NSLog(@"%s activeTabKey:%@", __PRETTY_FUNCTION__, activeTabKey);
-  nestedState = [self saveNestedStateInContext:_ctx];
-  [_ctx appendElementIDComponent:@"b"];
-  [_ctx appendElementIDComponent:activeTabKey];
-  
-  [_ctx setObject:activeTabKey forKey:UIxTabView_BODY];
-  
-#if DEBUG_TAKEVALUES
-  [[_ctx component] debugWithFormat:@"UIxTabView: body takes values, eid='%@'",
-                    [_ctx elementID]];
-#endif
-  
-  [self->template takeValuesFromRequest:_req inContext:_ctx];
-  
-  [_ctx removeObjectForKey:UIxTabView_BODY];
-  [_ctx deleteLastElementIDComponent]; // activeKey
-  [_ctx deleteLastElementIDComponent]; /* 'b' */
-  [self restoreNestedState:nestedState inContext:_ctx];
-}
-
-- (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
-  NSString *key;
-  id       result;
-  id       nestedState;
-  
-  if ((key = [_ctx currentElementID]) == nil)
-    return nil;
-  
-  result      = nil;
-  nestedState = [self saveNestedStateInContext:_ctx];
-    
-  if ([key isEqualToString:@"h"]) {
-    /* header action */
-    //NSString *urlKey;
-    
-    [_ctx consumeElementID];
-    [_ctx appendElementIDComponent:@"h"];
-#if 0
-    if ((urlKey = [_ctx currentElementID]) == nil) {
-      [[_ctx application]
-             debugWithFormat:@"missing active head tab key !"];
-    }
-    else {
-      //NSLog(@"clicked: %@", urlKey);
-      [_ctx consumeElementID];
-      [_ctx appendElementIDComponent:urlKey];
-    }
-#endif
-    
-    [_ctx setObject:self->selection forKey:UIxTabView_HEAD];
-    result = [self->template invokeActionForRequest:_req inContext:_ctx];
-    [_ctx removeObjectForKey:UIxTabView_HEAD];
-
-#if 0
-    if (urlKey)
-      [_ctx deleteLastElementIDComponent]; // active key
-#endif
-    [_ctx deleteLastElementIDComponent]; // 'h'
-  }
-  else if ([key isEqualToString:@"b"]) {
-    /* body action */
-    NSString *activeTabKey, *urlKey;
-    
-    [_ctx consumeElementID];
-    [_ctx appendElementIDComponent:@"b"];
-      
-    if ((urlKey = [_ctx currentElementID]) == nil) {
-      [[_ctx application]
-             debugWithFormat:@"missing active body tab key !"];
-    }
-    else {
-      //NSLog(@"clicked: %@", urlKey);
-      [_ctx consumeElementID];
-      [_ctx appendElementIDComponent:urlKey];
-    }
-    
-    activeTabKey = [self->selection stringValueInComponent:[_ctx component]];
-    [_ctx setObject:activeTabKey forKey:UIxTabView_BODY];
-    
-    result = [self->template invokeActionForRequest:_req inContext:_ctx];
-      
-    [_ctx removeObjectForKey:UIxTabView_BODY];
-
-    if (urlKey)
-      [_ctx deleteLastElementIDComponent]; // active key
-    [_ctx deleteLastElementIDComponent]; // 'b'
-  }
-  else {
-    [[_ctx application]
-           debugWithFormat:@"unknown tab container key '%@'", key];
-  }
-    
-  [self restoreNestedState:nestedState inContext:_ctx];
-  return result;
-}
-
-- (NSString *)_tabViewCountInContext:(WOContext *)_ctx {
-  int count;
-  count = [[_ctx valueForKey:@"UIxTabViewScriptDone"] intValue];
-  return [NSString stringWithFormat:@"%d",count];
-}
-
-- (NSString *)scriptHref:(UIxTabItemInfo *)_info
-  inContext:(WOContext *)_ctx
-  isLeft:(BOOL)_isLeft
-  keys:(NSArray *)_keys
-{
-  NSMutableString *result = [NSMutableString string];
-  UIxTabItemInfo *tmp;
-  NSString       *activeKey;
-  int            i, cnt;
-  NSString       *elID;
-  NSString       *tstring;
-  
-  activeKey = [self->selection stringValueInComponent:[_ctx component]];
-  [result appendString:@"JavaScript:showTab("];
-  [result appendString:_info->key];
-  [result appendString:@"Tab);"];
-  
-  [result appendString:@"swapCorners("];
-  tstring = (!_isLeft)
-    ? @"tabCorner%@,tabCornerLeft%@);"
-    : @"tabCornerLeft%@,tabCorner%@);";
-  elID = [self _tabViewCountInContext:_ctx];
-  [result appendString:[NSString stringWithFormat:tstring,elID,elID]];
-  
-  for (i=0, cnt = [_keys count]; i < cnt; i++) {
-    tmp = [_keys objectAtIndex:i];
-
-    if ((tmp->isScript || [tmp->key isEqualToString:activeKey])
-        && ![tmp->key isEqualToString:_info->key]) {
-      [result appendString:@"hideTab("];
-      [result appendString:tmp->key];
-      [result appendString:@"Tab);"];
-    }
-  }
-  return result;
-}
-
-- (void)appendLink:(UIxTabItemInfo *)_info
-  toResponse:(WOResponse *)_response
-  inContext:(WOContext *)_ctx
-  isActive:(BOOL)_isActive isLeft:(BOOL)_isLeft
-  doScript:(BOOL)_doScript keys:(NSArray *)_keys
-{
-  NSString *headUri    = nil;
-  NSString *label      = nil;
-  NSString *styleName  = nil;
-  WEClientCapabilities *ccaps;
-  WOComponent *comp;
-
-  ccaps = [[_ctx request] clientCapabilities];
-
-  comp = [_ctx component];
-  headUri = _info->uri;
-
-  if ((label = _info->label) == nil)
-    label = _info->key;
-  
-  if (_isActive) {
-    styleName = (_info->selectedTabStyle)
-      ? _info->selectedTabStyle
-      : [self->selectedTabStyle stringValueInComponent:comp];
-  }
-  else {
-    styleName = (_info->tabStyle)
-      ? _info->tabStyle
-      : [self->tabStyle stringValueInComponent:comp];
-  }
-  
-  [_response appendContentString:@"<td align='center' valign='middle'"];
-  
-  if (styleName) {
-      [_response appendContentString:@" class='"];
-      [_response appendContentHTMLAttributeValue:styleName];
-      [_response appendContentCharacter:'\''];
-  }
-
-  // click on td background
-  if ([ccaps isInternetExplorer] && [ccaps isJavaScriptBrowser]) {
-      [_response appendContentString:@" onclick=\"window.location.href='"];
-      [_response appendContentHTMLAttributeValue:headUri];
-      [_response appendContentString:@"'\""];
-  }
-  
-  [_response appendContentCharacter:'>'];
-
-  [_response appendContentString:@"<a href=\""];
-  
-  [_response appendContentHTMLAttributeValue:headUri];
-  
-  [_response appendContentString:@"\" "];
-  [_response appendContentString:
-               [NSString stringWithFormat:@"name='%@TabLink'", _info->key]];
-  [_response appendContentString:@">"];
-  
-  if ([label length] < 1)
-      label = _info->key;
-  [_response appendContentString:@"<nobr>"];
-  [_response appendContentHTMLString:label];
-  [_response appendContentString:@"</nobr>"];
-  
-  [_response appendContentString:@"</a>"];
-
-  [_response appendContentString:@"</td>"];
-}
-
-- (void)appendSubmitButton:(UIxTabItemInfo *)_info
-  toResponse:(WOResponse *)_response
-  inContext:(WOContext *)_ctx
-  isActive:(BOOL)_isActive isLeft:(BOOL)_left
-  doScript:(BOOL)_doScript   keys:(NSArray *)_keys
-{
-  [self appendLink:_info
-        toResponse:_response
-        inContext:_ctx
-        isActive:_isActive isLeft:_left
-        doScript:NO keys:_keys];
-}
-
-- (void)_appendTabViewJSScriptToResponse:(WOResponse *)_response
-  inContext:(WOContext *)_ctx
-{
-  [_response appendContentString:
-               @"<script language=\"JavaScript\">\n<!--\n\n"
-               @"function showTab(obj) {\n"
-#if DEBUG_JS
-               @"  if (obj==null) { alert('missing tab obj ..'); return; }\n"
-               @"  if (obj['Div']==null) {"
-               @"    alert('missing div key in ' + obj); return; }\n"
-               @"  if (obj['Div'].style==null) {"
-               @"    alert('missing style key in div ' + obj['Div']);return; }\n"
-#endif
-               @"  obj['Div'].style.display = \"\";\n"
-               @"  obj['Img'].src = obj[\"Ar\"][1].src;\n"
-               @"  obj['link'].href = obj[\"href2\"];\n"
-               @"}\n"
-               @"function hideTab(obj) {\n"
-#if DEBUG_JS
-               @"  if (obj==null) { alert('missing tab obj ..'); return; }\n"
-               @"  if (obj['Div']==null) {"
-               @"    alert('missing div key in ' + obj); return; }\n"
-               @"  if (obj['Div'].style==null) {"
-               @"    alert('missing style key in div ' + obj['Div']);return; }\n"
-#endif
-               @" obj['Div'].style.display = \"none\";\n"
-               @" obj['Img'].src = obj[\"Ar\"][0].src;\n"
-               @" obj['link'].href = obj[\"href1\"];\n"
-               @"}\n"
-               @"function swapCorners(obj1,obj2) {\n"
-               @"   if (obj1==null) { alert('missing corner 1'); return; }\n"
-               @"   if (obj2==null) { alert('missing corner 2'); return; }\n"
-               @"   obj1.style.display = \"none\";\n"
-               @"   obj2.style.display = \"\";\n"
-               @"}\n"
-               @"//-->\n</script>"];
-}
-
-- (void)_appendHeaderRowToResponse:(WOResponse *)_response
-  inContext:(WOContext *)_ctx
-  keys:(NSArray *)keys activeKey:(NSString *)activeKey
-  doScript:(BOOL)doScript
-{
-  unsigned  i, count;
-  BOOL      doForm;
-  NSString  *styleName;
-  
-  doForm = NO;  /* generate form controls ? */
-  
-  [_response appendContentString:@"<tr><td colspan='2'>"];
-  
-  styleName = [self->headerStyle stringValueInComponent:[_ctx component]];
-  if(styleName) {
-      [_response appendContentString:
-          @"<table border='0' cellpadding='0' cellspacing='0' class='"];
-      [_response appendContentHTMLAttributeValue:styleName];
-      [_response appendContentString:@"'><tr>"];
-  }
-  else {
-      [_response appendContentString:
-          @"<table border='0' cellpadding='0' cellspacing='0'><tr>"];
-  }
-
-  for (i = 0, count = [keys count]; i < count; i++) {
-    UIxTabItemInfo *info;
-    NSString       *key;
-    BOOL           isActive;
-    
-    info     = [keys objectAtIndex:i];
-    key      = info->key;
-    isActive = [key isEqualToString:activeKey];
-    
-    [_ctx appendElementIDComponent:key];
-    
-    if (doForm) {
-      /* tab is inside of a FORM, so produce submit buttons */
-      [self appendSubmitButton:info
-            toResponse:_response
-            inContext:_ctx
-            isActive:isActive
-            isLeft:(i == 0) ? YES : NO
-            doScript:NO
-            keys:keys];
-    }
-    else {
-      /* tab is not in a FORM, generate hyperlinks for tab */
-      [self appendLink:info
-            toResponse:_response
-            inContext:_ctx
-            isActive:isActive
-            isLeft:(i == 0) ? YES : NO
-            doScript:NO
-            keys:keys];
-    }
-    
-    [_ctx deleteLastElementIDComponent];
-  }
-  //  [_response appendContentString:@"<td></td>"];
-  [_response appendContentString:@"</tr></table>"];
-  [_response appendContentString:@"</td></tr>"];
-}
-
-- (void)_appendHeaderFootRowToResponse:(WOResponse *)_response
-  inContext:(WOContext *)_ctx
-  bgcolor:(NSString *)bgcolor
-  doScript:(BOOL)doScript
-  isLeftActive:(BOOL)isLeftActive
-{
-  NSString *styleName;
-  [_response appendContentString:@"  <tr"];
-    
-  styleName = [self->bodyStyle stringValueInComponent:[_ctx component]];
-  if(styleName) {
-    [_response appendContentString:@" class='"];
-    [_response appendContentHTMLAttributeValue:styleName];
-    [_response appendContentCharacter:'\''];
-  }
-  if (bgcolor) {
-    [_response appendContentString:@" bgcolor=\""];
-    [_response appendContentHTMLAttributeValue:bgcolor];
-    [_response appendContentString:@"\""];
-  }
-  [_response appendContentString:@">\n"];
-    
-  /* left corner */
-  [_response appendContentString:@"    <td align=\"left\" width=\"10\">"];
-  
-  if (isLeftActive)
-    [_response appendContentString:@"&nbsp;"];
-  
-  if (!isLeftActive) {
-    NSString *uri;
-    
-    uri = [self->leftCornerIcon stringValueInComponent:[_ctx component]];
-    if ((uri = WEUriOfResource(uri, _ctx))) {
-      [_response appendContentString:@"<img border=\"0\" alt=\"\" src=\""];
-      [_response appendContentString:uri];
-      [_response appendContentString:@"\" />"];
-    }
-    else
-      [_response appendContentString:@"&nbsp;"];
-  }
-  
-  [_response appendContentString:@"</td>"];
-
-  /* right corner */
-  [_response appendContentString:@"    <td align=\"right\">"];
-  {
-    NSString *uri;
-      
-    uri = [self->rightCornerIcon stringValueInComponent:[_ctx component]];
-    if ((uri = WEUriOfResource(uri, _ctx))) {
-      [_response appendContentString:@"<img border=\"0\" alt=\"\" src=\""];
-      [_response appendContentString:uri];
-      [_response appendContentString:@"\" />"];
-    }
-    else
-      [_response appendContentString:@"&nbsp;"];
-  }
-  [_response appendContentString:@"</td>\n"];
-    
-  [_response appendContentString:@"  </tr>\n"];
-}
-
-- (void)_appendBodyRowToResponse:(WOResponse *)_response
-  inContext:(WOContext *)_ctx
-  bgcolor:(NSString *)bgcolor
-  activeKey:(NSString *)activeKey
-{
-  WEClientCapabilities *ccaps;
-  BOOL indentContent;
-  NSString *styleName;
-
-  styleName = [self->bodyStyle stringValueInComponent:[_ctx component]];
-  ccaps = [[_ctx request] clientCapabilities];
-
-  /* put additional padding table into content ??? */
-  indentContent = [ccaps isFastTableBrowser] && ![ccaps isTextModeBrowser];
-  
-  [_response appendContentString:@"<tr"];
-  if(styleName) {
-    [_response appendContentString:@" class='"];
-    [_response appendContentHTMLAttributeValue:styleName];
-    [_response appendContentCharacter:'\''];
-  }
-  [_response appendContentString:@"><td colspan='2'"];
-  if (bgcolor) {
-    [_response appendContentString:@" bgcolor=\""];
-    [_response appendContentHTMLAttributeValue:bgcolor];
-    [_response appendContentCharacter:'\"'];
-  }
-  [_response appendContentCharacter:'>'];
-    
-  if (indentContent) {
-    /* start padding table */
-    [_response appendContentString:
-               @"<table border='0' width='100%'"
-               @" cellpadding='10' cellspacing='0'>"];
-    [_response appendContentString:@"<tr><td>"];
-  }
-    
-  [_ctx appendElementIDComponent:@"b"];
-  [_ctx appendElementIDComponent:activeKey];
-  
-  /* generate currently active body */
-  {
-    [_ctx setObject:activeKey forKey:UIxTabView_BODY];
-    [self->template appendToResponse:_response inContext:_ctx];
-    [_ctx removeObjectForKey:UIxTabView_BODY];
-  }
-  
-  [_ctx deleteLastElementIDComponent]; // activeKey
-  [_ctx deleteLastElementIDComponent]; // 'b'
-    
-  if (indentContent)
-    /* close padding table */
-    [_response appendContentString:@"</td></tr></table>"];
-    
-  [_response appendContentString:@"</td></tr>"];
-}
-
-- (BOOL)isLeftActiveInKeys:(NSArray *)keys activeKey:(NSString *)activeKey{
-  unsigned i, count;
-  BOOL isLeftActive;
-  
-  isLeftActive = NO;
-  
-  for (i = 0, count = [keys count]; i < count; i++) {
-    UIxTabItemInfo *info;
-    
-    info = [keys objectAtIndex:i];
-    
-    if ((i == 0) && [info->key isEqualToString:activeKey])
-      isLeftActive = YES;
-  }
-  
-  return isLeftActive;
-}
-
-- (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
-  WOComponent  *cmp;
-  NSString     *bgcolor;
-  BOOL         isLeftActive;
-  id           nestedState;
-  NSString     *activeKey;
-  NSArray      *keys;
-  int          tabViewCount; /* used for image id's and writing script once */
-  
-  tabViewCount  = [[_ctx valueForKey:@"UIxTabViewScriptDone"] intValue];
-  cmp           = [_ctx component];
-  
-  /* save state */
-  
-  nestedState = [self saveNestedStateInContext:_ctx];
-  
-  /* configure */
-  
-  activeKey = [self->selection stringValueInComponent:cmp];
-  
-  bgcolor = [self->bgColor stringValueInComponent:cmp];
-  bgcolor = [bgcolor stringValue];
-  
-  [_ctx appendElementIDComponent:@"h"];
-  
-  /* collect & process keys (= available tabs) */
-  
-  keys = [self collectKeysInContext:_ctx];
-  
-  if (![[keys valueForKey:@"key"] containsObject:activeKey])
-    /* selection is not available in keys */
-    activeKey = nil;
-  
-  if ((activeKey == nil) && ([keys count] > 0)) {
-    /* no or invalid selection, use first key */
-    activeKey = [[keys objectAtIndex:0] key];
-    if ([self->selection isValueSettable])
-      [self->selection setValue:activeKey inComponent:[_ctx component]];
-  }
-  
-  /* start appending */
-  
-  /* count up for unique tabCorner/tabCornerLeft images */
-  [_ctx takeValue:[NSNumber numberWithInt:(tabViewCount + 1)]
-        forKey:@"UIxTabViewScriptDone"];
-  
-  [_response appendContentString:
-               @"<table border='0' width='100%'"
-               @" cellpadding='0' cellspacing='0'>"];
-  
-  /* find out whether left is active */
-  
-  isLeftActive = [self isLeftActiveInKeys:keys activeKey:activeKey];
-  
-  /* generate header row */
-  
-  [self _appendHeaderRowToResponse:_response inContext:_ctx
-        keys:keys activeKey:activeKey
-        doScript:NO];
-  
-  [_ctx deleteLastElementIDComponent]; // 'h' for head
-  [_ctx removeObjectForKey:UIxTabView_HEAD];
-
-  /* body row */
-  
-  [self _appendBodyRowToResponse:_response inContext:_ctx
-        bgcolor:bgcolor
-        activeKey:activeKey];
-  
-  /* close table */
-  
-  [_response appendContentString:@"</table>"];
-  [_ctx removeObjectForKey:UIxTabView_ACTIVEKEY];
-  [_ctx removeObjectForKey:UIxTabView_KEYS];
-  [self restoreNestedState:nestedState inContext:_ctx];
-}
-
-@end /* UIxTabView */
index 40842b648495d1fa76f7290cb8ba618991e54142..f36e2bc855b2d43cd244a60a72270dec3c04b957 100644 (file)
       else
        [address appendString: email];
        
-      url = [NSString stringWithFormat: @"Mail/compose?mailto=%@", address];
+      url = [NSString stringWithFormat: @"%@/Mail/compose?mailto=%@",
+                     [self userFolderPath], address];
     }
   else
-    url = @"Mail/compose";
-
-  return
-    [self redirectToLocation: [self relativePathToUserFolderSubPath: url]];
+    url = [NSString stringWithFormat: @"%@/Mail/compose", [self userFolderPath]];
+  
+  return [self redirectToLocation: url];
 }
 
 - (id) newAction
index 5fadd32d1e5f21ca7347cf32b82bf7e140d92937..78f7a101c40fc3f06b69693ec7b397ae33f06697 100644 (file)
@@ -19,6 +19,7 @@ Attendees = "Attendees";
 request_info = "invites you to participate in a meeting.";
 "Add to calendar" = "Add to calendar";
 "Delete from calendar" = "Delete from calendar";
+"Update status" = "Update status";
 Accept = "Accept";
 Decline = "Decline";
 Tentative = "Tentative";
index b5db9cecc31a6b038f0460836eda3031f160870c..e126835230883b2b93bfc55340c7d2b47824c521 100644 (file)
@@ -19,6 +19,7 @@ Attendees = "Invités";
 request_info = "vous invite Ã  une réunion.";
 "Add to calendar" = "Ajouter Ã  l'agenda";
 "Delete from calendar" = "Effacer de l'agenda";
+"Update status" = "Intégrer les modifications";
 Accept = "Accepter";
 Decline = "Decliner";
 Tentative = "Tentative";
index b96425ebc689f849cf35f5b608d04ee2392a776d..0dbc78445ffd71e488ca31428fcda78d847bdfd4 100644 (file)
@@ -19,6 +19,7 @@ Attendees           = "Attendees";
 request_info        = "invites you to participate in a meeting.";
 "Add to calendar" = "Add to calendar";
 "Delete from calendar" = "Delete from calendar";
+"Update status" = "Update status";
 Accept = "Accept";
 Decline = "Decline";
 Tentative = "Tentative";
index c4c8cb78bcd4af17be9c296b249f76fc5ee9148a..e51f518cf2b86177431bf0b6a15d3e2606b1ebdf 100644 (file)
 #import <NGObjWeb/WOResponse.h>
 
 #import <NGCards/iCalCalendar.h>
+#import <NGCards/iCalEvent.h>
 #import <NGCards/iCalPerson.h>
 
 #import <UI/Common/WODirectAction+SOGo.h>
 
+#import <NGImap4/NGImap4EnvelopeAddress.h>
+
+#import <SoObjects/Appointments/iCalPerson+SOGo.h>
 #import <SoObjects/Appointments/SOGoAppointmentObject.h>
 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
+#import <SoObjects/Mailer/SOGoMailObject.h>
 #import <SoObjects/SOGo/SOGoUser.h>
 #import <SoObjects/SOGo/iCalEntityObject+Utilities.h>
 #import <SoObjects/Mailer/SOGoMailBodyPart.h>
 }
 
 - (SOGoAppointmentObject *) _eventObjectWithUID: (NSString *) uid
+                                       forUser: (SOGoUser *) user
 {
   SOGoAppointmentFolder *personalFolder;
   SOGoAppointmentObject *eventObject;
 
-  personalFolder
-    = [[context activeUser] personalCalendarFolderInContext: context];
+  personalFolder = [user personalCalendarFolderInContext: context];
   eventObject = [personalFolder lookupName: uid
                                inContext: context acquire: NO];
   if (![eventObject isKindOfClass: [SOGoAppointmentObject class]])
   return eventObject;
 }
 
+- (SOGoAppointmentObject *) _eventObjectWithUID: (NSString *) uid
+{
+  return [self _eventObjectWithUID: uid forUser: [context activeUser]];
+}
+
 - (iCalEvent *)
   _setupChosenEventAndEventObject: (SOGoAppointmentObject **) eventObject
 {
@@ -86,7 +96,7 @@
        chosenEvent = emailEvent;
       else
        {
-         calendarEvent = (iCalEvent *) [*eventObject component: NO];
+         calendarEvent = (iCalEvent *) [*eventObject component: NO secure: NO];
          if ([calendarEvent compare: emailEvent] == NSOrderedAscending)
            chosenEvent = emailEvent;
          else
   return chosenEvent;
 }
 
+#warning this is code copied from SOGoAppointmentObject...
+- (void) _updateAttendee: (iCalPerson *) attendee
+           withSequence: (NSNumber *) sequence
+              andCalUID: (NSString *) calUID
+                 forUID: (NSString *) uid
+{
+  SOGoAppointmentObject *eventObject;
+  iCalEvent *event;
+  iCalPerson *otherAttendee;
+  NSString *iCalString;
+
+  eventObject = [self _eventObjectWithUID: calUID
+                     forUser: [SOGoUser userWithLogin: uid roles: nil]];
+  if (![eventObject isNew])
+    {
+      event = [eventObject component: NO secure: NO];
+      if ([[event sequence] compare: sequence]
+         == NSOrderedSame)
+       {
+         otherAttendee
+           = [event findParticipantWithEmail: [attendee rfc822Email]];
+         [otherAttendee setPartStat: [attendee partStat]];
+         iCalString = [[event parent] versitString];
+         [eventObject saveContentString: iCalString];
+       }
+    }
+}
+
 - (WOResponse *) _changePartStatusAction: (NSString *) newStatus
 {
   WOResponse *response;
   SOGoAppointmentObject *eventObject;
   iCalEvent *chosenEvent;
   iCalPerson *user;
-  iCalCalendar *calendar;
-  NSString *rsvp, *method;
+  iCalCalendar *emailCalendar, *calendar;
+  NSString *rsvp, *method, *organizerUID;
 
   chosenEvent = [self _setupChosenEventAndEventObject: &eventObject];
   if (chosenEvent)
       user = [chosenEvent findParticipant: [context activeUser]];
       [user setPartStat: newStatus];
       calendar = [chosenEvent parent];
-      method = [[calendar method] lowercaseString];
+      emailCalendar = [[self _emailEvent] parent];
+      method = [[emailCalendar method] lowercaseString];
       if ([method isEqualToString: @"request"])
        {
          [calendar setMethod: @""];
       else
        rsvp = nil;
       [eventObject saveContentString: [calendar versitString]];
-      if (rsvp && [rsvp isEqualToString: @"true"])
+      if ([rsvp isEqualToString: @"true"])
        [eventObject sendResponseToOrganizer];
+      organizerUID = [[chosenEvent organizer] uid];
+      if (organizerUID)
+       [self _updateAttendee: user withSequence: [chosenEvent sequence]
+             andCalUID: [chosenEvent uid] forUID: organizerUID];
       response = [self responseWith204];
     }
   else
   return [self _changePartStatusAction: @"DECLINED"];
 }
 
+- (WOResponse *) deleteFromCalendarAction
+{
+  iCalEvent *emailEvent;
+  SOGoAppointmentObject *eventObject;
+  WOResponse *response;
+
+  emailEvent = [self _emailEvent];
+  if (emailEvent)
+    {
+      eventObject = [self _eventObjectWithUID: [emailEvent uid]];
+      response = [self responseWith204];
+    }
+  else
+    {
+      response = [context response];
+      [response setStatus: 409];
+    }
+
+  return response;
+}
+
+- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
+{
+  NSString *emailFrom;
+  SOGoMailObject *mailObject;
+  NGImap4EnvelopeAddress *address;
+
+  mailObject = [[self clientObject] mailObject];
+  address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
+  emailFrom = [address baseEMail];
+
+  return [event findParticipantWithEmail: emailFrom];
+}
+
+- (BOOL) _updateParticipantStatusInEvent: (iCalEvent *) calendarEvent
+                              fromEvent: (iCalEvent *) emailEvent
+                               inObject: (SOGoAppointmentObject *) eventObject
+{
+  iCalPerson *calendarParticipant, *mailParticipant;
+  NSString *partStat;
+  BOOL result;
+
+  calendarParticipant = [self _emailParticipantWithEvent: calendarEvent];
+  mailParticipant = [self _emailParticipantWithEvent: emailEvent];
+  if (calendarParticipant && mailParticipant)
+    {
+      result = YES;
+      partStat = [mailParticipant partStat];
+      if ([partStat caseInsensitiveCompare: [calendarParticipant partStat]]
+         != NSOrderedSame)
+       {
+         [calendarParticipant setPartStat: [partStat uppercaseString]];
+         [eventObject saveComponent: calendarEvent];
+       }
+    }
+  else
+    result = NO;
+
+  return result;
+}
+
+- (WOResponse *) updateUserStatusAction
+{
+  iCalEvent *emailEvent, *calendarEvent;
+  SOGoAppointmentObject *eventObject;
+  WOResponse *response;
+
+  response = nil;
+
+  emailEvent = [self _emailEvent];
+  if (emailEvent)
+    {
+      eventObject = [self _eventObjectWithUID: [emailEvent uid]];
+      calendarEvent = [eventObject component: NO secure: NO];
+      if (([[emailEvent sequence] compare: [calendarEvent sequence]]
+         != NSOrderedDescending)
+         && ([self _updateParticipantStatusInEvent: calendarEvent
+                   fromEvent: emailEvent
+                   inObject: eventObject]))
+       response = [self responseWith204];
+    }
+
+  if (!response)
+    {
+      response = [context response];
+      [response setStatus: 409];
+    }
+
+  return response;
+}
+
 // - (WOResponse *) markTentativeAction
 // {
 //   return [self _changePartStatusAction: @"TENTATIVE"];
index 98d75abe40648e9680ce0e5eeb91d43ddbf91e23..0728008bc9ac21f553c07630784c514ea42b35ad 100644 (file)
 
 #import "UIxMailPartViewer.h"
 
-@class SOGoDateFormatter;
 @class iCalEvent;
 @class iCalCalendar;
 
+@class SOGoAppointmentObject;
+@class SOGoDateFormatter;
+
 @interface UIxMailPartICalViewer : UIxMailPartViewer
 {
   iCalCalendar *inCalendar;
@@ -35,7 +37,7 @@
   id attendee;
   SOGoDateFormatter *dateFormatter;
   id item;
-  id storedEventObject;
+  SOGoAppointmentObject *storedEventObject;
   iCalEvent *storedEvent;
 }
 
index 56df0e45e7182d55c9ab43eaa0aaf62d82ca532c..b30a88174f4c5b138260c8239907c29819f73244 100644 (file)
@@ -28,7 +28,6 @@
 #import <NGObjWeb/WOResponse.h>
 
 #import <NGExtensions/NSCalendarDate+misc.h>
-#import <NGExtensions/NSNull+misc.h>
 #import <NGExtensions/NSObject+Logs.h>
 
 #import <NGImap4/NGImap4EnvelopeAddress.h>
 
 #import <SoObjects/SOGo/SOGoDateFormatter.h>
 #import <SoObjects/SOGo/SOGoUser.h>
+#import <SoObjects/Appointments/iCalEntityObject+SOGo.h>
 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
 #import <SoObjects/Appointments/SOGoAppointmentObject.h>
 #import <SoObjects/Mailer/SOGoMailObject.h>
+#import <SoObjects/Mailer/SOGoMailBodyPart.h>
 
 #import "UIxMailPartICalViewer.h"
 
 
 - (BOOL) couldParseCalendar
 {
-  return [[self inCalendar] isNotNull];
+  return (([self inCalendar]));
 }
 
 - (iCalEvent *) inEvent
 {
   NSArray *events;
  
-  if (inEvent)
-    return [inEvent isNotNull] ? inEvent : nil;
-  events = [[self inCalendar] events];
-  if ([events count] > 0) {
-    inEvent = [[events objectAtIndex:0] retain];
-    return inEvent;
-  }
-  else {
-    inEvent = [[NSNull null] retain];
-    return nil;
-  }
+  if (!inEvent)
+    {
+      events = [[self inCalendar] events];
+      if ([events count] > 0)
+       inEvent = [[events objectAtIndex:0] retain];
+    }
+
+  return inEvent;
 }
 
 /* formatters */
 
 - (SOGoDateFormatter *) dateFormatter
 {
-  if (dateFormatter == nil) {
-    dateFormatter = [[context activeUser] dateFormatterInContext: context];
-    [dateFormatter retain];
-  }
+  if (!dateFormatter)
+    {
+      dateFormatter = [[context activeUser] dateFormatterInContext: context];
+      [dateFormatter retain];
+    }
 
   return dateFormatter;
 }
 
 - (void) setAttendee: (id) _attendee
 {
-  ASSIGN(attendee, _attendee);
+  ASSIGN (attendee, _attendee);
 }
 
 - (id) attendee
 
 /* calendar folder support */
 
-- (id) calendarFolder
+- (SOGoAppointmentFolder *) calendarFolder
 {
   /* return scheduling calendar of currently logged-in user */
   SOGoUser *user;
   return [folder lookupName: @"personal" inContext: context acquire: NO];
 }
 
-- (id) storedEventObject
+- (SOGoAppointmentObject *) storedEventObject
 {
   /* lookup object in the users Calendar */
-  id calendar;
-  if (storedEventObject)
-    return [storedEventObject isNotNull] ? storedEventObject : nil;
+  SOGoAppointmentFolder *calendar;
+  NSString *filename;
  
-  calendar = [self calendarFolder];
-  if ([calendar isKindOfClass:[NSException class]]) {
-    [self errorWithFormat:@"Did not find Calendar folder: %@", calendar];
-  }
-  else {
-    NSString *filename;
-    filename = [calendar resourceNameForEventUID:[[self inEvent] uid]];
-    if (filename) {
-      // TODO: When we get an exception, this might be an auth issue meaning
-      // that the UID indeed exists but that the user has no access to
-      // the object.
-      // Of course this is quite unusual for the private calendar though.
-      id tmp;
-      tmp = [calendar lookupName:filename inContext:[self context] acquire:NO];
-      if ([tmp isNotNull] && ![tmp isKindOfClass:[NSException class]])
-       storedEventObject = [tmp retain];
+  if (!storedEventObject)
+    {
+      calendar = [self calendarFolder];
+      if ([calendar isKindOfClass: [NSException class]])
+       [self errorWithFormat:@"Did not find Calendar folder: %@", calendar];
+      else
+       {
+         filename = [calendar resourceNameForEventUID:[[self inEvent] uid]];
+         if (filename)
+           {
+             storedEventObject = [calendar lookupName: filename
+                                           inContext: [self context]
+                                           acquire: NO];
+             if ([storedEventObject isKindOfClass: [NSException class]])
+               storedEventObject = nil;
+             else
+               [storedEventObject retain];
+           }
+       }
     }
-  }
-  if (storedEventObject == nil)
-    storedEventObject = [[NSNull null] retain];
  
   return storedEventObject;
 }
 
 - (BOOL) isEventStoredInCalendar
 {
-  return [[self storedEventObject] isNotNull];
+  return (([self storedEventObject]));
 }
 
 - (iCalEvent *) storedEvent
 {
-  return (iCalEvent *) [(SOGoAppointmentObject *)[self storedEventObject] component: NO];
+  if (!storedEvent)
+    {
+      storedEvent = [[self storedEventObject] component: NO secure: NO];
+      [storedEvent retain];
+    }
+
+  return storedEvent;
 }
 
 /* organizer tracking */
 {
   iCalEvent *authorativeEvent;
 
-  if ([[self storedEvent] compare: [self inEvent]]
-      == NSOrderedAscending)
+  [self storedEvent];
+  if (!storedEvent
+      || ([storedEvent compare: [self inEvent]] == NSOrderedAscending))
     authorativeEvent = inEvent;
   else
-    authorativeEvent = storedEventObject;
+    authorativeEvent = [self storedEvent];
 
   return authorativeEvent;
 }
 
 - (BOOL) isLoggedInUserTheOrganizer
 {
-  iCalPerson *organizer;
-  organizer = [[self authorativeEvent] organizer];
-
-  return [[context activeUser] hasEmail: [organizer rfc822Email]];
+  return [[self authorativeEvent] userIsOrganizer: [context activeUser]];
 }
 
 - (BOOL) isLoggedInUserAnAttendee
 {
-  NSString *loginEMail;
-  if ((loginEMail = [self loggedInUserEMail]) == nil) {
-    [self warnWithFormat:@"Could not determine email of logged in user?"];
-    return NO;
-  }
-
-  return [[self authorativeEvent] isParticipant:loginEMail];
+  return [[self authorativeEvent] userIsParticipant: [context activeUser]];
 }
 
 /* derived fields */
 
 - (BOOL) isReplySenderAnAttendee
 {
-  return [[self storedReplyAttendee] isNotNull];
+  return (([self storedReplyAttendee]));
+}
+
+- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
+{
+  NSString *emailFrom;
+  SOGoMailObject *mailObject;
+  NGImap4EnvelopeAddress *address;
+
+  mailObject = [[self clientObject] mailObject];
+  address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
+  emailFrom = [address baseEMail];
+
+  return [event findParticipantWithEmail: emailFrom];
+}
+
+- (BOOL) hasSenderStatusChanged
+{
+  iCalPerson *emailParticipant, *calendarParticipant;
+
+  [self inEvent];
+  [self storedEvent];
+  emailParticipant = [self _emailParticipantWithEvent: inEvent];
+  calendarParticipant = [self _emailParticipantWithEvent: storedEvent];
+
+  return ([[emailParticipant partStat]
+           caseInsensitiveCompare: [calendarParticipant partStat]]
+         != NSOrderedSame);
 }
 
 @end /* UIxMailPartICalViewer */
index e60be0bbcc989c872090bc911ac9d00cb29088f0..11d289f39b3ebaef7b0a54da8d4f89de8e2c18c1 100644 (file)
 
 #import "UIxMailRenderingContext.h"
 
+@interface UIxMailRenderingContext (Private)
+
+- (BOOL) _shouldDisplayAsAttachment: (NSDictionary *) info;
+
+@end
+
+@implementation UIxMailRenderingContext (Private)
+
+- (BOOL) _shouldDisplayAsAttachment: (NSDictionary *) info
+{
+  NSString *s;
+
+  s = [[info objectForKey:@"disposition"] objectForKey: @"type"];
+       
+  return (s && [s caseInsensitiveCompare: @"ATTACHMENT"] == NSOrderedSame);
+}
+
+@end
+
 @implementation UIxMailRenderingContext
 
 static BOOL showNamedTextAttachmentsInline = NO;
@@ -235,13 +254,12 @@ static BOOL showNamedTextAttachmentsInline = NO;
 
   if ([mt isEqualToString:@"multipart"])
     {
-      if ([st isEqualToString:@"mixed"])
+      if ([st isEqualToString:@"mixed"] || [st isEqualToString:@"related"])
        return [self mixedViewer];
       else if ([st isEqualToString:@"signed"])
        return [self signedViewer];
-      else if ([st isEqualToString:@"alternative"]
-             || [st isEqualToString:@"related"])
-      return [self alternativeViewer];
+      else if ([st isEqualToString:@"alternative"])
+       return [self alternativeViewer];
     
     if ([st isEqualToString:@"report"])
       /* this is used by mail-delivery reports */
@@ -249,19 +267,9 @@ static BOOL showNamedTextAttachmentsInline = NO;
     }
   else if ([mt isEqualToString:@"text"])
     {
-    /* 
-       Note: in the _info dictionary we do not get the content-disposition
-             information (inline vs attachment). Our hack is to check for the
-            'name' parameter.
-    */
     if ([st isEqualToString:@"plain"] || [st isEqualToString:@"html"]) {
-      if (!showNamedTextAttachmentsInline) {
-       NSString *n;
-       
-       n = [[_info objectForKey:@"parameterList"] objectForKey:@"name"];
-       if ([n isNotNull] && [n length] > 0)
-         return [self linkViewer];
-      }
+      if (!showNamedTextAttachmentsInline && [self _shouldDisplayAsAttachment: _info])
+       return [self linkViewer];
       
       return [st isEqualToString:@"html"] 
        ? [self htmlViewer] : [self textViewer];
@@ -269,10 +277,15 @@ static BOOL showNamedTextAttachmentsInline = NO;
     
     if ([st isEqualToString:@"calendar"])
       return [self iCalViewer];
-  }
+    }
   
   if ([mt isEqualToString:@"image"])
-    return [self imageViewer];
+    {
+      if ([self _shouldDisplayAsAttachment: _info])
+       return [self linkViewer];
+     
+      return [self imageViewer];
+    }
   
   if ([mt isEqualToString:@"message"] && [st isEqualToString:@"rfc822"])
     return [self messageViewer];
index 31e8b466baccd63eef6f66283569155dccf2afb6..ddf40447a18b09e8d76b1dfd408272c7912faca8 100644 (file)
           actionClass = "UIxMailPartICalActions";
           actionName  = "decline";
         };
+       updateUserStatus = {
+          protectedBy = "View";
+          actionClass = "UIxMailPartICalActions";
+          actionName  = "updateUserStatus";
+       };
        /*        tentative = {
           protectedBy = "View";
           actionClass = "UIxMailPartICalAction";
           protectedBy = "View";
           actionClass = "UIxMailPartICalAction";
           actionName  = "addToCalendar";
-        };
+        }; */
         deleteFromCalendar = {
           protectedBy = "View";
           actionClass = "UIxMailPartICalAction";
           actionName  = "deleteFromCalendar";
-         }; */
+       };
       };
     };
   };
index a2ba2dbef9c6e83971cae7191b7e9d5125cca51e..804c01e127295c04395688262184cc06ab73f54d 100644 (file)
       cssClass = "tbicon_delete";
       label = "Delete";
       tooltip = "Delete selected message or folder"; },
-    { link = "#";
-      isSafe = NO;
-      image = "tb-mail-junk-flat-24x24.png";
-      cssClass = "tbicon_junk";
-      label = "Junk";
-      tooltip = "Mark the selected messages as junk"; },
+//    { link = "#";
+//      isSafe = NO;
+//      image = "tb-mail-junk-flat-24x24.png";
+//      cssClass = "tbicon_junk";
+//      label = "Junk";
+//      tooltip = "Mark the selected messages as junk"; },
   ),
   (
    { link = "#";
index cdcc83bf05e8e75575f8181bbd657e94d4a5aab3..c241105eb5efeadb49895c02631c0670b17bdb81 100644 (file)
@@ -64,8 +64,6 @@
 - (void) setBcc: (NSArray *) _bcc;
 - (NSArray *) bcc;
 
-- (NSArray *) properlySplitAddresses: (NSArray *) _addresses;
-
 - (void) getAddressesFromFormValues: (NSDictionary *) _dict;
 - (NSString *) getIndexFromIdentifier: (NSString *) _identifier;
 
@@ -108,7 +106,7 @@ static NSArray *headers = nil;
 
 - (void) setTo: (NSArray *) _to
 {
-  ASSIGN (to, [self properlySplitAddresses: _to]);
+  ASSIGN (to, _to);
 }
 
 - (NSArray *) to
@@ -127,7 +125,7 @@ static NSArray *headers = nil;
 
 - (void) setCc: (NSArray *) _cc
 {
-  ASSIGN (cc, [self properlySplitAddresses: _cc]);
+  ASSIGN (cc, _cc);
 }
 
 - (NSArray *) cc
@@ -137,7 +135,7 @@ static NSArray *headers = nil;
 
 - (void) setBcc: (NSArray *) _bcc
 {
-  ASSIGN (bcc, [self properlySplitAddresses: _bcc]);
+  ASSIGN (bcc, _bcc);
 }
 
 - (NSArray *) bcc
@@ -180,7 +178,7 @@ static NSArray *headers = nil;
   NSMutableArray *ma;
   
   ma = [NSMutableArray arrayWithCapacity:3];
-  if ([to isNotNull])
+  if ([to isNotNull] && [to count] > 0)
     [ma addObject: to];
   if ([cc isNotNull])
     [ma addObject: cc];
@@ -237,52 +235,6 @@ static NSArray *headers = nil;
   return @"";
 }
 
-/* address handling */
-
-- (NSArray *) properlySplitAddresses: (NSArray *) _addresses
-{
-  NSString            *addrs;
-  NGMailAddressParser *parser;
-  NSArray             *result;
-  NSMutableArray      *ma;
-  unsigned            i, count;
-
-  if (!_addresses || [_addresses count] == 0)
-    return nil;
-
-  /* create one huge string, then split it using the parser */
-  addrs = [_addresses componentsJoinedByString:@","];
-  parser = [NGMailAddressParser mailAddressParserWithString:addrs];
-  result = [parser parseAddressList];
-  if(result == nil) {
-    [self debugWithFormat:@"Couldn't parse given addresses:%@", _addresses];
-    return _addresses;
-  }
-
-  count = [result count];
-  ma = [NSMutableArray arrayWithCapacity:count];
-  for (i = 0; i < count; i++) {
-    NGMailAddress   *addr;
-    NSMutableString *s;
-    BOOL hasName = NO;
-
-    s = [[NSMutableString alloc] init];
-    addr = [result objectAtIndex:i];
-    if([addr displayName]) {
-      [s appendString:[addr displayName]];
-      [s appendString:@" "];
-      hasName = YES;
-    }
-    if(hasName)
-      [s appendString:@"<"];
-    [s appendString:[addr address]];
-    if(hasName)
-      [s appendString:@">"];
-    [ma addObject:s];
-  }
-  return ma;
-}
-
 /* handling requests */
 
 - (void) _fillAddresses: (NSMutableArray *) addresses
index 444e345bb6be2f76f42bfec0f6aa8b3b19edb5b7..be23c936301f3fe1af7801cd624bcc77160ca217 100644 (file)
   unsigned int minutes;
   iCalRecurrenceRule *rule;
 
-  event = (iCalEvent *) [[self clientObject] component: NO];
+  event = (iCalEvent *) [[self clientObject] component: NO secure: YES];
   if (event)
     {
       startDate = [event startDate];
 
 - (id <WOActionResults>) saveAction
 {
-  SOGoAppointmentObject *clientObject;
-  NSString *iCalString;
-
-  clientObject = [self clientObject];
-  NSLog(@"saveAction, clientObject = %@", clientObject);
-
-  iCalString = [[clientObject calendar: NO] versitString];
-
-  NSLog(@"saveAction, iCalString = %@", iCalString);
-  [clientObject saveContentString: iCalString];
+  [[self clientObject] saveComponent: event];
 
   return [self jsCloseWithRefreshMethod: @"refreshEventsAndDisplay()"];
 }
   iCalRecurrenceRule *rule;
 
   clientObject = [self clientObject];
-  event = (iCalEvent *) [clientObject component: YES];
+  event = (iCalEvent *) [clientObject component: YES secure: NO];
 
   [super takeValuesFromRequest: _rq inContext: _ctx];
 
 
 // TODO: add tentatively
 
-- (id) acceptOrDeclineAction: (BOOL) accept
+- (id) acceptAction
 {
-  [[self clientObject] changeParticipationStatus: (accept
-                                                  ? @"ACCEPTED"
-                                                  : @"DECLINED")];
+  [[self clientObject] changeParticipationStatus: @"ACCEPTED"];
 
   return self;
 }
 
-- (id) acceptAction
-{
-  return [self acceptOrDeclineAction: YES];
-}
-
 - (id) declineAction
 {
-  return [self acceptOrDeclineAction: NO];
+  [[self clientObject] changeParticipationStatus: @"DECLINED"];
+
+  return self;
 }
 
 @end
index 8eec9b4d41cd571314e114f35a28e27197210520..e9e478c6a1d6b0dd5b7d2ddf9203a570d9582979 100644 (file)
@@ -59,6 +59,7 @@
   NSString *componentOwner;
 
   NSString *attendeesNames;
+  NSString *attendeesUIDs;
   NSString *attendeesEmails;
 }
 
 - (void) setAttendeesNames: (NSString *) newAttendeesNames;
 - (NSString *) attendeesNames;
 
+- (void) setAttendeesUIDs: (NSString *) newAttendeesUIDs;
+- (NSString *) attendeesUIDs;
+
 - (void) setAttendeesEmails: (NSString *) newAttendeesEmails;
 - (NSString *) attendeesEmails;
 
index 5f108ba8cd49a1c635119dbae392211737ddad4b..04e4ee1af709b73d657c3c4d57a5ed31321ee875 100644 (file)
 #import <NGExtensions/NSObject+Logs.h>
 #import <NGExtensions/NSString+misc.h>
 
+#import <SoObjects/Appointments/iCalPerson+SOGo.h>
 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
 #import <SoObjects/Appointments/SOGoAppointmentFolders.h>
 #import <SoObjects/Appointments/SOGoAppointmentObject.h>
 #import <SoObjects/Appointments/SOGoTaskObject.h>
+#import <SoObjects/SOGo/LDAPUserManager.h>
 #import <SoObjects/SOGo/NSString+Utilities.h>
 #import <SoObjects/SOGo/SOGoUser.h>
 #import <SoObjects/SOGo/SOGoPermissions.h>
@@ -65,6 +67,7 @@
       componentOwner = @"";
       organizer = nil;
       attendeesNames = nil;
+      attendeesUIDs = nil;
       attendeesEmails = nil;
       calendarList = nil;
     }
@@ -86,6 +89,7 @@
   [cycleEnd release];
   [url release];
   [attendeesNames release];
+  [attendeesUIDs release];
   [attendeesEmails release];
   [calendarList release];
 
 {
   NSEnumerator *attendees;
   iCalPerson *currentAttendee;
-  NSMutableString *names, *emails;
+  NSMutableString *names, *uids, *emails;
+  NSString *uid;
+  LDAPUserManager *um;
 
   names = [NSMutableString new];
+  uids = [NSMutableString new];
   emails = [NSMutableString new];
+  um = [LDAPUserManager sharedUserManager];
 
   attendees = [[component attendees] objectEnumerator];
   currentAttendee = [attendees nextObject];
     {
       [names appendFormat: @"%@,", [currentAttendee cn]];
       [emails appendFormat: @"%@,", [currentAttendee rfc822Email]];
+      uid = [um getUIDForEmail: [currentAttendee rfc822Email]];
+      if (uid != nil)
+       [uids appendFormat: @"%@,", uid];
+      else
+       [uids appendString: @","];
       currentAttendee = [attendees nextObject];
     }
 
   if ([names length] > 0)
     {
       ASSIGN (attendeesNames, [names substringToIndex: [names length] - 1]);
+      ASSIGN (attendeesUIDs, [uids substringToIndex: [uids length] - 1]);
       ASSIGN (attendeesEmails,
              [emails substringToIndex: [emails length] - 1]);
     }
   return url;
 }
 
+- (BOOL) hasOrganizer
+{
+  return (![organizer isVoid]);
+}
+
+- (NSString *) organizerName
+{
+  return [organizer mailAddress];
+}
+
 - (void) setAttendeesNames: (NSString *) newAttendeesNames
 {
   ASSIGN (attendeesNames, newAttendeesNames);
   return attendeesNames;
 }
 
+- (void) setAttendeesUIDs: (NSString *) newAttendeesUIDs
+{
+  ASSIGN (attendeesUIDs, newAttendeesUIDs);
+}
+
+- (NSString *) attendeesUIDs
+{
+  return attendeesUIDs;
+}
+
 - (void) setAttendeesEmails: (NSString *) newAttendeesEmails
 {
   ASSIGN (attendeesEmails, newAttendeesEmails);
                respondsToSelector: @selector(saveContentString:)];
 }
 
-- (BOOL) containsConflict: (id) _component
-{
-  [self subclassResponsibility: _cmd];
-
-  return NO;
-}
-
 /* access */
 
-#if 0
-- (iCalPerson *) getOrganizer
-{
-  iCalPerson *p;
-  NSString *emailProp;
-  
-  emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
-  p = [[[iCalPerson alloc] init] autorelease];
-  [p setEmail:emailProp];
-  [p setCn:[self cnForUser]];
-  return p;
-}
-#endif
-
 - (BOOL) isMyComponent
 {
   return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
              [currentAttendee setCn: [names objectAtIndex: count]];
              [currentAttendee setEmail: currentEmail];
              [currentAttendee setRole: @"REQ-PARTICIPANT"];
+             [currentAttendee setRsvp: @"TRUE"];
              [currentAttendee
                setParticipationStatus: iCalPersonPartStatNeedsAction];
            }
index 3f6054829ef4b87283e726b6dff0f66fbe0fd99b..41c39fc99af5a0449fb41194d25af87c0e0fadef 100644 (file)
   NSString *duration;
   unsigned int minutes;
 
-  todo = (iCalToDo *) [[self clientObject] component: NO];
+  todo = (iCalToDo *) [[self clientObject] component: NO secure: YES];
   if (todo)
     {
       startDate = [todo startDate];
 
 - (id <WOActionResults>) saveAction
 {
-  SOGoTaskObject *clientObject;
-  NSString *iCalString;
-
-  clientObject = [self clientObject];
-  iCalString = [[clientObject calendar: NO] versitString];
-  [clientObject saveContentString: iCalString];
+  [[self clientObject] saveComponent: todo];
 
   return [self jsCloseWithRefreshMethod: @"refreshTasks()"];
 }
 
+// - (id <WOActionResults>) saveAction
+// {
+//   SOGoTaskObject *clientObject;
+//   NSString *iCalString;
+
+//   clientObject = [self clientObject];
+//   iCalString = [[clientObject calendar: NO secure: NO] versitString];
+//   [clientObject saveContentString: iCalString];
+
+//   return [self jsCloseWithRefreshMethod: @"refreshTasks()"];
+// }
+
 - (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
                            inContext: (WOContext*) context
 {
   SOGoTaskObject *clientObject;
 
   clientObject = [self clientObject];
-  todo = (iCalToDo *) [clientObject component: YES];
+  todo = (iCalToDo *) [clientObject component: YES secure: NO];
 
   [super takeValuesFromRequest: _rq inContext: _ctx];
 
   NSString *newStatus, *iCalString;
 
   clientObject = [self clientObject];
-  todo = (iCalToDo *) [clientObject component: NO];
+  todo = (iCalToDo *) [clientObject component: NO secure: NO];
   if (todo)
     {
       newStatus = [self queryParameterForKey: @"status"];
          [todo setStatus: @"IN-PROCESS"];
        }
 
-      iCalString = [[clientObject calendar: NO] versitString];
+      iCalString = [[clientObject calendar: NO secure: NO] versitString];
       [clientObject saveContentString: iCalString];
     }
 
index fa69e46dd8339cca8cb9d7497e460ff42b35a10d..96894d145b1ddf470c7457324bd6f84850118cd4 100644 (file)
         </div>
       </div>
       <div id="buttons">
-       <var:if condition="canCreateOrModify"
-         ><input
-           type="submit"
-           class="button"
-           label:value="Save"
-           name="save:method" /></var:if>
         <input
           type="submit"
           class="button"
           label:value="Cancel"
           name="cancel"
           onclick="window.close(); return false;" />
+        <var:if condition="canCreateOrModify"
+       ><input
+           type="submit"
+           class="button"
+           label:value="Save"
+           name="save:method" /></var:if>
       </div>
     </form>
   </var:component>
index 87b072dab81d4ae1cb4080bf4fb027bdd8ee1913..948ee3a67c7f8e0c4cf2640ea9a0a4613346f51a 100644 (file)
@@ -9,6 +9,9 @@
   >
   <!-- TODO: add iMIP actions -->
 
+  <input id="iCalendarAttachment" type="hidden"
+    var:value="pathToAttachmentObject"/>
+
   <var:if condition="couldParseCalendar" const:negate="1">
     <fieldset>
       <legend>Parsing Error</legend>
        </var:if>
       </legend>
       
-      <var:if condition="inCalendar.method" const:value="REQUEST">
+      <var:if condition="inCalendar.method.uppercaseString" const:value="REQUEST">
        <!-- sent to attendees to propose or update a meeting -->
        <var:if condition="isLoggedInUserAnAttendee">
-         <p class="uix_ical_toolbar" id="iCalendarToolbar">
-           <input id="iCalendarAttachment" type="hidden"
-             var:value="pathToAttachmentObject"/>
-           <input id="iCalendarAccept" class="button"
-             type="button" label:value="Accept"/>
-           <input id="iCalendarDecline" class="button"
-             type="button" label:value="Decline"/>
-           <input id="iCalendarTentative" class="button"
-             type="button" label:value="Tentative"/>
-           <var:if condition="isEventStoredInCalendar" const:negate="YES">
-             | <input id="iCalendarAddToCalendar" class="button"
-               type="button" label:value="Add to calendar"/>
-           </var:if>
-         </p>
+         <var:if condition="$storedReplyAttendee.partStatWithDefault"
+           const:value="NEEDS-ACTION" const:negate="YES">
+           <p class="uix_ical_toolbar" id="iCalendarToolbar">
+             <input id="iCalendarAccept" class="button"
+               type="button" label:value="Accept"/>
+             <input id="iCalendarDecline" class="button"
+               type="button" label:value="Decline"/>
+             <!-- <input id="iCalendarTentative" class="button"
+             type="button" label:value="Tentative"/> -->
+             <var:if condition="isEventStoredInCalendar" const:negate="YES">
+               | <input id="iCalendarAddToCalendar" class="button"
+                 type="button" label:value="Add to calendar"/>
+             </var:if>
+           </p>
+         </var:if>
 
          <p>
            <var:string label:value="Organizer" />
       </var:if>
 
        
-      <var:if condition="inCalendar.method" const:value="REPLY">
+      <var:if condition="inCalendar.method.uppercaseString" const:value="REPLY">
        <!-- sent to organizer to update the status of the participant -->
        <var:if condition="isReplySenderAnAttendee" const:negate="1">
          <p><var:string label:value="reply_info_no_attendee" /></p>
        </var:if>
 
        <var:if condition="isReplySenderAnAttendee">
-         <p class="uix_ical_toolbar">
-           <a var:href="addStatusReplyLink"
-             var:_newstat="$inReplyAttendee.partStatWithDefault"
-             var:_sender="replySenderBaseEMail"
-             label:string="do_update_status"/>
-         </p>
-
+         <var:if condition="hasSenderStatusChanged"
+           ><p class="uix_ical_toolbar">
+             <input id="iCalendarUpdateUserStatus" class="button"
+               type="button" label:value="Update status"/>
+           </p
+             ></var:if>
+         
          <!-- TODO: replies to events not in the calendar? -->
 
          <p>
        </var:if>
       </var:if>
 
-      <var:if condition="inCalendar.method" const:value="CANCEL">
+      <var:if condition="inCalendar.method.uppercaseString" const:value="CANCEL">
        <!-- sent to attendees to notify of the attendee being removed or the
        event being deleted -->
        <var:if condition="isEventStoredInCalendar">
       </var:if>
 
 
-      <var:if condition="inCalendar.method" const:value="ADD">
+      <var:if condition="inCalendar.method.uppercaseString" const:value="ADD">
        <!-- TODO -->
        <p><var:string label:value="add_info_text" /></p>
       </var:if>
 
 
-      <var:if condition="inCalendar.method" const:value="PUBLISH">
+      <var:if condition="inCalendar.method.uppercaseString" const:value="PUBLISH">
        <!-- none-scheduling event sent to someone for adding to the calendar -->
        <p><var:string label:value="publish_info_text" /></p>
       </var:if>
        -->
       </var:if>
 
-
       <!-- the user comment is used in replies -->
       <var:if condition="inEvent.userComment.isNotEmpty">
        <div class="linked_attachment_meta" style="background-color: white;">
index 16ad19fcd8712ba4399b059332a5b83e3ea7a010..5a4d704283f79af66dcbc95a05ac7374b312acb9 100644 (file)
@@ -11,6 +11,7 @@
   const:popup="YES"
   title="name"
   var:toolbar="toolbar"
+  const:cssFiles="UIxComponentEditor.css"
   const:jsFiles="skycalendar.js,UIxComponentEditor.js">
 
   <script type="text/javascript">
             label:noSelectionString="prio_0"
             string="itemPriorityText" selection="priority"/>
       </span></span>
-      <label id="attendeesLabel" style="display: none;"><var:string label:value="Attendees:"
+      <var:if condition="hasOrganizer"
+       ><label id="organizerLabel"><var:string label:value="Organizer:"
+           /><span class="content"><var:string value="organizerName"/></span></label>
+      </var:if>
+      <label id="attendeesLabel"><var:string label:value="Attendees:"
          /><span class="content"
          ><a href="#" id="attendeesHref"><!-- space --></a></span></label>
       <hr />
@@ -75,6 +80,8 @@
        var:value="privacy"/>
       <input type="hidden" name="attendeesNames" id="attendeesNames"
        var:value="attendeesNames"/>
+      <input type="hidden" name="attendeesUIDs" id="attendeesUIDs"
+       var:value="attendeesUIDs"/>
       <input type="hidden" name="attendeesEmails" id="attendeesEmails"
        var:value="attendeesEmails"/>
       <input type="hidden" name="calendarFoldersList"
diff --git a/UI/Templates/UIxAppNavView.wox b/UI/Templates/UIxAppNavView.wox
deleted file mode 100644 (file)
index 31faad4..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version='1.0' standalone='yes'?>
-<span xmlns="http://www.w3.org/1999/xhtml"
-      xmlns:var="http://www.skyrix.com/od/binding"
-      xmlns:const="http://www.skyrix.com/od/constant"
-      xmlns:uix="OGo:uix"
-      xmlns:label="OGo:label"
->
-  <span class="defaultfont"
-  ><b><var:string label:value="User" />:</b> <var:string value="context.activeUser.cn" /></span>
-  <var:if condition="context.isUIxDebugEnabled">
-    <span class="defaultfont">
-      (<b><var:string label:value="Path"
-          />:</b><var:entity const:name="nbsp" />
-      <var:foreach list="navPathElements" item="element">
-        <var:if condition="element" value="lastElement" const:negate="YES">
-          <a var:href="element.url"><var:string value="element.name" /></a>
-          /
-        </var:if>
-        <var:if condition="element" value="lastElement">
-          <var:string value="element.name" />
-        </var:if>
-      </var:foreach>)
-    </span>
-  </var:if>
-</span>
index 828487d20890e1d01e1dc9b5b0789bafbcd75668..e6dbf22e7a4d7ebf0026fd97a7ad04cfa3447f81 100644 (file)
        <var:if condition="hasPageSpecificCSS"
          ><link type="text/css" rel="stylesheet" var:href="pageCSSURL"
            /></var:if>
+       <var:foreach list="additionalCSSFiles" item="item"
+         ><link type="text/css" rel="stylesheet" var:href="item"
+           />
+       </var:foreach>
        <var:if-ie
          ><link type="text/css" rel="stylesheet" rsrc:href="iefixes.css"
            /></var:if-ie>
@@ -79,6 +83,7 @@
            <var:string value="productLocalizableStrings" const:escapeHTML="NO"/>
          </script>
          <script type="text/javascript" rsrc:src="events.js"><!-- space --></script>
+         <script type="text/javascript" rsrc:src="fastinit.js"><!-- space --></script>
          <script type="text/javascript" rsrc:src="prototype.js"><!-- space --></script>
          <script type="text/javascript" rsrc:src="tablekit.js"><!-- space --></script>
          <script type="text/javascript" rsrc:src="tablekit-trueresize.js"><!-- space --></script>
diff --git a/UI/Templates/UIxPrintPageFrame.wox b/UI/Templates/UIxPrintPageFrame.wox
deleted file mode 100644 (file)
index 36bd9be..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version='1.0' standalone='yes'?>
-
-<html xmlns="http://www.w3.org/1999/xhtml"
-      xmlns:var="http://www.skyrix.com/od/binding"
-      xmlns:const="http://www.skyrix.com/od/constant"
-      xmlns:rsrc="OGo:url"
->
-  <head>
-    <title>
-      <var:string value="title"/>
-    </title>
-    <meta name="description" content="SOGo Web Interface"/>
-    <meta name="author" content="SKYRIX Software AG"/>
-    <meta name="robots" content="stop"/>
-    <link type="text/css" rel="stylesheet" rsrc:href="uix.css"/>
-    <link type="text/css" rel="stylesheet" rsrc:href="calendar.css"/>
-    <link href="mailto:hh@skyrix.com" rev="made"/>
-  </head>
-
-  <body margin="10" onload="javascript:window.print()">
-    <var:component-content/>
-  </body>
-</html>
\ No newline at end of file
index 3fd5439dd1ad697c2f0c46c4391f508b2fa7ee59..d38ebfe2b9f7a94dd122269400d2efaf852a165d 100644 (file)
       <var:foreach list="toolbarConfig" item="toolbarGroup"
         ><var:foreach list="toolbarGroup" item="buttonInfo"
           ><var:if condition="isButtonEnabled"
-            ><a class="toolbarButton"
+            ><button class="toolbarButton"
               var:href="buttonInfo.link"
               var:target="buttonInfo.target"
               var:onclick="buttonInfo.onclick"
               var:title="buttonTooltip"
-              ><span class="toolbarButton"
-                ><img class="buttonImage"
+              ><img class="buttonImage"
                   var:src="buttonImage"
                   var:alt="buttonTooltip"
                   /><var:if condition="hasMenu"
@@ -29,8 +28,7 @@
                   ><var:string
                     value="buttonLabel"
                     /></span
-                  ></span
-                ></a
+                ></button
               ></var:if
             ><var:if condition="isButtonEnabled"
             const:negate="YES"
index a58c509d59fadf36741c4245d7251b3361d409f2..31da8b1774681302939977afc43dcafaaf941401 100644 (file)
@@ -764,4 +764,4 @@ function initContacts(event) {
    }
 }
 
-addEvent(window, 'load', initContacts);
+FastInit.addOnLoad(initContacts);
index ead44e6fc0bf1e376e006f9852e12d7f060bab3f..04970b43a2c66c8117ae0e0054e6267437e36df9 100644 (file)
@@ -379,6 +379,11 @@ DIV.mailer_mailcontent
   bottom: 0px;
   overflow: auto; }
 
+DIV.mailer_mailcontent TABLE
+{
+  table-layout: auto;
+}
+
 TD.mailer_fieldname
 {
   white-space: nowrap;
index bce12ad7fda881e35e01ea180a485bdcf6182d96..7f4eaa97b594df450be95c50aeeb114d4924d7d5 100644 (file)
@@ -211,14 +211,18 @@ function deleteSelectedMessages(sender) {
   var messageList = $("messageList");
   var rowIds = messageList.getSelectedRowsId();
 
-  for (var i = 0; i < rowIds.length; i++) {
-    var url;
-    var rowId = rowIds[i].substr(4);
-    var messageId = currentMailbox + "/" + rowId;
-    url = ApplicationBaseURL + messageId + "/trash";
-    deleteMessageRequestCount++;
-    var data = { "id": rowId, "mailbox": currentMailbox, "messageId": messageId };
-    triggerAjaxRequest(url, deleteSelectedMessagesCallback, data);
+  if (rowIds.length > 0) {
+    for (var i = 0; i < rowIds.length; i++) {
+      var url;
+      var rowId = rowIds[i].substr(4);
+      var messageId = currentMailbox + "/" + rowId;
+      url = ApplicationBaseURL + messageId + "/trash";
+      deleteMessageRequestCount++;
+      var data = { "id": rowId, "mailbox": currentMailbox, "messageId": messageId };
+      triggerAjaxRequest(url, deleteSelectedMessagesCallback, data);
+    }
+  } else {
+    window.alert(labels["Please select a message."]);
   }
 
   return false;
@@ -751,6 +755,7 @@ function configureiCalLinksInMessage() {
   var buttons = { "iCalendarAccept": "accept",
                  "iCalendarDecline": "decline",
                  "iCalendarTentative": "tentative",
+                 "iCalendarUpdateUserStatus": "updateUserStatus",
                  "iCalendarAddToCalendar": "addToCalendar",
                  "iCalendarDeleteFromCalendar": "deleteFromCalendar" };
 
@@ -768,11 +773,13 @@ function onICalendarButtonClick(event) {
   var link = $("iCalendarAttachment").value;
   if (link) {
     var urlstr = link + "/" + this.action;
+    log ("click: " + urlstr);
     triggerAjaxRequest(urlstr, ICalendarButtonCallback,
                       currentMailbox + "/"
                       + currentMessages[currentMailbox]);
-    window.alert(urlstr);
   }
+  else
+    log("no link");
 }
 
 function ICalendarButtonCallback(http) {
@@ -1170,7 +1177,7 @@ function openInbox(node) {
 }
 
 function initMailer(event) {
-  if (!document.body.hasClassName("popup")) {
+  if (!$(document.body).hasClassName("popup")) {
 //     initDnd();
     initMailboxTree();
     initMessageCheckTimer();
@@ -1652,7 +1659,7 @@ function getMenus() {
   return menus;
 }
 
-addEvent(window, 'load', initMailer);
+FastInit.addOnLoad(initMailer);
 
 function Mailbox(type, name) {
   this.type = type;
index 6f4392d0a88ba5f45d72b8064b95bcceb879282d..cad1169e02471d2e0892fb122d02881e27cd166d 100644 (file)
@@ -38,4 +38,4 @@ function onLoginCallback(http) {
   }
 }
 
-addEvent(window, 'load', initLogin);
+FastInit.addOnLoad(initLogin);
index cd70d984e70029935ea5a980a4af3e0e9fac11e4..98a683c855b5c13080f85bfb4919abfd95a29ae8 100644 (file)
@@ -261,10 +261,12 @@ TABLE#dateSelectorTable
 { background-color: #deebf7;
   border: 1px solid #deebf7; }
 
+#dateSelector TD.dayOfToday._selected
+{ background-color: #4b6983; }
+
 #dateSelector TD._selected A
 { color: #fff; }
 
-
 TABLE#eventsList
 { width: 100%; }
 
@@ -283,10 +285,8 @@ TABLE#eventsList TH
 ._unfocused#dateSelector TD._selected,
 UL._unfocused > LI._selected,
 TABLE._unfocused#eventsList TR._selected TD
-{
-  background-color: #d4d0c8 !important;
-  color: #fff !important;
-}
+{ background-color: #d4d0c8 !important;
+  color: #fff !important; }
 
 SPAN.dayCellLabel
 { color: #77a;
index d68fa0c7952aaaafc60253f7b7767411a1557492..31f3f738b84a5f94b57ef8feb79f78975cae420b 100644 (file)
@@ -452,7 +452,7 @@ function restoreCurrentDaySelection(div) {
 }
 
 function changeDateSelectorDisplay(day, keepCurrentDay) {
-  var url = ApplicationBaseURL + "/dateselector";
+  var url = ApplicationBaseURL + "dateselector";
   if (day)
     url += "?day=" + day;
 
@@ -480,7 +480,7 @@ function changeDateSelectorDisplay(day, keepCurrentDay) {
 }
 
 function changeCalendarDisplay(data, newView) {
-  var url = ApplicationBaseURL + "/" + ((newView) ? newView : currentView);
+  var url = ApplicationBaseURL + ((newView) ? newView : currentView);
 
   selectedCalendarCell = null;
 
@@ -493,9 +493,55 @@ function changeCalendarDisplay(data, newView) {
 
   if (!day)
     day = currentDay;
-  if (day)
-    url += "?day=" + day;
 
+  if (day) {
+    var divs = $$('div.day[day='+day+']');
+    if (divs.length > 0) {
+      // Don't reload the view if the event is present in current view
+
+      // Find day number
+      var dayNumber;
+      var classes = $w(divs[0].className);
+      for (var i = 0; i < classes.length; i++) {
+       if (classes[i] == 'day')
+         continue;
+       if (classes[i] == 'selectedDay')
+         break;
+       dayNumber = classes[i];
+       break;
+      }
+
+      // Deselect previous day
+      var selectedDivs = $$('div.day.selectedDay');
+      selectedDivs.each(function(div) {
+         div.removeClassName('selectedDay');
+       });
+      
+      // Select new day
+      selectedDivs = $$('div.day.'+dayNumber);
+      selectedDivs.each(function(div) {
+         div.addClassName('selectedDay');
+       });
+      
+      // Deselect day in date selector
+      if (document.selectedDate)
+       document.selectedDate.deselect();
+      
+      // Select day in date selector
+      var selectedLink = $$('table#dateSelectorTable a[day='+day+']');
+      if (selectedLink.length > 0) {
+       selectedCell = selectedLink[0].up(1);
+       selectedCell.select();
+       document.selectedDate = selectedCell;
+      }
+
+      // Scroll to event
+      scrollDayView(scrollEvent);
+
+      return false;
+    }
+    url += "?day=" + day;
+  }
 //   if (newView)
 //     log ("switching to view: " + newView);
 //   log ("changeCalendarDisplay: " + url);
@@ -538,18 +584,24 @@ function onMonthOverview() {
 }
 
 function scrollDayView(scrollEvent) {
+
+  if (currentView == "monthview")
+    return;
+
   var offset = 0;
   var daysView = $("daysView");
   var hours =
     $(daysView.childNodesWithTag("div")[0]).childNodesWithTag("div");
 
-  if (scrollEvent && scrollEvent.siblings) {
-    var classes = scrollEvent.siblings[0].getAttribute("class").split(" ");
-    for (var i = 0; i < classes.length; i++)
+  if (scrollEvent) {
+    var divs = $$("div#calendarContent div." + eventClass(scrollEvent));
+    var classes = $w(divs[0].className);
+    for (var i = 0; i < classes.length; i++) {
       if (classes[i].startsWith("starts")) {
        var starts = Math.floor(parseInt(classes[i].substr(6)) / 4);
        offset = hours[starts].offsetTop;
       }
+    }
   }
   else
     offset = hours[8].offsetTop;
@@ -564,7 +616,7 @@ function onClickableCellsDblClick(event) {
   event.returnValue = false;
 }
 
-function refreshCalendarEvents() {
+function refreshCalendarEvents(scrollEvent) {
    var todayDate = new Date();
    var sd;
    var ed;
@@ -610,7 +662,7 @@ function refreshCalendarEvents() {
    var url = ApplicationBaseURL + "/eventslist?sd=" + sd + "&ed=" + ed;
    document.refreshCalendarEventsAjaxRequest
       = triggerAjaxRequest(url, refreshCalendarEventsCallback,
-                          {"startDate": sd, "endDate": ed});
+                          {"startDate": sd, "endDate": ed, "scrollEvent": scrollEvent});
 }
 
 function refreshCalendarEventsCallback(http) {
@@ -625,6 +677,8 @@ function refreshCalendarEventsCallback(http) {
                          http.callbackData["startDate"],
                          http.callbackData["endDate"]);
     }
+    if (http.callbackData["scrollEvent"])
+      scrollDayView(http.callbackData["scrollEvent"]);
   }
   else
      log("AJAX error when refreshing calendar events");
@@ -655,7 +709,6 @@ function drawCalendarEvent(eventData, sd, ed) {
    var startHour = null;
    var endHour = null;
 
-   var siblings = new Array();
    for (var i = 0; i < days.length; i++)
       if (days[i].earlierDate(viewStartDate) == viewStartDate
          && days[i].laterDate(viewEndDate) == viewEndDate) {
@@ -687,8 +740,6 @@ function drawCalendarEvent(eventData, sd, ed) {
 
         var eventDiv = newEventDIV(eventData[0], eventData[1], starts, lasts,
                                    null, null, title);
-        siblings.push(eventDiv);
-        eventDiv.siblings = siblings;
         var dayString = days[i].getDayString();
 //      log("day: " + dayString);
         var parentDiv = null;
@@ -731,18 +782,20 @@ function drawCalendarEvent(eventData, sd, ed) {
         if (parentDiv)
           parentDiv.appendChild(eventDiv);
       }
+}
 
-   var eventTR = $(eventData[0]);
-   if (eventTR)
-     eventTR.siblings = siblings;
+function eventClass(cname) {
+  return  escape(cname.replace(".", "-"));
 }
 
+
 function newEventDIV(cname, calendar, starts, lasts,
                     startHour, endHour, title) {
    var eventDiv = document.createElement("div");
    eventDiv.cname = escape(cname);
    eventDiv.calendar = calendar;
    $(eventDiv).addClassName("event");
+   $(eventDiv).addClassName(eventClass(cname));
    $(eventDiv).addClassName("starts" + starts);
    $(eventDiv).addClassName("lasts" + lasts);
    for (var i = 1; i < 5; i++) {
@@ -800,12 +853,11 @@ function calendarDisplayCallback(http) {
     var contentView;
     if (currentView == "monthview")
       contentView = $("calendarContent");
-    else {
-      var scrollEvent = http.callbackData.scrollEvent;
-      scrollDayView($(scrollEvent));
+    else
       contentView = $("daysView");
-    }
-    refreshCalendarEvents();
+    
+    refreshCalendarEvents(http.callbackData.scrollEvent);
+    
     var days = document.getElementsByClassName("day", contentView);
     if (currentView == "monthview")
       for (var i = 0; i < days.length; i++) {
@@ -980,9 +1032,9 @@ function onListFilterChange() {
 
 function onEventClick(event) {
   changeCalendarDisplay( { "day": this.day,
-                          "scrollEvent": this.getAttribute("id") } );
+       "scrollEvent": this.getAttribute("id") } );
   changeDateSelectorDisplay(this.day);
-
+  
   return onRowClick(event);
 }
 
@@ -1660,4 +1712,4 @@ function initCalendars() {
    }
 }
 
-addEvent(window, 'load', initCalendars);
+FastInit.addOnLoad(initCalendars);
index 4bce6213628ba61ee10f67a0bc81c8dff1ef0f82..2aee3cfc7947f79f6f6b474f9054108830803d50 100644 (file)
@@ -142,4 +142,4 @@ function onAclLoadHandler() {
    this.userRightsWidth = window.opener.getUsersRightsWindowWidth();
 }
 
-addEvent(window, 'load', onAclLoadHandler);
+FastInit.addOnLoad(onAclLoadHandler);
index 3fbb575845cce413b335b3df1bba0e09055761a9..6dd1cd4a97963c58f8f9f01f2e6285a9bde41994 100644 (file)
@@ -286,4 +286,4 @@ function onAppointmentEditorLoad() {
   initTimeWidgets(widgets);
 }
 
-addEvent(window, 'load', onAppointmentEditorLoad);
+FastInit.addOnLoad(onAppointmentEditorLoad);
index dff86b27bcb5945abb36ac4bf22496fe00d18785..beed7baeeff00ad8e942b886935b33ad4af6e594 100644 (file)
@@ -1,9 +1,9 @@
 var resultsDiv;
-var searchField;
 var running = false;
 var address;
 var delay = 500;
 var requestField;
+var searchField;
 var awaitingFreeBusyRequests = new Array();
 var additionalDays = 2;
 
@@ -11,6 +11,7 @@ var dayStartHour = 8;
 var dayEndHour = 18;
 
 var attendeesNames;
+var attendeesUIDs;
 var attendeesEmails;
 
 function onContactKeydown(event) {
@@ -57,16 +58,17 @@ function onContactKeydown(event) {
 }
 
 function triggerRequest() {
-  if (document.contactLookupAjaxRequest) {
-    document.contactLookupAjaxRequest.aborted = yes;
-    document.contactLookupAjaxRequest.abort();
+  if (requestField) {
+    if (document.contactLookupAjaxRequest) {
+      document.contactLookupAjaxRequest.aborted = yes;
+      document.contactLookupAjaxRequest.abort();
+    }
+    var urlstr = ( UserFolderURL + "Contacts/contactSearch?search="
+                  + escape(requestField.value) );
+    document.contactLookupAjaxRequest = triggerAjaxRequest(urlstr,
+                                                          updateResults,
+                                                          requestField);
   }
-  var urlstr = ( UserFolderURL + "Contacts/contactSearch?search="
-                 + escape(requestField.value) );
-  //log (urlstr);
-  document.contactLookupAjaxRequest = triggerAjaxRequest(urlstr,
-                                                         updateResults,
-                                                         requestField);
 }
 
 function updateResults(http) {
@@ -74,8 +76,9 @@ function updateResults(http) {
     var menu = $('attendeesMenu');
     var list = menu.down("ul");
 
-    searchField = http.callbackData;
+    searchField = http.callbackData; // requestField
     searchField.hasfreebusy = false;
+    searchField.setAttribute("uid", null);
 
     if (http.status == 200) {
       var start = searchField.value.length;
@@ -123,9 +126,7 @@ function updateResults(http) {
        if (data.length == 1) {
          var contact = data[0];
          if (contact["uid"].length > 0)
-           searchField.uid = contact["uid"];
-         else
-           searchField.uid = null;
+           searchField.setAttribute("uid", contact["uid"]);
          var completeEmail = contact["name"] + " <" + contact["email"] + ">";
          if (contact["name"].substring(0, searchField.value.length).toUpperCase()
              == searchField.value.toUpperCase())
@@ -153,29 +154,13 @@ function updateResults(http) {
 
 function onAttendeeResultClick(event) {
   if (searchField) {
-    searchField.uid = this.uid;
+    searchField.setAttribute("uid", this.getAttribute("uid"));
     searchField.value = this.firstChild.nodeValue.trim();
     searchField.confirmedValue = searchField.value;
     searchField.blur(); // triggers checkAttendee function call
   }
 }
 
-function UIDLookupCallback(http) {
-  if (http.readyState == 4) {
-    if (http.status == 200) {
-      var searchField = http.callbackData;
-      var start = searchField.value.length;
-      var text = http.responseText.split(":");
-      if (text[0].length > 0) {
-        searchField.uid = text[0];
-        displayFreeBusyForNode(searchField);
-      }
-      else
-        searchField.uid = null
-    }
-  }
-}
-
 function resetFreeBusyZone() {
   var table = $("freeBusy");
   var row = table.tHead.rows[2];
@@ -270,6 +255,9 @@ function newAttendee(event) {
 }
 
 function checkAttendee() {
+  if (document.currentPopupMenu)
+    hideMenu(document.currentPopupMenu);
+
   if (document.currentPopupMenu && !this.confirmedValue) {
     // Hack for IE7; blur event is triggered on input field when
     // selecting a menu item
@@ -277,53 +265,56 @@ function checkAttendee() {
     if (visible)
       return;
   }
+
   this.focussed = false;
-  var th = this.parentNode.parentNode;
-  var tbody = th.parentNode;
+  var row = this.parentNode.parentNode;
+  var tbody = row.parentNode;
   if (tbody && this.value.trim().length == 0)
-    tbody.removeChild(th);
+    tbody.removeChild(row);
   else if (!this.hasfreebusy) {
     if (this.confirmedValue)
       this.value = this.confirmedValue;
     displayFreeBusyForNode(this);
     this.hasfreebusy = true;
   }
-  resetAttendeesValue();
+
+  requestField = null;
+  searchField = null;
 }
 
 function displayFreeBusyForNode(node) {
-   if (document.contactFreeBusyAjaxRequest)
+  var nodes = node.parentNode.parentNode.cells;
+  if (node.getAttribute("uid")) {
+    if (document.contactFreeBusyAjaxRequest)
       awaitingFreeBusyRequests.push(node);
-   else {
-      var nodes = node.parentNode.parentNode.cells;
-      if (node.uid) {
-        for (var i = 1; i < nodes.length; i++) {
-           $(nodes[i]).removeClassName("noFreeBusy");
-           nodes[i].innerHTML = ('<span class="freeBusyZoneElement"></span>'
-                                 + '<span class="freeBusyZoneElement"></span>'
-                                 + '<span class="freeBusyZoneElement"></span>'
-                                 + '<span class="freeBusyZoneElement"></span>');
-        }
-        if (document.contactFreeBusyAjaxRequest) {
-           document.contactFreeBusyAjaxRequest.aborted = true;
-           document.contactFreeBusyAjaxRequest.abort();
-        }
-        var sd = $('startTime_date').valueAsShortDateString();
-        var ed = $('endTime_date').valueAsShortDateString();
-        var urlstr = ( UserFolderURL + "../" + node.uid + "/freebusy.ifb/ajaxRead?"
-                       + "sday=" + sd + "&eday=" + ed + "&additional=" +
-                       additionalDays );
-        document.contactFreeBusyAjaxRequest
-           = triggerAjaxRequest(urlstr,
-                                updateFreeBusyData,
-                                node);
-      } else {
-        for (var i = 1; i < nodes.length; i++) {
-           $(nodes[i]).addClassName("noFreeBusy");
-           nodes[i].innerHTML = '';
-        }
+    else {
+      for (var i = 1; i < nodes.length; i++) {
+       $(nodes[i]).removeClassName("noFreeBusy");
+       $(nodes[i]).innerHTML = ('<span class="freeBusyZoneElement"></span>'
+                                + '<span class="freeBusyZoneElement"></span>'
+                                + '<span class="freeBusyZoneElement"></span>'
+                                + '<span class="freeBusyZoneElement"></span>');
       }
-   }
+      if (document.contactFreeBusyAjaxRequest) {
+       document.contactFreeBusyAjaxRequest.aborted = true;
+       document.contactFreeBusyAjaxRequest.abort();
+      }
+      var sd = $('startTime_date').valueAsShortDateString();
+      var ed = $('endTime_date').valueAsShortDateString();
+      var urlstr = ( UserFolderURL + "../" + node.getAttribute("uid") + "/freebusy.ifb/ajaxRead?"
+                    + "sday=" + sd + "&eday=" + ed + "&additional=" +
+                    additionalDays );
+      document.contactFreeBusyAjaxRequest
+       = triggerAjaxRequest(urlstr,
+                            updateFreeBusyData,
+                            node);
+    }
+  } else {
+    for (var i = 1; i < nodes.length; i++) {
+      $(nodes[i]).addClassName("noFreeBusy");
+      $(nodes[i]).update();
+    }
+  }
 }
 
 function setSlot(tds, nbr, status) {
@@ -362,22 +353,6 @@ function updateFreeBusyData(http) {
   }
 }
 
-function resetAttendeesValue() {
-  var table = $("freeBusy");
-  var inputs = table.getElementsByTagName("input");
-  for (var i = 0; i < inputs.length - 2; i++) {
-    var currentInput = inputs[i];
-    var uid = currentInput.getAttribute("uid");
-    if (uid) {
-      currentInput.uid = uid;
-      currentInput.setAttribute("uid", null);
-    }
-    currentInput.setAttribute("autocomplete", "off");
-  }
-  inputs[inputs.length - 2].setAttribute("autocomplete", "off");
-  Event.observe(inputs[inputs.length - 2], "click", newAttendee);
-}
-
 function resetAllFreeBusys() {
   var table = $("freeBusy");
   var inputs = table.getElementsByTagName("input");
@@ -409,25 +384,33 @@ function initializeWindowButtons() {
 
 function onEditorOkClick(event) {
    preventDefault(event);
-
+   
    attendeesNames = new Array();
+   attendeesUIDs = new Array();
    attendeesEmails = new Array();
-
+   
    var table = $("freeBusy");
    var inputs = table.getElementsByTagName("input");
    for (var i = 0; i < inputs.length - 2; i++) {
      var name = extractEmailName(inputs[i].value);
-     if (!(name && name.length > 0))
-       name = inputs[i].uid;
      var email = extractEmailAddress(inputs[i].value);
+     var uid = "";
+     if (inputs[i].getAttribute("uid"))
+       uid = inputs[i].getAttribute("uid");
+     if (!(name && name.length > 0))
+       if (inputs[i].uid)
+        name = inputs[i].uid;
+       else
+        name = email;
      var pos = attendeesEmails.indexOf(email);
      if (pos == -1)
        pos = attendeesEmails.length;
      attendeesNames[pos] = name;
+     attendeesUIDs[pos] = uid;
      attendeesEmails[pos] = email;
    }
-
    parent$("attendeesNames").value = attendeesNames.join(",");
+   parent$("attendeesUIDs").value = attendeesUIDs.join(",");
    parent$("attendeesEmails").value = attendeesEmails.join(",");
    window.opener.refreshAttendees();
 
@@ -507,7 +490,6 @@ function onTimeDateWidgetChange(event) {
   prepareTableHeaders();
   prepareTableRows();
   redisplayFreeBusyZone();
-  resetAttendeesValue();
   resetAllFreeBusys();
 }
 
@@ -563,53 +545,52 @@ function prepareTableRows() {
 
 function prepareAttendees() {
    var value = parent$("attendeesNames").value;
+   var table = $("freeBusy");
    if (value.length > 0) {
       attendeesNames = parent$("attendeesNames").value.split(",");
+      attendeesUIDs = parent$("attendeesUIDs").value.split(",");
       attendeesEmails = parent$("attendeesEmails").value.split(",");
 
-      var body = $("freeBusy").tBodies[0];
+      var tbody = table.tBodies[0];
+      var model = tbody.rows[tbody.rows.length - 1];
+      var newAttendeeRow = tbody.rows[tbody.rows.length - 2];
       for (var i = 0; i < attendeesNames.length; i++) {
-        var tr = body.insertRow(i);
-        var td = document.createElement("td");
-        $(td).addClassName("attendees");
-        var input = document.createElement("input");
+        var row = model.cloneNode(true);
+        tbody.insertBefore(row, newAttendeeRow);
+        $(row).removeClassName("attendeeModel");
+        var input = $(row).down("input");
         var value = "";
-        if (attendeesNames[i].length > 0)
+        if (attendeesNames[i].length > 0 && attendeesNames[i] != attendeesEmails[i])
            value += attendeesNames[i] + " ";
         value += "<" + attendeesEmails[i] + ">";
         input.value = value;
-        $(input).addClassName("textField");
+        if (attendeesUIDs[i].length > 0)
+          input.setAttribute("uid", attendeesUIDs[i]);
+        input.setAttribute("name", "");
         input.setAttribute("modified", "0");
         input.observe("blur", checkAttendee);
         input.observe("keydown", onContactKeydown);
-        tr.appendChild(td);
-        td.appendChild(input);
         displayFreeBusyForNode(input);
       }
    }
    else {
       attendeesNames = new Array();
+      attendeesUIDs = new Array();
       attendeesEmails = new Array();
    }
-}
 
-function initializeFreebusys() {
-   var inputs = $("freeBusy").getElementsByTagName("input");
-   var baseUrl = UserFolderURL + "Contacts/contactSearch?search=";
-   for (var i = 0; i < attendeesEmails.length; i++)
-      triggerAjaxRequest(baseUrl + attendeesEmails[i],
-                        UIDLookupCallback, inputs[i]);
+   var inputs = table.getElementsByTagName("input");
+   inputs[inputs.length - 2].setAttribute("autocomplete", "off");
+   Event.observe(inputs[inputs.length - 2], "click", newAttendee);
 }
 
 function onFreeBusyLoadHandler() {
    initializeWindowButtons();
    initializeTimeWidgets();
-   prepareAttendees();
    prepareTableHeaders();
    prepareTableRows();
    redisplayFreeBusyZone();
-   resetAttendeesValue();
-   initializeFreebusys();
+   prepareAttendees();
 }
 
-document.observe("dom:loaded", onFreeBusyLoadHandler);
+FastInit.addOnLoad(onFreeBusyLoadHandler);
index 174cf154c56d35cbfdef0274a3dc105697595335..1c739b5ca5534dc2fa2461143e686c90bbf225a9 100644 (file)
@@ -6,4 +6,4 @@ function initACLButtons() {
   Event.observe($("cancelButton"), "click", onCancelClick);
 }
 
-addEvent(window, "load", initACLButtons);
+FastInit.addOnLoad(initACLButtons);
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..da6488dd1971952a1affbc26ebd4c0d6f41aded2 100644 (file)
@@ -0,0 +1,2 @@
+#attendeesLabel
+{ display: none; }
index 6ff4d3f008bcb716f7fa21e0d39738ee53e06c0c..45b91ae6a5714ae01500e83eb4f7372f49b47a2f 100644 (file)
@@ -10,9 +10,8 @@ function onPopupAttendeesWindow(event) {
 function onSelectPrivacy(event) {
    if (event.button == 0 || (isSafari() && event.button == 1)) {
       var node = getTarget(event);
-      if (node.tagName != 'A')
-       node = $(node).getParentWithTagName("a");
-      node = $(node).childNodesWithTag("span")[0];
+      if (node.tagName != 'BUTTON')
+       node = $(node).up("button");
       popupToolbarMenu(node, "privacy-menu");
       Event.stop(event);
 //       preventDefault(event);
@@ -158,4 +157,4 @@ function onComponentEditorLoad(event) {
                    false);
 }
 
-addEvent(window, 'load', onComponentEditorLoad);
+FastInit.addOnLoad(onComponentEditorLoad);
index 23f334a7ae7dcad2384faa19a8ef5f8f9cd5180b..76910d0322c770bc235bbe10eb0e9c054cbb8e55 100644 (file)
@@ -134,4 +134,4 @@ function initEditorForm() {
   $("givenName").onkeyup = onFnNewValue;
 }
 
-addEvent(window, 'load', initEditorForm);
+FastInit.addOnLoad(initEditorForm);
index 1734da55a20543f6afe2efb16023f534917c5464..62faa6c6831d798713ec78c8fb5db94b78614b94 100644 (file)
@@ -141,4 +141,4 @@ function initUserFoldersWindow() {
    Event.observe($("addButton"), "click",  onConfirmFolderSelection);
 }
 
-addEvent(window, 'load', initUserFoldersWindow);
+FastInit.addOnLoad(initUserFoldersWindow);
index ee8e8fccb663f91fad97a24fc82f4d91a662221a..a38422dc48a9d11c390818c75994daf4ce96fc57 100644 (file)
@@ -7,4 +7,4 @@ function initACLButtons() {
    Event.observe(button, "click", onCancelClick);
 }
 
-addEvent(window, "load", initACLButtons);
+FastInit.addOnLoad(initACLButtons);
index bf46f21a106b328e36331a4aae6b96fba66ed70f..328b8a4cdf913f828ccf1b98ce592fa364263a42 100644 (file)
@@ -151,4 +151,4 @@ UL#attachments LI IMG
   right: 0em;
   bottom: 0em;
   top: 13em;
-  width: 100%; }
+  width: 99%; }
index 212f171528ca76d1f6e4902bdd284732bbfcaf06..54846025a1d91e89159f329fe18698ed3f8e1dab 100644 (file)
@@ -4,11 +4,9 @@ function onContactAdd() {
   var selector = null;
   var selectorURL = '?popup=YES&selectorId=mailer-contacts';
  
-  urlstr = ApplicationBaseURL;
-  if (urlstr[urlstr.length-1] != '/')
-    urlstr += '/';
-  urlstr += ("../../" + UserLogin + "/Contacts/"
-             + contactSelectorAction + selectorURL);
+  urlstr = ApplicationBaseURL 
+    + "../Contacts/"
+    + contactSelectorAction + selectorURL;
   var w = window.open(urlstr, "Addressbook",
                       "width=640,height=400,resizable=1,scrollbars=0");
   w.selector = selector;
@@ -368,4 +366,4 @@ function onMailEditorClose(event) {
   Event.stopObserving(window, "beforeunload", onMailEditorClose);
 }
 
-addEvent(window, 'load', initMailEditor);
+FastInit.addOnLoad(initMailEditor);
index b9cb5946c7b00331d0ad372512f07abaf2d813c2..dd19748678335dbc0f832b1943f0e327f4e676b9 100644 (file)
@@ -9,4 +9,4 @@ function initPopupMailer(event) {
   resizeMailContent();
 }
 
-addEvent(window, 'load', initPopupMailer);
+FastInit.addOnLoad(initPopupMailer);
index df7c1a68c82fc4941f8c88addf853150e63bf1d6..4593693d895c08bc062df7bf671faaaa86f1cd18 100644 (file)
@@ -219,9 +219,8 @@ function hasRecipients() {
   var count;
   
   count = this.getAddressCount();
-  if (count > 0)
-    return true;
-  return false;
+
+  return (count > 0)
 }
 
 /* addressbook helpers */
index 6ef7e2fd65f35a45fee8f12f3fac01f998b834aa..65d471b6cb6d2595b7dfecf36d8224388f5f198b 100644 (file)
@@ -6,4 +6,4 @@ function initACLButtons() {
    $("cancelButton").addEventListener("click", onCancelClick, false);
 }
 
-addEvent(window, "load", initACLButtons);
+FastInit.addOnLoad(initACLButtons);
index 5d98047afb0c05558dba82cbc6b20d116a7e20a9..c49cc7183a222069d3f7436dd767bcb5825c2519 100644 (file)
@@ -297,4 +297,4 @@ function onTaskEditorLoad() {
   initializeStatusLine();
 }
 
-addEvent(window, 'load', onTaskEditorLoad);
+FastInit.addOnLoad(onTaskEditorLoad);
index 512a750e347cb1525a25c3143c19dc8a6ffc0ee4..f8038276e100c9c08429fd6a18bc4f01129dbc5a 100644 (file)
@@ -33,7 +33,7 @@ IMG#progressIndicator
   margin-top: 1.5em;
   margin-right: 1em; }
 
-SPAN#toolbar IMG#progressIndicator
+DIV#toolbar IMG#progressIndicator
 { margin-top: 0.75em; }
 
 DIV#pageContent
@@ -232,12 +232,14 @@ SPAN.toolbarSeparator
   width: 0px;
   padding: 0px; }
 
-A.toolbarButton
+BUTTON.toolbarButton,
+SPAN.toolbarButton,
+SPAN.disabledToolbarButton
 { color: #000;
-  text-decoration: none; }
-
-SPAN.toolbarButton, SPAN.disabledToolbarButton
-{ cursor: default;
+  font-family: Lucida Grande, Bitstream VeraSans, Tahoma;
+  font-size: 8pt;
+  text-decoration: none;
+  cursor: default;
   display: inline;
   float: left;
   text-align: center;
@@ -249,12 +251,16 @@ SPAN.toolbarButton, SPAN.disabledToolbarButton
   border-bottom: 1px solid transparent;
   padding: 1px 2px;
   background-color: transparent;
-  color: -moz-DialogText; }
+  color: -moz-DialogText;
+  overflow: visible;
+  margin: 0px; }
 
 SPAN.disabledToolbarButton
 { -moz-opacity: 0.4;
-  opacity: 0.4; }
+  opacity: 0.4;
+  padding-top: 2px; }
 
+BUTTON.toolbarButton:hover,
 SPAN.toolbarButton:hover
 { color: -moz-buttonhovertext;
   border-top: 1px solid #fff;
@@ -262,6 +268,7 @@ SPAN.toolbarButton:hover
   border-bottom: 1px solid #828482;
   border-right: 1px solid #828482; }
 
+BUTTON.toolbarButton:active,
 SPAN.toolbarButton:active
 { color: -moz-buttonhovertext;
   border-top: 1px solid #828482;
index 96c4b00368cb81106491aff8bf5f07291f7e9648..c546c7922a43a51465c2fd3e372c953ea436d819 100644 (file)
@@ -257,9 +257,10 @@ function openMailComposeWindow(url, wId) {
 
 function openMailTo(senderMailTo) {
   var mailto = sanitizeMailTo(senderMailTo);
+
   if (mailto.length > 0)
     openMailComposeWindow(ApplicationBaseURL
-                         + "/../Mail/compose?mailto=" + mailto);
+                         + "../Mail/compose?mailto=" + mailto);
 
   return false; /* stop following the link */
 }
@@ -1402,7 +1403,7 @@ function onFinalLoadHandler(event) {
     safetyNet.parentNode.removeChild(safetyNet);
 }
 
-document.observe("dom:loaded", onLoadHandler);
+FastInit.addOnLoad(onLoadHandler);
 
 function parent$(element) {
   return this.opener.document.getElementById(element);