See ChangeLog for full details.

Monotone-Parent: 4ebf80607b2a71f3b2192bd3ba8c310f15bd37f5
Monotone-Revision: 8b8f40b39b92d25a5eb1161d5efe0bd91fd3e207

Monotone-Author: ludovic@Sophos.ca
Monotone-Date: 2007-10-10T18:44:26
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Ludovic Marcotte 2007-10-10 18:44:26 +00:00
parent dc48bb9e4c
commit 778658c75a
11 changed files with 272 additions and 35 deletions

View File

@ -1,3 +1,17 @@
2007-10-10 Ludovic Marcotte <ludovic@inverse.ca>
* 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 <ludovic@inverse.ca>
* UI/WebServerResources/MailerUI.js

View File

@ -221,6 +221,7 @@ static NSNumber *sharedYes = nil;
return filterData;
}
#warning filters is leaked here
- (NSArray *) _parseCalendarFilters: (id <DOMElement>) 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

View File

@ -30,6 +30,9 @@
#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"
@ -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 <DOMElement>) filterElement
{
NSMutableDictionary *filterData;
id <DOMNode> parentNode;
id <DOMNodeList> 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 <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];
}
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: @" <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"];
}
- (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 <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;

View File

@ -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

View File

@ -30,6 +30,7 @@
#import <NGLdap/NGLdapEntry.h>
#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

View File

@ -44,6 +44,7 @@
+ (id) sharedUserManager;
- (NSArray *) sourceIDs;
- (NSDictionary *) metadataForSourceID: (NSString *) sourceID;
- (NSArray *) authenticationSourceIDs;
- (NSArray *) addressBookSourceIDs;

View File

@ -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

View File

@ -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 <wsourdeau@inverse.ca>
*
@ -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: @"<a href=\"%@%@\">%@</a>",
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

View File

@ -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;
}

View File

@ -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];
}

View File

@ -50,6 +50,11 @@
string="item.displayName"
selection="componentCalendar"
/></span></span>
<span class="checkBoxList"><var:string label:value="Priority:" />
<span class="content"><var:popup list="priorities" item="item"
label:noSelectionString="prio_0"
string="itemPriorityText" selection="priority"/>
</span></span>
<label id="attendeesLabel" style="display: none;"><var:string label:value="Attendees:"
/><span class="content"
><a href="#" id="attendeesHref"><!-- space --></a></span></label>