From: wolfgang Date: Mon, 5 Nov 2007 16:32:09 +0000 (+0000) Subject: git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1233 d1b88da0-ebda-0310... X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=66ec3b8f4c73bb420ee3db31432d3d5423f03070;p=scalable-opengroupware.org git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1233 d1b88da0-ebda-0310-925b-ed51d893ca5b --- diff --git a/SoObjects/Contacts/GNUmakefile b/SoObjects/Contacts/GNUmakefile index a68f8212..37c9ad23 100644 --- a/SoObjects/Contacts/GNUmakefile +++ b/SoObjects/Contacts/GNUmakefile @@ -1,3 +1,4 @@ + # GNUstep makefile include ../common.make @@ -7,8 +8,8 @@ BUNDLE_NAME = Contacts Contacts_PRINCIPAL_CLASS = SOGoContactsProduct Contacts_OBJC_FILES = \ + NSObject+CardDAV.m \ Product.m \ - \ SOGoContactFolders.m \ SOGoContactGCSEntry.m \ SOGoContactGCSFolder.m \ diff --git a/SoObjects/Contacts/NSObject+CardDAV.h b/SoObjects/Contacts/NSObject+CardDAV.h new file mode 100644 index 00000000..d87dff40 --- /dev/null +++ b/SoObjects/Contacts/NSObject+CardDAV.h @@ -0,0 +1,32 @@ +/* NSObject+CardDAV.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Ludovic Marcotte + * + * 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 __Contacts_NSObject_CardDAV_H__ +#define __Contacts_NSObject_CardDAV_H__ + +@interface NSObject (CardDAV) + +- (id) davAddressbookQuery: (id) queryContext; + +@end + +#endif diff --git a/SoObjects/Contacts/NSObject+CardDAV.m b/SoObjects/Contacts/NSObject+CardDAV.m new file mode 100644 index 00000000..1a1df88b --- /dev/null +++ b/SoObjects/Contacts/NSObject+CardDAV.m @@ -0,0 +1,164 @@ +/* NSObject+CardDAV.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Ludovic Marcotte + * + * 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 "SOGoContactFolder.h" +#import "SOGoContactGCSEntry.h" + +#import +#import +#import +#import +#import +#import +#import +#import + +@implementation NSObject (CardDAV) + +- (void) _appendComponentsMatchingFilters: (NSArray *) filters + toResponse: (WOResponse *) response + context: (id) context +{ + unsigned int count, max; + NSDictionary *currentFilter, *contact; + NSEnumerator *contacts; + NSString *baseURL; + id o; + + o = (id)self; + baseURL = [o baseURLInContext: context]; + + max = [filters count]; + for (count = 0; count < max; count++) + { + currentFilter = [filters objectAtIndex: count]; + contacts = [[o lookupContactsWithFilter: [[currentFilter allValues] lastObject] + sortBy: @"c_givenname" + ordering: NSOrderedDescending] + objectEnumerator]; + + while ((contact = [contacts nextObject])) + { + [o appendObject: contact + withBaseURL: baseURL + toREPORTResponse: response]; + } + } +} + +- (BOOL) _isValidFilter: (NSString *) theString +{ + if ([theString caseInsensitiveCompare: @"sn"] == NSOrderedSame) + return YES; + + if ([theString caseInsensitiveCompare: @"givenname"] == NSOrderedSame) + return YES; + + if ([theString caseInsensitiveCompare: @"mail"] == NSOrderedSame) + return YES; + + if ([theString caseInsensitiveCompare: @"telephonenumber"] == NSOrderedSame) + return YES; + + return NO; +} + +- (NSDictionary *) _parseContactFilter: (id ) filterElement +{ + NSMutableDictionary *filterData; + id parentNode; + id ranges; + + parentNode = [filterElement parentNode]; + + if ([[parentNode tagName] isEqualToString: @"filter"] && + [self _isValidFilter: [filterElement attribute: @"name"]]) + { + ranges = [filterElement getElementsByTagName: @"text-match"]; + + if ([(NSArray *)ranges count] && [(NSArray *)[[ranges objectAtIndex: 0] childNodes] count]) + { + filterData = [NSMutableDictionary new]; + [filterData autorelease]; + [filterData setObject: [[(NSArray *)[[ranges objectAtIndex: 0] childNodes] lastObject] data] + forKey: [filterElement attribute: @"name"]]; + } + } + else + filterData = nil; + + return filterData; +} + +- (NSArray *) _parseContactFilters: (id ) parentNode +{ + NSEnumerator *children; + id node; + NSMutableArray *filters; + NSDictionary *filter; + + filters = [[NSMutableArray new] autorelease]; + + children = [[parentNode getElementsByTagName: @"prop-filter"] + objectEnumerator]; + + node = [children nextObject]; + + while (node) + { + filter = [self _parseContactFilter: node]; + if (filter) + [filters addObject: filter]; + node = [children nextObject]; + } + + return filters; +} + +- (id) davAddressbookQuery: (id) queryContext +{ + WOResponse *r; + NSArray *filters; + id document; + + r = [queryContext response]; + [r setStatus: 207]; + [r setContentEncoding: NSUTF8StringEncoding]; + [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; + [r setHeader: @"no-cache" forKey: @"pragma"]; + [r setHeader: @"no-cache" forKey: @"cache-control"]; + [r appendContentString:@"\r\n"]; + [r appendContentString: @"\r\n"]; + + document = [[queryContext request] contentAsDOMDocument]; + filters = [self _parseContactFilters: [document documentElement]]; + + [self _appendComponentsMatchingFilters: filters + toResponse: r + context: queryContext]; + [r appendContentString:@"\r\n"]; + + return r; +} + +@end diff --git a/SoObjects/Contacts/SOGoContactFolder.h b/SoObjects/Contacts/SOGoContactFolder.h index c1ff8968..709e4ae6 100644 --- a/SoObjects/Contacts/SOGoContactFolder.h +++ b/SoObjects/Contacts/SOGoContactFolder.h @@ -36,6 +36,7 @@ @class NSString, NSArray; @class SOGoContactObject; @class SOGoObject; +@class WOResponse; @protocol SOGoContactObject; @@ -43,6 +44,10 @@ @protocol SOGoContactFolder +- (void) appendObject: (NSDictionary *) object + withBaseURL: (NSString *) baseURL + toREPORTResponse: (WOResponse *) r; + - (NSArray *) lookupContactsWithFilter: (NSString *) filter sortBy: (NSString *) sortKey ordering: (NSComparisonResult) sortOrdering; diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.h b/SoObjects/Contacts/SOGoContactGCSFolder.h index d10913d0..9ac8fb1f 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.h +++ b/SoObjects/Contacts/SOGoContactGCSFolder.h @@ -25,6 +25,7 @@ #import #import "SOGoContactFolder.h" +#import "NSObject+CardDAV.h" @class NSArray; @class NSString; diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.m b/SoObjects/Contacts/SOGoContactGCSFolder.m index 26e0e974..f248f514 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.m +++ b/SoObjects/Contacts/SOGoContactGCSFolder.m @@ -25,16 +25,12 @@ #import #import #import -#import #import #import #import #import #import #import -#import -#import -#import #import #import "SOGoContactGCSEntry.h" @@ -71,6 +67,7 @@ isPut = NO; obj = [super lookupName:_key inContext:_ctx acquire:NO]; + if (!obj) { if ([[[_ctx request] method] isEqualToString: @"PUT"]) @@ -174,74 +171,38 @@ return newRecords; } -- (BOOL) _isValidFilter: (NSString *) theString -{ - if ([theString caseInsensitiveCompare: @"sn"] == NSOrderedSame) - return YES; - - if ([theString caseInsensitiveCompare: @"givenname"] == NSOrderedSame) - return YES; - - if ([theString caseInsensitiveCompare: @"mail"] == NSOrderedSame) - return YES; - - if ([theString caseInsensitiveCompare: @"telephonenumber"] == NSOrderedSame) - return YES; - - return NO; -} - -- (NSDictionary *) _parseContactFilter: (id ) filterElement +- (NSArray *) lookupContactsWithFilter: (NSString *) filter + sortBy: (NSString *) sortKey + ordering: (NSComparisonResult) sortOrdering { - NSMutableDictionary *filterData; - id parentNode; - id ranges; + NSArray *fields, *dbRecords, *records; + EOQualifier *qualifier; + EOSortOrdering *ordering; - parentNode = [filterElement parentNode]; + fields = folderListingFields; + qualifier = [self _qualifierForFilter: filter]; + dbRecords = [[self ocsFolder] fetchFields: fields + matchingQualifier: qualifier]; - if ([[parentNode tagName] isEqualToString: @"filter"] && - [self _isValidFilter: [filterElement attribute: @"name"]]) + if ([dbRecords count] > 0) { - ranges = [filterElement getElementsByTagName: @"text-match"]; - - if ([ranges count] && [[[ranges objectAtIndex: 0] childNodes] count]) - { - filterData = [NSMutableDictionary new]; - [filterData autorelease]; - [filterData setObject: [[[[ranges objectAtIndex: 0] childNodes] lastObject] data] - forKey: [filterElement attribute: @"name"]]; - } + records = [self _flattenedRecords: dbRecords]; + ordering + = [EOSortOrdering sortOrderingWithKey: sortKey + selector: ((sortOrdering == NSOrderedDescending) + ? EOCompareCaseInsensitiveDescending + : EOCompareCaseInsensitiveAscending)]; + records + = [records sortedArrayUsingKeyOrderArray: + [NSArray arrayWithObject: ordering]]; } else - filterData = nil; - - return filterData; -} - -#warning filters is leaked here -- (NSArray *) _parseContactFilters: (id ) parentNode -{ - NSEnumerator *children; - id node; - NSMutableArray *filters; - NSDictionary *filter; - - filters = [NSMutableArray new]; - - children = [[parentNode getElementsByTagName: @"prop-filter"] - objectEnumerator]; - - node = [children nextObject]; - - while (node) - { - filter = [self _parseContactFilter: node]; - if (filter) - [filters addObject: filter]; - node = [children nextObject]; - } + records = nil; + // else + //[self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__]; - return filters; + [self debugWithFormat:@"fetched %i records.", [records count]]; + return records; } - (void) appendObject: (NSDictionary *) object @@ -280,99 +241,6 @@ [r appendContentString: @" \r\n"]; } -- (void) _appendComponentsMatchingFilters: (NSArray *) filters - toResponse: (WOResponse *) response -{ - unsigned int count, max; - NSDictionary *currentFilter, *contact; - NSEnumerator *contacts; - NSString *baseURL; - - baseURL = [self baseURLInContext: context]; - - max = [filters count]; - for (count = 0; count < max; count++) - { - currentFilter = [filters objectAtIndex: count]; - contacts = [[self lookupContactsWithFilter: [[currentFilter allValues] lastObject] - sortBy: @"c_givenname" - ordering: NSOrderedDescending] - objectEnumerator]; - - while ((contact = [contacts nextObject])) - { - [self appendObject: contact - withBaseURL: baseURL - toREPORTResponse: response]; - } - } -} - -- (NSArray *) lookupContactsWithFilter: (NSString *) filter - sortBy: (NSString *) sortKey - ordering: (NSComparisonResult) sortOrdering -{ - NSArray *fields, *dbRecords, *records; - EOQualifier *qualifier; - EOSortOrdering *ordering; - - fields = folderListingFields; - qualifier = [self _qualifierForFilter: filter]; - dbRecords = [[self ocsFolder] fetchFields: fields - matchingQualifier: qualifier]; - - if ([dbRecords count] > 0) - { - records = [self _flattenedRecords: dbRecords]; - ordering - = [EOSortOrdering sortOrderingWithKey: sortKey - selector: ((sortOrdering == NSOrderedDescending) - ? EOCompareCaseInsensitiveDescending - : EOCompareCaseInsensitiveAscending)]; - records - = [records sortedArrayUsingKeyOrderArray: - [NSArray arrayWithObject: ordering]]; - } - else - records = nil; -// else -// [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__]; - - [self debugWithFormat:@"fetched %i records.", [records count]]; - return records; -} - -- (NSArray *) davNamespaces -{ - return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"]; -} - -- (id) davAddressbookQuery: (id) queryContext -{ - WOResponse *r; - NSArray *filters; - id document; - - r = [context response]; - [r setStatus: 207]; - [r setContentEncoding: NSUTF8StringEncoding]; - [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; - [r setHeader: @"no-cache" forKey: @"pragma"]; - [r setHeader: @"no-cache" forKey: @"cache-control"]; - [r appendContentString:@"\r\n"]; - [r appendContentString: @"\r\n"]; - - document = [[context request] contentAsDOMDocument]; - filters = [self _parseContactFilters: [document documentElement]]; - - [self _appendComponentsMatchingFilters: filters - toResponse: r]; - [r appendContentString:@"\r\n"]; - - return r; -} - - (NSArray *) davComplianceClassesInContext: (id)_ctx { NSMutableArray *classes; @@ -390,6 +258,11 @@ return classes; } +- (NSArray *) davNamespaces +{ + return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"]; +} + - (NSString *) groupDavResourceType { return @"vcard-collection"; diff --git a/SoObjects/Contacts/SOGoContactLDAPFolder.h b/SoObjects/Contacts/SOGoContactLDAPFolder.h index e405a3fa..7cddf7e6 100644 --- a/SoObjects/Contacts/SOGoContactLDAPFolder.h +++ b/SoObjects/Contacts/SOGoContactLDAPFolder.h @@ -24,6 +24,7 @@ #define SOGOCONTACTLDAPFOLDER_H #import "SOGoContactFolder.h" +#import "NSObject+CardDAV.h" @class NSMutableDictionary; diff --git a/SoObjects/Contacts/SOGoContactLDAPFolder.m b/SoObjects/Contacts/SOGoContactLDAPFolder.m index 4a266c4b..13772500 100644 --- a/SoObjects/Contacts/SOGoContactLDAPFolder.m +++ b/SoObjects/Contacts/SOGoContactLDAPFolder.m @@ -34,13 +34,56 @@ #import #import +#import #import "SOGoContactLDIFEntry.h" #import "SOGoContactLDAPFolder.h" +#import +#import +#import @class WOContext; + + @implementation SOGoContactLDAPFolder +- (void) appendObject: (NSDictionary *) object + withBaseURL: (NSString *) baseURL + toREPORTResponse: (WOResponse *) r +{ + SOGoContactLDIFEntry *component; + Class componentClass; + NSString *name, *etagLine, *contactString; + + name = [object objectForKey: @"c_name"]; + componentClass = [SOGoContactLDIFEntry class]; + + + component = [componentClass contactEntryWithName: name withLDIFEntry: object inContainer: self]; + + [r appendContentString: @" \r\n"]; + [r appendContentString: @" "]; + [r appendContentString: baseURL]; + if (![baseURL hasSuffix: @"/"]) + [r appendContentString: @"/"]; + [r appendContentString: name]; + [r appendContentString: @"\r\n"]; + + [r appendContentString: @" \r\n"]; + [r appendContentString: @" \r\n"]; + etagLine = [NSString stringWithFormat: @" %@\r\n", + [component davEntityTag]]; + [r appendContentString: etagLine]; + [r appendContentString: @" \r\n"]; + [r appendContentString: @" HTTP/1.1 200 OK\r\n"]; + [r appendContentString: @" \r\n"]; + [r appendContentString: @" "]; + contactString = [[component contentAsString] stringByEscapingXMLString]; + [r appendContentString: contactString]; + [r appendContentString: @"\r\n"]; + [r appendContentString: @" \r\n"]; +} + + (id) folderWithName: (NSString *) aName andDisplayName: (NSString *) aDisplayName inContainer: (id) aContainer @@ -99,6 +142,11 @@ return displayName; } +- (NSArray *) davNamespaces +{ + return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"]; +} + - (id) lookupName: (NSString *) objectName inContext: (WOContext *) lookupContext acquire: (BOOL) acquire @@ -106,20 +154,52 @@ id obj; NSDictionary *ldifEntry; -// NSLog (@"looking up name '%@'...", name); + //NSLog (@"looking up name '%@'...", objectName); /* first check attributes directly bound to the application */ ignoreSoObjectHunger = YES; obj = [super lookupName: objectName inContext: lookupContext acquire: NO]; ignoreSoObjectHunger = NO; + if (!obj) { ldifEntry = [ldapSource lookupContactEntry: objectName]; +#if 0 obj = ((ldifEntry) ? [SOGoContactLDIFEntry contactEntryWithName: objectName withLDIFEntry: ldifEntry inContainer: self] : [NSException exceptionWithHTTPStatus: 404]); +#else + if (ldifEntry) + obj = [SOGoContactLDIFEntry contactEntryWithName: objectName + withLDIFEntry: ldifEntry + inContainer: self]; + else + { + NSArray *davNamespaces; + NSDictionary *davInvocation; + NSString *objcMethod; + + davNamespaces = [self davNamespaces]; + if ([davNamespaces count] > 0) + { + davInvocation = [objectName asDavInvocation]; + if (davInvocation + && [davNamespaces + containsObject: [davInvocation objectForKey: @"ns"]]) + { + objcMethod = [[davInvocation objectForKey: @"method"] + davMethodToObjC]; + obj = [[SoSelectorInvocation alloc] + initWithSelectorNamed: + [NSString stringWithFormat: @"%@:", objcMethod] + addContextParameter: YES]; + [obj autorelease]; + } + } + } +#endif } return obj; diff --git a/SoObjects/Mailer/NSData+Mail.m b/SoObjects/Mailer/NSData+Mail.m index eb3051f6..52455320 100644 --- a/SoObjects/Mailer/NSData+Mail.m +++ b/SoObjects/Mailer/NSData+Mail.m @@ -52,4 +52,50 @@ return decodedData; } +- (NSString *) decodedSubject +{ + const char *cData, *endFlag; + unsigned int len; + NSString *converted, *decodedSubject; + + cData = [self bytes]; + len = [self length]; + + if (len) + { + decodedSubject = nil; + if (len > 6) + { + endFlag = cData + len - 2; + if (*cData == '=' && *(cData + 1) == '?' + && *endFlag == '?' && *(endFlag + 1) == '=') + { + converted + = [[NSString alloc] initWithData: self + encoding: NSASCIIStringEncoding]; + if (converted) + { + [converted autorelease]; + decodedSubject = [converted stringByDecodingQuotedPrintable]; + } + } + } + if (!decodedSubject) + { + decodedSubject + = [[NSString alloc] initWithData: self + encoding: NSUTF8StringEncoding]; + if (!decodedSubject) + decodedSubject + = [[NSString alloc] initWithData: self + encoding: NSISOLatin1StringEncoding]; + [decodedSubject autorelease]; + } + } + else + decodedSubject = @""; + + return decodedSubject; +} + @end diff --git a/SoObjects/Mailer/NSString+Mail.h b/SoObjects/Mailer/NSString+Mail.h index 25471101..72bc984a 100644 --- a/SoObjects/Mailer/NSString+Mail.h +++ b/SoObjects/Mailer/NSString+Mail.h @@ -29,6 +29,8 @@ - (NSString *) htmlToText; +- (NSString *) decodedSubject; + @end #endif /* NSSTRING_MAIL_H */ diff --git a/SoObjects/Mailer/NSString+Mail.m b/SoObjects/Mailer/NSString+Mail.m index 494becdc..084280a2 100644 --- a/SoObjects/Mailer/NSString+Mail.m +++ b/SoObjects/Mailer/NSString+Mail.m @@ -28,6 +28,7 @@ #import #import #import +#import #import #import @@ -348,4 +349,20 @@ return [handler result]; } +- (NSString *) decodedSubject +{ + NSString *decodedSubject; + + if ([self hasPrefix: @"=?"] && [self hasSuffix: @"?="]) + { + decodedSubject = [self stringByDecodingQuotedPrintable]; + if (!decodedSubject) + decodedSubject = self; + } + else + decodedSubject = self; + + return decodedSubject; +} + @end diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.m b/SoObjects/Mailer/SOGoMailObject+Draft.m index 0ce4da0d..28b31f55 100644 --- a/SoObjects/Mailer/SOGoMailObject+Draft.m +++ b/SoObjects/Mailer/SOGoMailObject+Draft.m @@ -54,7 +54,7 @@ hasPrefix = NO; - subject = [[self envelope] subject]; + subject = [self decodedSubject]; i = 0; while (!hasPrefix && replyPrefixes[i]) if ([subject hasPrefix: replyPrefixes[i]]) @@ -145,7 +145,7 @@ }; unsigned int count, length; - subject = [[self envelope] subject]; + subject = [self decodedSubject]; length = [subject length]; if (!length) { @@ -173,7 +173,7 @@ { NSString *subject, *newSubject; - subject = [[self envelope] subject]; + subject = [self decodedSubject]; if ([subject length] > 0) newSubject = [NSString stringWithFormat: @"[Fwd: %@]", subject]; else diff --git a/SoObjects/Mailer/SOGoMailObject.h b/SoObjects/Mailer/SOGoMailObject.h index 80e09538..e5d11e82 100644 --- a/SoObjects/Mailer/SOGoMailObject.h +++ b/SoObjects/Mailer/SOGoMailObject.h @@ -58,6 +58,7 @@ - (NGImap4Envelope *)envelope; - (NSString *)subject; +- (NSString *)decodedSubject; - (NSCalendarDate *)date; - (NSArray *)fromEnvelopeAddresses; - (NSArray *)toEnvelopeAddresses; diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 15d53137..934f1c61 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -261,6 +261,11 @@ static BOOL debugSoParts = NO; return [[self envelope] subject]; } +- (NSString *) decodedSubject +{ + return [[self subject] decodedSubject]; +} + - (NSCalendarDate *) date { NSTimeZone *userTZ; diff --git a/UI/MailPartViewers/English.lproj/Localizable.strings b/UI/MailPartViewers/English.lproj/Localizable.strings index 0dc239af..c120183f 100644 --- a/UI/MailPartViewers/English.lproj/Localizable.strings +++ b/UI/MailPartViewers/English.lproj/Localizable.strings @@ -11,7 +11,11 @@ add_info_text = "iMIP 'ADD' requests are not yet supported by SOGo."; publish_info_text = "The sender informs you of the attached event."; cancel_info_text = "Your invitation or the whole event was canceled."; request_info_no_attendee = "is proposing a meeting to the attendees. You receive this mail as a notification, you are not scheduled as a participant."; -Organizer = "Organizer"; +Appointment = "Appointment"; + +Organizer = "Organisateur"; +Time = "Time"; +Attendees = "Attendees"; request_info = "invites you to participate in a meeting."; do_add_to_cal = "add to calendar"; do_del_from_cal = "delete from calendar"; @@ -21,3 +25,7 @@ do_tentative = "tentative"; do_update_status = "update status in calendar"; reply_info_no_attendee = "You received a reply to a scheduling event but the sender of the reply is not a participant."; reply_info = "This is a reply to an event invitation done by you."; + +"Untitled" = "Untitled"; + +"Size" = "Size"; diff --git a/UI/MailPartViewers/French.lproj/Localizable.strings b/UI/MailPartViewers/French.lproj/Localizable.strings index 6b90b000..ad6492f8 100644 --- a/UI/MailPartViewers/French.lproj/Localizable.strings +++ b/UI/MailPartViewers/French.lproj/Localizable.strings @@ -26,4 +26,6 @@ do_update_status = "mettre l'agenda à jour"; reply_info_no_attendee = "Vous avez reçu une réponse à un événement mais l'expéditeur n'est pas un invité."; reply_info = "Ceci est une réponse à un événement que vous avez organisé."; +"Untitled" = "Sans titre"; + "Size" = "Taille"; diff --git a/UI/MailPartViewers/German.lproj/Localizable.strings b/UI/MailPartViewers/German.lproj/Localizable.strings index 6b90b000..c120183f 100644 --- a/UI/MailPartViewers/German.lproj/Localizable.strings +++ b/UI/MailPartViewers/German.lproj/Localizable.strings @@ -1,29 +1,31 @@ -ACCEPTED = "Accepté"; -COMPLETED = "Terminé"; -DECLINED = "Refusé"; -DELEGATED = "Délégué"; -IN-PROCESS = "En cours de traitement"; -NEEDS-ACTION = "Prise de décision nécessaire"; -TENTATIVE = "Proposition"; -organized_by_you = "vous êtes l'organisateur"; -you_are_an_attendee = "vous êtes invité"; +ACCEPTED = "accepted"; +COMPLETED = "completed"; +DECLINED = "declined"; +DELEGATED = "delegated"; +IN-PROCESS = "in process"; +NEEDS-ACTION = "needs action"; +TENTATIVE = "tentative"; +organized_by_you = "organized by you"; +you_are_an_attendee = "you are an attendee"; add_info_text = "iMIP 'ADD' requests are not yet supported by SOGo."; -publish_info_text = "L'expéditeur vous informe de l'événement attaché."; -cancel_info_text = "Votre invitation ou l'événement au complet a été annulé."; -request_info_no_attendee = "propose une réunion entre les invités. Ce message tint seulement lieu d'avis, vous n'êtes pas indiqué comme invité."; -Appointment = "Événement"; +publish_info_text = "The sender informs you of the attached event."; +cancel_info_text = "Your invitation or the whole event was canceled."; +request_info_no_attendee = "is proposing a meeting to the attendees. You receive this mail as a notification, you are not scheduled as a participant."; +Appointment = "Appointment"; Organizer = "Organisateur"; -Time = "Date"; -Attendees = "Invités"; -request_info = "vous invite à une réunion."; -do_add_to_cal = "ajouter à l'agenda"; -do_del_from_cal = "effacer de l'agenda"; -do_accept = "accepter"; -do_decline = "decliner"; +Time = "Time"; +Attendees = "Attendees"; +request_info = "invites you to participate in a meeting."; +do_add_to_cal = "add to calendar"; +do_del_from_cal = "delete from calendar"; +do_accept = "accept"; +do_decline = "decline"; do_tentative = "tentative"; -do_update_status = "mettre l'agenda à jour"; -reply_info_no_attendee = "Vous avez reçu une réponse à un événement mais l'expéditeur n'est pas un invité."; -reply_info = "Ceci est une réponse à un événement que vous avez organisé."; +do_update_status = "update status in calendar"; +reply_info_no_attendee = "You received a reply to a scheduling event but the sender of the reply is not a participant."; +reply_info = "This is a reply to an event invitation done by you."; -"Size" = "Taille"; +"Untitled" = "Untitled"; + +"Size" = "Size"; diff --git a/UI/MailPartViewers/UIxMailPartMessageViewer.m b/UI/MailPartViewers/UIxMailPartMessageViewer.m index 3a18cb76..123941cb 100644 --- a/UI/MailPartViewers/UIxMailPartMessageViewer.m +++ b/UI/MailPartViewers/UIxMailPartMessageViewer.m @@ -22,6 +22,9 @@ #import #import +#import +#import + #import #import "UIxMailRenderingContext.h" @@ -144,6 +147,19 @@ return [formattedComponents componentsJoinedByString: @", "]; } +- (NSString *) messageSubject +{ + id baseSubject; + NSString *subject; + + baseSubject = [[self envelope] subject]; + subject = [baseSubject decodedSubject]; + if (![subject length]) + subject = [self labelForKey: @"Untitled"]; + + return subject; +} + - (NSString *) fromAddresses { NSArray *from; diff --git a/UI/MailerUI/English.lproj/Localizable.strings b/UI/MailerUI/English.lproj/Localizable.strings index d3d7eb06..3bfff173 100644 --- a/UI/MailerUI/English.lproj/Localizable.strings +++ b/UI/MailerUI/English.lproj/Localizable.strings @@ -127,6 +127,8 @@ "Mark Unread" = "Mark Unread"; "Mark Read" = "Mark Read"; +"Untitled" = "Untitled"; + /* Tree */ "SentFolderName" = "Sent"; diff --git a/UI/MailerUI/French.lproj/Localizable.strings b/UI/MailerUI/French.lproj/Localizable.strings index a818ceb0..8a3d11b3 100644 --- a/UI/MailerUI/French.lproj/Localizable.strings +++ b/UI/MailerUI/French.lproj/Localizable.strings @@ -122,11 +122,13 @@ "next" = "Suivant"; "last" = "Dernier"; +"msgnumber_to" = "à"; +"msgnumber_of" = "de"; + "Mark Unread" = "Marquer comme non lu"; "Mark Read" = "Marquer comme lu"; -"msgnumber_to" = "à"; -"msgnumber_of" = "de"; +"Untitled" = "Sans titre"; /* Tree */ diff --git a/UI/MailerUI/GNUmakefile b/UI/MailerUI/GNUmakefile index 544e1b15..08c96e70 100644 --- a/UI/MailerUI/GNUmakefile +++ b/UI/MailerUI/GNUmakefile @@ -12,7 +12,6 @@ MailerUI_OBJC_FILES += \ MailerUIProduct.m \ \ UIxMailFormatter.m \ - UIxSubjectFormatter.m \ UIxEnvelopeAddressFormatter.m \ WOContext+UIxMailer.m \ \ diff --git a/UI/MailerUI/German.lproj/Localizable.strings b/UI/MailerUI/German.lproj/Localizable.strings index 4dd0652b..6812036f 100644 --- a/UI/MailerUI/German.lproj/Localizable.strings +++ b/UI/MailerUI/German.lproj/Localizable.strings @@ -105,11 +105,13 @@ "next" = "Nächste"; "last" = "Letzter"; +"msgnumber_to" = "bis"; +"msgnumber_of" = "von"; + "Mark Unread" = "Als ungelesen markieren"; "Mark Read" = "Als gelesen markieren"; -"msgnumber_to" = "bis"; -"msgnumber_of" = "von"; +"Untitled" = "Untitled"; /* Tree */ diff --git a/UI/MailerUI/UIxMailFormatter.h b/UI/MailerUI/UIxMailFormatter.h index 2270e287..847cda67 100644 --- a/UI/MailerUI/UIxMailFormatter.h +++ b/UI/MailerUI/UIxMailFormatter.h @@ -63,32 +63,6 @@ @end -/* - TODO: the subject formatter should deal with the various 're:' like prefixes - and translate them into the native languages? - (or something like Re(5): ?) -*/ - -@interface UIxSubjectFormatter : UIxMailFormatter -{ - unsigned maxLength; -} - -/* configuration */ - -- (unsigned int)maxLength; - -/* labels */ - -- (NSString *)missingSubjectLabel; - -/* specific formatters */ - -- (NSString *)stringForStringValue:(NSString *)_subject; -- (NSString *)stringForDataValue:(NSData *)_subject; - -@end - @interface UIxEnvelopeAddressFormatter : UIxMailFormatter { NSString *separator; diff --git a/UI/MailerUI/UIxMailListView.m b/UI/MailerUI/UIxMailListView.m index 2ccc79b2..be291094 100644 --- a/UI/MailerUI/UIxMailListView.m +++ b/UI/MailerUI/UIxMailListView.m @@ -100,18 +100,13 @@ - (NSString *) messageSubject { + id baseSubject; NSString *subject; - id envSubject; - envSubject = [[message valueForKey: @"envelope"] subject]; - if ([envSubject isKindOfClass: [NSData class]]) - { - subject = [[NSString alloc] initWithData: envSubject - encoding: NSUTF8StringEncoding]; - [subject autorelease]; - } - else - subject = envSubject; + baseSubject = [[message valueForKey: @"envelope"] subject]; + subject = [baseSubject decodedSubject]; + if (![subject length]) + subject = [self labelForKey: @"Untitled"]; return subject; } diff --git a/UI/MailerUI/UIxMailView.m b/UI/MailerUI/UIxMailView.m index e3da019f..986b2674 100644 --- a/UI/MailerUI/UIxMailView.m +++ b/UI/MailerUI/UIxMailView.m @@ -88,16 +88,22 @@ static NSString *mailETag = nil; return currentAddress; } -- (NSString *) objectTitle +- (NSString *) messageSubject { - return [[self clientObject] subject]; + NSString *subject; + + subject = [[self clientObject] decodedSubject]; + if (![subject length]) + subject = [self labelForKey: @"Untitled"]; + + return subject; } - (NSString *) panelTitle { return [NSString stringWithFormat: @"%@: %@", [self labelForKey: @"View Mail"], - [self objectTitle]]; + [self messageSubject]]; } /* links (DUP to UIxMailPartViewer!) */ diff --git a/UI/MailerUI/UIxSubjectFormatter.m b/UI/MailerUI/UIxSubjectFormatter.m deleted file mode 100644 index 784c3a93..00000000 --- a/UI/MailerUI/UIxSubjectFormatter.m +++ /dev/null @@ -1,155 +0,0 @@ -/* - Copyright (C) 2004-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 -#import -#import - -#import "UIxMailFormatter.h" - -@implementation UIxSubjectFormatter - -static Class StrClass = Nil; -static Class DataClass = Nil; - -+ (void)initialize { - StrClass = [NSString class]; - DataClass = [NSData class]; -} - -- (id)init { - if ((self = [super init])) { - self->maxLength = 64; - } - return self; -} - -/* configuration */ - -- (unsigned int)maxLength { - return self->maxLength; -} - -- (BOOL)shouldDecodeQP { - return YES; -} - -/* labels */ - -- (NSString *)missingSubjectLabel { - return [self labelForKey:@"no_subject"]; -} - -/* specific formatters */ - -- (NSString *)stringForStringValue:(NSString *)_subject { - NSString *s; - - /* quoted printable */ - if ([self shouldDecodeQP] && [_subject hasPrefix:@"=?"]) { - /* - Now this is interesting. An NSString should not contain QP markers since - it is already 'charset decoded'. This is also why the NGMime parser - expects an NSData. - - Sample: - =?iso-8859-1?q?Yannick=20DAmboise?= - - Note: -stringByDecodingQuotedPrintable only expands =D0 like charcodes! - */ - NSData *data; - - /* header field data should always be ASCII */ - data = [_subject dataUsingEncoding:NSUTF8StringEncoding]; - return [self stringForDataValue:data]; - } - - if ([_subject length] == 0) - return [self missingSubjectLabel]; - - if ([_subject length] <= [self maxLength]) - return _subject; - - s = [_subject substringToIndex:([self maxLength] - 3)]; - return [s stringByAppendingString:@"..."]; -} - -- (NSString *)stringForDataValue:(NSData *)_subject { - NSString *s, *r; - unsigned len; - - if ((len = [_subject length]) == 0) - return [self missingSubjectLabel]; - - /* check for quoted printable */ - - if (len > 6 && [self shouldDecodeQP]) { - const unsigned char *b; - - b = [_subject bytes]; - if (b[0] == '=' && b[1] == '?') { - /* eg: '=?iso-8859-1?q?Yannick=20DAmboise?=' */ - id t; - - t = [_subject decodeQuotedPrintableValueOfMIMEHeaderField:@"subject"]; - if ([t isNotNull]) - return [self stringForObjectValue:t]; - else - [self warnWithFormat:@"decoding QP failed: '%@'", t]; - } - } - - /* continue NSData processing */ - - [self warnWithFormat:@"NSData subject, using UTF-8 to decode."]; - - // TODO: exception handler? - s = [[NSString alloc] initWithData:_subject encoding:NSUTF8StringEncoding]; - if (s == nil) { - [self errorWithFormat:@"could do not decode NSData subject!"]; - return [self labelForKey:@"Error_CouldNotDecodeSubject"]; - } - - if ([s hasPrefix:@"=?"]) { // TODO: this should never happen? - [self warnWithFormat:@"subject still has QP signature: '%@'", s]; - r = [s copy]; - } - else - r = [[self stringForStringValue:s] copy]; - [s release]; - return [r autorelease]; -} - -/* formatter entry function */ - -- (NSString *)stringForObjectValue:(id)_subject { - if (![_subject isNotNull]) - return [self missingSubjectLabel]; - - if ([_subject isKindOfClass:StrClass]) - return [self stringForStringValue:_subject]; - if ([_subject isKindOfClass:DataClass]) - return [self stringForDataValue:_subject]; - - return [self stringForStringValue:[_subject stringValue]]; -} - -@end /* UIxSubjectFormatter */ diff --git a/UI/MailerUI/WOContext+UIxMailer.h b/UI/MailerUI/WOContext+UIxMailer.h index 05e94d29..281a693d 100644 --- a/UI/MailerUI/WOContext+UIxMailer.h +++ b/UI/MailerUI/WOContext+UIxMailer.h @@ -32,12 +32,11 @@ @class NSFormatter; -@interface WOContext(UIxMailer) +@interface WOContext (UIxMailer) -- (NSFormatter *)mailSubjectFormatter; -- (NSFormatter *)mailDateFormatter; -- (NSFormatter *)mailEnvelopeAddressFormatter; -- (NSFormatter *)mailEnvelopeFullAddressFormatter; +- (NSFormatter *) mailDateFormatter; +- (NSFormatter *) mailEnvelopeAddressFormatter; +- (NSFormatter *) mailEnvelopeFullAddressFormatter; @end diff --git a/UI/MailerUI/WOContext+UIxMailer.m b/UI/MailerUI/WOContext+UIxMailer.m index fbe50299..716d14b9 100644 --- a/UI/MailerUI/WOContext+UIxMailer.m +++ b/UI/MailerUI/WOContext+UIxMailer.m @@ -33,11 +33,6 @@ // TODO: make configurable // TODO: cache! -- (NSFormatter *) mailSubjectFormatter -{ - return [[[UIxSubjectFormatter alloc] init] autorelease]; -} - - (NSFormatter *) mailDateFormatter { return [[self activeUser] dateFormatterInContext: self]; diff --git a/UI/Templates/MailPartViewers/UIxMailPartMessageViewer.wox b/UI/Templates/MailPartViewers/UIxMailPartMessageViewer.wox index 49011477..f762c682 100644 --- a/UI/Templates/MailPartViewers/UIxMailPartMessageViewer.wox +++ b/UI/Templates/MailPartViewers/UIxMailPartMessageViewer.wox @@ -9,8 +9,7 @@
: -
+
:
: diff --git a/UI/Templates/MailerUI/UIxMailView.wox b/UI/Templates/MailerUI/UIxMailView.wox index 74f56378..de0d6526 100644 --- a/UI/Templates/MailerUI/UIxMailView.wox +++ b/UI/Templates/MailerUI/UIxMailView.wox @@ -13,8 +13,7 @@ : - + diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index ba7d525e..dc2a9a02 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -14,7 +14,7 @@ function validateEditorInput(sender) { if (field.value == "") errortext = errortext + labels.error_missingsubject + "\n"; - if (!UIxRecipientSelectorHasRecipients()) + if (!hasRecipients()) errortext = errortext + labels.error_missingrecipients + "\n"; if (errortext.length > 0) { diff --git a/UI/WebServerResources/MailerUI.js b/UI/WebServerResources/MailerUI.js index 6e4d62e0..a87b84a8 100644 --- a/UI/WebServerResources/MailerUI.js +++ b/UI/WebServerResources/MailerUI.js @@ -405,7 +405,7 @@ function openMailbox(mailbox, reload, idx) { var url = ApplicationBaseURL + encodeURI(mailbox) + "/view?noframe=1"; var messageContent = $("messageContent"); messageContent.update(); - lastClickedRow = null; // from generic.js + lastClickedRow = -1; // from generic.js var currentMessage; if (!idx) { diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index e896992a..361c0708 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -317,7 +317,7 @@ function eventsListCallback(http) { var params = parseQueryParameters(http.callbackData); sortKey = params["sort"]; sortOrder = params["desc"]; - lastClickedRow = null; // from generic.js + lastClickedRow = -1; // from generic.js if (http.responseText.length > 0) { var data = http.responseText.evalJSON(true); diff --git a/UI/WebServerResources/UIxMailEditor.js b/UI/WebServerResources/UIxMailEditor.js index 2c8b26b7..212f1715 100644 --- a/UI/WebServerResources/UIxMailEditor.js +++ b/UI/WebServerResources/UIxMailEditor.js @@ -129,7 +129,7 @@ function validateEditorInput(sender) { if (field.value == "") errortext = errortext + labels["error_missingsubject"] + "\n"; - if (!UIxRecipientSelectorHasRecipients()) + if (!hasRecipients()) errortext = errortext + labels["error_missingrecipients"] + "\n"; if (errortext.length > 0) { diff --git a/UI/WebServerResources/UIxMailToSelection.js b/UI/WebServerResources/UIxMailToSelection.js index 8c04320c..df7c1a68 100644 --- a/UI/WebServerResources/UIxMailToSelection.js +++ b/UI/WebServerResources/UIxMailToSelection.js @@ -23,7 +23,7 @@ * It's required that "currentIndex" is defined in a top level context. * * Exports: - * defines UIxRecipientSelectorHasRecipients() returning a bool for the + * defines hasRecipients() returning a bool for the * surrounding context to check. */ @@ -179,16 +179,16 @@ function getAddressIDs() { addressIDs = new Array(); - addressList = $("addressList"); + addressList = $("addressList").tBodies[0]; rows = addressList.childNodes; count = rows.length; for (i = 0; i < count; i++) { var row, rowId; - row = addressList.childNodes[i]; + row = rows[i]; rowId = row.id; - if (rowId && rowId != 'row_last') { + if (rowId && rowId != 'lastRow') { var idx; idx = this.getIndexFromIdentifier(rowId); @@ -215,7 +215,7 @@ function getAddressCount() { return addressCount; } -function UIxRecipientSelectorHasRecipients() { +function hasRecipients() { var count; count = this.getAddressCount(); diff --git a/UI/WebServerResources/generic.js b/UI/WebServerResources/generic.js index 044347e6..5103916e 100644 --- a/UI/WebServerResources/generic.js +++ b/UI/WebServerResources/generic.js @@ -31,7 +31,7 @@ var menus = new Array(); var search = {}; var sorting = {}; -var lastClickedRow = null; +var lastClickedRow = -1; var weekStartIsMonday = true; @@ -495,7 +495,7 @@ function onRowClick(event) { var initialSelection = $(node.parentNode).getSelectedNodes(); if ((event.shiftKey == 1 || event.ctrlKey == 1) - && lastClickedRow + && (lastClickedRow >= 0) && (acceptMultiSelect(node.parentNode) || acceptMultiSelect(node.parentNode.parentNode))) { if (event.shiftKey)