diff --git a/ChangeLog b/ChangeLog index 3eb1a972e..2ed0371b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2007-10-10 Ludovic Marcotte + + * UI/Scheduler/UIxComponentEditor.m + Implemented event/task priority support. + + * SoObjects/Contacts/SOGoContactGCSFolder.m + Added CardDAV support. + + * SoObjects/SOGo/LDAPUserManager.m and SOGoUser.m + Implemented From: based on LDAP results based on + the MailFieldNames attribute (an array) specified + in every LDAP-based authentication sources. + + 2007-10-05 Ludovic Marcotte * UI/WebServerResources/MailerUI.js diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 1a2329e51..a2306fba6 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -221,6 +221,7 @@ static NSNumber *sharedYes = nil; return filterData; } +#warning filters is leaked here - (NSArray *) _parseCalendarFilters: (id ) parentNode { NSEnumerator *children; @@ -258,6 +259,7 @@ static NSNumber *sharedYes = nil; max = [filters count]; for (count = 0; count < max; count++) { +#warning huh? why not objectAtIndex: count? currentFilter = [filters objectAtIndex: 0]; apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"] to: [currentFilter objectForKey: @"end"] @@ -932,7 +934,7 @@ static NSNumber *sharedYes = nil; @"c_status", @"c_classification", @"c_isallday", @"c_isopaque", @"c_participants", @"c_partmails", - @"c_partstates", @"c_sequence", @"c_priority", + @"c_partstates", @"c_sequence", @"c_priority", @"c_cycleinfo", nil]; return [self fetchFields: infos from: _startDate to: _endDate diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.m b/SoObjects/Contacts/SOGoContactGCSFolder.m index b8856acd9..1a58723dd 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.m +++ b/SoObjects/Contacts/SOGoContactGCSFolder.m @@ -30,6 +30,9 @@ #import #import #import +#import +#import +#import #import #import "SOGoContactGCSEntry.h" @@ -100,6 +103,7 @@ if (filter && [filter length] > 0) { +#warning why we do not use %%%@%% everywhere? qs = [NSString stringWithFormat: @"(c_sn isCaseInsensitiveLike: '%@%%') OR " @"(c_givenname isCaseInsensitiveLike: '%@%%') OR " @@ -169,6 +173,141 @@ 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 +{ + NSMutableDictionary *filterData; + id parentNode; + id ranges; + NSString *componentName; + + parentNode = [filterElement parentNode]; + + if ([[parentNode tagName] isEqualToString: @"filter"] && + [self _isValidFilter: [filterElement attribute: @"name"]]) + { + 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"]]; + } + } + 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]; + } + + return filters; +} + +- (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]; + } + } +} + +- (void) appendObject: (NSDictionary *) object + withBaseURL: (NSString *) baseURL + toREPORTResponse: (WOResponse *) r +{ + SOGoContactGCSEntry *component; + Class componentClass; + NSString *name, *etagLine, *contactString; + + name = [object objectForKey: @"c_name"]; + componentClass = [SOGoContactGCSEntry class]; + + component = [componentClass objectWithName: name 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"]; +} + - (NSArray *) lookupContactsWithFilter: (NSString *) filter sortBy: (NSString *) sortKey ordering: (NSComparisonResult) sortOrdering @@ -177,13 +316,11 @@ EOQualifier *qualifier; EOSortOrdering *ordering; -// NSLog (@"fetching records matching '%@', sorted by '%@' in order %d", -// filter, sortKey, sortOrdering); - fields = folderListingFields; qualifier = [self _qualifierForFilter: filter]; dbRecords = [[self ocsFolder] fetchFields: fields matchingQualifier: qualifier]; + if ([dbRecords count] > 0) { records = [self _flattenedRecords: dbRecords]; @@ -201,7 +338,7 @@ // else // [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__]; - //[self debugWithFormat:@"fetched %i records.", [records count]]; + [self debugWithFormat:@"fetched %i records.", [records count]]; return records; } @@ -210,6 +347,32 @@ 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; diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 3e8a3c7b4..201e16b8a 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -31,6 +31,7 @@ @interface LDAPSource : NSObject { + NSString *sourceID; NSString *bindDN; NSString *hostname; unsigned int port; @@ -67,6 +68,7 @@ - (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) entryID; - (NSArray *) allEntryIDs; - (NSArray *) fetchContactsMatching: (NSString *) filter; +- (NSString *) sourceID; @end diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index c2350e446..28475ad18 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -30,6 +30,7 @@ #import #import "LDAPSource.h" +#import "LDAPUserManager.h" static NSArray *commonSearchFields; static int timeLimit; @@ -114,8 +115,9 @@ static int sizeLimit; @"locality", @"birthyear", @"serialNumber", - @"calFBURL", + @"calFBURL", @"proxyAddresses", nil]; + [commonSearchFields retain]; } } @@ -138,6 +140,7 @@ static int sizeLimit; hostname = nil; port = 389; password = nil; + sourceID = nil; baseDN = nil; IDField = @"cn"; /* the first part of a user DN */ @@ -163,6 +166,7 @@ static int sizeLimit; [UIDField release]; [bindFields release]; [ldapConnection release]; + [sourceID release]; [super dealloc]; } @@ -170,6 +174,8 @@ static int sizeLimit; { self = [self init]; + ASSIGN(sourceID, [udSource objectForKey: @"id"]); + [self setBindDN: [udSource objectForKey: @"bindDN"] hostname: [udSource objectForKey: @"hostname"] port: [udSource objectForKey: @"port"] @@ -340,6 +346,8 @@ static int sizeLimit; - (NSArray *) _searchAttributes { + NSArray *attrs; + if (!searchAttributes) { searchAttributes = [NSMutableArray new]; @@ -350,6 +358,12 @@ static int sizeLimit; [searchAttributes addObjectsFromArray: commonSearchFields]; } + // We also include our MailFieldNames in the search + if ((attrs = [[[LDAPUserManager sharedUserManager] metadataForSourceID: sourceID] objectForKey: @"MailFieldNames"])) + { + [searchAttributes addObjectsFromArray: attrs]; + } + return searchAttributes; } @@ -493,4 +507,9 @@ static int sizeLimit; return contactEntry; } +- (NSString *) sourceID +{ + return sourceID; +} + @end diff --git a/SoObjects/SOGo/LDAPUserManager.h b/SoObjects/SOGo/LDAPUserManager.h index 6a3c6510b..5ab95c923 100644 --- a/SoObjects/SOGo/LDAPUserManager.h +++ b/SoObjects/SOGo/LDAPUserManager.h @@ -44,6 +44,7 @@ + (id) sharedUserManager; - (NSArray *) sourceIDs; +- (NSDictionary *) metadataForSourceID: (NSString *) sourceID; - (NSArray *) authenticationSourceIDs; - (NSArray *) addressBookSourceIDs; diff --git a/SoObjects/SOGo/LDAPUserManager.m b/SoObjects/SOGo/LDAPUserManager.m index 01ab01a36..76e181088 100644 --- a/SoObjects/SOGo/LDAPUserManager.m +++ b/SoObjects/SOGo/LDAPUserManager.m @@ -75,6 +75,9 @@ static NSString *defaultMailDomain = nil; value = [udSource objectForKey: @"displayName"]; if (value) [metadata setObject: value forKey: @"displayName"]; + value = [udSource objectForKey: @"MailFieldNames"]; + if (value) + [metadata setObject: value forKey: @"MailFieldNames"]; [sourcesMetadata setObject: metadata forKey: sourceID]; } @@ -151,6 +154,11 @@ static NSString *defaultMailDomain = nil; return sourceIDs; } +- (NSDictionary *) metadataForSourceID: (NSString *) sourceID +{ + return [sourcesMetadata objectForKey: sourceID]; +} + - (NSArray *) authenticationSourceIDs { return [self _sourcesOfType: @"canAuthenticate"]; @@ -295,7 +303,8 @@ static NSString *defaultMailDomain = nil; NSEnumerator *ldapSources; LDAPSource *currentSource; NSString *cn, *email, *c_uid; - + NSArray *attrs; + emails = [NSMutableArray array]; cn = nil; c_uid = nil; @@ -311,15 +320,18 @@ static NSString *defaultMailDomain = nil; cn = [userEntry objectForKey: @"c_cn"]; if (!c_uid) c_uid = [userEntry objectForKey: @"c_uid"]; - email = [userEntry objectForKey: @"mail"]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; - email = [userEntry objectForKey: @"mozillaSecondEmail"]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; - email = [userEntry objectForKey: @"xmozillasecondemail"]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; + + 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]; + } + } } currentSource = [ldapSources nextObject]; } @@ -332,7 +344,13 @@ static NSString *defaultMailDomain = nil; [currentUser setObject: emails forKey: @"emails"]; [currentUser setObject: cn forKey: @"cn"]; [currentUser setObject: c_uid forKey: @"c_uid"]; - [self _fillContactMailRecords: currentUser]; + + // If our LDAP queries gave us nothing, we add at least one default + // email address based on the default domain. + if ([emails count] == 0) + { + [self _fillContactMailRecords: currentUser]; + } } - (void) _retainUser: (NSDictionary *) newUser diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index e5d67de73..1192ccd70 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -1,6 +1,6 @@ /* NSString+Utilities.m - this file is part of SOGo * - * Copyright (C) 2006 Inverse group conseil + * Copyright (C) 2006 Inverse groupe conseil * * Author: Wolfgang Sourdeau * @@ -155,7 +155,7 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil; if (!urlAfterEndingChars) { urlAfterEndingChars = [NSMutableCharacterSet new]; - [urlAfterEndingChars addCharactersInString: @"\t \r\n"]; + [urlAfterEndingChars addCharactersInString: @"[]\t \r\n"]; } start = refRange.location; @@ -165,7 +165,7 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil; start--; start++; length = [self length] - start; - workRange = NSMakeRange (start, length); + workRange = NSMakeRange(start, length); workRange = [self rangeOfCharacterFromSet: urlAfterEndingChars options: NSLiteralSearch range: workRange]; if (workRange.location != NSNotFound) @@ -193,13 +193,15 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil; while (httpRange.location != NSNotFound) { if ([ranges hasRangeIntersection: httpRange]) - rest.location = NSMaxRange (httpRange); + rest.location = NSMaxRange(httpRange); else { currentURL = [selfCopy _rangeOfURLInRange: httpRange]; urlText = [selfCopy substringFromRange: currentURL]; if ([urlText length] > matchLength) { + if ([urlText hasPrefix: prefix]) prefix = @""; + newUrlText = [NSString stringWithFormat: @"%@", prefix, urlText, urlText]; [selfCopy replaceCharactersInRange: currentURL @@ -208,8 +210,9 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil; = NSMakeRange (currentURL.location, [newUrlText length]); [ranges addRange: currentURL]; } - rest.location = NSMaxRange (currentURL); + rest.location = NSMaxRange(currentURL); } + length = [selfCopy length]; rest.length = length - rest.location; httpRange = [selfCopy rangeOfString: match diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 9dedfae46..0197ca939 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -365,22 +365,31 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; if (!mailAccounts) { + NSArray *mails; + int i; + mailAccount = [NSMutableDictionary dictionary]; name = [NSString stringWithFormat: @"%@@%@", login, fallbackIMAP4Server]; [mailAccount setObject: login forKey: @"userName"]; [mailAccount setObject: fallbackIMAP4Server forKey: @"serverName"]; [mailAccount setObject: name forKey: @"name"]; - identity = [NSMutableDictionary dictionary]; - fullName = [self cn]; - if (![fullName length]) - fullName = login; - [identity setObject: fullName forKey: @"fullName"]; - [identity setObject: [self systemEmail] forKey: @"email"]; - [identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"]; - identities = [NSMutableArray array]; - [identities addObject: identity]; + mails = [self allEmails]; + + for (i = 0; i < [mails count]; i++) + { + identity = [NSMutableDictionary dictionary]; + fullName = [self cn]; + if (![fullName length]) + fullName = login; + [identity setObject: fullName forKey: @"fullName"]; + [identity setObject: [mails objectAtIndex: i] forKey: @"email"]; + + if (i == 0) [identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"]; + [identities addObject: identity]; + } + [mailAccount setObject: identities forKey: @"identities"]; mailAccounts = [NSMutableArray new]; @@ -499,7 +508,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; [(WOContext *)_ctx setObject: ((folder) ? folder - : [NSNull null]) + : (id)[NSNull null]) forKey: @"ActiveUserHomeFolder"]; return folder; } diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index 9046b3229..b82a1b299 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -373,14 +373,15 @@ - (NSArray *) priorities { /* 0 == undefined - 5 == normal + 9 == low + 5 == medium 1 == high */ static NSArray *priorities = nil; if (!priorities) { - priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil]; + priorities = [NSArray arrayWithObjects: @"9", @"5", @"1", nil]; [priorities retain]; } @@ -856,8 +857,8 @@ [component setUid: [clientObject nameInContainer]]; [component setCreated: now]; [component setTimeStampAsDate: now]; - [component setPriority: @"0"]; } + [component setPriority: priority]; [component setLastModified: now]; } diff --git a/UI/Templates/SchedulerUI/UIxComponentEditor.wox b/UI/Templates/SchedulerUI/UIxComponentEditor.wox index 14e763516..afa92bf3c 100644 --- a/UI/Templates/SchedulerUI/UIxComponentEditor.wox +++ b/UI/Templates/SchedulerUI/UIxComponentEditor.wox @@ -50,6 +50,11 @@ string="item.displayName" selection="componentCalendar" /> + + +