From: wolfgang Date: Sun, 18 Nov 2007 10:17:54 +0000 (+0000) Subject: git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1265 d1b88da0-ebda-0310... X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8706636b0bc9b38b0e1fcfd39089daaafc76e7ca;p=scalable-opengroupware.org git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1265 d1b88da0-ebda-0310-925b-ed51d893ca5b --- diff --git a/ChangeLog b/ChangeLog index f77b4a32..1f711462 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,169 @@ +2007-11-18 Wolfgang Sourdeau + + * 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 + + * 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 + + * 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 * SoObjects/SOGo/iCalEntityObject+Utilities.m ([iCalEntityObject diff --git a/NEWS b/NEWS index 768a45e0..58483631 100644 --- a/NEWS +++ b/NEWS @@ -18,7 +18,7 @@ - improved support for multiple selection in tables and lists; - improved IE7 and Safari support: attendees selector, email file attachments; - updated PrototypeJS to version 1.6.0; -- improved address completion in attendees selector; +- improved address completion and freebusy timeline in attendees selector; - changed look of message composition window to Thunderbird 2.0; - countless bugfixes; diff --git a/SOPE/NGCards/CardElement.h b/SOPE/NGCards/CardElement.h index ebd89f1e..de633d07 100644 --- a/SOPE/NGCards/CardElement.h +++ b/SOPE/NGCards/CardElement.h @@ -33,7 +33,7 @@ @class CardGroup; -@interface CardElement : NSObject +@interface CardElement : NSObject { NSString *tag; NSMutableArray *values; @@ -56,7 +56,7 @@ values: (NSArray *) someValues; - (void) setParent: (CardGroup *) aParent; -- (CardGroup *) parent; +- (id) parent; - (void) setTag: (NSString *) aTag; diff --git a/SOPE/NGCards/CardElement.m b/SOPE/NGCards/CardElement.m index cc369755..9767359b 100644 --- a/SOPE/NGCards/CardElement.m +++ b/SOPE/NGCards/CardElement.m @@ -116,7 +116,7 @@ parent = aParent; } -- (CardGroup *) parent +- (id) parent { return parent; } @@ -524,4 +524,19 @@ 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 diff --git a/SOPE/NGCards/CardGroup.h b/SOPE/NGCards/CardGroup.h index 6800f076..1dfb6c7e 100644 --- a/SOPE/NGCards/CardGroup.h +++ b/SOPE/NGCards/CardGroup.h @@ -29,7 +29,7 @@ @class NSMutableArray; @class NSString; -@interface CardGroup : CardElement +@interface CardGroup : CardElement { NSMutableArray *children; } diff --git a/SOPE/NGCards/CardGroup.m b/SOPE/NGCards/CardGroup.m index fc45e695..33063496 100644 --- a/SOPE/NGCards/CardGroup.m +++ b/SOPE/NGCards/CardGroup.m @@ -159,29 +159,32 @@ static NGCardsSaxHandler *sax = nil; NSString *childTag; CardElement *newChild; - childTag = [aChild tag]; - newChild = nil; - mappedClass = [self classForTag: [childTag uppercaseString]]; - if (mappedClass) + if (aChild) { - if (![aChild isKindOfClass: mappedClass]) - { - NSLog (@"warning: new child to entity '%@': '%@' converted to '%@'", - tag, childTag, NSStringFromClass(mappedClass)); - if ([aChild isKindOfClass: [CardGroup class]]) - newChild = [(CardGroup *) aChild groupWithClass: mappedClass]; - else - newChild = [aChild elementWithClass: mappedClass]; - } + childTag = [aChild tag]; + newChild = nil; + mappedClass = [self classForTag: [childTag uppercaseString]]; + if (mappedClass) + { + if (![aChild isKindOfClass: mappedClass]) + { + NSLog (@"warning: new child to entity '%@': '%@' converted to '%@'", + tag, childTag, NSStringFromClass(mappedClass)); + if ([aChild isKindOfClass: [CardGroup class]]) + newChild = [(CardGroup *) aChild groupWithClass: mappedClass]; + else + newChild = [aChild elementWithClass: mappedClass]; + } + } + // else + // NSLog (@"warning: no mapped class for tag '%@'", + // childTag); + + if (!newChild) + newChild = aChild; + [children addObject: newChild]; + [newChild setParent: self]; } -// else -// NSLog (@"warning: no mapped class for tag '%@'", -// childTag); - - if (!newChild) - newChild = aChild; - [children addObject: newChild]; - [newChild setParent: self]; } - (CardElement *) uniqueChildWithTag: (NSString *) aTag @@ -214,17 +217,20 @@ static NGCardsSaxHandler *sax = nil; NSString *childTag; NSEnumerator *existing; - childTag = [aChild tag]; - existing = [[self childrenWithTag: childTag] objectEnumerator]; - - currentChild = [existing nextObject]; - while (currentChild) + if (aChild) { - [children removeObject: currentChild]; + childTag = [aChild tag]; + existing = [[self childrenWithTag: childTag] objectEnumerator]; + currentChild = [existing nextObject]; - } + while (currentChild) + { + [children removeObject: currentChild]; + currentChild = [existing nextObject]; + } - [self addChild: aChild]; + [self addChild: aChild]; + } } - (CardElement *) firstChildWithTag: (NSString *) aTag; @@ -260,12 +266,8 @@ static NGCardsSaxHandler *sax = nil; NSEnumerator *newChildren; newChildren = [someChildren objectEnumerator]; - currentChild = [newChildren nextObject]; - while (currentChild) - { - [self addChild: currentChild]; - currentChild = [newChildren nextObject]; - } + while ((currentChild = [newChildren nextObject])) + [self addChild: currentChild]; } - (NSArray *) children @@ -343,9 +345,14 @@ static NGCardsSaxHandler *sax = nil; - (void) setChildrenAsCopy: (NSMutableArray *) someChildren { - [children release]; - children = someChildren; - [children retain]; + NSEnumerator *list; + CardElement *currentChild; + + ASSIGN (children, someChildren); + + list = [children objectEnumerator]; + while ((currentChild = [list nextObject])) + [currentChild setParent: self]; } - (void) addChildWithTag: (NSString *) aTag @@ -358,12 +365,8 @@ static NGCardsSaxHandler *sax = nil; newChild = [CardElement simpleElementWithTag: aTag value: aValue]; types = [someTypes objectEnumerator]; - type = [types nextObject]; - while (type) - { - [newChild addType: type]; - type = [types nextObject]; - } + while ((type = [types nextObject])) + [newChild addType: type]; [self addChild: newChild]; } @@ -400,6 +403,8 @@ static NGCardsSaxHandler *sax = nil; [children replaceObjectAtIndex: index withObject: newElement]; } +/* NSCopying */ + - (id) copyWithZone: (NSZone *) aZone { CardGroup *new; @@ -410,4 +415,16 @@ static NGCardsSaxHandler *sax = nil; return new; } +/* NSMutableCopying */ + +- (id) mutableCopyWithZone: (NSZone *) aZone +{ + CardGroup *new; + + new = [super mutableCopyWithZone: aZone]; + [new setChildrenAsCopy: [children mutableCopyWithZone: aZone]]; + + return new; +} + @end diff --git a/SOPE/NGCards/ChangeLog b/SOPE/NGCards/ChangeLog index ec0c0c54..55ec998f 100644 --- a/SOPE/NGCards/ChangeLog +++ b/SOPE/NGCards/ChangeLog @@ -1,3 +1,21 @@ +2007-11-18 Wolfgang Sourdeau + + * 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 * iCalTimeZonePeriod.m ([iCalTimeZonePeriod diff --git a/SOPE/NGCards/iCalCalendar.m b/SOPE/NGCards/iCalCalendar.m index 060509ee..b478b7f9 100644 --- a/SOPE/NGCards/iCalCalendar.m +++ b/SOPE/NGCards/iCalCalendar.m @@ -101,7 +101,8 @@ - (void) setMethod: (NSString *) _value { - [[self uniqueChildWithTag: @"method"] setValue: 0 to: _value]; + [[self uniqueChildWithTag: @"method"] setValue: 0 + to: [_value uppercaseString]]; } - (NSString *) method diff --git a/SOPE/NGCards/iCalEventChanges.m b/SOPE/NGCards/iCalEventChanges.m index 4105c180..045e8ca0 100644 --- a/SOPE/NGCards/iCalEventChanges.m +++ b/SOPE/NGCards/iCalEventChanges.m @@ -41,13 +41,13 @@ - (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]; } @@ -55,13 +55,13 @@ } - (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]; } @@ -86,13 +86,13 @@ 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++) { @@ -110,7 +110,7 @@ } } if(!found) - [self->insertedAttendees addObject:tp]; + [insertedAttendees addObject:tp]; } } @@ -119,31 +119,31 @@ - (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 { @@ -169,27 +169,27 @@ } - (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 */ @@ -200,10 +200,10 @@ 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; diff --git a/SOPE/NGCards/iCalPerson.m b/SOPE/NGCards/iCalPerson.m index 68e3deef..11b76507 100644 --- a/SOPE/NGCards/iCalPerson.m +++ b/SOPE/NGCards/iCalPerson.m @@ -85,7 +85,7 @@ - (NSString *) rsvp { - return [self value: 0 ofAttribute: @"rsvp"]; + return [[self value: 0 ofAttribute: @"rsvp"] lowercaseString]; } // - (void)setXuid:(NSString *)_s { diff --git a/SOPE/sope-patchset-r1546.diff b/SOPE/sope-patchset-r1546.diff index e26b5934..216e640f 100644 --- a/SOPE/sope-patchset-r1546.diff +++ b/SOPE/sope-patchset-r1546.diff @@ -443,6 +443,72 @@ Index: sope-mime/NGMail/NGSmtpClient.m [self->connection flush]; reply = [self receiveReply]; +Index: sope-mime/NGMail/NGMimeMessageGenerator.m +=================================================================== +--- sope-mime/NGMail/NGMimeMessageGenerator.m (révision 1546) ++++ sope-mime/NGMail/NGMimeMessageGenerator.m (copie de travail) +@@ -86,37 +86,40 @@ + char *des = NULL; + unsigned int cnt; + BOOL doEnc; +- NSString *str; ++// NSString *str; + + // TODO: this s***s big time! ++// NSLog (@"class: '%@'", NSStringFromClass ([_data class])); ++// #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY ++// str = [[NSString alloc] initWithData:_data ++// encoding:NSISOLatin1StringEncoding]; ++// str = [str autorelease]; ++ ++// #else ++// str = [[NSString alloc] initWithData:_data ++// encoding:NSISOLatin9StringEncoding]; ++// #endif ++// bytes = [str cString]; ++// length = [str cStringLength]; + +-#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY +- str = [[NSString alloc] initWithData:_data +- encoding:NSISOLatin1StringEncoding]; +-#else +- str = [[NSString alloc] initWithData:_data +- encoding:NSISOLatin9StringEncoding]; +-#endif +- str = [str autorelease]; +- +- bytes = [str cString]; +- length = [str cStringLength]; +- ++ bytes = [_data bytes]; ++ length = [_data length]; ++ + /* check whether we need to encode */ +- +- for (cnt = 0, doEnc = NO; cnt < length; cnt++) { +- if ((unsigned char)bytes[cnt] > 127) { ++ cnt = 0; ++ doEnc = NO; ++ while (!doEnc && cnt < length) ++ if ((unsigned char)bytes[cnt] > 127) + doEnc = YES; +- break; +- } +- } +- ++ else ++ cnt++; ++ + if (!doEnc) + return _data; + + /* encode quoted printable */ + { +- char iso[] = "=?iso-8859-15?q?"; ++ char iso[] = "=?utf-8?q?"; + unsigned isoLen = 16; + char isoEnd[] = "?="; + unsigned isoEndLen = 2; Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m =================================================================== --- sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m (révision 1546) @@ -557,6 +623,15 @@ Index: sope-mime/NGMime/NGMimePartParser.m =================================================================== --- sope-mime/NGMime/NGMimePartParser.m (révision 1546) +++ sope-mime/NGMime/NGMimePartParser.m (copie de travail) +@@ -227,7 +227,7 @@ + } + + + (NSStringEncoding)defaultHeaderFieldEncoding { +- return NSISOLatin1StringEncoding; ++ return NSUTF8StringEncoding; + } + + - (id)valueOfHeaderField:(NSString *)_name data:(id)_data { @@ -1091,7 +1091,10 @@ id bodyParser = nil; @@ -569,6 +644,142 @@ Index: sope-mime/NGMime/NGMimePartParser.m contentType = ([ctype isKindOfClass:[NGMimeType class]]) ? ctype : [NGMimeType mimeType:[ctype stringValue]]; +Index: sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m +=================================================================== +--- sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m (révision 1546) ++++ sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m (copie de travail) +@@ -49,80 +49,70 @@ + + // TODO: move the stuff below to some NSString or NSData category? + +- data = [NSMutableData dataWithCapacity:64]; ++ data = [NSMutableData dataWithCapacity: 64]; + tmp = [field type]; + [data appendBytes:[tmp cString] length:[tmp length]]; + tmp = [field filename]; + if (tmp != nil) { + [data appendBytes:"; " length:2]; + [data appendBytes:"filename=\"" length:10]; +- { +- unsigned char *ctmp; +- int cnt, len; +- BOOL doEnc; +- +- // TODO: unicode? +- len = [tmp cStringLength]; +- ctmp = malloc(len + 3); +- [tmp getCString:(char *)ctmp]; ctmp[len] = '\0'; +- cnt = 0; +- doEnc = NO; +- while (cnt < len) { +- if ((unsigned char)ctmp[cnt] > 127) { +- doEnc = YES; +- break; +- } +- cnt++; ++ ++ NSData *d; ++ unsigned char* bytes; ++ unsigned length; ++ int cnt; ++ BOOL doEnc; ++ ++ //d = [tmp dataUsingEncoding: NSUTF8StringEncoding]; ++ //bytes = [d bytes]; ++ //length = [d length]; ++ bytes = [tmp cStringUsingEncoding: NSUTF8StringEncoding]; ++ length = strlen(bytes); ++ ++ cnt = 0; ++ doEnc = NO; ++ while (cnt < length) { ++ if ((unsigned char)bytes[cnt] > 127) { ++ doEnc = YES; ++ break; + } +- if (doEnc) { +- char iso[] = "=?iso-8859-15?q?"; +- unsigned isoLen = 16; +- char isoEnd[] = "?="; +- unsigned isoEndLen = 2; +- unsigned desLen; +- char *des; +- +- if (ctmp) free(ctmp); +- { +- NSData *data; ++ cnt++; ++ } + +-#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY +- data = [tmp dataUsingEncoding:NSISOLatin1StringEncoding]; +-#else +- data = [tmp dataUsingEncoding:NSISOLatin9StringEncoding]; +-#endif +- +- len = [data length]; +- ctmp = malloc(len+1); +- [data getBytes:ctmp]; ctmp[len] = '\0'; +- } +- +- desLen = len * 3 + 20; +- des = calloc(desLen + 10, sizeof(char)); +- +- memcpy(des, ctmp, cnt); +- memcpy(des + cnt, iso, isoLen); +- desLen = +- NGEncodeQuotedPrintableMime((unsigned char *)ctmp + cnt, len - cnt, +- (unsigned char *)des + cnt + isoLen, +- desLen - cnt - isoLen); +- if ((int)desLen != -1) { +- memcpy(des + cnt + isoLen + desLen, isoEnd, isoEndLen); +- [data appendBytes:des length:(cnt + isoLen + desLen + isoEndLen)]; +- } +- else { ++ if (doEnc) ++ { ++ char iso[] = "=?utf-8?q?"; ++ unsigned isoLen = 10; ++ char isoEnd[] = "?="; ++ unsigned isoEndLen = 2; ++ int desLen; ++ char *des; ++ ++ desLen = length * 3 + 20; ++ ++ des = calloc(desLen + 2, sizeof(char)); ++ ++ memcpy(des, iso, isoLen); ++ desLen = NGEncodeQuotedPrintableMime((unsigned char *)bytes, length, ++ (unsigned char *)(des + isoLen), ++ desLen - isoLen); ++ if (desLen != -1) { ++ memcpy(des + isoLen + desLen, isoEnd, isoEndLen); ++ [data appendBytes:des length:(isoLen + desLen + isoEndLen)]; ++ } ++ else { + [self logWithFormat:@"WARNING(%s:%i): An error occour during " + @"quoted-printable decoding", + __PRETTY_FUNCTION__, __LINE__]; +- } +- if (des) free(des); ++ if (des != NULL) free(des); ++ } + } +- else { +- [data appendBytes:ctmp length:len]; ++ else ++ { ++ [data appendBytes:[tmp cString] length:[tmp length]]; + } +- } +- // [data appendBytes:[tmp cString] length:[tmp length]]; +- [data appendBytes:"\"" length:1]; ++ ++ [data appendBytes:"\"" length:1]; + } + return data; + } Index: sope-core/NGExtensions/FdExt.subproj/NSString+Encoding.m =================================================================== --- sope-core/NGExtensions/FdExt.subproj/NSString+Encoding.m (révision 1546) diff --git a/SoObjects/Appointments/GNUmakefile b/SoObjects/Appointments/GNUmakefile index 99a7b165..8924bd4b 100644 --- a/SoObjects/Appointments/GNUmakefile +++ b/SoObjects/Appointments/GNUmakefile @@ -10,6 +10,9 @@ Appointments_OBJC_FILES = \ Product.m \ NSArray+Appointments.m \ iCalEntityObject+SOGo.m \ + iCalEvent+SOGo.m \ + iCalEventChanges+SOGo.m \ + iCalPerson+SOGo.m \ \ SOGoCalendarComponent.m \ SOGoAppointmentObject.m \ @@ -24,6 +27,7 @@ Appointments_OBJC_FILES = \ SOGoAptMailUpdate.m \ SOGoAptMailRemoval.m \ SOGoAptMailDeletion.m \ + SOGoAptMailICalReply.m \ Appointments_RESOURCE_FILES += \ Version \ @@ -31,14 +35,17 @@ Appointments_RESOURCE_FILES += \ Appointments_COMPONENTS += \ SOGoAptMailEnglishInvitation.wo \ + SOGoAptMailEnglishICalReply.wo \ SOGoAptMailEnglishUpdate.wo \ SOGoAptMailEnglishRemoval.wo \ SOGoAptMailEnglishDeletion.wo \ SOGoAptMailFrenchInvitation.wo \ + SOGoAptMailFrenchICalReply.wo \ SOGoAptMailFrenchUpdate.wo \ SOGoAptMailFrenchRemoval.wo \ SOGoAptMailFrenchDeletion.wo \ SOGoAptMailGermanInvitation.wo \ + SOGoAptMailGermanICalReply.wo \ SOGoAptMailGermanUpdate.wo \ SOGoAptMailGermanRemoval.wo \ SOGoAptMailGermanDeletion.wo \ diff --git a/SoObjects/Appointments/SOGoAppointmentObject.h b/SoObjects/Appointments/SOGoAppointmentObject.h index 8262970f..7b3b2379 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.h +++ b/SoObjects/Appointments/SOGoAppointmentObject.h @@ -46,12 +46,14 @@ @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 diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 12f97b38..58de668a 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -22,7 +22,7 @@ #import #import -#import +#import #import #import #import @@ -30,14 +30,19 @@ #import #import +#import #import #import #import #import +#import #import #import "NSArray+Appointments.h" #import "SOGoAppointmentFolder.h" +#import "iCalEventChanges+SOGo.h" +#import "iCalEntityObject+SOGo.h" +#import "iCalPerson+SOGo.h" #import "SOGoAppointmentObject.h" @@ -48,387 +53,325 @@ return @"vevent"; } -/* iCal handling */ -- (NSArray *) attendeeUIDsFromAppointment: (iCalEvent *) _apt +- (SOGoAppointmentObject *) _lookupEvent: (NSString *) eventUID + forUID: (NSString *) uid { - LDAPUserManager *um; - NSMutableArray *uids; - NSArray *attendees; - unsigned i, count; - NSString *email, *uid; - - if (![_apt isNotNull]) - return nil; - - if ((attendees = [_apt attendees]) == nil) - return nil; - count = [attendees count]; - uids = [NSMutableArray arrayWithCapacity:count + 1]; - - um = [LDAPUserManager sharedUserManager]; - - /* add organizer */ - - email = [[_apt organizer] rfc822Email]; - if ([email isNotNull]) { - uid = [um getUIDForEmail: email]; - if ([uid isNotNull]) { - [uids addObject:uid]; - } - else - [self logWithFormat:@"Note: got no uid for organizer: '%@'", email]; - } - - /* add attendees */ - - for (i = 0; i < count; i++) + SOGoAppointmentFolder *folder; + SOGoAppointmentObject *object; + NSString *possibleName; + + folder = [container lookupCalendarFolderForUID: uid]; + object = [folder lookupName: nameInContainer + inContext: context acquire: NO]; + if ([object isKindOfClass: [NSException class]]) { - iCalPerson *person; - - person = [attendees objectAtIndex:i]; - email = [person rfc822Email]; - if (![email isNotNull]) continue; - - uid = [um getUIDForEmail:email]; - if (![uid isNotNull]) { - [self logWithFormat:@"Note: got no uid for email: '%@'", email]; - continue; - } - if (![uids containsObject:uid]) - [uids addObject:uid]; + possibleName = [folder resourceNameForEventUID: eventUID]; + if (possibleName) + { + object = [folder lookupName: nameInContainer + inContext: context acquire: NO]; + if ([object isKindOfClass: [NSException class]]) + object = nil; + } } - return uids; -} + if (!object) + object = [SOGoAppointmentObject objectWithName: nameInContainer + inContainer: folder]; -/* store in all the other folders */ + return object; +} -- (NSException *) saveContentString: (NSString *) _iCal - inUIDs: (NSArray *) _uids +- (void) _addOrUpdateEvent: (iCalEvent *) event + forUID: (NSString *) uid { - NSEnumerator *e; - id folder; - NSException *allErrors = nil; - NSException *error; - SOGoAppointmentObject *apt; - - e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context] - objectEnumerator]; - while ((folder = [e nextObject])) + SOGoAppointmentObject *object; + NSString *iCalString, *userLogin; + + userLogin = [[context activeUser] login]; + if (![uid isEqualToString: userLogin]) { - apt = [SOGoAppointmentObject objectWithName: nameInContainer - inContainer: folder]; - error = [apt primarySaveContentString:_iCal]; - if (error) - { - [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder]; - // TODO: make compound - allErrors = error; - } + object = [self _lookupEvent: [event uid] forUID: uid]; + iCalString = [[event parent] versitString]; + [object saveContentString: iCalString]; } - - return allErrors; } -- (NSException *) deleteInUIDs: (NSArray *) _uids +- (void) _removeEventFromUID: (NSString *) uid { - NSEnumerator *e; - id folder; - NSException *allErrors = nil; - NSException *error; - SOGoAppointmentObject *apt; - - e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context] - objectEnumerator]; - while ((folder = [e nextObject])) + SOGoAppointmentFolder *folder; + SOGoAppointmentObject *object; + NSString *userLogin; + + userLogin = [[context activeUser] login]; + if (![uid isEqualToString: userLogin]) { - apt = [folder lookupName: [self nameInContainer] - inContext: context - acquire:NO]; - if ([apt isKindOfClass: [NSException class]]) { - [self logWithFormat: @"%@", [(NSException *) apt reason]]; - continue; - } - - if ((error = [apt primaryDelete]) != nil) { - [self logWithFormat:@"Note: failed to delete in folder: %@", folder]; - // TODO: make compound - allErrors = error; - } + folder = [container lookupCalendarFolderForUID: uid]; + object = [folder lookupName: nameInContainer + inContext: context acquire: NO]; + if (![object isKindOfClass: [NSException class]]) + [object delete]; } - - return allErrors; } -/* "iCal multifolder saves" */ -- (BOOL) _aptIsStillRelevant: (iCalEvent *) appointment +- (void) _handleRemovedUsers: (NSArray *) attendees { - NSCalendarDate *now; - - now = [NSCalendarDate calendarDate]; + NSEnumerator *enumerator; + iCalPerson *currentAttendee; + NSString *currentUID; - return ([[appointment endDate] earlierDate: now] == now); + enumerator = [attendees objectEnumerator]; + while ((currentAttendee = [enumerator nextObject])) + { + currentUID = [currentAttendee uid]; + if (currentUID) + [self _removeEventFromUID: currentUID]; + } } -- (NSException *) saveContentString: (NSString *) _iCal - baseSequence: (int) _v +- (void) _requireResponseFromAttendees: (NSArray *) attendees { - /* - Note: we need to delete in all participants folders and send iMIP messages - for all external accounts. - - Steps: - - fetch stored content - - parse old content - - check if sequence matches (or if 0=ignore) - - extract old attendee list + organizer (make unique) - - parse new content (ensure that sequence is increased!) - - extract new attendee list + organizer (make unique) - - make a diff => new, same, removed - - write to new, same - - delete in removed folders - - send iMIP mail for all folders not found - */ - LDAPUserManager *um; - iCalEvent *oldApt, *newApt; - iCalEventChanges *changes; - iCalPerson *organizer; - NSString *oldContent, *uid; - NSArray *uids, *props; - NSMutableArray *attendees, *storeUIDs, *removedUIDs; - NSException *storeError, *delError; - BOOL updateForcesReconsider; - - if ([[context request] handledByDefaultHandler]) - { - updateForcesReconsider = NO; - - if ([_iCal length] == 0) - return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */ - reason: @"got no iCalendar content to store!"]; + NSEnumerator *enumerator; + iCalPerson *currentAttendee; - um = [LDAPUserManager sharedUserManager]; - - /* handle old content */ - - oldContent = [self contentAsString]; /* if nil, this is a new appointment */ - if ([oldContent length] == 0) - { - /* new appointment */ - [self debugWithFormat:@"saving new appointment: %@", _iCal]; - oldApt = nil; - } - else - oldApt = (iCalEvent *) [self component: NO]; - - /* compare sequence if requested */ - if (_v != 0) { - // TODO - } - - /* handle new content */ - - newApt = (iCalEvent *) [self component: NO]; - if (!newApt) - return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */ - reason: @"could not parse iCalendar content!"]; - - /* diff */ - - changes = [iCalEventChanges changesFromEvent: oldApt toEvent: newApt]; - uids = [self getUIDsForICalPersons: [changes deletedAttendees]]; - removedUIDs = [NSMutableArray arrayWithArray: uids]; - - uids = [self getUIDsForICalPersons: [newApt attendees]]; - storeUIDs = [NSMutableArray arrayWithArray: uids]; - props = [changes updatedProperties]; + enumerator = [attendees objectEnumerator]; + while ((currentAttendee = [enumerator nextObject])) + { + [currentAttendee setRsvp: @"TRUE"]; + [currentAttendee setParticipationStatus: iCalPersonPartStatNeedsAction]; + } +} - /* detect whether sequence has to be increased */ - if ([changes hasChanges]) - [newApt increaseSequence]; +- (void) _handleSequenceUpdateInEvent: (iCalEvent *) newEvent + ignoringAttendees: (NSArray *) attendees + fromOldEvent: (iCalEvent *) oldEvent +{ + NSMutableArray *updateAttendees, *updateUIDs; + NSEnumerator *enumerator; + iCalPerson *currentAttendee; + NSString *currentUID; - /* preserve organizer */ + updateAttendees = [NSMutableArray arrayWithArray: [newEvent attendees]]; + [updateAttendees removeObjectsInArray: attendees]; - organizer = [newApt organizer]; - uid = [self getUIDForICalPerson: organizer]; - if (!uid) - uid = [self ownerInContext: nil]; - if (uid) - { - [storeUIDs addObjectUniquely: uid]; - [removedUIDs removeObject: uid]; - } + updateUIDs = [NSMutableArray arrayWithCapacity: [updateAttendees count]]; + enumerator = [updateAttendees objectEnumerator]; + while ((currentAttendee = [enumerator nextObject])) + { + currentUID = [currentAttendee uid]; + if (currentUID) + [self _addOrUpdateEvent: newEvent + forUID: currentUID]; + } - /* organizer might have changed completely */ + [self sendEMailUsingTemplateNamed: @"Update" + forOldObject: oldEvent + andNewObject: [newEvent itipEntryWithMethod: @"request"] + toAttendees: updateAttendees]; +} - if (oldApt && ([props containsObject: @"organizer"])) - { - uid = [self getUIDForICalPerson: [oldApt organizer]]; - if (uid && ![storeUIDs containsObject: uid]) - [removedUIDs addObjectUniquely: uid]; - } +- (void) _handleAddedUsers: (NSArray *) attendees + fromEvent: (iCalEvent *) newEvent +{ + NSEnumerator *enumerator; + iCalPerson *currentAttendee; + NSString *currentUID; - [self debugWithFormat: @"UID ops:\n store: %@\n remove: %@", - storeUIDs, removedUIDs]; + enumerator = [attendees objectEnumerator]; + while ((currentAttendee = [enumerator nextObject])) + { + currentUID = [currentAttendee uid]; + if (currentUID) + [self _addOrUpdateEvent: newEvent + forUID: currentUID]; + } +} - /* if time did change, all participants have to re-decide ... - * ... exception from that rule: the organizer - */ +- (void) _handleUpdatedEvent: (iCalEvent *) newEvent + fromOldEvent: (iCalEvent *) oldEvent +{ + NSArray *attendees; + iCalEventChanges *changes; - if (oldApt - && ([props containsObject: @"startDate"] - || [props containsObject: @"endDate"] - || [props containsObject: @"duration"])) - { - NSArray *ps; - unsigned i, count; - - ps = [newApt attendees]; - count = [ps count]; - for (i = 0; i < count; i++) { - iCalPerson *p; - - p = [ps objectAtIndex:i]; - if (![p hasSameEmailAddress:organizer]) - [p setParticipationStatus:iCalPersonPartStatNeedsAction]; - } - _iCal = [[newApt parent] versitString]; - updateForcesReconsider = YES; - } + changes = [newEvent getChangesRelativeToEvent: oldEvent]; + attendees = [changes deletedAttendees]; + if ([attendees count]) + { + [self _handleRemovedUsers: attendees]; + [self sendEMailUsingTemplateNamed: @"Deletion" + forOldObject: oldEvent + andNewObject: [newEvent itipEntryWithMethod: @"cancel"] + toAttendees: attendees]; + } - /* perform storing */ + attendees = [changes insertedAttendees]; + if ([changes sequenceShouldBeIncreased]) + { + [newEvent increaseSequence]; + [self _requireResponseFromAttendees: [newEvent attendees]]; + [self _handleSequenceUpdateInEvent: newEvent + ignoringAttendees: attendees + fromOldEvent: oldEvent]; + } + else + [self _requireResponseFromAttendees: attendees]; - storeError = [self saveContentString: _iCal inUIDs: storeUIDs]; - delError = [self deleteInUIDs: removedUIDs]; + if ([attendees count]) + { + [self _handleAddedUsers: attendees fromEvent: newEvent]; + [self sendEMailUsingTemplateNamed: @"Invitation" + forOldObject: oldEvent + andNewObject: [newEvent itipEntryWithMethod: @"request"] + toAttendees: attendees]; + } +} - // TODO: make compound - if (storeError != nil) return storeError; - if (delError != nil) return delError; +- (void) saveComponent: (iCalEvent *) newEvent +{ + iCalEvent *oldEvent; + NSArray *attendees; - /* email notifications */ - if ([self sendEMailNotifications] - && [self _aptIsStillRelevant: newApt]) + [[newEvent parent] setMethod: @""]; + if ([newEvent userIsOrganizer: [context activeUser]]) + { + oldEvent = [self component: NO secure: NO]; + if (oldEvent) + [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent]; + else { - iCalEvent *requestApt; - - requestApt = [newApt copy]; - [(iCalCalendar *) [requestApt parent] setMethod: @"request"]; - attendees - = [NSMutableArray arrayWithArray: [changes insertedAttendees]]; - [attendees removePerson: organizer]; - [self sendEMailUsingTemplateNamed: @"Invitation" - forOldObject: nil - andNewObject: requestApt - toAttendees: attendees]; - [requestApt release]; - - if (updateForcesReconsider) - { - iCalEvent *updatedApt; - - updatedApt = [newApt copy]; - [(iCalCalendar *) [updatedApt parent] setMethod: @"request"]; - attendees = [NSMutableArray arrayWithArray:[newApt attendees]]; - [attendees removeObjectsInArray:[changes insertedAttendees]]; - [attendees removePerson:organizer]; - [self sendEMailUsingTemplateNamed: @"Update" - forOldObject: oldApt - andNewObject: updatedApt - toAttendees: attendees]; - [updatedApt release]; - } - - attendees - = [NSMutableArray arrayWithArray: [changes deletedAttendees]]; - [attendees removePerson: organizer]; + attendees = [newEvent attendeesWithoutUser: [context activeUser]]; if ([attendees count]) { - iCalEvent *cancelledApt; - - cancelledApt = [newApt copy]; - [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"]; - [self sendEMailUsingTemplateNamed: @"Removal" + [self _handleAddedUsers: attendees fromEvent: newEvent]; + [self sendEMailUsingTemplateNamed: @"Invitation" forOldObject: nil - andNewObject: cancelledApt + andNewObject: [newEvent itipEntryWithMethod: @"request"] toAttendees: attendees]; - [cancelledApt release]; } + + if (![[newEvent attendees] count]) + [[newEvent uniqueChildWithTag: @"organizer"] setValue: 0 + to: @""]; } } - else - [self primarySaveContentString: _iCal]; - return nil; + [super saveComponent: newEvent]; } -- (NSException *) deleteWithBaseSequence: (int)_v +- (NSException *) _updateAttendee: (iCalPerson *) attendee + forEventUID: (NSString *) eventUID + withSequence: (NSNumber *) sequence + forUID: (NSString *) uid { - /* - Note: We need to delete in all participants folders and send iMIP messages - for all external accounts. - Delete is basically identical to save with all attendees and the - organizer being deleted. - - Steps: - - fetch stored content - - parse old content - - check if sequence matches (or if 0=ignore) - - extract old attendee list + organizer (make unique) - - delete in removed folders - - send iMIP mail for all folders not found - */ - iCalEvent *apt; - NSMutableArray *attendees, *removedUIDs; + SOGoAppointmentObject *eventObject; + iCalEvent *event; + iCalPerson *otherAttendee; + NSString *iCalString; NSException *error; - if ([[context request] handledByDefaultHandler]) + error = nil; + + eventObject = [self _lookupEvent: eventUID forUID: uid]; + if (![eventObject isNew]) { - /* load existing content */ - apt = (iCalEvent *) [self component: NO]; - - /* compare sequence if requested */ + event = [eventObject component: NO secure: NO]; + if ([[event sequence] compare: sequence] + == NSOrderedSame) + { + otherAttendee = [event findParticipant: [context activeUser]]; + [otherAttendee setPartStat: [attendee partStat]]; + iCalString = [[event parent] versitString]; + error = [eventObject saveContentString: iCalString]; + } + } -// if (_v != 0) { -// // TODO -// } - - removedUIDs = [NSMutableArray arrayWithArray: - [self attendeeUIDsFromAppointment: apt]]; - if (![removedUIDs containsObject: owner]) - [removedUIDs addObject: owner]; + return error; +} + +- (NSException *) _handleAttendee: (iCalPerson *) attendee + statusChange: (NSString *) newStatus + inEvent: (iCalEvent *) event +{ + NSString *newContent, *currentStatus, *organizerUID; + NSException *ex; - if ([self sendEMailNotifications] - && [self _aptIsStillRelevant: apt]) + ex = nil; + + currentStatus = [attendee partStat]; + if ([currentStatus caseInsensitiveCompare: newStatus] + != NSOrderedSame) + { + [attendee setPartStat: newStatus]; + newContent = [[event parent] versitString]; + ex = [self saveContentString: newContent]; + if (!(ex || [event userIsOrganizer: [context activeUser]])) { - /* send notification email to attendees excluding organizer */ - attendees = [NSMutableArray arrayWithArray: [apt attendees]]; - [attendees removePerson: [apt organizer]]; - - /* flag appointment as being cancelled */ - [(iCalCalendar *) [apt parent] setMethod: @"cancel"]; - [apt increaseSequence]; - - /* remove all attendees to signal complete removal */ - [apt removeAllAttendees]; - - /* send notification email */ - [self sendEMailUsingTemplateNamed: @"Deletion" - forOldObject: nil - andNewObject: apt - toAttendees: attendees]; + if ([[attendee rsvp] isEqualToString: @"true"]) + [self sendResponseToOrganizer]; + organizerUID = [[event organizer] uid]; + if (organizerUID) + ex = [self _updateAttendee: attendee + forEventUID: [event uid] + withSequence: [event sequence] + forUID: organizerUID]; } + } + + return ex; +} - error = [self deleteInUIDs: removedUIDs]; +- (NSException *) changeParticipationStatus: (NSString *) _status +{ + iCalEvent *event; + iCalPerson *attendee; + NSException *ex; + + ex = nil; + + event = [self component: NO secure: NO]; + if (event) + { + attendee = [event findParticipant: [context activeUser]]; + if (attendee) + ex = [self _handleAttendee: attendee statusChange: _status + inEvent: event]; + else + ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */ + reason: @"user does not participate in this " + @"calendar event"]; } else - error = [self primaryDelete]; + ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */ + reason: @"unable to parse event record"]; - return error; + return ex; } -- (NSException *) saveContentString: (NSString *) _iCalString +- (void) prepareDelete { - return [self saveContentString: _iCalString baseSequence: 0]; + iCalEvent *event; + SOGoUser *currentUser; + NSArray *attendees; + + if ([[context request] handledByDefaultHandler]) + { + currentUser = [context activeUser]; + event = [self component: NO secure: NO]; + if ([event userIsOrganizer: currentUser]) + { + attendees = [event attendeesWithoutUser: currentUser]; + if ([attendees count]) + { + [self _handleRemovedUsers: attendees]; + [self sendEMailUsingTemplateNamed: @"Deletion" + forOldObject: nil + andNewObject: [event itipEntryWithMethod: @"cancel"] + toAttendees: attendees]; + } + } + else if ([event userIsParticipant: currentUser]) + [self changeParticipationStatus: @"DECLINED"]; + } } /* message type */ diff --git a/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.html b/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.html new file mode 100644 index 00000000..c861c271 --- /dev/null +++ b/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.html @@ -0,0 +1,4 @@ +<#IsSubject>Re: rendez-vous le <#AptStartDate/> à <#AptStartTime/> +<#IsBody> +<#Attendee/> has <#HasAccepted>accepted<#HasDeclined>declined your invitation. + diff --git a/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.wod b/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.wod new file mode 100644 index 00000000..71a95461 --- /dev/null +++ b/SoObjects/Appointments/SOGoAptMailEnglishICalReply.wo/SOGoAptMailEnglishICalReply.wod @@ -0,0 +1,33 @@ +AptStartDate: WOString { + value = startDate; + dateformat = "%d/%m/%y"; + escapeHTML = NO; +} + +AptStartTime: WOString { + value = startDate; + dateformat = "%H:%M"; + escapeHTML = NO; +} + +Attendee: WOString { + value = attendee.cnWithoutQuotes; + escapeHTML = NO; +} + +HasAccepted: WOConditional { + condition = hasAccepted; +} + +HasDeclined: WOConditional { + condition = hasDeclined; +} + +IsSubject: WOConditional { + condition = isSubject; +} + +IsBody: WOConditional { + condition = isSubject; + negate = YES; +} diff --git a/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.html b/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.html new file mode 100644 index 00000000..b12b4993 --- /dev/null +++ b/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.html @@ -0,0 +1,4 @@ +<#IsSubject>Re: rendez-vous le <#AptStartDate/> à <#AptStartTime/> +<#IsBody> +<#Attendee/> a <#HasAccepted>accepté<#HasDeclined>décliné votre invitation. + diff --git a/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.wod b/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.wod new file mode 100644 index 00000000..71a95461 --- /dev/null +++ b/SoObjects/Appointments/SOGoAptMailFrenchICalReply.wo/SOGoAptMailFrenchICalReply.wod @@ -0,0 +1,33 @@ +AptStartDate: WOString { + value = startDate; + dateformat = "%d/%m/%y"; + escapeHTML = NO; +} + +AptStartTime: WOString { + value = startDate; + dateformat = "%H:%M"; + escapeHTML = NO; +} + +Attendee: WOString { + value = attendee.cnWithoutQuotes; + escapeHTML = NO; +} + +HasAccepted: WOConditional { + condition = hasAccepted; +} + +HasDeclined: WOConditional { + condition = hasDeclined; +} + +IsSubject: WOConditional { + condition = isSubject; +} + +IsBody: WOConditional { + condition = isSubject; + negate = YES; +} diff --git a/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.html b/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.html new file mode 100644 index 00000000..c861c271 --- /dev/null +++ b/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.html @@ -0,0 +1,4 @@ +<#IsSubject>Re: rendez-vous le <#AptStartDate/> à <#AptStartTime/> +<#IsBody> +<#Attendee/> has <#HasAccepted>accepted<#HasDeclined>declined your invitation. + diff --git a/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.wod b/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.wod new file mode 100644 index 00000000..71a95461 --- /dev/null +++ b/SoObjects/Appointments/SOGoAptMailGermanICalReply.wo/SOGoAptMailGermanICalReply.wod @@ -0,0 +1,33 @@ +AptStartDate: WOString { + value = startDate; + dateformat = "%d/%m/%y"; + escapeHTML = NO; +} + +AptStartTime: WOString { + value = startDate; + dateformat = "%H:%M"; + escapeHTML = NO; +} + +Attendee: WOString { + value = attendee.cnWithoutQuotes; + escapeHTML = NO; +} + +HasAccepted: WOConditional { + condition = hasAccepted; +} + +HasDeclined: WOConditional { + condition = hasDeclined; +} + +IsSubject: WOConditional { + condition = isSubject; +} + +IsBody: WOConditional { + condition = isSubject; + negate = YES; +} diff --git a/SoObjects/Appointments/SOGoAptMailICalReply.h b/SoObjects/Appointments/SOGoAptMailICalReply.h new file mode 100644 index 00000000..3b530895 --- /dev/null +++ b/SoObjects/Appointments/SOGoAptMailICalReply.h @@ -0,0 +1,59 @@ +/* SOGoAptMailICalReply.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 + +@class NSString; +@class NSCalendarDate; + +@class iCalPerson; +@class iCalEntityObject; + +/* + * NOTE: We inherit from SoComponent in order to get the correct + * resourceManager required for this product + */ +@interface SOGoAptMailICalReply : SoComponent +{ + iCalEntityObject *apt; + iCalPerson *attendee; + NSString *homePageURL; + BOOL isSubject; +} + +- (void) setApt: (iCalEntityObject *) newApt; +- (iCalEntityObject *) apt; + +- (void) setAttendee: (iCalPerson *) newAttendee; +- (iCalPerson *) attendee; + +/* Content Generation */ + +- (NSString *) getSubject; +- (NSString *) getBody; + +@end + +#endif /* SOGOAPTMAILICALREPLY_H */ diff --git a/SoObjects/Appointments/SOGoAptMailICalReply.m b/SoObjects/Appointments/SOGoAptMailICalReply.m new file mode 100644 index 00000000..02a366e7 --- /dev/null +++ b/SoObjects/Appointments/SOGoAptMailICalReply.m @@ -0,0 +1,178 @@ +/* SOGoAptMailICalReply - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 +#import +#import + +#import +#import +#import +#import + +#import +#import + +#import +#import + +#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 diff --git a/SoObjects/Appointments/SOGoCalendarComponent.h b/SoObjects/Appointments/SOGoCalendarComponent.h index 4bb746a3..a0973e08 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.h +++ b/SoObjects/Appointments/SOGoCalendarComponent.h @@ -35,21 +35,19 @@ @class SOGoUser; @interface SOGoCalendarComponent : SOGoContentObject -{ - iCalCalendar *calendar; - NSString *calContent; -} - (NSString *) componentTag; -- (iCalCalendar *) calendar: (BOOL) create; -- (iCalRepeatableEntityObject *) component: (BOOL) create; -- (NSException *) primarySaveContentString: (NSString *) _iCalString; -- (NSException *) primaryDelete; +- (iCalCalendar *) calendar: (BOOL) create + secure: (BOOL) secure; +- (id) component: (BOOL) create secure: (BOOL) secure; -- (NSException *) delete; +// - (NSException *) primarySaveContentString: (NSString *) _iCalString; +// - (NSException *) primaryDelete; -- (NSException *) changeParticipationStatus: (NSString *) _status; +// - (NSException *) delete; + +- (void) saveComponent: (iCalRepeatableEntityObject *) newObject; /* mail notifications */ - (BOOL) sendEMailNotifications; @@ -64,7 +62,6 @@ - (iCalPerson *) findParticipantWithUID: (NSString *) uid; - (iCalPerson *) iCalPersonWithUID: (NSString *) uid; -- (NSString *) getUIDForICalPerson: (iCalPerson *) person; - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons; @end diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 72cb73c3..10274987 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -30,6 +30,7 @@ #import #import #import +#import #import #import #import @@ -42,10 +43,13 @@ #import #import #import +#import #import +#import "SOGoAptMailICalReply.h" #import "SOGoAptMailNotification.h" #import "iCalEntityObject+SOGo.h" +#import "iCalPerson+SOGo.h" #import "SOGoCalendarComponent.h" static BOOL sendEMailNotifications = NO; @@ -67,26 +71,6 @@ static BOOL sendEMailNotifications = NO; } } -- (id) init -{ - if ((self = [super init])) - { - calendar = nil; - calContent = nil; - } - - return self; -} - -- (void) dealloc -{ - if (calendar) - [calendar release]; - if (calContent) - [calContent release]; - [super dealloc]; -} - - (NSString *) davContentType { return @"text/calendar"; @@ -111,16 +95,15 @@ static BOOL sendEMailNotifications = NO; [component removeAllAlarms]; } -- (NSString *) contentAsString +- (NSString *) secureContentAsString { iCalCalendar *tmpCalendar; iCalRepeatableEntityObject *tmpComponent; // NSArray *roles; // NSString *uid; SoSecurityManager *sm; + NSString *iCalString; - if (!calContent) - { // uid = [[context activeUser] login]; // roles = [self aclsForUser: uid]; // if ([roles containsObject: SOGoCalendarRole_Organizer] @@ -139,112 +122,75 @@ static BOOL sendEMailNotifications = NO; // else // calContent = nil; - sm = [SoSecurityManager sharedSecurityManager]; - if (![sm validatePermission: SOGoCalendarPerm_ViewAllComponent - onObject: self inContext: context]) - calContent = content; - else if (![sm validatePermission: SOGoCalendarPerm_ViewDAndT - onObject: self inContext: context]) - { - tmpCalendar = [[self calendar: NO] copy]; - tmpComponent = (iCalRepeatableEntityObject *) - [tmpCalendar firstChildWithTag: [self componentTag]]; - [self _filterComponent: tmpComponent]; - calContent = [tmpCalendar versitString]; - [tmpCalendar release]; - } - else - calContent = nil; - - [calContent retain]; + sm = [SoSecurityManager sharedSecurityManager]; + if (![sm validatePermission: SOGoCalendarPerm_ViewAllComponent + onObject: self inContext: context]) + iCalString = content; + else if (![sm validatePermission: SOGoCalendarPerm_ViewDAndT + onObject: self inContext: context]) + { + tmpCalendar = [[self calendar: NO secure: NO] copy]; + tmpComponent = (iCalRepeatableEntityObject *) + [tmpCalendar firstChildWithTag: [self componentTag]]; + [self _filterComponent: tmpComponent]; + iCalString = [tmpCalendar versitString]; + [tmpCalendar release]; } + else + iCalString = nil; - return calContent; -} - -- (void) setContentString: (NSString *) newContent -{ - [super setContentString: newContent]; - ASSIGN (calendar, nil); - ASSIGN (calContent, nil); + return iCalString; } -// - (NSException *) saveContentString: (NSString *) contentString -// baseVersion: (unsigned int) baseVersion -// { -// NSException *result; - -// result = [super saveContentString: contentString -// baseVersion: baseVersion]; -// if (!result && calContent) -// { -// [calContent release]; -// calContent = nil; -// } - -// return result; -// } - -- (iCalCalendar *) calendar: (BOOL) create +- (iCalCalendar *) calendar: (BOOL) create secure: (BOOL) secure { - NSString *iCalString, *componentTag; + NSString *componentTag; CardGroup *newComponent; + iCalCalendar *calendar; + NSString *iCalString; + + if (secure) + iCalString = [self secureContentAsString]; + else + iCalString = content; - if (!calendar) + if ([iCalString length] > 0) + calendar = [iCalCalendar parseSingleFromSource: iCalString]; + else { - iCalString = [super contentAsString]; - if ([iCalString length] > 0) - calendar = [iCalCalendar parseSingleFromSource: iCalString]; + if (create) + { + calendar = [iCalCalendar groupWithTag: @"vcalendar"]; + [calendar setVersion: @"2.0"]; + [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"]; + componentTag = [[self componentTag] uppercaseString]; + newComponent = [[calendar classForTag: componentTag] + groupWithTag: componentTag]; + [calendar addChild: newComponent]; + } else - { - if (create) - { - calendar = [iCalCalendar groupWithTag: @"vcalendar"]; - [calendar setVersion: @"2.0"]; - [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"]; - componentTag = [[self componentTag] uppercaseString]; - newComponent = [[calendar classForTag: componentTag] - groupWithTag: componentTag]; - [calendar addChild: newComponent]; - } - } - if (calendar) - [calendar retain]; + calendar = nil; } return calendar; } -- (iCalRepeatableEntityObject *) component: (BOOL) create -{ - return - (iCalRepeatableEntityObject *) [[self calendar: create] - firstChildWithTag: [self componentTag]]; -} - -/* raw saving */ - -- (NSException *) primarySaveContentString: (NSString *) _iCalString +- (id) component: (BOOL) create secure: (BOOL) secure { - return [super saveContentString: _iCalString]; + return [[self calendar: create secure: secure] + firstChildWithTag: [self componentTag]]; } -- (NSException *) primaryDelete +- (void) saveComponent: (iCalRepeatableEntityObject *) newObject { - return [super delete]; -} + NSString *newiCalString; -- (NSException *) deleteWithBaseSequence: (int) a -{ - [self subclassResponsibility: _cmd]; + newiCalString = [[newObject parent] versitString]; - return nil; + [self saveContentString: newiCalString]; } -- (NSException *) delete -{ - return [self deleteWithBaseSequence: 0]; -} +/* raw saving */ /* EMail Notifications */ - (NSString *) homePageURLForPerson: (iCalPerson *) _person @@ -262,58 +208,13 @@ static BOOL sendEMailNotifications = NO; baseURL = @"http://localhost/"; [self warnWithFormat:@"Unable to create baseURL from context!"]; } - uid = [[LDAPUserManager sharedUserManager] - getUIDForEmail: [_person rfc822Email]]; + uid = [_person uid]; return ((uid) ? [NSString stringWithFormat:@"%@%@", baseURL, uid] : nil); } -- (NSException *) changeParticipationStatus: (NSString *) _status -{ - iCalRepeatableEntityObject *component; - iCalPerson *person; - NSString *newContent; - NSException *ex; - - ex = nil; - - component = [self component: NO]; - if (component) - { - person = [self findParticipantWithUID: owner]; - if (person) - { - // TODO: send iMIP reply mails? - [person setPartStat: _status]; - newContent = [[component parent] versitString]; - if (newContent) - { - ex = [self saveContentString: newContent]; - if (ex) - // TODO: why is the exception wrapped? - /* Server Error */ - ex = [NSException exceptionWithHTTPStatus: 500 - reason: [ex reason]]; - } - else - ex - = [NSException exceptionWithHTTPStatus: 500 /* Server Error */ - reason: @"Could not generate iCalendar data ..."]; - } - else - ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */ - reason: @"user does not participate in this " - @"calendar component"]; - } - else - ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */ - reason: @"unable to parse component record"]; - - return ex; -} - - (BOOL) sendEMailNotifications { return sendEMailNotifications; @@ -335,7 +236,7 @@ static BOOL sendEMailNotifications = NO; { NSString *pageName; iCalPerson *organizer; - NSString *cn, *email, *sender, *iCalString; + NSString *email, *sender, *iCalString; WOApplication *app; unsigned i, count; iCalPerson *attendee; @@ -347,115 +248,196 @@ static BOOL sendEMailNotifications = NO; NGMimeBodyPart *bodyPart; NGMimeMultipartBody *body; - if ([_attendees count]) + if (sendEMailNotifications + && [_newObject isStillRelevant]) { - /* sender */ - - organizer = [_newObject organizer]; - cn = [organizer cnWithoutQuotes]; - if (cn) - sender = [NSString stringWithFormat:@"%@ <%@>", - cn, - [organizer rfc822Email]]; - else - sender = [organizer rfc822Email]; + count = [_attendees count]; + if (count) + { + /* sender */ + organizer = [_newObject organizer]; + sender = [organizer mailAddress]; + + NSLog (@"sending '%@' from %@", + [(iCalCalendar *) [_newObject parent] method], organizer); - /* generate iCalString once */ - iCalString = [[_newObject parent] versitString]; + /* generate iCalString once */ + iCalString = [[_newObject parent] versitString]; - /* get WOApplication instance */ - app = [WOApplication application]; + /* get WOApplication instance */ + app = [WOApplication application]; - /* generate dynamic message content */ + /* generate dynamic message content */ - count = [_attendees count]; - for (i = 0; i < count; i++) - { - attendee = [_attendees objectAtIndex:i]; - - /* construct recipient */ - cn = [attendee cn]; - email = [attendee rfc822Email]; - if (cn) - recipient = [NSString stringWithFormat: @"%@ <%@>", - cn, email]; - else - recipient = email; - - language = [[context activeUser] language]; + for (i = 0; i < count; i++) + { + attendee = [_attendees objectAtIndex: i]; + if (![[attendee uid] isEqualToString: owner]) + { + /* construct recipient */ + recipient = [attendee mailAddress]; + email = [attendee rfc822Email]; + + NSLog (@"recipient: %@", recipient); + language = [[context activeUser] language]; #warning this could be optimized in a class hierarchy common with the \ - SOGoObject acl notification mechanism - /* create page name */ - // TODO: select user's default language? - pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@", - language, - _pageName]; - /* construct message content */ - p = [app pageWithName: pageName inContext: context]; - [p setNewApt: _newObject]; - [p setOldApt: _oldObject]; - [p setHomePageURL: [self homePageURLForPerson: attendee]]; - [p setViewTZ: [self timeZoneForUser: email]]; - subject = [p getSubject]; - text = [p getBody]; - - /* construct message */ - headerMap = [NGMutableHashMap hashMapWithCapacity: 5]; + SOGoObject acl notification mechanism + /* create page name */ + pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@", + language, _pageName]; + /* construct message content */ + p = [app pageWithName: pageName inContext: context]; + [p setNewApt: _newObject]; + [p setOldApt: _oldObject]; + [p setHomePageURL: [self homePageURLForPerson: attendee]]; + [p setViewTZ: [self timeZoneForUser: email]]; + subject = [p getSubject]; + text = [p getBody]; + + /* construct message */ + headerMap = [NGMutableHashMap hashMapWithCapacity: 5]; - /* NOTE: multipart/alternative seems like the correct choice but - * unfortunately Thunderbird doesn't offer the rich content alternative - * at all. Mail.app shows the rich content alternative _only_ - * so we'll stick with multipart/mixed for the time being. - */ - [headerMap setObject: @"multipart/mixed" forKey: @"content-type"]; - [headerMap setObject: sender forKey: @"From"]; - [headerMap setObject: recipient forKey: @"To"]; - mailDate = [[NSCalendarDate date] rfc822DateString]; - [headerMap setObject: mailDate forKey: @"date"]; - [headerMap setObject: subject forKey: @"Subject"]; - msg = [NGMimeMessage messageWithHeader: headerMap]; - - /* multipart body */ - body = [[NGMimeMultipartBody alloc] initWithPart: msg]; - - /* text part */ - headerMap = [NGMutableHashMap hashMapWithCapacity: 1]; - [headerMap setObject: @"text/plain; charset=utf-8" - forKey: @"content-type"]; - bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap]; - [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]]; - - /* attach text part to multipart body */ - [body addBodyPart: bodyPart]; + /* NOTE: multipart/alternative seems like the correct choice but + * unfortunately Thunderbird doesn't offer the rich content alternative + * at all. Mail.app shows the rich content alternative _only_ + * so we'll stick with multipart/mixed for the time being. + */ + [headerMap setObject: @"multipart/mixed" forKey: @"content-type"]; + [headerMap setObject: sender forKey: @"From"]; + [headerMap setObject: recipient forKey: @"To"]; + mailDate = [[NSCalendarDate date] rfc822DateString]; + [headerMap setObject: mailDate forKey: @"date"]; + [headerMap setObject: subject forKey: @"Subject"]; + msg = [NGMimeMessage messageWithHeader: headerMap]; + + /* multipart body */ + body = [[NGMimeMultipartBody alloc] initWithPart: msg]; + + /* text part */ + headerMap = [NGMutableHashMap hashMapWithCapacity: 1]; + [headerMap setObject: @"text/plain; charset=utf-8" + forKey: @"content-type"]; + bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap]; + [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]]; + + /* attach text part to multipart body */ + [body addBodyPart: bodyPart]; - /* calendar part */ - header = [NSString stringWithFormat: @"text/calendar; method=%@;" - @" charset=utf-8", - [(iCalCalendar *) [_newObject parent] method]]; - headerMap = [NGMutableHashMap hashMapWithCapacity: 1]; - [headerMap setObject:header forKey: @"content-type"]; - bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap]; - [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]]; - - /* attach calendar part to multipart body */ - [body addBodyPart: bodyPart]; + /* calendar part */ + header = [NSString stringWithFormat: @"text/calendar; method=%@;" + @" charset=utf-8", + [(iCalCalendar *) [_newObject parent] method]]; + headerMap = [NGMutableHashMap hashMapWithCapacity: 1]; + [headerMap setObject:header forKey: @"content-type"]; + bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap]; + [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]]; + + /* attach calendar part to multipart body */ + [body addBodyPart: bodyPart]; - /* attach multipart body to message */ - [msg setBody: body]; - [body release]; - - /* send the damn thing */ - [[SOGoMailer sharedMailer] - sendMimePart: msg - toRecipients: [NSArray arrayWithObject: email] - sender: [organizer rfc822Email]]; - } + /* attach multipart body to message */ + [msg setBody: body]; + [body release]; + + /* send the damn thing */ + [[SOGoMailer sharedMailer] + sendMimePart: msg + toRecipients: [NSArray arrayWithObject: email] + sender: [organizer rfc822Email]]; + } + } + } } } - (void) sendResponseToOrganizer { -#warning THIS IS A STUB + NSString *pageName, *language, *mailDate, *email; + WOApplication *app; + iCalPerson *organizer, *attendee; + NSString *iCalString; + iCalEvent *event; + SOGoAptMailICalReply *p; + NGMutableHashMap *headerMap; + NGMimeMessage *msg; + NGMimeBodyPart *bodyPart; + NGMimeMultipartBody *body; + NSData *bodyData; + + event = [[self component: NO secure: NO] itipEntryWithMethod: @"reply"]; + if (![event userIsOrganizer: [context activeUser]]) + { + organizer = [event organizer]; + attendee = [event findParticipant: [context activeUser]]; + [event setAttendees: [NSArray arrayWithObject: attendee]]; + + /* get WOApplication instance */ + app = [WOApplication application]; + + language = [[context activeUser] language]; + /* create page name */ + pageName + = [NSString stringWithFormat: @"SOGoAptMail%@ICalReply", language]; + /* construct message content */ + p = [app pageWithName: pageName inContext: context]; + [p setApt: event]; + [p setAttendee: attendee]; + + /* construct message */ + headerMap = [NGMutableHashMap hashMapWithCapacity: 5]; + + /* NOTE: multipart/alternative seems like the correct choice but + * unfortunately Thunderbird doesn't offer the rich content alternative + * at all. Mail.app shows the rich content alternative _only_ + * so we'll stick with multipart/mixed for the time being. + */ + [headerMap setObject: @"multipart/mixed" forKey: @"content-type"]; + [headerMap setObject: [attendee mailAddress] forKey: @"From"]; + [headerMap setObject: [organizer mailAddress] forKey: @"To"]; + mailDate = [[NSCalendarDate date] rfc822DateString]; + [headerMap setObject: mailDate forKey: @"date"]; + [headerMap setObject: [p getSubject] forKey: @"Subject"]; + msg = [NGMimeMessage messageWithHeader: headerMap]; + + NSLog (@"sending 'REPLY' from %@ to %@", + [attendee mailAddress], [organizer mailAddress]); + + /* multipart body */ + body = [[NGMimeMultipartBody alloc] initWithPart: msg]; + + /* text part */ + headerMap = [NGMutableHashMap hashMapWithCapacity: 1]; + [headerMap setObject: @"text/plain; charset=utf-8" + forKey: @"content-type"]; + bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap]; + bodyData = [[p getBody] dataUsingEncoding: NSUTF8StringEncoding]; + [bodyPart setBody: bodyData]; + + /* attach text part to multipart body */ + [body addBodyPart: bodyPart]; + + /* calendar part */ + headerMap = [NGMutableHashMap hashMapWithCapacity: 1]; + [headerMap setObject: @"text/calendar; method=REPLY; charset=utf-8" + forKey: @"content-type"]; + bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap]; + iCalString = [[event parent] versitString]; + [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]]; + + /* attach calendar part to multipart body */ + [body addBodyPart: bodyPart]; + + /* attach multipart body to message */ + [msg setBody: body]; + [body release]; + + /* send the damn thing */ + email = [organizer rfc822Email]; + [[SOGoMailer sharedMailer] + sendMimePart: msg + toRecipients: [NSArray arrayWithObject: email] + sender: [attendee rfc822Email]]; + } } // - (BOOL) isOrganizerOrOwner: (SOGoUser *) user @@ -481,7 +463,7 @@ static BOOL sendEMailNotifications = NO; SOGoUser *user; user = [SOGoUser userWithLogin: uid roles: nil]; - component = [self component: NO]; + component = [self component: NO secure: NO]; return [component findParticipant: user]; } @@ -503,31 +485,20 @@ static BOOL sendEMailNotifications = NO; return person; } -- (NSString *) getUIDForICalPerson: (iCalPerson *) person -{ - LDAPUserManager *um; - - um = [LDAPUserManager sharedUserManager]; - - return [um getUIDForEmail: [person rfc822Email]]; -} - - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons { iCalPerson *currentPerson; NSEnumerator *persons; NSMutableArray *uids; NSString *uid; - LDAPUserManager *um; uids = [NSMutableArray array]; - um = [LDAPUserManager sharedUserManager]; persons = [iCalPersons objectEnumerator]; currentPerson = [persons nextObject]; while (currentPerson) { - uid = [um getUIDForEmail: [currentPerson rfc822Email]]; + uid = [currentPerson uid]; if (uid) [uids addObject: uid]; currentPerson = [persons nextObject]; @@ -591,7 +562,7 @@ static BOOL sendEMailNotifications = NO; if ([superAcls count] > 0) [roles addObjectsFromArray: superAcls]; - component = [self component: NO]; + component = [self component: NO secure: NO]; ownerRole = [self _roleOfOwner: component]; if ([owner isEqualToString: uid]) [roles addObject: ownerRole]; diff --git a/SoObjects/Appointments/SOGoTaskObject.h b/SoObjects/Appointments/SOGoTaskObject.h index da442022..c369e6fb 100644 --- a/SoObjects/Appointments/SOGoTaskObject.h +++ b/SoObjects/Appointments/SOGoTaskObject.h @@ -44,13 +44,6 @@ @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__ */ diff --git a/SoObjects/Appointments/SOGoTaskObject.m b/SoObjects/Appointments/SOGoTaskObject.m index 5c628fa6..16bbb625 100644 --- a/SoObjects/Appointments/SOGoTaskObject.m +++ b/SoObjects/Appointments/SOGoTaskObject.m @@ -38,415 +38,17 @@ #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"; } diff --git a/SoObjects/Appointments/iCalEntityObject+SOGo.h b/SoObjects/Appointments/iCalEntityObject+SOGo.h index 7f8702b9..f26ae5a1 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.h +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.h @@ -25,11 +25,20 @@ #import +@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 */ diff --git a/SoObjects/Appointments/iCalEntityObject+SOGo.m b/SoObjects/Appointments/iCalEntityObject+SOGo.m index 3a441116..83cd25a5 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.m +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.m @@ -23,11 +23,14 @@ #import #import +#import #import #import #import +#import "iCalPerson+SOGo.h" + #import "iCalEntityObject+SOGo.h" @implementation iCalEntityObject (SOGoExtensions) @@ -52,6 +55,7 @@ return isParticipant; } +#warning user could be a delegate, we will need to handle that someday - (BOOL) userIsOrganizer: (SOGoUser *) user { NSString *orgMail; @@ -61,4 +65,66 @@ return [user hasEmail: orgMail]; } +- (NSArray *) attendeeUIDs +{ + NSEnumerator *attendees; + NSString *uid; + iCalPerson *currentAttendee; + NSMutableArray *uids; + + uids = [NSMutableArray array]; + + attendees = [[self attendees] objectEnumerator]; + while ((currentAttendee = [attendees nextObject])) + { + uid = [currentAttendee uid]; + if (uid) + [uids addObject: uid]; + } + + return uids; +} + +#warning this method should be implemented in a category of iCalToDo +- (BOOL) isStillRelevant +{ + [self subclassResponsibility: _cmd]; + return NO; +} + +- (id) itipEntryWithMethod: (NSString *) method +{ + iCalCalendar *newCalendar; + iCalEntityObject *newEntry; + + newCalendar = [parent mutableCopy]; + [newCalendar autorelease]; + [newCalendar setMethod: method]; + newEntry = (iCalEntityObject *) [newCalendar firstChildWithTag: tag]; + + return newEntry; +} + +- (NSArray *) attendeesWithoutUser: (SOGoUser *) user +{ + NSMutableArray *newAttendees; + NSArray *oldAttendees; + unsigned int count, max; + iCalPerson *currentAttendee; + NSString *userID; + + userID = [user login]; + oldAttendees = [self attendees]; + max = [oldAttendees count]; + newAttendees = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + currentAttendee = [oldAttendees objectAtIndex: count]; + if (![[currentAttendee uid] isEqualToString: userID]) + [newAttendees addObject: currentAttendee]; + } + + return newAttendees; +} + @end diff --git a/SoObjects/Appointments/iCalEventChanges+SOGo.h b/SoObjects/Appointments/iCalEventChanges+SOGo.h new file mode 100644 index 00000000..1c4ed55f --- /dev/null +++ b/SoObjects/Appointments/iCalEventChanges+SOGo.h @@ -0,0 +1,34 @@ +/* iCalEventChanges+SOGo.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 + +@interface iCalEventChanges (SOGoExtensions) + +- (BOOL) sequenceShouldBeIncreased; + +@end + +#endif /* ICALEVENTCHANGES_SOGO_H */ diff --git a/SoObjects/Appointments/iCalEventChanges+SOGo.m b/SoObjects/Appointments/iCalEventChanges+SOGo.m new file mode 100644 index 00000000..b1e25c60 --- /dev/null +++ b/SoObjects/Appointments/iCalEventChanges+SOGo.m @@ -0,0 +1,52 @@ +/* iCalEventChanges+SOGo.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 +#import +#import + +#import "iCalEventChanges+SOGo.h" + +@implementation iCalEventChanges (SOGoExtensions) + +- (BOOL) sequenceShouldBeIncreased +{ + NSString *properties[] = {@"organizer", @"startDate", @"endDate", /* vtask: + @"due" */ + @"rdate", @"rrule", @"exdate", @"exrule", + @"status", @"location", nil}; + NSString **currentProperty; + BOOL updateRequired; + + updateRequired = NO; + + currentProperty = properties; + while (!updateRequired && *currentProperty) + if ([updatedProperties containsObject: *currentProperty]) + updateRequired = YES; + else + currentProperty++; + + return updateRequired; +} + +@end diff --git a/SoObjects/Appointments/iCalPerson+SOGo.h b/SoObjects/Appointments/iCalPerson+SOGo.h new file mode 100644 index 00000000..06312acd --- /dev/null +++ b/SoObjects/Appointments/iCalPerson+SOGo.h @@ -0,0 +1,39 @@ +/* iCalPerson+SOGo.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 + +#import + +@class NSString; + +@interface iCalPerson (SOGoExtension) + +- (NSString *) mailAddress; +- (NSString *) uid; + +@end + +#endif /* ICALPERSON_SOGO_H */ diff --git a/SoObjects/Appointments/iCalPerson+SOGo.m b/SoObjects/Appointments/iCalPerson+SOGo.m new file mode 100644 index 00000000..53d828c4 --- /dev/null +++ b/SoObjects/Appointments/iCalPerson+SOGo.m @@ -0,0 +1,53 @@ +/* iCalPerson+SOGo.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 + +#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 diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index e4293472..f56c1be8 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -51,6 +51,7 @@ #import #import #import +#import #import #import @@ -614,10 +615,14 @@ static BOOL showTextAttachmentsInline = NO; SOGoUser *currentUser; [sourceMail fetchCoreInfos]; - - info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward] - forKey: @"subject"]; - [self setHeaders: info]; + + if ([sourceMail subjectForForward]) + { + info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward] + forKey: @"subject"]; + [self setHeaders: info]; + } + [self setSourceURL: [sourceMail imap4URLString]]; [self setSourceFlag: @"$Forwarded"]; @@ -905,7 +910,7 @@ static BOOL showTextAttachmentsInline = NO; cdtype = @"inline"; else cdtype = @"attachment"; - + cd = [cdtype stringByAppendingString: @"; filename=\""]; cd = [cd stringByAppendingString: _name]; cd = [cd stringByAppendingString: @"\""]; @@ -950,7 +955,13 @@ static BOOL showTextAttachmentsInline = NO; is7bit = YES; } if ((s = [self contentDispositionForAttachmentWithName:_name])) - [map setObject:s forKey: @"content-disposition"]; + { + NGMimeContentDispositionHeaderField *o; + + o = [[NGMimeContentDispositionHeaderField alloc] initWithString: s]; + [map setObject:o forKey: @"content-disposition"]; + [o release]; + } /* prepare body content */ diff --git a/SoObjects/Mailer/SOGoMailBaseObject.m b/SoObjects/Mailer/SOGoMailBaseObject.m index 6feb888a..d4387d52 100644 --- a/SoObjects/Mailer/SOGoMailBaseObject.m +++ b/SoObjects/Mailer/SOGoMailBaseObject.m @@ -207,7 +207,7 @@ static BOOL debugOn = YES; - (BOOL)isBodyPartKey:(NSString *)_key inContext:(id)_ctx { /* - Every key starting with a digit is consider an IMAP4 mime part key, used in + Every key starting with a digit is considered an IMAP4 mime part key, used in SOGoMailObject and SOGoMailBodyPart. */ if ([_key length] == 0) diff --git a/SoObjects/Mailer/SOGoMailBodyPart.m b/SoObjects/Mailer/SOGoMailBodyPart.m index 78be1fb6..80829127 100644 --- a/SoObjects/Mailer/SOGoMailBodyPart.m +++ b/SoObjects/Mailer/SOGoMailBodyPart.m @@ -349,7 +349,8 @@ static BOOL debugOn = NO; if ([mimeType isEqualToString: @"image/gif"] || [mimeType isEqualToString: @"image/png"] - || [mimeType isEqualToString: @"image/jpg"]) + || [mimeType isEqualToString: @"image/jpg"] + || [mimeType isEqualToString: @"image/jpeg"]) classString = @"SOGoImageMailBodyPart"; else if ([mimeType isEqualToString: @"text/calendar"]) classString = @"SOGoCalendarMailBodyPart"; @@ -366,7 +367,7 @@ static BOOL debugOn = NO; if (classString) klazz = NSClassFromString (classString); else - klazz = Nil; + klazz = [SOGoMailBodyPart class]; return klazz; } diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.m b/SoObjects/Mailer/SOGoMailObject+Draft.m index 91f3a17a..ee542561 100644 --- a/SoObjects/Mailer/SOGoMailObject+Draft.m +++ b/SoObjects/Mailer/SOGoMailObject+Draft.m @@ -80,6 +80,8 @@ int index; BOOL htmlContent; + content = @""; + if ([keys count]) { types = [keys objectsForKey: @"mimeType"]; @@ -91,23 +93,23 @@ } 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; } @@ -180,7 +182,7 @@ subject = [self decodedSubject]; if ([subject length] > 0) - newSubject = [NSString stringWithFormat: @"[Fwd: %@]", subject]; + newSubject = [NSString stringWithFormat: @"Fwd: %@", subject]; else newSubject = subject; diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index f6969e97..4c44bd99 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -656,8 +656,13 @@ static BOOL debugSoParts = NO; [s autorelease]; } else - s = [NSString stringWithData: mailData - usingEncodingNamed: charset]; + { + s = [NSString stringWithData: mailData usingEncodingNamed: charset]; + + // If it has failed, we try at least using UTF-8. Normally, this can NOT fail + if (!s) + s = [[[NSString alloc] initWithData: mailData encoding: NSISOLatin1StringEncoding] autorelease]; + } } else s = nil; diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 201e16b8..c2edec3d 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -41,6 +41,7 @@ NSString *IDField; /* the first part of a user DN */ NSString *CNField; NSString *UIDField; + NSArray *mailFields; NSString *bindFields; NGLdapConnection *ldapConnection; @@ -59,6 +60,7 @@ IDField: (NSString *) newIDField CNField: (NSString *) newCNField UIDField: (NSString *) newUIDField + mailFields: (NSArray *) newMailFields andBindFields: (NSString *) newBindFields; - (BOOL) checkLogin: (NSString *) login diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index 28475ad1..b2cd7ad3 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -146,6 +146,8 @@ static int sizeLimit; IDField = @"cn"; /* the first part of a user DN */ CNField = @"cn"; UIDField = @"uid"; + mailFields = [NSArray arrayWithObject: @"mail"]; + [mailFields retain]; bindFields = nil; ldapConnection = nil; @@ -164,6 +166,7 @@ static int sizeLimit; [IDField release]; [CNField release]; [UIDField release]; + [mailFields release]; [bindFields release]; [ldapConnection release]; [sourceID release]; @@ -183,7 +186,8 @@ static int sizeLimit; [self setBaseDN: [udSource objectForKey: @"baseDN"] IDField: [udSource objectForKey: @"IDFieldName"] CNField: [udSource objectForKey: @"CNFieldName"] - UIDField: [udSource objectForKey: @"UIDFieldName"] + UIDField: [udSource objectForKey: @"UIDFieldName"] + mailFields: [udSource objectForKey: @"MailFieldNames"] andBindFields: [udSource objectForKey: @"bindFields"]]; return self; @@ -205,6 +209,7 @@ static int sizeLimit; IDField: (NSString *) newIDField CNField: (NSString *) newCNField UIDField: (NSString *) newUIDField + mailFields: (NSArray *) newMailFields andBindFields: (NSString *) newBindFields { ASSIGN (baseDN, newBaseDN); @@ -214,6 +219,8 @@ static int sizeLimit; ASSIGN (CNField, newCNField); if (UIDField) ASSIGN (UIDField, newUIDField); + if (newMailFields) + ASSIGN (mailFields, newMailFields); if (newBindFields) ASSIGN (bindFields, newBindFields); } @@ -346,8 +353,6 @@ static int sizeLimit; - (NSArray *) _searchAttributes { - NSArray *attrs; - if (!searchAttributes) { searchAttributes = [NSMutableArray new]; @@ -355,15 +360,10 @@ static int sizeLimit; [searchAttributes addObject: CNField]; if (UIDField) [searchAttributes addObject: UIDField]; + [searchAttributes addObjectsFromArray: mailFields]; [searchAttributes addObjectsFromArray: commonSearchFields]; } - // We also include our MailFieldNames in the search - if ((attrs = [[[LDAPUserManager sharedUserManager] metadataForSourceID: sourceID] objectForKey: @"MailFieldNames"])) - { - [searchAttributes addObjectsFromArray: attrs]; - } - return searchAttributes; } @@ -397,6 +397,25 @@ static int sizeLimit; return ids; } +- (void) _fillEmailsOfEntry: (NGLdapEntry *) ldapEntry + intoContactEntry: (NSMutableDictionary *) contactEntry +{ + NSEnumerator *emailFields; + NSString *currentFieldName, *value; + NSMutableArray *emails; + + emails = [NSMutableArray new]; + emailFields = [mailFields objectEnumerator]; + while ((currentFieldName = [emailFields nextObject])) + { + value = [[ldapEntry attributeWithName: currentFieldName] stringValueAtIndex: 0]; + if (value) + [emails addObject: value]; + } + [emails autorelease]; + [contactEntry setObject: emails forKey: @"c_emails"]; +} + - (NSDictionary *) _convertLDAPEntryToContact: (NGLdapEntry *) ldapEntry { NSMutableDictionary *contactEntry; @@ -426,6 +445,7 @@ static int sizeLimit; if (!value) value = @""; [contactEntry setObject: value forKey: @"c_cn"]; + [self _fillEmailsOfEntry: ldapEntry intoContactEntry: contactEntry]; return contactEntry; } diff --git a/SoObjects/SOGo/LDAPUserManager.m b/SoObjects/SOGo/LDAPUserManager.m index be9ed79d..6dcdd3af 100644 --- a/SoObjects/SOGo/LDAPUserManager.m +++ b/SoObjects/SOGo/LDAPUserManager.m @@ -27,6 +27,7 @@ #import #import +#import "NSArray+Utilities.h" #import "LDAPSource.h" #import "LDAPUserManager.h" @@ -289,9 +290,7 @@ static NSString *defaultMailDomain = nil; emails = [contact objectForKey: @"emails"]; uid = [contact objectForKey: @"c_uid"]; systemEmail = [NSString stringWithFormat: @"%@@%@", uid, defaultMailDomain]; - if ([emails containsObject: systemEmail]) - [emails removeObject: systemEmail]; - [emails addObject: systemEmail]; + [emails addObjectUniquely: systemEmail]; [contact setObject: [emails objectAtIndex: 0] forKey: @"c_email"]; } @@ -302,8 +301,8 @@ static NSString *defaultMailDomain = nil; NSDictionary *userEntry; NSEnumerator *ldapSources; LDAPSource *currentSource; - NSString *cn, *email, *c_uid; - NSArray *attrs; + NSString *cn, *c_uid; + NSArray *c_emails; emails = [NSMutableArray array]; cn = nil; @@ -320,18 +319,9 @@ static NSString *defaultMailDomain = nil; cn = [userEntry objectForKey: @"c_cn"]; if (!c_uid) c_uid = [userEntry objectForKey: @"c_uid"]; - - if ((attrs = [[sourcesMetadata objectForKey: [currentSource sourceID]] objectForKey: @"MailFieldNames"])) - { - int i; - - for (i = 0; i < [attrs count]; i++) - { - email = [userEntry objectForKey: [attrs objectAtIndex: i]]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; - } - } + c_emails = [userEntry objectForKey: @"c_emails"]; + if ([c_emails count]) + [emails addObjectsFromArray: c_emails]; } currentSource = [ldapSources nextObject]; } diff --git a/SoObjects/SOGo/SOGoContentObject.h b/SoObjects/SOGo/SOGoContentObject.h index 4fc2c7a8..807b9874 100644 --- a/SoObjects/SOGo/SOGoContentObject.h +++ b/SoObjects/SOGo/SOGoContentObject.h @@ -48,7 +48,6 @@ /* content */ - (BOOL) isNew; -- (void) setContentString: (NSString *) newContent; - (NSString *) contentAsString; - (NSException *) saveContentString: (NSString *) _str baseVersion: (unsigned int) _baseVersion; @@ -65,4 +64,10 @@ @end +@interface SOGoContentObject (OptionalMethods) + +- (void) prepareDelete; + +@end + #endif /* __SOGo_SOGoContentObject_H__ */ diff --git a/SoObjects/SOGo/SOGoContentObject.m b/SoObjects/SOGo/SOGoContentObject.m index 7e43dbbb..e45b458f 100644 --- a/SoObjects/SOGo/SOGoContentObject.m +++ b/SoObjects/SOGo/SOGoContentObject.m @@ -138,11 +138,6 @@ return content; } -- (void) setContentString: (NSString *) newContent -{ - ASSIGN (content, newContent); -} - - (NSException *) saveContentString: (NSString *) newContent baseVersion: (unsigned int) newBaseVersion { @@ -152,11 +147,12 @@ 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]; diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 5b938f09..9322f247 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -350,7 +350,11 @@ static NSString *defaultUserID = @""; deleteObject = [self lookupName: currentID inContext: context acquire: NO]; if (![deleteObject isKindOfClass: [NSException class]]) - [deleteObject delete]; + { + if ([deleteObject respondsToSelector: @selector (prepareDelete)]) + [deleteObject prepareDelete]; + [deleteObject delete]; + } } } diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index 10b9f0fd..64e1f8a0 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -34,17 +34,19 @@ context.activeUser */ -@class NSString; @class NSArray; @class NSDictionary; +@class NSString; +@class NSTimeZone; @class NSURL; @class NSUserDefaults; -@class NSTimeZone; + @class WOContext; + @class SOGoAppointmentFolder; @class SOGoAppointmentFolders; @class SOGoDateFormatter; -@class WOContext; +@class SOGoUserFolder; extern NSString *SOGoWeekStartHideWeekNumbers; extern NSString *SOGoWeekStartJanuary1; @@ -68,6 +70,7 @@ extern NSString *SOGoWeekStartFirstFullWeek; NSTimeZone *userTimeZone; SOGoDateFormatter *dateFormatter; NSMutableArray *mailAccounts; + SOGoUserFolder *homeFolder; } + (NSString *) language; @@ -119,7 +122,7 @@ extern NSString *SOGoWeekStartFirstFullWeek; /* folders */ -- (id) homeFolderInContext: (id) _ctx; +- (SOGoUserFolder *) homeFolderInContext: (id) context; - (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context; - (SOGoAppointmentFolder *) diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 9488c1dd..95f32ac9 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -142,6 +142,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; language = nil; currentPassword = nil; dateFormatter = nil; + homeFolder = nil; } return self; @@ -176,6 +177,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; [allEmails release]; [language release]; [dateFormatter release]; + [homeFolder release]; [super dealloc]; } @@ -506,7 +508,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; messageForwarding = [[self userDefaults] stringForKey: @"MessageForwarding"]; if (![messageForwarding length]) - messageForwarding = @"attached"; + messageForwarding = @"inline"; return messageForwarding; } @@ -528,27 +530,17 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; // TODO: those methods should check whether the traversal stack in the context // already contains proper folders to improve caching behaviour -- (id) homeFolderInContext: (id) _ctx +- (SOGoUserFolder *) homeFolderInContext: (id) context { - /* Note: watch out for cyclic references */ - // TODO: maybe we should add an [activeUser reset] method to SOPE - id folder; - - folder = [(WOContext *)_ctx objectForKey:@"ActiveUserHomeFolder"]; - if (folder != nil) - return [folder isNotNull] ? folder : nil; - - folder = [[WOApplication application] lookupName: [self login] - inContext: _ctx - acquire: NO]; - if ([folder isKindOfClass:[NSException class]]) - return folder; - - [(WOContext *)_ctx setObject: ((folder) - ? folder - : (id)[NSNull null]) - forKey: @"ActiveUserHomeFolder"]; - return folder; + if (!homeFolder) + { + homeFolder = [[WOApplication application] lookupName: [self login] + inContext: context + acquire: NO]; + [homeFolder retain]; + } + + return homeFolder; } - (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context diff --git a/UI/Common/GNUmakefile b/UI/Common/GNUmakefile index 873df3e6..d9530e53 100644 --- a/UI/Common/GNUmakefile +++ b/UI/Common/GNUmakefile @@ -11,16 +11,12 @@ CommonUI_LANGUAGES = English French German CommonUI_OBJC_FILES += \ CommonUIProduct.m \ UIxPageFrame.m \ - UIxPrintPageFrame.m \ - UIxAppNavView.m \ \ UIxAclEditor.m \ UIxObjectActions.m \ UIxFolderActions.m \ UIxParentFolderActions.m \ UIxElemBuilder.m \ - UIxTabView.m \ - UIxTabItem.m \ UIxUserRightsEditor.m \ \ UIxToolbar.m \ diff --git a/UI/Common/UIxAppNavView.m b/UI/Common/UIxAppNavView.m deleted file mode 100644 index 87dfc098..00000000 --- a/UI/Common/UIxAppNavView.m +++ /dev/null @@ -1,101 +0,0 @@ -/* - Copyright (C) 2004 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#import -#import - -#import - -@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 */ diff --git a/UI/Common/UIxPageFrame.h b/UI/Common/UIxPageFrame.h index 1c3094a5..d87126f2 100644 --- a/UI/Common/UIxPageFrame.h +++ b/UI/Common/UIxPageFrame.h @@ -40,6 +40,7 @@ NSString *toolbar; id item; BOOL isPopup; + NSMutableArray *additionalCSSFiles; NSMutableArray *additionalJSFiles; } diff --git a/UI/Common/UIxPageFrame.m b/UI/Common/UIxPageFrame.m index efda984e..9aaa5df6 100644 --- a/UI/Common/UIxPageFrame.m +++ b/UI/Common/UIxPageFrame.m @@ -241,6 +241,29 @@ return ([[self productJavaScriptURL] length] > 0); } +- (void) setCssFiles: (NSString *) newCSSFiles +{ + NSEnumerator *cssFiles; + NSString *currentFile, *filename; + + [additionalCSSFiles release]; + additionalCSSFiles = [NSMutableArray new]; + + cssFiles + = [[newCSSFiles componentsSeparatedByString: @","] objectEnumerator]; + while ((currentFile = [cssFiles nextObject])) + { + filename = [self urlForResourceFilename: + [currentFile stringByTrimmingSpaces]]; + [additionalCSSFiles addObject: filename]; + } +} + +- (NSArray *) additionalCSSFiles +{ + return additionalCSSFiles; +} + - (void) setJsFiles: (NSString *) newJSFiles { NSEnumerator *jsFiles; diff --git a/UI/Common/UIxPrintPageFrame.m b/UI/Common/UIxPrintPageFrame.m deleted file mode 100644 index 48bb2da7..00000000 --- a/UI/Common/UIxPrintPageFrame.m +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright (C) 2000-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#import - -@class NSString; - -@interface UIxPrintPageFrame : SoComponent -{ - NSString *title; -} - -@end - -@implementation UIxPrintPageFrame - -- (void) dealloc -{ - [title release]; - [super dealloc]; -} - -/* accessors */ - -- (void)setTitle: (NSString *) _value -{ - ASSIGNCOPY (title, _value); -} - -- (NSString *) title -{ - return title; -} - -@end /* UIxPrintPageFrame */ diff --git a/UI/Common/UIxTabItem.m b/UI/Common/UIxTabItem.m deleted file mode 100644 index 28f40889..00000000 --- a/UI/Common/UIxTabItem.m +++ /dev/null @@ -1,475 +0,0 @@ -/* - Copyright (C) 2000-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#include "UIxTabView.h" -#include "common.h" - -#if DEBUG -# define DEBUG_JS 1 -#endif - -/* context keys */ -extern NSString *UIxTabView_HEAD; -extern NSString *UIxTabView_BODY; -extern NSString *UIxTabView_KEYS; -extern NSString *UIxTabView_SCRIPT; -extern NSString *UIxTabView_ACTIVEKEY; -extern NSString *UIxTabView_COLLECT; - -@implementation UIxTabItem - -static Class StrClass = Nil; - -+ (int)version { - return [super version] + 0; -} -+ (void)initialize { - StrClass = [NSString class]; -} - -static NSString *retStrForInt(int i) { - switch(i) { - case 0: return @"0"; - case 1: return @"1"; - case 2: return @"2"; - case 3: return @"3"; - case 4: return @"4"; - case 5: return @"5"; - case 6: return @"6"; - case 7: return @"7"; - case 8: return @"8"; - case 9: return @"9"; - case 10: return @"10"; - // TODO: find useful count! - default: - return [[StrClass alloc] initWithFormat:@"%i", i]; - } -} - -- (id)initWithName:(NSString *)_name - associations:(NSDictionary *)_config - template:(WOElement *)_subs -{ - if ((self = [super initWithName:_name associations:_config template:_subs])) { - self->key = WOExtGetProperty(_config, @"key"); - self->label = WOExtGetProperty(_config, @"label"); - - self->isScript = WOExtGetProperty(_config, @"isScript"); - self->href = WOExtGetProperty(_config, @"href"); - - self->icon = WOExtGetProperty(_config, @"icon"); - self->action = WOExtGetProperty(_config, @"action"); - - self->tabStyle = WOExtGetProperty(_config, @"tabStyle"); - self->selectedTabStyle = WOExtGetProperty(_config, @"selectedTabStyle"); - - self->tabIcon = WOExtGetProperty(_config, @"tabIcon"); - self->leftTabIcon = WOExtGetProperty(_config, @"leftTabIcon"); - self->selectedTabIcon = WOExtGetProperty(_config, @"selectedTabIcon"); - - self->asBackground = WOExtGetProperty(_config, @"asBackground"); - self->width = WOExtGetProperty(_config, @"width"); - self->height = WOExtGetProperty(_config, @"height"); - self->activeBgColor = WOExtGetProperty(_config, @"activeBgColor"); - self->inactiveBgColor = WOExtGetProperty(_config, @"inactiveBgColor"); - - self->template = [_subs retain]; - } - return self; -} - -- (void)dealloc { - [self->key release]; - [self->label release]; - - [self->href release]; - - [self->action release]; - - [self->isScript release]; - [self->template release]; - - [self->tabStyle release]; - [self->selectedTabStyle release]; - - [self->icon release]; - [self->leftTabIcon release]; - [self->selectedTabIcon release]; - [self->tabIcon release]; - - [self->asBackground release]; - [self->width release]; - [self->height release]; - - [self->activeBgColor release]; - [self->inactiveBgColor release]; - - [super dealloc]; -} - -/* responder */ - -- (void)takeValuesFromRequest:(WORequest *)_rq inContext:(WOContext *)_ctx { - NSString *activeTabKey; - NSString *myTabKey; - BOOL doCheck; - - if ([_ctx objectForKey:UIxTabView_HEAD]) { - /* head clicks */ - [[_ctx component] debugWithFormat: - @"UIxTabItem: head takes (no) values, eid='%@'", - [_ctx elementID]]; - return; - } - - if ((activeTabKey = [_ctx objectForKey:UIxTabView_BODY]) == nil) { - [[_ctx component] debugWithFormat:@"UIxTabItem: invalid state"]; - [self->template takeValuesFromRequest:_rq inContext:_ctx]; - return; - } - - myTabKey = [self->key stringValueInComponent:[_ctx component]]; - doCheck = [self->isScript boolValueInComponent:[_ctx component]]; - - if ([activeTabKey isEqualToString:myTabKey] || doCheck) { -#if ADD_OWN_ELEMENTIDS - [_ctx appendElementIDComponent:activeTabKey]; -#endif - -#if DEBUG_TAKEVALUES - [[_ctx component] debugWithFormat: - @"UIxTabItem: body takes values, eid='%@'", - [_ctx elementID]]; -#endif - - [self->template takeValuesFromRequest:_rq inContext:_ctx]; -#if ADD_OWN_ELEMENTIDS - [_ctx deleteLastElementIDComponent]; -#endif - } -#if DEBUG_TAKEVALUES - else { - [[_ctx component] debugWithFormat: - @"UIxTabItem: body takes no values, eid='%@'", - [_ctx elementID]]; - } -#endif -} - -- (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx { - id result; - WOAssociation *tmp; - NSString *activeTabKey; - - if ((tmp = [_ctx objectForKey:UIxTabView_HEAD])) { - /* click on tab icon */ - NSString *tabkey; - - tabkey = [_ctx currentElementID]; - [_ctx consumeElementID]; - [_ctx appendElementIDComponent:tabkey]; - - if ([tmp isValueSettable]) - [tmp setValue:tabkey inComponent:[_ctx component]]; - -#if 0 - result = [self->action valueInComponent:[_ctx component]]; -#endif - - [_ctx deleteLastElementIDComponent]; - } - else if ((activeTabKey = [_ctx objectForKey:UIxTabView_BODY])) { - /* clicked somewhere in the (active) body */ - result = [self->template invokeActionForRequest:_req inContext:_ctx]; - } - else { - [[_ctx component] logWithFormat:@"UIxTabItem: invalid invoke state"]; - result = [self->template invokeActionForRequest:_req inContext:_ctx]; - } - - return result; -} - -/* info collection */ - -- (void)_collectInContext:(WOContext *)_ctx key:(NSString *)k { - BOOL isLeft = NO; - NSMutableArray *keys; - UIxTabItemInfo *info; - WOComponent *cmp; - - cmp = [_ctx component]; - keys = [_ctx objectForKey:UIxTabView_KEYS]; - if (keys == nil) { - keys = [[[NSMutableArray alloc] init] autorelease]; - [_ctx setObject:keys forKey:UIxTabView_KEYS]; - isLeft = YES; - } - - if (k == nil) { - /* auto-assign a key */ - k = retStrForInt([keys count]); - } - else - k = [k retain]; - [_ctx appendElementIDComponent:k]; - - info = [[UIxTabItemInfo alloc] init]; - info->key = [k copy]; - info->label = [[self->label stringValueInComponent:cmp] copy]; - info->icon = [[self->icon stringValueInComponent:cmp] copy]; -#if 0 - info->uri = [[_ctx componentActionURL] copy]; -#else - info->uri = [[self->href stringValueInComponent:cmp] copy]; -#endif - info->isScript = [self->isScript boolValueInComponent:cmp]; - info->tabIcon = [[self->tabIcon stringValueInComponent:cmp] copy]; - info->leftIcon = [[self->leftTabIcon stringValueInComponent:cmp] copy]; - info->selIcon = [[self->selectedTabIcon stringValueInComponent:cmp] - copy]; - info->tabStyle = [[self->tabStyle stringValueInComponent:cmp] copy]; - info->selectedTabStyle = [[self->selectedTabStyle stringValueInComponent:cmp] - copy]; - - if (self->asBackground == nil) - info->asBackground = 0; - else { - info->asBackground - = ([self->asBackground boolValueInComponent:cmp]) ? 1 : -1; - } - info->width = [[self->width stringValueInComponent:cmp] copy]; - info->height = [[self->height stringValueInComponent:cmp] copy]; - info->activeBg = [[self->activeBgColor stringValueInComponent:cmp] - copy]; - info->inactiveBg = [[self->inactiveBgColor stringValueInComponent:cmp] - copy]; - - if (info->leftIcon == nil) info->leftIcon = [info->tabIcon copy]; - - [keys addObject:info]; - [info release]; - [k release]; - - [_ctx deleteLastElementIDComponent]; -} - -/* header generation */ - -- (void)_appendHeadToResponse:(WOResponse *)_response - inContext:(WOContext *)_ctx - activeKey:(NSString *)activeKey - key:(NSString *)k -{ - /* head is currently generated in UIxTabView */ -#if 0 - // note: some associations can be inherited by UIxTabView ! - BOOL doImages; - WOComponent *comp; - BOOL doBgIcon; - NSString *label; - NSString *w, *h; - - doImages = ![[[_ctx request] clientCapabilities] isTextModeBrowser]; - comp = [_ctx component]; - - doBgIcon = self->asBackground && doImages - ? [self->asBackground boolValueInComponent:comp] ? YES : NO - : NO; - - if ((label = [self->label stringValueInComponent:comp]) == nil) - label = k; - - if (doImages) { - /* lookup image */ - NSString *imgName = nil; - // ... - - imgUri = WEUriOfResource(imgName, _ctx); - if ([imgUri length] < 1) - doImages = NO; - } - - // .... _isActive -#endif -} - -/* body generation */ - -- (void)_appendBodyToResponse:(WOResponse *)_response - inContext:(WOContext *)_ctx - activeKey:(NSString *)tmp - key:(NSString *)k -{ - BOOL doScript; - BOOL isScript_; - BOOL isActive; - - doScript = [[_ctx objectForKey:UIxTabView_SCRIPT] boolValue]; - isScript_ = [self->isScript boolValueInComponent:[_ctx component]]; - isActive = [tmp isEqualToString:k]; - - if (doScript && (isActive || isScript_)) { - [_response appendContentString:@"
\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:@"
"]; - - jsout = [NSString alloc]; - jsout = [jsout initWithFormat: - @""]; - } -} - -/* master generation method */ - -- (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx { - NSString *k; - BOOL doForm; - id tmp; - - doForm = [_ctx isInForm]; - k = [self->key stringValueInComponent:[_ctx component]]; - - if ((tmp = [_ctx objectForKey:UIxTabView_HEAD])) { - if ([tmp isEqual:UIxTabView_COLLECT]) { - [self _collectInContext:_ctx key:k]; - } - else { - [self _appendHeadToResponse:_response inContext:_ctx - activeKey:tmp key:k]; - } - } - else if ((tmp = [_ctx objectForKey:UIxTabView_BODY])) { - [self _appendBodyToResponse:_response inContext:_ctx - activeKey:tmp key:k]; - } - else { - [self warnWithFormat:@"(%s): invalid UIxTabItem state !!!", - __PRETTY_FUNCTION__]; - [_response appendContentString:@"[invalid state]"]; - } -} - -@end /* UIxTabItem */ - -@implementation UIxTabItemInfo - -- (void)dealloc { - [self->uri release]; - [self->icon release]; - [self->label release]; - [self->key release]; - [self->tabStyle release]; - [self->selectedTabStyle release]; - [self->tabIcon release]; - [self->selIcon release]; - [self->leftIcon release]; - [self->width release]; - [self->height release]; - [self->activeBg release]; - [self->inactiveBg release]; - - [super dealloc]; -} - -/* accessors */ - -- (NSString *)key { - return self->key; -} -- (NSString *)label { - return self->label; -} -- (NSString *)icon { - return self->icon; -} -- (NSString *)uri { - return self->uri; -} -- (BOOL)isScript { - return self->isScript; -} - -- (int)asBackground { - return self->asBackground; -} - -- (NSString *)width { - return self->width; -} - -- (NSString *)height { - return self->height; -} - -- (NSString *)activeBg { - return self->activeBg; -} - -- (NSString *)inactiveBg { - return self->inactiveBg; -} - -@end /* UIxTabItemInfo */ diff --git a/UI/Common/UIxTabView.h b/UI/Common/UIxTabView.h deleted file mode 100644 index a2e6a1f1..00000000 --- a/UI/Common/UIxTabView.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - Copyright (C) 2000-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#ifndef __UIxTabView_H__ -#define __UIxTabView_H__ - -/* - This is a library private header ! -*/ - -#include - -/* - Does not support tab-head-creation from nested components !!! - - hh: Why not ??? -> Because selection is manipulated in sub-elements - - UIxTabView creates element-IDs like - - .h.*.$key. for the tab-items (head-mode) - .b.$key... for the tab-content (content-mode) (new, hh) - - !!! UIxTabView JavaScript can't handle duplicate tab-keys !!! -*/ - -@interface UIxTabView : WODynamicElement -{ - WOAssociation *selection; - - /* config: */ - WOAssociation *headerStyle; - WOAssociation *bodyStyle; - WOAssociation *tabStyle; - WOAssociation *selectedTabStyle; - - /* old config: */ - WOAssociation *bgColor; - WOAssociation *nonSelectedBgColor; - WOAssociation *leftCornerIcon; - WOAssociation *rightCornerIcon; - - WOAssociation *tabIcon; - WOAssociation *leftTabIcon; - WOAssociation *selectedTabIcon; - - WOAssociation *asBackground; - WOAssociation *width; - WOAssociation *height; - WOAssociation *activeBgColor; - WOAssociation *inactiveBgColor; - - WOAssociation *fontColor; - WOAssociation *fontSize; - WOAssociation *fontFace; - - id template; -} - -@end - -@interface UIxTabItem : WODynamicElement -{ - WOAssociation *key; - WOAssociation *label; - - WOAssociation *href; - WOAssociation *isScript; - - WOAssociation *action; - WOAssociation *icon; - - /* config: */ - WOAssociation *tabStyle; - WOAssociation *selectedTabStyle; - - /* old config */ - WOAssociation *tabIcon; - WOAssociation *leftTabIcon; - WOAssociation *selectedTabIcon; - - WOAssociation *asBackground; - WOAssociation *width; - WOAssociation *height; - WOAssociation *activeBgColor; - WOAssociation *inactiveBgColor; - - id template; -} - -@end - -@interface UIxTabItemInfo : NSObject -{ -@public - NSString *label; - NSString *icon; - NSString *key; - NSString *uri; - NSString *tabIcon; - NSString *leftIcon; - NSString *selIcon; - NSString *tabStyle; - NSString *selectedTabStyle; - - int asBackground; // 0 -> not set, 1 -> YES, else -> NO - NSString *width; - NSString *height; - NSString *activeBg; - NSString *inactiveBg; - - BOOL isScript; -} -@end - -#endif /* __UIxTabView_H__ */ diff --git a/UI/Common/UIxTabView.m b/UI/Common/UIxTabView.m deleted file mode 100644 index 6bc96ed6..00000000 --- a/UI/Common/UIxTabView.m +++ /dev/null @@ -1,684 +0,0 @@ -/* - Copyright (C) 2000-2004 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#include "UIxTabView.h" -#include "common.h" -#include -#include -#include -#include - -#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:@"']; - - [_response appendContentString:@"key]]; - [_response appendContentString:@">"]; - - if ([label length] < 1) - label = _info->key; - [_response appendContentString:@""]; - [_response appendContentHTMLString:label]; - [_response appendContentString:@""]; - - [_response appendContentString:@""]; - - [_response appendContentString:@""]; -} - -- (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: - @""]; -} - -- (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:@""]; - - styleName = [self->headerStyle stringValueInComponent:[_ctx component]]; - if(styleName) { - [_response appendContentString: - @""]; - } - else { - [_response appendContentString: - @"
"]; - } - - 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:@""]; - [_response appendContentString:@"
"]; - [_response appendContentString:@""]; -} - -- (void)_appendHeaderFootRowToResponse:(WOResponse *)_response - inContext:(WOContext *)_ctx - bgcolor:(NSString *)bgcolor - doScript:(BOOL)doScript - isLeftActive:(BOOL)isLeftActive -{ - NSString *styleName; - [_response appendContentString:@" 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:@" "]; - - if (isLeftActive) - [_response appendContentString:@" "]; - - if (!isLeftActive) { - NSString *uri; - - uri = [self->leftCornerIcon stringValueInComponent:[_ctx component]]; - if ((uri = WEUriOfResource(uri, _ctx))) { - [_response appendContentString:@"\"\""]; - } - else - [_response appendContentString:@" "]; - } - - [_response appendContentString:@""]; - - /* right corner */ - [_response appendContentString:@" "]; - { - NSString *uri; - - uri = [self->rightCornerIcon stringValueInComponent:[_ctx component]]; - if ((uri = WEUriOfResource(uri, _ctx))) { - [_response appendContentString:@"\"\""]; - } - else - [_response appendContentString:@" "]; - } - [_response appendContentString:@"\n"]; - - [_response appendContentString:@" \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:@"']; - - if (indentContent) { - /* start padding table */ - [_response appendContentString: - @""]; - [_response appendContentString:@"
"]; - } - - [_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:@"
"]; - - [_response appendContentString:@""]; -} - -- (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: - @""]; - - /* 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:@"
"]; - [_ctx removeObjectForKey:UIxTabView_ACTIVEKEY]; - [_ctx removeObjectForKey:UIxTabView_KEYS]; - [self restoreNestedState:nestedState inContext:_ctx]; -} - -@end /* UIxTabView */ diff --git a/UI/Contacts/UIxContactEditor.m b/UI/Contacts/UIxContactEditor.m index 40842b64..f36e2bc8 100644 --- a/UI/Contacts/UIxContactEditor.m +++ b/UI/Contacts/UIxContactEditor.m @@ -560,13 +560,13 @@ 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 diff --git a/UI/MailPartViewers/English.lproj/Localizable.strings b/UI/MailPartViewers/English.lproj/Localizable.strings index 5fadd32d..78f7a101 100644 --- a/UI/MailPartViewers/English.lproj/Localizable.strings +++ b/UI/MailPartViewers/English.lproj/Localizable.strings @@ -19,6 +19,7 @@ Attendees = "Attendees"; request_info = "invites you to participate in a meeting."; "Add to calendar" = "Add to calendar"; "Delete from calendar" = "Delete from calendar"; +"Update status" = "Update status"; Accept = "Accept"; Decline = "Decline"; Tentative = "Tentative"; diff --git a/UI/MailPartViewers/French.lproj/Localizable.strings b/UI/MailPartViewers/French.lproj/Localizable.strings index b5db9cec..e1268352 100644 --- a/UI/MailPartViewers/French.lproj/Localizable.strings +++ b/UI/MailPartViewers/French.lproj/Localizable.strings @@ -19,6 +19,7 @@ Attendees = "Invités"; request_info = "vous invite à une réunion."; "Add to calendar" = "Ajouter à l'agenda"; "Delete from calendar" = "Effacer de l'agenda"; +"Update status" = "Intégrer les modifications"; Accept = "Accepter"; Decline = "Decliner"; Tentative = "Tentative"; diff --git a/UI/MailPartViewers/German.lproj/Localizable.strings b/UI/MailPartViewers/German.lproj/Localizable.strings index b96425eb..0dbc7844 100644 --- a/UI/MailPartViewers/German.lproj/Localizable.strings +++ b/UI/MailPartViewers/German.lproj/Localizable.strings @@ -19,6 +19,7 @@ Attendees = "Attendees"; request_info = "invites you to participate in a meeting."; "Add to calendar" = "Add to calendar"; "Delete from calendar" = "Delete from calendar"; +"Update status" = "Update status"; Accept = "Accept"; Decline = "Decline"; Tentative = "Tentative"; diff --git a/UI/MailPartViewers/UIxMailPartICalActions.m b/UI/MailPartViewers/UIxMailPartICalActions.m index c4c8cb78..e51f518c 100644 --- a/UI/MailPartViewers/UIxMailPartICalActions.m +++ b/UI/MailPartViewers/UIxMailPartICalActions.m @@ -26,12 +26,17 @@ #import #import +#import #import #import +#import + +#import #import #import +#import #import #import #import @@ -58,12 +63,12 @@ } - (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]]) @@ -73,6 +78,11 @@ return eventObject; } +- (SOGoAppointmentObject *) _eventObjectWithUID: (NSString *) uid +{ + return [self _eventObjectWithUID: uid forUser: [context activeUser]]; +} + - (iCalEvent *) _setupChosenEventAndEventObject: (SOGoAppointmentObject **) eventObject { @@ -86,7 +96,7 @@ chosenEvent = emailEvent; else { - calendarEvent = (iCalEvent *) [*eventObject component: NO]; + calendarEvent = (iCalEvent *) [*eventObject component: NO secure: NO]; if ([calendarEvent compare: emailEvent] == NSOrderedAscending) chosenEvent = emailEvent; else @@ -99,14 +109,42 @@ 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) @@ -114,7 +152,8 @@ 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: @""]; @@ -123,8 +162,12 @@ 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 @@ -146,6 +189,97 @@ 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"]; diff --git a/UI/MailPartViewers/UIxMailPartICalViewer.h b/UI/MailPartViewers/UIxMailPartICalViewer.h index 98d75abe..0728008b 100644 --- a/UI/MailPartViewers/UIxMailPartICalViewer.h +++ b/UI/MailPartViewers/UIxMailPartICalViewer.h @@ -24,10 +24,12 @@ #import "UIxMailPartViewer.h" -@class SOGoDateFormatter; @class iCalEvent; @class iCalCalendar; +@class SOGoAppointmentObject; +@class SOGoDateFormatter; + @interface UIxMailPartICalViewer : UIxMailPartViewer { iCalCalendar *inCalendar; @@ -35,7 +37,7 @@ id attendee; SOGoDateFormatter *dateFormatter; id item; - id storedEventObject; + SOGoAppointmentObject *storedEventObject; iCalEvent *storedEvent; } diff --git a/UI/MailPartViewers/UIxMailPartICalViewer.m b/UI/MailPartViewers/UIxMailPartICalViewer.m index 56df0e45..b30a8817 100644 --- a/UI/MailPartViewers/UIxMailPartICalViewer.m +++ b/UI/MailPartViewers/UIxMailPartICalViewer.m @@ -28,7 +28,6 @@ #import #import -#import #import #import @@ -40,9 +39,11 @@ #import #import +#import #import #import #import +#import #import "UIxMailPartICalViewer.h" @@ -109,35 +110,32 @@ - (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; } @@ -146,7 +144,7 @@ - (void) setAttendee: (id) _attendee { - ASSIGN(attendee, _attendee); + ASSIGN (attendee, _attendee); } - (id) attendee @@ -220,7 +218,7 @@ /* calendar folder support */ -- (id) calendarFolder +- (SOGoAppointmentFolder *) calendarFolder { /* return scheduling calendar of currently logged-in user */ SOGoUser *user; @@ -234,49 +232,50 @@ 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 */ @@ -294,34 +293,24 @@ { 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 */ @@ -405,7 +394,34 @@ - (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 */ diff --git a/UI/MailPartViewers/UIxMailRenderingContext.m b/UI/MailPartViewers/UIxMailRenderingContext.m index e60be0bb..11d289f3 100644 --- a/UI/MailPartViewers/UIxMailRenderingContext.m +++ b/UI/MailPartViewers/UIxMailRenderingContext.m @@ -29,6 +29,25 @@ #import "UIxMailRenderingContext.h" +@interface UIxMailRenderingContext (Private) + +- (BOOL) _shouldDisplayAsAttachment: (NSDictionary *) info; + +@end + +@implementation UIxMailRenderingContext (Private) + +- (BOOL) _shouldDisplayAsAttachment: (NSDictionary *) info +{ + NSString *s; + + s = [[info objectForKey:@"disposition"] objectForKey: @"type"]; + + return (s && [s caseInsensitiveCompare: @"ATTACHMENT"] == NSOrderedSame); +} + +@end + @implementation UIxMailRenderingContext static BOOL showNamedTextAttachmentsInline = NO; @@ -235,13 +254,12 @@ static BOOL showNamedTextAttachmentsInline = NO; if ([mt isEqualToString:@"multipart"]) { - if ([st isEqualToString:@"mixed"]) + if ([st isEqualToString:@"mixed"] || [st isEqualToString:@"related"]) return [self mixedViewer]; else if ([st isEqualToString:@"signed"]) return [self signedViewer]; - else if ([st isEqualToString:@"alternative"] - || [st isEqualToString:@"related"]) - return [self alternativeViewer]; + else if ([st isEqualToString:@"alternative"]) + return [self alternativeViewer]; if ([st isEqualToString:@"report"]) /* this is used by mail-delivery reports */ @@ -249,19 +267,9 @@ static BOOL showNamedTextAttachmentsInline = NO; } else if ([mt isEqualToString:@"text"]) { - /* - Note: in the _info dictionary we do not get the content-disposition - information (inline vs attachment). Our hack is to check for the - 'name' parameter. - */ if ([st isEqualToString:@"plain"] || [st isEqualToString:@"html"]) { - if (!showNamedTextAttachmentsInline) { - NSString *n; - - n = [[_info objectForKey:@"parameterList"] objectForKey:@"name"]; - if ([n isNotNull] && [n length] > 0) - return [self linkViewer]; - } + if (!showNamedTextAttachmentsInline && [self _shouldDisplayAsAttachment: _info]) + return [self linkViewer]; return [st isEqualToString:@"html"] ? [self htmlViewer] : [self textViewer]; @@ -269,10 +277,15 @@ static BOOL showNamedTextAttachmentsInline = NO; if ([st isEqualToString:@"calendar"]) return [self iCalViewer]; - } + } if ([mt isEqualToString:@"image"]) - return [self imageViewer]; + { + if ([self _shouldDisplayAsAttachment: _info]) + return [self linkViewer]; + + return [self imageViewer]; + } if ([mt isEqualToString:@"message"] && [st isEqualToString:@"rfc822"]) return [self messageViewer]; diff --git a/UI/MailPartViewers/product.plist b/UI/MailPartViewers/product.plist index 31e8b466..ddf40447 100644 --- a/UI/MailPartViewers/product.plist +++ b/UI/MailPartViewers/product.plist @@ -20,6 +20,11 @@ actionClass = "UIxMailPartICalActions"; actionName = "decline"; }; + updateUserStatus = { + protectedBy = "View"; + actionClass = "UIxMailPartICalActions"; + actionName = "updateUserStatus"; + }; /* tentative = { protectedBy = "View"; actionClass = "UIxMailPartICalAction"; @@ -29,12 +34,12 @@ protectedBy = "View"; actionClass = "UIxMailPartICalAction"; actionName = "addToCalendar"; - }; + }; */ deleteFromCalendar = { protectedBy = "View"; actionClass = "UIxMailPartICalAction"; actionName = "deleteFromCalendar"; - }; */ + }; }; }; }; diff --git a/UI/MailerUI/Toolbars/SOGoMailObject.toolbar b/UI/MailerUI/Toolbars/SOGoMailObject.toolbar index a2ba2dbe..804c01e1 100644 --- a/UI/MailerUI/Toolbars/SOGoMailObject.toolbar +++ b/UI/MailerUI/Toolbars/SOGoMailObject.toolbar @@ -56,12 +56,12 @@ 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 = "#"; diff --git a/UI/MailerUI/UIxMailToSelection.m b/UI/MailerUI/UIxMailToSelection.m index cdcc83bf..c241105e 100644 --- a/UI/MailerUI/UIxMailToSelection.m +++ b/UI/MailerUI/UIxMailToSelection.m @@ -64,8 +64,6 @@ - (void) setBcc: (NSArray *) _bcc; - (NSArray *) bcc; -- (NSArray *) properlySplitAddresses: (NSArray *) _addresses; - - (void) getAddressesFromFormValues: (NSDictionary *) _dict; - (NSString *) getIndexFromIdentifier: (NSString *) _identifier; @@ -108,7 +106,7 @@ static NSArray *headers = nil; - (void) setTo: (NSArray *) _to { - ASSIGN (to, [self properlySplitAddresses: _to]); + ASSIGN (to, _to); } - (NSArray *) to @@ -127,7 +125,7 @@ static NSArray *headers = nil; - (void) setCc: (NSArray *) _cc { - ASSIGN (cc, [self properlySplitAddresses: _cc]); + ASSIGN (cc, _cc); } - (NSArray *) cc @@ -137,7 +135,7 @@ static NSArray *headers = nil; - (void) setBcc: (NSArray *) _bcc { - ASSIGN (bcc, [self properlySplitAddresses: _bcc]); + ASSIGN (bcc, _bcc); } - (NSArray *) bcc @@ -180,7 +178,7 @@ static NSArray *headers = nil; NSMutableArray *ma; ma = [NSMutableArray arrayWithCapacity:3]; - if ([to isNotNull]) + if ([to isNotNull] && [to count] > 0) [ma addObject: to]; if ([cc isNotNull]) [ma addObject: cc]; @@ -237,52 +235,6 @@ static NSArray *headers = nil; return @""; } -/* address handling */ - -- (NSArray *) properlySplitAddresses: (NSArray *) _addresses -{ - NSString *addrs; - NGMailAddressParser *parser; - NSArray *result; - NSMutableArray *ma; - unsigned i, count; - - if (!_addresses || [_addresses count] == 0) - return nil; - - /* create one huge string, then split it using the parser */ - addrs = [_addresses componentsJoinedByString:@","]; - parser = [NGMailAddressParser mailAddressParserWithString:addrs]; - result = [parser parseAddressList]; - if(result == nil) { - [self debugWithFormat:@"Couldn't parse given addresses:%@", _addresses]; - return _addresses; - } - - count = [result count]; - ma = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) { - NGMailAddress *addr; - NSMutableString *s; - BOOL hasName = NO; - - s = [[NSMutableString alloc] init]; - addr = [result objectAtIndex:i]; - if([addr displayName]) { - [s appendString:[addr displayName]]; - [s appendString:@" "]; - hasName = YES; - } - if(hasName) - [s appendString:@"<"]; - [s appendString:[addr address]]; - if(hasName) - [s appendString:@">"]; - [ma addObject:s]; - } - return ma; -} - /* handling requests */ - (void) _fillAddresses: (NSMutableArray *) addresses diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index 444e345b..be23c936 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -264,7 +264,7 @@ unsigned int minutes; iCalRecurrenceRule *rule; - event = (iCalEvent *) [[self clientObject] component: NO]; + event = (iCalEvent *) [[self clientObject] component: NO secure: YES]; if (event) { startDate = [event startDate]; @@ -352,16 +352,7 @@ - (id ) 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()"]; } @@ -385,7 +376,7 @@ iCalRecurrenceRule *rule; clientObject = [self clientObject]; - event = (iCalEvent *) [clientObject component: YES]; + event = (iCalEvent *) [clientObject component: YES secure: NO]; [super takeValuesFromRequest: _rq inContext: _ctx]; @@ -443,23 +434,18 @@ // 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 diff --git a/UI/Scheduler/UIxComponentEditor.h b/UI/Scheduler/UIxComponentEditor.h index 8eec9b4d..e9e478c6 100644 --- a/UI/Scheduler/UIxComponentEditor.h +++ b/UI/Scheduler/UIxComponentEditor.h @@ -59,6 +59,7 @@ NSString *componentOwner; NSString *attendeesNames; + NSString *attendeesUIDs; NSString *attendeesEmails; } @@ -107,6 +108,9 @@ - (void) setAttendeesNames: (NSString *) newAttendeesNames; - (NSString *) attendeesNames; +- (void) setAttendeesUIDs: (NSString *) newAttendeesUIDs; +- (NSString *) attendeesUIDs; + - (void) setAttendeesEmails: (NSString *) newAttendeesEmails; - (NSString *) attendeesEmails; diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index 5f108ba8..04e4ee1a 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -41,10 +41,12 @@ #import #import +#import #import #import #import #import +#import #import #import #import @@ -65,6 +67,7 @@ componentOwner = @""; organizer = nil; attendeesNames = nil; + attendeesUIDs = nil; attendeesEmails = nil; calendarList = nil; } @@ -86,6 +89,7 @@ [cycleEnd release]; [url release]; [attendeesNames release]; + [attendeesUIDs release]; [attendeesEmails release]; [calendarList release]; @@ -96,10 +100,14 @@ { 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]; @@ -107,12 +115,18 @@ { [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]); } @@ -232,6 +246,16 @@ return url; } +- (BOOL) hasOrganizer +{ + return (![organizer isVoid]); +} + +- (NSString *) organizerName +{ + return [organizer mailAddress]; +} + - (void) setAttendeesNames: (NSString *) newAttendeesNames { ASSIGN (attendeesNames, newAttendeesNames); @@ -242,6 +266,16 @@ return attendeesNames; } +- (void) setAttendeesUIDs: (NSString *) newAttendeesUIDs +{ + ASSIGN (attendeesUIDs, newAttendeesUIDs); +} + +- (NSString *) attendeesUIDs +{ + return attendeesUIDs; +} + - (void) setAttendeesEmails: (NSString *) newAttendeesEmails { ASSIGN (attendeesEmails, newAttendeesEmails); @@ -671,29 +705,8 @@ 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]]); @@ -822,6 +835,7 @@ [currentAttendee setCn: [names objectAtIndex: count]]; [currentAttendee setEmail: currentEmail]; [currentAttendee setRole: @"REQ-PARTICIPANT"]; + [currentAttendee setRsvp: @"TRUE"]; [currentAttendee setParticipationStatus: iCalPersonPartStatNeedsAction]; } diff --git a/UI/Scheduler/UIxTaskEditor.m b/UI/Scheduler/UIxTaskEditor.m index 3f605482..41c39fc9 100644 --- a/UI/Scheduler/UIxTaskEditor.m +++ b/UI/Scheduler/UIxTaskEditor.m @@ -282,7 +282,7 @@ NSString *duration; unsigned int minutes; - todo = (iCalToDo *) [[self clientObject] component: NO]; + todo = (iCalToDo *) [[self clientObject] component: NO secure: YES]; if (todo) { startDate = [todo startDate]; @@ -345,16 +345,23 @@ - (id ) 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 ) 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 { @@ -372,7 +379,7 @@ SOGoTaskObject *clientObject; clientObject = [self clientObject]; - todo = (iCalToDo *) [clientObject component: YES]; + todo = (iCalToDo *) [clientObject component: YES secure: NO]; [super takeValuesFromRequest: _rq inContext: _ctx]; @@ -428,7 +435,7 @@ NSString *newStatus, *iCalString; clientObject = [self clientObject]; - todo = (iCalToDo *) [clientObject component: NO]; + todo = (iCalToDo *) [clientObject component: NO secure: NO]; if (todo) { newStatus = [self queryParameterForKey: @"status"]; @@ -441,7 +448,7 @@ [todo setStatus: @"IN-PROCESS"]; } - iCalString = [[clientObject calendar: NO] versitString]; + iCalString = [[clientObject calendar: NO secure: NO] versitString]; [clientObject saveContentString: iCalString]; } diff --git a/UI/Templates/ContactsUI/UIxContactEditor.wox b/UI/Templates/ContactsUI/UIxContactEditor.wox index fa69e46d..96894d14 100644 --- a/UI/Templates/ContactsUI/UIxContactEditor.wox +++ b/UI/Templates/ContactsUI/UIxContactEditor.wox @@ -335,18 +335,18 @@
- +
diff --git a/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox b/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox index 87b072da..948ee3a6 100644 --- a/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox +++ b/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox @@ -9,6 +9,9 @@ > + +
Parsing Error @@ -33,23 +36,24 @@ - + -

- - - - - - | - -

+ +

+ + + + + | + +

+

@@ -70,20 +74,20 @@ - +

-

- -

- +

+ +

+

@@ -96,7 +100,7 @@ - + @@ -113,13 +117,13 @@ - +

- +

@@ -145,7 +149,6 @@ -->
-
diff --git a/UI/Templates/SchedulerUI/UIxComponentEditor.wox b/UI/Templates/SchedulerUI/UIxComponentEditor.wox index 16ad19fc..5a4d7042 100644 --- a/UI/Templates/SchedulerUI/UIxComponentEditor.wox +++ b/UI/Templates/SchedulerUI/UIxComponentEditor.wox @@ -11,6 +11,7 @@ const:popup="YES" title="name" var:toolbar="toolbar" + const:cssFiles="UIxComponentEditor.css" const:jsFiles="skycalendar.js,UIxComponentEditor.js"> + diff --git a/UI/Templates/UIxPrintPageFrame.wox b/UI/Templates/UIxPrintPageFrame.wox deleted file mode 100644 index 36bd9bed..00000000 --- a/UI/Templates/UIxPrintPageFrame.wox +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - <var:string value="title"/> - - - - - - - - - - - - - \ No newline at end of file diff --git a/UI/Templates/UIxToolbar.wox b/UI/Templates/UIxToolbar.wox index 3fd5439d..d38ebfe2 100644 --- a/UI/Templates/UIxToolbar.wox +++ b/UI/Templates/UIxToolbar.wox @@ -10,13 +10,12 @@ 0) { + for (var i = 0; i < rowIds.length; i++) { + var url; + var rowId = rowIds[i].substr(4); + var messageId = currentMailbox + "/" + rowId; + url = ApplicationBaseURL + messageId + "/trash"; + deleteMessageRequestCount++; + var data = { "id": rowId, "mailbox": currentMailbox, "messageId": messageId }; + triggerAjaxRequest(url, deleteSelectedMessagesCallback, data); + } + } else { + window.alert(labels["Please select a message."]); } return false; @@ -751,6 +755,7 @@ function configureiCalLinksInMessage() { var buttons = { "iCalendarAccept": "accept", "iCalendarDecline": "decline", "iCalendarTentative": "tentative", + "iCalendarUpdateUserStatus": "updateUserStatus", "iCalendarAddToCalendar": "addToCalendar", "iCalendarDeleteFromCalendar": "deleteFromCalendar" }; @@ -768,11 +773,13 @@ function onICalendarButtonClick(event) { var link = $("iCalendarAttachment").value; if (link) { var urlstr = link + "/" + this.action; + log ("click: " + urlstr); triggerAjaxRequest(urlstr, ICalendarButtonCallback, currentMailbox + "/" + currentMessages[currentMailbox]); - window.alert(urlstr); } + else + log("no link"); } function ICalendarButtonCallback(http) { @@ -1170,7 +1177,7 @@ function openInbox(node) { } function initMailer(event) { - if (!document.body.hasClassName("popup")) { + if (!$(document.body).hasClassName("popup")) { // initDnd(); initMailboxTree(); initMessageCheckTimer(); @@ -1652,7 +1659,7 @@ function getMenus() { return menus; } -addEvent(window, 'load', initMailer); +FastInit.addOnLoad(initMailer); function Mailbox(type, name) { this.type = type; diff --git a/UI/WebServerResources/SOGoRootPage.js b/UI/WebServerResources/SOGoRootPage.js index 6f4392d0..cad1169e 100644 --- a/UI/WebServerResources/SOGoRootPage.js +++ b/UI/WebServerResources/SOGoRootPage.js @@ -38,4 +38,4 @@ function onLoginCallback(http) { } } -addEvent(window, 'load', initLogin); +FastInit.addOnLoad(initLogin); diff --git a/UI/WebServerResources/SchedulerUI.css b/UI/WebServerResources/SchedulerUI.css index cd70d984..98a683c8 100644 --- a/UI/WebServerResources/SchedulerUI.css +++ b/UI/WebServerResources/SchedulerUI.css @@ -261,10 +261,12 @@ TABLE#dateSelectorTable { background-color: #deebf7; border: 1px solid #deebf7; } +#dateSelector TD.dayOfToday._selected +{ background-color: #4b6983; } + #dateSelector TD._selected A { color: #fff; } - TABLE#eventsList { width: 100%; } @@ -283,10 +285,8 @@ TABLE#eventsList TH ._unfocused#dateSelector TD._selected, UL._unfocused > LI._selected, TABLE._unfocused#eventsList TR._selected TD -{ - background-color: #d4d0c8 !important; - color: #fff !important; -} +{ background-color: #d4d0c8 !important; + color: #fff !important; } SPAN.dayCellLabel { color: #77a; diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index d68fa0c7..31f3f738 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -452,7 +452,7 @@ function restoreCurrentDaySelection(div) { } function changeDateSelectorDisplay(day, keepCurrentDay) { - var url = ApplicationBaseURL + "/dateselector"; + var url = ApplicationBaseURL + "dateselector"; if (day) url += "?day=" + day; @@ -480,7 +480,7 @@ function changeDateSelectorDisplay(day, keepCurrentDay) { } function changeCalendarDisplay(data, newView) { - var url = ApplicationBaseURL + "/" + ((newView) ? newView : currentView); + var url = ApplicationBaseURL + ((newView) ? newView : currentView); selectedCalendarCell = null; @@ -493,9 +493,55 @@ function changeCalendarDisplay(data, newView) { if (!day) day = currentDay; - if (day) - url += "?day=" + day; + if (day) { + var divs = $$('div.day[day='+day+']'); + if (divs.length > 0) { + // Don't reload the view if the event is present in current view + + // Find day number + var dayNumber; + var classes = $w(divs[0].className); + for (var i = 0; i < classes.length; i++) { + if (classes[i] == 'day') + continue; + if (classes[i] == 'selectedDay') + break; + dayNumber = classes[i]; + break; + } + + // Deselect previous day + var selectedDivs = $$('div.day.selectedDay'); + selectedDivs.each(function(div) { + div.removeClassName('selectedDay'); + }); + + // Select new day + selectedDivs = $$('div.day.'+dayNumber); + selectedDivs.each(function(div) { + div.addClassName('selectedDay'); + }); + + // Deselect day in date selector + if (document.selectedDate) + document.selectedDate.deselect(); + + // Select day in date selector + var selectedLink = $$('table#dateSelectorTable a[day='+day+']'); + if (selectedLink.length > 0) { + selectedCell = selectedLink[0].up(1); + selectedCell.select(); + document.selectedDate = selectedCell; + } + + // Scroll to event + scrollDayView(scrollEvent); + + return false; + } + url += "?day=" + day; + } // if (newView) // log ("switching to view: " + newView); // log ("changeCalendarDisplay: " + url); @@ -538,18 +584,24 @@ function onMonthOverview() { } function scrollDayView(scrollEvent) { + + if (currentView == "monthview") + return; + var offset = 0; var daysView = $("daysView"); var hours = $(daysView.childNodesWithTag("div")[0]).childNodesWithTag("div"); - if (scrollEvent && scrollEvent.siblings) { - var classes = scrollEvent.siblings[0].getAttribute("class").split(" "); - for (var i = 0; i < classes.length; i++) + if (scrollEvent) { + var divs = $$("div#calendarContent div." + eventClass(scrollEvent)); + var classes = $w(divs[0].className); + for (var i = 0; i < classes.length; i++) { if (classes[i].startsWith("starts")) { var starts = Math.floor(parseInt(classes[i].substr(6)) / 4); offset = hours[starts].offsetTop; } + } } else offset = hours[8].offsetTop; @@ -564,7 +616,7 @@ function onClickableCellsDblClick(event) { event.returnValue = false; } -function refreshCalendarEvents() { +function refreshCalendarEvents(scrollEvent) { var todayDate = new Date(); var sd; var ed; @@ -610,7 +662,7 @@ function refreshCalendarEvents() { var url = ApplicationBaseURL + "/eventslist?sd=" + sd + "&ed=" + ed; document.refreshCalendarEventsAjaxRequest = triggerAjaxRequest(url, refreshCalendarEventsCallback, - {"startDate": sd, "endDate": ed}); + {"startDate": sd, "endDate": ed, "scrollEvent": scrollEvent}); } function refreshCalendarEventsCallback(http) { @@ -625,6 +677,8 @@ function refreshCalendarEventsCallback(http) { http.callbackData["startDate"], http.callbackData["endDate"]); } + if (http.callbackData["scrollEvent"]) + scrollDayView(http.callbackData["scrollEvent"]); } else log("AJAX error when refreshing calendar events"); @@ -655,7 +709,6 @@ function drawCalendarEvent(eventData, sd, ed) { var startHour = null; var endHour = null; - var siblings = new Array(); for (var i = 0; i < days.length; i++) if (days[i].earlierDate(viewStartDate) == viewStartDate && days[i].laterDate(viewEndDate) == viewEndDate) { @@ -687,8 +740,6 @@ function drawCalendarEvent(eventData, sd, ed) { var eventDiv = newEventDIV(eventData[0], eventData[1], starts, lasts, null, null, title); - siblings.push(eventDiv); - eventDiv.siblings = siblings; var dayString = days[i].getDayString(); // log("day: " + dayString); var parentDiv = null; @@ -731,18 +782,20 @@ function drawCalendarEvent(eventData, sd, ed) { if (parentDiv) parentDiv.appendChild(eventDiv); } +} - var eventTR = $(eventData[0]); - if (eventTR) - eventTR.siblings = siblings; +function eventClass(cname) { + return escape(cname.replace(".", "-")); } + function newEventDIV(cname, calendar, starts, lasts, startHour, endHour, title) { var eventDiv = document.createElement("div"); eventDiv.cname = escape(cname); eventDiv.calendar = calendar; $(eventDiv).addClassName("event"); + $(eventDiv).addClassName(eventClass(cname)); $(eventDiv).addClassName("starts" + starts); $(eventDiv).addClassName("lasts" + lasts); for (var i = 1; i < 5; i++) { @@ -800,12 +853,11 @@ function calendarDisplayCallback(http) { var contentView; if (currentView == "monthview") contentView = $("calendarContent"); - else { - var scrollEvent = http.callbackData.scrollEvent; - scrollDayView($(scrollEvent)); + else contentView = $("daysView"); - } - refreshCalendarEvents(); + + refreshCalendarEvents(http.callbackData.scrollEvent); + var days = document.getElementsByClassName("day", contentView); if (currentView == "monthview") for (var i = 0; i < days.length; i++) { @@ -980,9 +1032,9 @@ function onListFilterChange() { function onEventClick(event) { changeCalendarDisplay( { "day": this.day, - "scrollEvent": this.getAttribute("id") } ); + "scrollEvent": this.getAttribute("id") } ); changeDateSelectorDisplay(this.day); - + return onRowClick(event); } @@ -1660,4 +1712,4 @@ function initCalendars() { } } -addEvent(window, 'load', initCalendars); +FastInit.addOnLoad(initCalendars); diff --git a/UI/WebServerResources/UIxAclEditor.js b/UI/WebServerResources/UIxAclEditor.js index 4bce6213..2aee3cfc 100644 --- a/UI/WebServerResources/UIxAclEditor.js +++ b/UI/WebServerResources/UIxAclEditor.js @@ -142,4 +142,4 @@ function onAclLoadHandler() { this.userRightsWidth = window.opener.getUsersRightsWindowWidth(); } -addEvent(window, 'load', onAclLoadHandler); +FastInit.addOnLoad(onAclLoadHandler); diff --git a/UI/WebServerResources/UIxAppointmentEditor.js b/UI/WebServerResources/UIxAppointmentEditor.js index 3fbb5758..6dd1cd4a 100644 --- a/UI/WebServerResources/UIxAppointmentEditor.js +++ b/UI/WebServerResources/UIxAppointmentEditor.js @@ -286,4 +286,4 @@ function onAppointmentEditorLoad() { initTimeWidgets(widgets); } -addEvent(window, 'load', onAppointmentEditorLoad); +FastInit.addOnLoad(onAppointmentEditorLoad); diff --git a/UI/WebServerResources/UIxAttendeesEditor.js b/UI/WebServerResources/UIxAttendeesEditor.js index dff86b27..beed7bae 100644 --- a/UI/WebServerResources/UIxAttendeesEditor.js +++ b/UI/WebServerResources/UIxAttendeesEditor.js @@ -1,9 +1,9 @@ var resultsDiv; -var searchField; var running = false; var address; var delay = 500; var requestField; +var searchField; var awaitingFreeBusyRequests = new Array(); var additionalDays = 2; @@ -11,6 +11,7 @@ var dayStartHour = 8; var dayEndHour = 18; var attendeesNames; +var attendeesUIDs; var attendeesEmails; function onContactKeydown(event) { @@ -57,16 +58,17 @@ function onContactKeydown(event) { } function triggerRequest() { - if (document.contactLookupAjaxRequest) { - document.contactLookupAjaxRequest.aborted = yes; - document.contactLookupAjaxRequest.abort(); + if (requestField) { + if (document.contactLookupAjaxRequest) { + document.contactLookupAjaxRequest.aborted = yes; + document.contactLookupAjaxRequest.abort(); + } + var urlstr = ( UserFolderURL + "Contacts/contactSearch?search=" + + escape(requestField.value) ); + document.contactLookupAjaxRequest = triggerAjaxRequest(urlstr, + updateResults, + requestField); } - var urlstr = ( UserFolderURL + "Contacts/contactSearch?search=" - + escape(requestField.value) ); - //log (urlstr); - document.contactLookupAjaxRequest = triggerAjaxRequest(urlstr, - updateResults, - requestField); } function updateResults(http) { @@ -74,8 +76,9 @@ function updateResults(http) { var menu = $('attendeesMenu'); var list = menu.down("ul"); - searchField = http.callbackData; + searchField = http.callbackData; // requestField searchField.hasfreebusy = false; + searchField.setAttribute("uid", null); if (http.status == 200) { var start = searchField.value.length; @@ -123,9 +126,7 @@ function updateResults(http) { if (data.length == 1) { var contact = data[0]; if (contact["uid"].length > 0) - searchField.uid = contact["uid"]; - else - searchField.uid = null; + searchField.setAttribute("uid", contact["uid"]); var completeEmail = contact["name"] + " <" + contact["email"] + ">"; if (contact["name"].substring(0, searchField.value.length).toUpperCase() == searchField.value.toUpperCase()) @@ -153,29 +154,13 @@ function updateResults(http) { function onAttendeeResultClick(event) { if (searchField) { - searchField.uid = this.uid; + searchField.setAttribute("uid", this.getAttribute("uid")); searchField.value = this.firstChild.nodeValue.trim(); searchField.confirmedValue = searchField.value; searchField.blur(); // triggers checkAttendee function call } } -function UIDLookupCallback(http) { - if (http.readyState == 4) { - if (http.status == 200) { - var searchField = http.callbackData; - var start = searchField.value.length; - var text = http.responseText.split(":"); - if (text[0].length > 0) { - searchField.uid = text[0]; - displayFreeBusyForNode(searchField); - } - else - searchField.uid = null - } - } -} - function resetFreeBusyZone() { var table = $("freeBusy"); var row = table.tHead.rows[2]; @@ -270,6 +255,9 @@ function newAttendee(event) { } function checkAttendee() { + if (document.currentPopupMenu) + hideMenu(document.currentPopupMenu); + if (document.currentPopupMenu && !this.confirmedValue) { // Hack for IE7; blur event is triggered on input field when // selecting a menu item @@ -277,53 +265,56 @@ function checkAttendee() { if (visible) return; } + this.focussed = false; - var th = this.parentNode.parentNode; - var tbody = th.parentNode; + var row = this.parentNode.parentNode; + var tbody = row.parentNode; if (tbody && this.value.trim().length == 0) - tbody.removeChild(th); + tbody.removeChild(row); else if (!this.hasfreebusy) { if (this.confirmedValue) this.value = this.confirmedValue; displayFreeBusyForNode(this); this.hasfreebusy = true; } - resetAttendeesValue(); + + requestField = null; + searchField = null; } function displayFreeBusyForNode(node) { - if (document.contactFreeBusyAjaxRequest) + var nodes = node.parentNode.parentNode.cells; + if (node.getAttribute("uid")) { + if (document.contactFreeBusyAjaxRequest) awaitingFreeBusyRequests.push(node); - else { - var nodes = node.parentNode.parentNode.cells; - if (node.uid) { - for (var i = 1; i < nodes.length; i++) { - $(nodes[i]).removeClassName("noFreeBusy"); - nodes[i].innerHTML = ('' - + '' - + '' - + ''); - } - 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 = ('' + + '' + + '' + + ''); } - } + if (document.contactFreeBusyAjaxRequest) { + document.contactFreeBusyAjaxRequest.aborted = true; + document.contactFreeBusyAjaxRequest.abort(); + } + var sd = $('startTime_date').valueAsShortDateString(); + var ed = $('endTime_date').valueAsShortDateString(); + var urlstr = ( UserFolderURL + "../" + node.getAttribute("uid") + "/freebusy.ifb/ajaxRead?" + + "sday=" + sd + "&eday=" + ed + "&additional=" + + additionalDays ); + document.contactFreeBusyAjaxRequest + = triggerAjaxRequest(urlstr, + updateFreeBusyData, + node); + } + } else { + for (var i = 1; i < nodes.length; i++) { + $(nodes[i]).addClassName("noFreeBusy"); + $(nodes[i]).update(); + } + } } function setSlot(tds, nbr, status) { @@ -362,22 +353,6 @@ function updateFreeBusyData(http) { } } -function resetAttendeesValue() { - var table = $("freeBusy"); - var inputs = table.getElementsByTagName("input"); - for (var i = 0; i < inputs.length - 2; i++) { - var currentInput = inputs[i]; - var uid = currentInput.getAttribute("uid"); - if (uid) { - currentInput.uid = uid; - currentInput.setAttribute("uid", null); - } - currentInput.setAttribute("autocomplete", "off"); - } - inputs[inputs.length - 2].setAttribute("autocomplete", "off"); - Event.observe(inputs[inputs.length - 2], "click", newAttendee); -} - function resetAllFreeBusys() { var table = $("freeBusy"); var inputs = table.getElementsByTagName("input"); @@ -409,25 +384,33 @@ function initializeWindowButtons() { function onEditorOkClick(event) { preventDefault(event); - + attendeesNames = new Array(); + attendeesUIDs = new Array(); attendeesEmails = new Array(); - + var table = $("freeBusy"); var inputs = table.getElementsByTagName("input"); for (var i = 0; i < inputs.length - 2; i++) { var name = extractEmailName(inputs[i].value); - if (!(name && name.length > 0)) - name = inputs[i].uid; var email = extractEmailAddress(inputs[i].value); + var uid = ""; + if (inputs[i].getAttribute("uid")) + uid = inputs[i].getAttribute("uid"); + if (!(name && name.length > 0)) + if (inputs[i].uid) + name = inputs[i].uid; + else + name = email; var pos = attendeesEmails.indexOf(email); if (pos == -1) pos = attendeesEmails.length; attendeesNames[pos] = name; + attendeesUIDs[pos] = uid; attendeesEmails[pos] = email; } - parent$("attendeesNames").value = attendeesNames.join(","); + parent$("attendeesUIDs").value = attendeesUIDs.join(","); parent$("attendeesEmails").value = attendeesEmails.join(","); window.opener.refreshAttendees(); @@ -507,7 +490,6 @@ function onTimeDateWidgetChange(event) { prepareTableHeaders(); prepareTableRows(); redisplayFreeBusyZone(); - resetAttendeesValue(); resetAllFreeBusys(); } @@ -563,53 +545,52 @@ function prepareTableRows() { function prepareAttendees() { var value = parent$("attendeesNames").value; + var table = $("freeBusy"); if (value.length > 0) { attendeesNames = parent$("attendeesNames").value.split(","); + attendeesUIDs = parent$("attendeesUIDs").value.split(","); attendeesEmails = parent$("attendeesEmails").value.split(","); - var body = $("freeBusy").tBodies[0]; + var tbody = table.tBodies[0]; + var model = tbody.rows[tbody.rows.length - 1]; + var newAttendeeRow = tbody.rows[tbody.rows.length - 2]; for (var i = 0; i < attendeesNames.length; i++) { - var tr = body.insertRow(i); - var td = document.createElement("td"); - $(td).addClassName("attendees"); - var input = document.createElement("input"); + var row = model.cloneNode(true); + tbody.insertBefore(row, newAttendeeRow); + $(row).removeClassName("attendeeModel"); + var input = $(row).down("input"); var value = ""; - if (attendeesNames[i].length > 0) + if (attendeesNames[i].length > 0 && attendeesNames[i] != attendeesEmails[i]) value += attendeesNames[i] + " "; value += "<" + attendeesEmails[i] + ">"; input.value = value; - $(input).addClassName("textField"); + if (attendeesUIDs[i].length > 0) + input.setAttribute("uid", attendeesUIDs[i]); + input.setAttribute("name", ""); input.setAttribute("modified", "0"); input.observe("blur", checkAttendee); input.observe("keydown", onContactKeydown); - tr.appendChild(td); - td.appendChild(input); displayFreeBusyForNode(input); } } else { attendeesNames = new Array(); + attendeesUIDs = new Array(); attendeesEmails = new Array(); } -} -function initializeFreebusys() { - var inputs = $("freeBusy").getElementsByTagName("input"); - var baseUrl = UserFolderURL + "Contacts/contactSearch?search="; - for (var i = 0; i < attendeesEmails.length; i++) - triggerAjaxRequest(baseUrl + attendeesEmails[i], - UIDLookupCallback, inputs[i]); + var inputs = table.getElementsByTagName("input"); + inputs[inputs.length - 2].setAttribute("autocomplete", "off"); + Event.observe(inputs[inputs.length - 2], "click", newAttendee); } function onFreeBusyLoadHandler() { initializeWindowButtons(); initializeTimeWidgets(); - prepareAttendees(); prepareTableHeaders(); prepareTableRows(); redisplayFreeBusyZone(); - resetAttendeesValue(); - initializeFreebusys(); + prepareAttendees(); } -document.observe("dom:loaded", onFreeBusyLoadHandler); +FastInit.addOnLoad(onFreeBusyLoadHandler); diff --git a/UI/WebServerResources/UIxCalUserRightsEditor.js b/UI/WebServerResources/UIxCalUserRightsEditor.js index 174cf154..1c739b5c 100644 --- a/UI/WebServerResources/UIxCalUserRightsEditor.js +++ b/UI/WebServerResources/UIxCalUserRightsEditor.js @@ -6,4 +6,4 @@ function initACLButtons() { Event.observe($("cancelButton"), "click", onCancelClick); } -addEvent(window, "load", initACLButtons); +FastInit.addOnLoad(initACLButtons); diff --git a/UI/WebServerResources/UIxComponentEditor.css b/UI/WebServerResources/UIxComponentEditor.css index e69de29b..da6488dd 100644 --- a/UI/WebServerResources/UIxComponentEditor.css +++ b/UI/WebServerResources/UIxComponentEditor.css @@ -0,0 +1,2 @@ +#attendeesLabel +{ display: none; } diff --git a/UI/WebServerResources/UIxComponentEditor.js b/UI/WebServerResources/UIxComponentEditor.js index 6ff4d3f0..45b91ae6 100644 --- a/UI/WebServerResources/UIxComponentEditor.js +++ b/UI/WebServerResources/UIxComponentEditor.js @@ -10,9 +10,8 @@ function onPopupAttendeesWindow(event) { function onSelectPrivacy(event) { if (event.button == 0 || (isSafari() && event.button == 1)) { var node = getTarget(event); - if (node.tagName != 'A') - node = $(node).getParentWithTagName("a"); - node = $(node).childNodesWithTag("span")[0]; + if (node.tagName != 'BUTTON') + node = $(node).up("button"); popupToolbarMenu(node, "privacy-menu"); Event.stop(event); // preventDefault(event); @@ -158,4 +157,4 @@ function onComponentEditorLoad(event) { false); } -addEvent(window, 'load', onComponentEditorLoad); +FastInit.addOnLoad(onComponentEditorLoad); diff --git a/UI/WebServerResources/UIxContactEditor.js b/UI/WebServerResources/UIxContactEditor.js index 23f334a7..76910d03 100644 --- a/UI/WebServerResources/UIxContactEditor.js +++ b/UI/WebServerResources/UIxContactEditor.js @@ -134,4 +134,4 @@ function initEditorForm() { $("givenName").onkeyup = onFnNewValue; } -addEvent(window, 'load', initEditorForm); +FastInit.addOnLoad(initEditorForm); diff --git a/UI/WebServerResources/UIxContactsUserFolders.js b/UI/WebServerResources/UIxContactsUserFolders.js index 1734da55..62faa6c6 100644 --- a/UI/WebServerResources/UIxContactsUserFolders.js +++ b/UI/WebServerResources/UIxContactsUserFolders.js @@ -141,4 +141,4 @@ function initUserFoldersWindow() { Event.observe($("addButton"), "click", onConfirmFolderSelection); } -addEvent(window, 'load', initUserFoldersWindow); +FastInit.addOnLoad(initUserFoldersWindow); diff --git a/UI/WebServerResources/UIxContactsUserRightsEditor.js b/UI/WebServerResources/UIxContactsUserRightsEditor.js index ee8e8fcc..a38422dc 100644 --- a/UI/WebServerResources/UIxContactsUserRightsEditor.js +++ b/UI/WebServerResources/UIxContactsUserRightsEditor.js @@ -7,4 +7,4 @@ function initACLButtons() { Event.observe(button, "click", onCancelClick); } -addEvent(window, "load", initACLButtons); +FastInit.addOnLoad(initACLButtons); diff --git a/UI/WebServerResources/UIxMailEditor.css b/UI/WebServerResources/UIxMailEditor.css index bf46f21a..328b8a4c 100644 --- a/UI/WebServerResources/UIxMailEditor.css +++ b/UI/WebServerResources/UIxMailEditor.css @@ -151,4 +151,4 @@ UL#attachments LI IMG right: 0em; bottom: 0em; top: 13em; - width: 100%; } + width: 99%; } diff --git a/UI/WebServerResources/UIxMailEditor.js b/UI/WebServerResources/UIxMailEditor.js index 212f1715..54846025 100644 --- a/UI/WebServerResources/UIxMailEditor.js +++ b/UI/WebServerResources/UIxMailEditor.js @@ -4,11 +4,9 @@ function onContactAdd() { var selector = null; var selectorURL = '?popup=YES&selectorId=mailer-contacts'; - urlstr = ApplicationBaseURL; - if (urlstr[urlstr.length-1] != '/') - urlstr += '/'; - urlstr += ("../../" + UserLogin + "/Contacts/" - + contactSelectorAction + selectorURL); + urlstr = ApplicationBaseURL + + "../Contacts/" + + contactSelectorAction + selectorURL; var w = window.open(urlstr, "Addressbook", "width=640,height=400,resizable=1,scrollbars=0"); w.selector = selector; @@ -368,4 +366,4 @@ function onMailEditorClose(event) { Event.stopObserving(window, "beforeunload", onMailEditorClose); } -addEvent(window, 'load', initMailEditor); +FastInit.addOnLoad(initMailEditor); diff --git a/UI/WebServerResources/UIxMailPopupView.js b/UI/WebServerResources/UIxMailPopupView.js index b9cb5946..dd197486 100644 --- a/UI/WebServerResources/UIxMailPopupView.js +++ b/UI/WebServerResources/UIxMailPopupView.js @@ -9,4 +9,4 @@ function initPopupMailer(event) { resizeMailContent(); } -addEvent(window, 'load', initPopupMailer); +FastInit.addOnLoad(initPopupMailer); diff --git a/UI/WebServerResources/UIxMailToSelection.js b/UI/WebServerResources/UIxMailToSelection.js index df7c1a68..4593693d 100644 --- a/UI/WebServerResources/UIxMailToSelection.js +++ b/UI/WebServerResources/UIxMailToSelection.js @@ -219,9 +219,8 @@ function hasRecipients() { var count; count = this.getAddressCount(); - if (count > 0) - return true; - return false; + + return (count > 0) } /* addressbook helpers */ diff --git a/UI/WebServerResources/UIxMailUserRightsEditor.js b/UI/WebServerResources/UIxMailUserRightsEditor.js index 6ef7e2fd..65d471b6 100644 --- a/UI/WebServerResources/UIxMailUserRightsEditor.js +++ b/UI/WebServerResources/UIxMailUserRightsEditor.js @@ -6,4 +6,4 @@ function initACLButtons() { $("cancelButton").addEventListener("click", onCancelClick, false); } -addEvent(window, "load", initACLButtons); +FastInit.addOnLoad(initACLButtons); diff --git a/UI/WebServerResources/UIxTaskEditor.js b/UI/WebServerResources/UIxTaskEditor.js index 5d98047a..c49cc718 100644 --- a/UI/WebServerResources/UIxTaskEditor.js +++ b/UI/WebServerResources/UIxTaskEditor.js @@ -297,4 +297,4 @@ function onTaskEditorLoad() { initializeStatusLine(); } -addEvent(window, 'load', onTaskEditorLoad); +FastInit.addOnLoad(onTaskEditorLoad); diff --git a/UI/WebServerResources/generic.css b/UI/WebServerResources/generic.css index 512a750e..f8038276 100644 --- a/UI/WebServerResources/generic.css +++ b/UI/WebServerResources/generic.css @@ -33,7 +33,7 @@ IMG#progressIndicator margin-top: 1.5em; margin-right: 1em; } -SPAN#toolbar IMG#progressIndicator +DIV#toolbar IMG#progressIndicator { margin-top: 0.75em; } DIV#pageContent @@ -232,12 +232,14 @@ SPAN.toolbarSeparator width: 0px; padding: 0px; } -A.toolbarButton +BUTTON.toolbarButton, +SPAN.toolbarButton, +SPAN.disabledToolbarButton { color: #000; - text-decoration: none; } - -SPAN.toolbarButton, SPAN.disabledToolbarButton -{ cursor: default; + font-family: Lucida Grande, Bitstream VeraSans, Tahoma; + font-size: 8pt; + text-decoration: none; + cursor: default; display: inline; float: left; text-align: center; @@ -249,12 +251,16 @@ SPAN.toolbarButton, SPAN.disabledToolbarButton border-bottom: 1px solid transparent; padding: 1px 2px; background-color: transparent; - color: -moz-DialogText; } + color: -moz-DialogText; + overflow: visible; + margin: 0px; } SPAN.disabledToolbarButton { -moz-opacity: 0.4; - opacity: 0.4; } + opacity: 0.4; + padding-top: 2px; } +BUTTON.toolbarButton:hover, SPAN.toolbarButton:hover { color: -moz-buttonhovertext; border-top: 1px solid #fff; @@ -262,6 +268,7 @@ SPAN.toolbarButton:hover border-bottom: 1px solid #828482; border-right: 1px solid #828482; } +BUTTON.toolbarButton:active, SPAN.toolbarButton:active { color: -moz-buttonhovertext; border-top: 1px solid #828482; diff --git a/UI/WebServerResources/generic.js b/UI/WebServerResources/generic.js index 96c4b003..c546c792 100644 --- a/UI/WebServerResources/generic.js +++ b/UI/WebServerResources/generic.js @@ -257,9 +257,10 @@ function openMailComposeWindow(url, wId) { function openMailTo(senderMailTo) { var mailto = sanitizeMailTo(senderMailTo); + if (mailto.length > 0) openMailComposeWindow(ApplicationBaseURL - + "/../Mail/compose?mailto=" + mailto); + + "../Mail/compose?mailto=" + mailto); return false; /* stop following the link */ } @@ -1402,7 +1403,7 @@ function onFinalLoadHandler(event) { safetyNet.parentNode.removeChild(safetyNet); } -document.observe("dom:loaded", onLoadHandler); +FastInit.addOnLoad(onLoadHandler); function parent$(element) { return this.opener.document.getElementById(element);