+
# GNUstep makefile
include ../common.make
Contacts_PRINCIPAL_CLASS = SOGoContactsProduct
Contacts_OBJC_FILES = \
+ NSObject+CardDAV.m \
Product.m \
- \
SOGoContactFolders.m \
SOGoContactGCSEntry.m \
SOGoContactGCSFolder.m \
--- /dev/null
+/* NSObject+CardDAV.h - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Ludovic Marcotte <ludovic@inverse.ca>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __Contacts_NSObject_CardDAV_H__
+#define __Contacts_NSObject_CardDAV_H__
+
+@interface NSObject (CardDAV)
+
+- (id) davAddressbookQuery: (id) queryContext;
+
+@end
+
+#endif
--- /dev/null
+/* NSObject+CardDAV.m - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Ludovic Marcotte <ludovic@inverse.ca>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#import "SOGoContactFolder.h"
+#import "SOGoContactGCSEntry.h"
+
+#import <DOM/DOMProtocols.h>
+#import <Foundation/NSArray.h>
+#import <NGExtensions/NSString+misc.h>
+#import <SaxObjC/SaxObjC.h>
+#import <SaxObjC/XMLNamespaces.h>
+#import <NGObjWeb/WOContext.h>
+#import <NGObjWeb/WORequest.h>
+#import <NGObjWeb/WOResponse.h>
+
+@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<SOGoContactFolder> o;
+
+ o = (id<SOGoContactFolder>)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 <DOMElement>) filterElement
+{
+ NSMutableDictionary *filterData;
+ id <DOMNode> parentNode;
+ id <DOMNodeList> 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 <DOMElement>) parentNode
+{
+ NSEnumerator *children;
+ id<DOMElement> 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 <DOMDocument> 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:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
+ [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
+ @" xmlns:C=\"urn:ietf:params:xml:ns:carddav\">\r\n"];
+
+ document = [[queryContext request] contentAsDOMDocument];
+ filters = [self _parseContactFilters: [document documentElement]];
+
+ [self _appendComponentsMatchingFilters: filters
+ toResponse: r
+ context: queryContext];
+ [r appendContentString:@"</D:multistatus>\r\n"];
+
+ return r;
+}
+
+@end
@class NSString, NSArray;
@class SOGoContactObject;
@class SOGoObject;
+@class WOResponse;
@protocol SOGoContactObject;
@protocol SOGoContactFolder <NSObject>
+- (void) appendObject: (NSDictionary *) object
+ withBaseURL: (NSString *) baseURL
+ toREPORTResponse: (WOResponse *) r;
+
- (NSArray *) lookupContactsWithFilter: (NSString *) filter
sortBy: (NSString *) sortKey
ordering: (NSComparisonResult) sortOrdering;
#import <SoObjects/SOGo/SOGoFolder.h>
#import "SOGoContactFolder.h"
+#import "NSObject+CardDAV.h"
@class NSArray;
@class NSString;
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoObject+SoDAV.h>
#import <NGObjWeb/WOContext.h>
-#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/WORequest.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <EOControl/EOQualifier.h>
#import <EOControl/EOSortOrdering.h>
#import <GDLContentStore/GCSFolder.h>
-#import <DOM/DOMProtocols.h>
-#import <SaxObjC/SaxObjC.h>
-#import <SaxObjC/XMLNamespaces.h>
#import <SoObjects/SOGo/NSDictionary+Utilities.h>
#import "SOGoContactGCSEntry.h"
isPut = NO;
obj = [super lookupName:_key inContext:_ctx acquire:NO];
+
if (!obj)
{
if ([[[_ctx request] method] isEqualToString: @"PUT"])
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 <DOMElement>) filterElement
+- (NSArray *) lookupContactsWithFilter: (NSString *) filter
+ sortBy: (NSString *) sortKey
+ ordering: (NSComparisonResult) sortOrdering
{
- NSMutableDictionary *filterData;
- id <DOMNode> parentNode;
- id <DOMNodeList> 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 <DOMElement>) parentNode
-{
- NSEnumerator *children;
- id<DOMElement> 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
[r appendContentString: @" </D:response>\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 <DOMDocument> 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:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
- [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
- @" xmlns:C=\"urn:ietf:params:xml:ns:carddav\">\r\n"];
-
- document = [[context request] contentAsDOMDocument];
- filters = [self _parseContactFilters: [document documentElement]];
-
- [self _appendComponentsMatchingFilters: filters
- toResponse: r];
- [r appendContentString:@"</D:multistatus>\r\n"];
-
- return r;
-}
-
- (NSArray *) davComplianceClassesInContext: (id)_ctx
{
NSMutableArray *classes;
return classes;
}
+- (NSArray *) davNamespaces
+{
+ return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"];
+}
+
- (NSString *) groupDavResourceType
{
return @"vcard-collection";
#define SOGOCONTACTLDAPFOLDER_H
#import "SOGoContactFolder.h"
+#import "NSObject+CardDAV.h"
@class NSMutableDictionary;
#import <SaxObjC/XMLNamespaces.h>
#import <SoObjects/SOGo/LDAPSource.h>
+#import <SoObjects/SOGo/NSString+Utilities.h>
#import "SOGoContactLDIFEntry.h"
#import "SOGoContactLDAPFolder.h"
+#import <NGExtensions/NSString+misc.h>
+#import <NGObjWeb/WORequest.h>
+#import <NGObjWeb/SoSelectorInvocation.h>
@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: @" <D:response>\r\n"];
+ [r appendContentString: @" <D:href>"];
+ [r appendContentString: baseURL];
+ if (![baseURL hasSuffix: @"/"])
+ [r appendContentString: @"/"];
+ [r appendContentString: name];
+ [r appendContentString: @"</D:href>\r\n"];
+
+ [r appendContentString: @" <D:propstat>\r\n"];
+ [r appendContentString: @" <D:prop>\r\n"];
+ etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
+ [component davEntityTag]];
+ [r appendContentString: etagLine];
+ [r appendContentString: @" </D:prop>\r\n"];
+ [r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
+ [r appendContentString: @" </D:propstat>\r\n"];
+ [r appendContentString: @" <C:addressbook-data>"];
+ contactString = [[component contentAsString] stringByEscapingXMLString];
+ [r appendContentString: contactString];
+ [r appendContentString: @"</C:addressbook-data>\r\n"];
+ [r appendContentString: @" </D:response>\r\n"];
+}
+
+ (id) folderWithName: (NSString *) aName
andDisplayName: (NSString *) aDisplayName
inContainer: (id) aContainer
return displayName;
}
+- (NSArray *) davNamespaces
+{
+ return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"];
+}
+
- (id) lookupName: (NSString *) objectName
inContext: (WOContext *) lookupContext
acquire: (BOOL) acquire
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;
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
- (NSString *) htmlToText;
+- (NSString *) decodedSubject;
+
@end
#endif /* NSSTRING_MAIL_H */
#import <SaxObjC/SaxLexicalHandler.h>
#import <SaxObjC/SaxXMLReader.h>
#import <SaxObjC/SaxXMLReaderFactory.h>
+#import <NGExtensions/NGQuotedPrintableCoding.h>
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSObject+Logs.h>
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
hasPrefix = NO;
- subject = [[self envelope] subject];
+ subject = [self decodedSubject];
i = 0;
while (!hasPrefix && replyPrefixes[i])
if ([subject hasPrefix: replyPrefixes[i]])
};
unsigned int count, length;
- subject = [[self envelope] subject];
+ subject = [self decodedSubject];
length = [subject length];
if (!length)
{
{
NSString *subject, *newSubject;
- subject = [[self envelope] subject];
+ subject = [self decodedSubject];
if ([subject length] > 0)
newSubject = [NSString stringWithFormat: @"[Fwd: %@]", subject];
else
- (NGImap4Envelope *)envelope;
- (NSString *)subject;
+- (NSString *)decodedSubject;
- (NSCalendarDate *)date;
- (NSArray *)fromEnvelopeAddresses;
- (NSArray *)toEnvelopeAddresses;
return [[self envelope] subject];
}
+- (NSString *) decodedSubject
+{
+ return [[self subject] decodedSubject];
+}
+
- (NSCalendarDate *) date
{
NSTimeZone *userTZ;
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";
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";
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";
-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";
#import <NGImap4/NGImap4Envelope.h>
#import <NGImap4/NGImap4EnvelopeAddress.h>
+#import <SoObjects/Mailer/NSData+Mail.h>
+#import <SoObjects/Mailer/NSString+Mail.h>
+
#import <UI/MailerUI/WOContext+UIxMailer.h>
#import "UIxMailRenderingContext.h"
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;
"Mark Unread" = "Mark Unread";
"Mark Read" = "Mark Read";
+"Untitled" = "Untitled";
+
/* Tree */
"SentFolderName" = "Sent";
"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 */
MailerUIProduct.m \
\
UIxMailFormatter.m \
- UIxSubjectFormatter.m \
UIxEnvelopeAddressFormatter.m \
WOContext+UIxMailer.m \
\
"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 */
@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;
- (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;
}
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!) */
+++ /dev/null
-/*
- 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 <NGExtensions/NSNull+misc.h>
-#import <NGExtensions/NSObject+Logs.h>
-#import <NGMail/NGMimeMessageParser.h>
-
-#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 */
@class NSFormatter;
-@interface WOContext(UIxMailer)
+@interface WOContext (UIxMailer)
-- (NSFormatter *)mailSubjectFormatter;
-- (NSFormatter *)mailDateFormatter;
-- (NSFormatter *)mailEnvelopeAddressFormatter;
-- (NSFormatter *)mailEnvelopeFullAddressFormatter;
+- (NSFormatter *) mailDateFormatter;
+- (NSFormatter *) mailEnvelopeAddressFormatter;
+- (NSFormatter *) mailEnvelopeFullAddressFormatter;
@end
// TODO: make configurable
// TODO: cache!
-- (NSFormatter *) mailSubjectFormatter
-{
- return [[[UIxSubjectFormatter alloc] init] autorelease];
-}
-
- (NSFormatter *) mailDateFormatter
{
return [[self activeUser] dateFormatterInContext: self];
<!-- TODO: the table is a DUP to UIxMailView, own component? -->
<div class="bodyFields">
<span class="fieldName"><var:string label:value="Subject"/>:</span>
- <var:string value="envelope.subject"
- formatter="context.mailSubjectFormatter"/><br/>
+ <var:string value="messageSubject"/><br/>
<span class="fieldName"><var:string label:value="From"/>:</span>
<var:string value="fromAddresses"/><br/>
<span class="fieldName"><var:string label:value="Date"/>:</span>
<tr class="mailer_fieldrow">
<td class="mailer_fieldname" ><var:string label:value="Subject"/>:</td>
<td class="mailer_subjectfieldvalue">
- <var:string value="clientObject.subject"
- formatter="context.mailSubjectFormatter"/>
+ <var:string value="messageSubject"/>
</td>
</tr>
<tr class="mailer_fieldrow">
if (field.value == "")
errortext = errortext + labels.error_missingsubject + "\n";
- if (!UIxRecipientSelectorHasRecipients())
+ if (!hasRecipients())
errortext = errortext + labels.error_missingrecipients + "\n";
if (errortext.length > 0) {
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) {
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);
if (field.value == "")
errortext = errortext + labels["error_missingsubject"] + "\n";
- if (!UIxRecipientSelectorHasRecipients())
+ if (!hasRecipients())
errortext = errortext + labels["error_missingrecipients"] + "\n";
if (errortext.length > 0) {
* 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.
*/
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);
return addressCount;
}
-function UIxRecipientSelectorHasRecipients() {
+function hasRecipients() {
var count;
count = this.getAddressCount();
var search = {};
var sorting = {};
-var lastClickedRow = null;
+var lastClickedRow = -1;
var weekStartIsMonday = true;
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)