+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
- 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;
@class CardGroup;
-@interface CardElement : NSObject <NSCopying>
+@interface CardElement : NSObject <NSCopying, NSMutableCopying>
{
NSString *tag;
NSMutableArray *values;
values: (NSArray *) someValues;
- (void) setParent: (CardGroup *) aParent;
-- (CardGroup *) parent;
+- (id) parent;
- (void) setTag: (NSString *) aTag;
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
@class NSMutableArray;
@class NSString;
-@interface CardGroup : CardElement <NSCopying>
+@interface CardGroup : CardElement <NSCopying, NSMutableCopying>
{
NSMutableArray *children;
}
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
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;
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
- (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
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];
}
[children replaceObjectAtIndex: index withObject: newElement];
}
+/* NSCopying */
+
- (id) copyWithZone: (NSZone *) aZone
{
CardGroup *new;
return new;
}
+/* NSMutableCopying */
+
+- (id) mutableCopyWithZone: (NSZone *) aZone
+{
+ CardGroup *new;
+
+ new = [super mutableCopyWithZone: aZone];
+ [new setChildrenAsCopy: [children mutableCopyWithZone: aZone]];
+
+ return new;
+}
+
@end
+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
- (void) setMethod: (NSString *) _value
{
- [[self uniqueChildWithTag: @"method"] setValue: 0 to: _value];
+ [[self uniqueChildWithTag: @"method"] setValue: 0
+ to: [_value uppercaseString]];
}
- (NSString *) method
- (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;
- (NSString *) rsvp
{
- return [self value: 0 ofAttribute: @"rsvp"];
+ return [[self value: 0 ofAttribute: @"rsvp"] lowercaseString];
}
// - (void)setXuid:(NSString *)_s {
[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)
===================================================================
--- 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;
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)
Product.m \
NSArray+Appointments.m \
iCalEntityObject+SOGo.m \
+ iCalEvent+SOGo.m \
+ iCalEventChanges+SOGo.m \
+ iCalPerson+SOGo.m \
\
SOGoCalendarComponent.m \
SOGoAppointmentObject.m \
SOGoAptMailUpdate.m \
SOGoAptMailRemoval.m \
SOGoAptMailDeletion.m \
+ SOGoAptMailICalReply.m \
Appointments_RESOURCE_FILES += \
Version \
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 \
@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
#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 */
--- /dev/null
+<#IsSubject>Re: rendez-vous le <#AptStartDate/> Ã <#AptStartTime/></#IsSubject>
+<#IsBody>
+<#Attendee/> has <#HasAccepted>accepted</#HasAccepted><#HasDeclined>declined</#HasDeclined> your invitation.
+</#IsBody>
--- /dev/null
+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;
+}
--- /dev/null
+<#IsSubject>Re: rendez-vous le <#AptStartDate/> Ã <#AptStartTime/></#IsSubject>
+<#IsBody>
+<#Attendee/> a <#HasAccepted>accepté</#HasAccepted><#HasDeclined>décliné</#HasDeclined> votre invitation.
+</#IsBody>
--- /dev/null
+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;
+}
--- /dev/null
+<#IsSubject>Re: rendez-vous le <#AptStartDate/> Ã <#AptStartTime/></#IsSubject>
+<#IsBody>
+<#Attendee/> has <#HasAccepted>accepted</#HasAccepted><#HasDeclined>declined</#HasDeclined> your invitation.
+</#IsBody>
--- /dev/null
+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;
+}
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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
@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;
- (iCalPerson *) findParticipantWithUID: (NSString *) uid;
- (iCalPerson *) iCalPersonWithUID: (NSString *) uid;
-- (NSString *) getUIDForICalPerson: (iCalPerson *) person;
- (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons;
@end
#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;
}
}
-- (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";
[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]
// 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
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;
{
NSString *pageName;
iCalPerson *organizer;
- NSString *cn, *email, *sender, *iCalString;
+ NSString *email, *sender, *iCalString;
WOApplication *app;
unsigned i, count;
iCalPerson *attendee;
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
SOGoUser *user;
user = [SOGoUser userWithLogin: uid roles: nil];
- component = [self component: NO];
+ component = [self component: NO secure: NO];
return [component findParticipant: user];
}
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];
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];
@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__ */
#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";
}
#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 */
#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)
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
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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
#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>
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"];
cdtype = @"inline";
else
cdtype = @"attachment";
-
+
cd = [cdtype stringByAppendingString: @"; filename=\""];
cd = [cd stringByAppendingString: _name];
cd = [cd stringByAppendingString: @"\""];
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 */
- (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)
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";
if (classString)
klazz = NSClassFromString (classString);
else
- klazz = Nil;
+ klazz = [SOGoMailBodyPart class];
return klazz;
}
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;
[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;
NSString *IDField; /* the first part of a user DN */
NSString *CNField;
NSString *UIDField;
+ NSArray *mailFields;
NSString *bindFields;
NGLdapConnection *ldapConnection;
IDField: (NSString *) newIDField
CNField: (NSString *) newCNField
UIDField: (NSString *) newUIDField
+ mailFields: (NSArray *) newMailFields
andBindFields: (NSString *) newBindFields;
- (BOOL) checkLogin: (NSString *) login
IDField = @"cn"; /* the first part of a user DN */
CNField = @"cn";
UIDField = @"uid";
+ mailFields = [NSArray arrayWithObject: @"mail"];
+ [mailFields retain];
bindFields = nil;
ldapConnection = nil;
[IDField release];
[CNField release];
[UIDField release];
+ [mailFields release];
[bindFields release];
[ldapConnection release];
[sourceID release];
[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;
IDField: (NSString *) newIDField
CNField: (NSString *) newCNField
UIDField: (NSString *) newUIDField
+ mailFields: (NSArray *) newMailFields
andBindFields: (NSString *) newBindFields
{
ASSIGN (baseDN, newBaseDN);
ASSIGN (CNField, newCNField);
if (UIDField)
ASSIGN (UIDField, newUIDField);
+ if (newMailFields)
+ ASSIGN (mailFields, newMailFields);
if (newBindFields)
ASSIGN (bindFields, newBindFields);
}
- (NSArray *) _searchAttributes
{
- NSArray *attrs;
-
if (!searchAttributes)
{
searchAttributes = [NSMutableArray new];
[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;
}
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;
if (!value)
value = @"";
[contactEntry setObject: value forKey: @"c_cn"];
+ [self _fillEmailsOfEntry: ldapEntry intoContactEntry: contactEntry];
return contactEntry;
}
#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSValue.h>
+#import "NSArray+Utilities.h"
#import "LDAPSource.h"
#import "LDAPUserManager.h"
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"];
}
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;
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];
}
/* 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__ */
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];
deleteObject = [self lookupName: currentID
inContext: context acquire: NO];
if (![deleteObject isKindOfClass: [NSException class]])
- [deleteObject delete];
+ {
+ if ([deleteObject respondsToSelector: @selector (prepareDelete)])
+ [deleteObject prepareDelete];
+ [deleteObject delete];
+ }
}
}
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;
NSTimeZone *userTimeZone;
SOGoDateFormatter *dateFormatter;
NSMutableArray *mailAccounts;
+ SOGoUserFolder *homeFolder;
}
+ (NSString *) language;
/* folders */
-- (id) homeFolderInContext: (id) _ctx;
+- (SOGoUserFolder *) homeFolderInContext: (id) context;
- (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context;
- (SOGoAppointmentFolder *)
language = nil;
currentPassword = nil;
dateFormatter = nil;
+ homeFolder = nil;
}
return self;
[allEmails release];
[language release];
[dateFormatter release];
+ [homeFolder release];
[super dealloc];
}
messageForwarding
= [[self userDefaults] stringForKey: @"MessageForwarding"];
if (![messageForwarding length])
- messageForwarding = @"attached";
+ messageForwarding = @"inline";
return messageForwarding;
}
// 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
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 \
+++ /dev/null
-/*
- 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 */
NSString *toolbar;
id item;
BOOL isPopup;
+ NSMutableArray *additionalCSSFiles;
NSMutableArray *additionalJSFiles;
}
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;
+++ /dev/null
-/*
- 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 */
+++ /dev/null
-/*
- 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 */
+++ /dev/null
-/*
- 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__ */
+++ /dev/null
-/*
- 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:@" "];
-
- 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:@" "];
- }
-
- [_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:@" "];
- }
- [_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 */
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
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";
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";
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";
#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
{
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"];
#import "UIxMailPartViewer.h"
-@class SOGoDateFormatter;
@class iCalEvent;
@class iCalCalendar;
+@class SOGoAppointmentObject;
+@class SOGoDateFormatter;
+
@interface UIxMailPartICalViewer : UIxMailPartViewer
{
iCalCalendar *inCalendar;
id attendee;
SOGoDateFormatter *dateFormatter;
id item;
- id storedEventObject;
+ SOGoAppointmentObject *storedEventObject;
iCalEvent *storedEvent;
}
#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 */
#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;
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 */
}
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];
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];
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";
- }; */
+ };
};
};
};
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 = "#";
- (void) setBcc: (NSArray *) _bcc;
- (NSArray *) bcc;
-- (NSArray *) properlySplitAddresses: (NSArray *) _addresses;
-
- (void) getAddressesFromFormValues: (NSDictionary *) _dict;
- (NSString *) getIndexFromIdentifier: (NSString *) _identifier;
- (void) setTo: (NSArray *) _to
{
- ASSIGN (to, [self properlySplitAddresses: _to]);
+ ASSIGN (to, _to);
}
- (NSArray *) to
- (void) setCc: (NSArray *) _cc
{
- ASSIGN (cc, [self properlySplitAddresses: _cc]);
+ ASSIGN (cc, _cc);
}
- (NSArray *) cc
- (void) setBcc: (NSArray *) _bcc
{
- ASSIGN (bcc, [self properlySplitAddresses: _bcc]);
+ ASSIGN (bcc, _bcc);
}
- (NSArray *) bcc
NSMutableArray *ma;
ma = [NSMutableArray arrayWithCapacity:3];
- if ([to isNotNull])
+ if ([to isNotNull] && [to count] > 0)
[ma addObject: to];
if ([cc isNotNull])
[ma addObject: cc];
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
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
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;
#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>
componentOwner = @"";
organizer = nil;
attendeesNames = nil;
+ attendeesUIDs = nil;
attendeesEmails = nil;
calendarList = nil;
}
[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];
}
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];
}
</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>
>
<!-- 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;">
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 />
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"
+++ /dev/null
-<?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>
<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>
<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>
+++ /dev/null
-<?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
<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"
><var:string
value="buttonLabel"
/></span
- ></span
- ></a
+ ></button
></var:if
><var:if condition="isButtonEnabled"
const:negate="YES"
}
}
-addEvent(window, 'load', initContacts);
+FastInit.addOnLoad(initContacts);
bottom: 0px;
overflow: auto; }
+DIV.mailer_mailcontent TABLE
+{
+ table-layout: auto;
+}
+
TD.mailer_fieldname
{
white-space: nowrap;
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;
var buttons = { "iCalendarAccept": "accept",
"iCalendarDecline": "decline",
"iCalendarTentative": "tentative",
+ "iCalendarUpdateUserStatus": "updateUserStatus",
"iCalendarAddToCalendar": "addToCalendar",
"iCalendarDeleteFromCalendar": "deleteFromCalendar" };
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) {
}
function initMailer(event) {
- if (!document.body.hasClassName("popup")) {
+ if (!$(document.body).hasClassName("popup")) {
// initDnd();
initMailboxTree();
initMessageCheckTimer();
return menus;
}
-addEvent(window, 'load', initMailer);
+FastInit.addOnLoad(initMailer);
function Mailbox(type, name) {
this.type = type;
}
}
-addEvent(window, 'load', initLogin);
+FastInit.addOnLoad(initLogin);
{ background-color: #deebf7;
border: 1px solid #deebf7; }
+#dateSelector TD.dayOfToday._selected
+{ background-color: #4b6983; }
+
#dateSelector TD._selected A
{ color: #fff; }
-
TABLE#eventsList
{ width: 100%; }
._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;
}
function changeDateSelectorDisplay(day, keepCurrentDay) {
- var url = ApplicationBaseURL + "/dateselector";
+ var url = ApplicationBaseURL + "dateselector";
if (day)
url += "?day=" + day;
}
function changeCalendarDisplay(data, newView) {
- var url = ApplicationBaseURL + "/" + ((newView) ? newView : currentView);
+ var url = ApplicationBaseURL + ((newView) ? newView : currentView);
selectedCalendarCell = null;
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);
}
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;
event.returnValue = false;
}
-function refreshCalendarEvents() {
+function refreshCalendarEvents(scrollEvent) {
var todayDate = new Date();
var sd;
var ed;
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) {
http.callbackData["startDate"],
http.callbackData["endDate"]);
}
+ if (http.callbackData["scrollEvent"])
+ scrollDayView(http.callbackData["scrollEvent"]);
}
else
log("AJAX error when refreshing calendar events");
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) {
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;
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++) {
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++) {
function onEventClick(event) {
changeCalendarDisplay( { "day": this.day,
- "scrollEvent": this.getAttribute("id") } );
+ "scrollEvent": this.getAttribute("id") } );
changeDateSelectorDisplay(this.day);
-
+
return onRowClick(event);
}
}
}
-addEvent(window, 'load', initCalendars);
+FastInit.addOnLoad(initCalendars);
this.userRightsWidth = window.opener.getUsersRightsWindowWidth();
}
-addEvent(window, 'load', onAclLoadHandler);
+FastInit.addOnLoad(onAclLoadHandler);
initTimeWidgets(widgets);
}
-addEvent(window, 'load', onAppointmentEditorLoad);
+FastInit.addOnLoad(onAppointmentEditorLoad);
var resultsDiv;
-var searchField;
var running = false;
var address;
var delay = 500;
var requestField;
+var searchField;
var awaitingFreeBusyRequests = new Array();
var additionalDays = 2;
var dayEndHour = 18;
var attendeesNames;
+var attendeesUIDs;
var attendeesEmails;
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) {
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;
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())
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];
}
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
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) {
}
}
-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");
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();
prepareTableHeaders();
prepareTableRows();
redisplayFreeBusyZone();
- resetAttendeesValue();
resetAllFreeBusys();
}
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);
Event.observe($("cancelButton"), "click", onCancelClick);
}
-addEvent(window, "load", initACLButtons);
+FastInit.addOnLoad(initACLButtons);
+#attendeesLabel
+{ display: none; }
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);
false);
}
-addEvent(window, 'load', onComponentEditorLoad);
+FastInit.addOnLoad(onComponentEditorLoad);
$("givenName").onkeyup = onFnNewValue;
}
-addEvent(window, 'load', initEditorForm);
+FastInit.addOnLoad(initEditorForm);
Event.observe($("addButton"), "click", onConfirmFolderSelection);
}
-addEvent(window, 'load', initUserFoldersWindow);
+FastInit.addOnLoad(initUserFoldersWindow);
Event.observe(button, "click", onCancelClick);
}
-addEvent(window, "load", initACLButtons);
+FastInit.addOnLoad(initACLButtons);
right: 0em;
bottom: 0em;
top: 13em;
- width: 100%; }
+ width: 99%; }
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;
Event.stopObserving(window, "beforeunload", onMailEditorClose);
}
-addEvent(window, 'load', initMailEditor);
+FastInit.addOnLoad(initMailEditor);
resizeMailContent();
}
-addEvent(window, 'load', initPopupMailer);
+FastInit.addOnLoad(initPopupMailer);
var count;
count = this.getAddressCount();
- if (count > 0)
- return true;
- return false;
+
+ return (count > 0)
}
/* addressbook helpers */
$("cancelButton").addEventListener("click", onCancelClick, false);
}
-addEvent(window, "load", initACLButtons);
+FastInit.addOnLoad(initACLButtons);
initializeStatusLine();
}
-addEvent(window, 'load', onTaskEditorLoad);
+FastInit.addOnLoad(onTaskEditorLoad);
margin-top: 1.5em;
margin-right: 1em; }
-SPAN#toolbar IMG#progressIndicator
+DIV#toolbar IMG#progressIndicator
{ margin-top: 0.75em; }
DIV#pageContent
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;
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;
border-bottom: 1px solid #828482;
border-right: 1px solid #828482; }
+BUTTON.toolbarButton:active,
SPAN.toolbarButton:active
{ color: -moz-buttonhovertext;
border-top: 1px solid #828482;
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 */
}
safetyNet.parentNode.removeChild(safetyNet);
}
-document.observe("dom:loaded", onLoadHandler);
+FastInit.addOnLoad(onLoadHandler);
function parent$(element) {
return this.opener.document.getElementById(element);