diff --git a/ChangeLog b/ChangeLog index 6a3b06321..97113cff4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,32 @@ +2008-10-03 Francis Lachapelle + + * UI/MainUI/SOGoUserHomePage.m ([WOResponse + _foldersResponseForResults]): include the value of the attribute value + from user defaults SOGoLDAPContactInfoAttribute. + + * UI/Contacts/UIxContactFoldersView.m ([WOActionResults + allContactSearchAction]): return a dictionary with the found + contacts and the original search string. + ([WOActionResults contactSearchAction]): idem. + + * SoObjects/SOGo/LDAPUserManager.m ([NSArray + _compactAndCompleteContacts:]): add the attribute from user + defaults SOGoLDAPContactInfoAttribute for each returned contact. + + * SoObjects/SOGo/LDAPSource.m ([EOQualifier _qualifierForFilter: + ]): the filter doesn't match only the beginning of the attribute's + value anymore. + ([NSArray _searchAttributes]): add the attribute from user + defaults SOGoLDAPContactInfoAttribute if not already included. + + * SoObjects/Contacts/SOGoContactLDAPFolder.m ([NSArray + _flattenedRecords]): added the key "contactInfo" with the value of + the attribute from user defaults SOGoLDAPContactInfoAttribute. + + * UI/Contacts/UIxContactFoldersView.m ([NSDictionary + _responseForResults:]): returns a dictionary instead of a + WOResponse. Currently only called by contactSearchAction. + 2008-10-03 Cyril Robert * SoObjects/Mailer/SOGoMailFolder.m Added copyUIDs and moveUIDs diff --git a/SoObjects/Contacts/SOGoContactLDAPFolder.m b/SoObjects/Contacts/SOGoContactLDAPFolder.m index db6bf55d7..0f726b732 100644 --- a/SoObjects/Contacts/SOGoContactLDAPFolder.m +++ b/SoObjects/Contacts/SOGoContactLDAPFolder.m @@ -1,6 +1,6 @@ /* SOGoContactLDAPFolder.m - this file is part of SOGo * - * Copyright (C) 2006 Inverse inc. + * Copyright (C) 2006-2008 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -24,6 +24,7 @@ #import #import #import +#import #import #import @@ -187,8 +188,10 @@ NSEnumerator *oldRecords; NSDictionary *oldRecord; NSMutableDictionary *newRecord; - NSString *data; + NSString *data, *contactInfo; + NSUserDefaults *ud; + ud = [NSUserDefaults standardUserDefaults]; newRecords = [[NSMutableArray alloc] initWithCapacity: [records count]]; [newRecords autorelease]; @@ -237,6 +240,13 @@ data = @""; [newRecord setObject: data forKey: @"phone"]; + contactInfo = [ud stringForKey: @"SOGoLDAPContactInfoAttribute"]; + if ([contactInfo length] > 0) { + data = [oldRecord objectForKey: contactInfo]; + if ([data length] > 0) + [newRecord setObject: data forKey: @"contactInfo"]; + } + [newRecords addObject: newRecord]; oldRecord = [oldRecords nextObject]; } diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index dfc37d341..b3822f832 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -23,6 +23,7 @@ #import #import #import +#import #import #import @@ -36,6 +37,7 @@ #import "LDAPSource.h" static NSArray *commonSearchFields; +static NSString *LDAPContactInfoAttribute = nil; static int timeLimit; static int sizeLimit; @@ -48,6 +50,7 @@ static int sizeLimit; if (!commonSearchFields) { ud = [NSUserDefaults standardUserDefaults]; + LDAPContactInfoAttribute = [ud stringForKey: @"SOGoLDAPContactInfoAttribute"]; sizeLimit = [ud integerForKey: @"SOGoLDAPQueryLimit"]; timeLimit = [ud integerForKey: @"SOGoLDAPQueryTimeout"]; @@ -121,6 +124,7 @@ static int sizeLimit; @"calFBURL", @"proxyAddresses", nil]; + [LDAPContactInfoAttribute retain]; [commonSearchFields retain]; } } @@ -341,7 +345,7 @@ static int sizeLimit; NSString *qs, *mailFormat, *fieldFormat; EOQualifier *qualifier; - fieldFormat = [NSString stringWithFormat: @"(%%@='%@*')", filter]; + fieldFormat = [NSString stringWithFormat: @"(%%@='*%@*')", filter]; mailFormat = [[mailFields stringsWithFormat: fieldFormat] componentsJoinedByString: @" OR "]; @@ -394,8 +398,12 @@ static int sizeLimit; - (NSArray *) _searchAttributes { + NSUserDefaults *ud; + NSString *contactInfo; + if (!searchAttributes) { + ud = [NSUserDefaults standardUserDefaults]; searchAttributes = [NSMutableArray new]; if (CNField) [searchAttributes addObject: CNField]; @@ -404,6 +412,12 @@ static int sizeLimit; [searchAttributes addObjectsFromArray: mailFields]; [searchAttributes addObjectsFromArray: [self _contraintsFields]]; [searchAttributes addObjectsFromArray: commonSearchFields]; + + // Add SOGoLDAPContactInfoAttribute from user defaults + contactInfo = [ud stringForKey: @"SOGoLDAPContactInfoAttribute"]; + if ([contactInfo length] > 0 && + ![searchAttributes containsObject: contactInfo]) + [searchAttributes addObject: contactInfo]; } return searchAttributes; diff --git a/SoObjects/SOGo/LDAPUserManager.m b/SoObjects/SOGo/LDAPUserManager.m index 3250e79a2..85c3f7b95 100644 --- a/SoObjects/SOGo/LDAPUserManager.m +++ b/SoObjects/SOGo/LDAPUserManager.m @@ -34,6 +34,7 @@ #import "LDAPUserManager.h" static NSString *defaultMailDomain = nil; +static NSString *LDAPContactInfoAttribute = nil; static BOOL defaultMailDomainIsConfigured = NO; static BOOL forceImapLoginWithEmail = NO; @@ -49,13 +50,16 @@ static BOOL forceImapLoginWithEmail = NO; defaultMailDomain = [ud stringForKey: @"SOGoDefaultMailDomain"]; [defaultMailDomain retain]; defaultMailDomainIsConfigured = YES; - } - if (!defaultMailDomain) - { - [self warnWithFormat: - @"no domain specified for SOGoDefaultMailDomain," - @" value set to 'localhost'"]; - defaultMailDomain = @"localhost"; + + if (!defaultMailDomain) + { + [self warnWithFormat: + @"no domain specified for SOGoDefaultMailDomain," + @" value set to 'localhost'"]; + defaultMailDomain = @"localhost"; + } + + LDAPContactInfoAttribute = [ud stringForKey: @"SOGoLDAPContactInfoAttribute"]; } if (!forceImapLoginWithEmail) forceImapLoginWithEmail = [ud boolForKey: @"SOGoForceIMAPLoginWithEmail"]; @@ -498,6 +502,11 @@ static BOOL forceImapLoginWithEmail = NO; email = [userEntry objectForKey: @"xmozillasecondemail"]; if (email && ![emails containsObject: email]) [emails addObject: email]; + if ([LDAPContactInfoAttribute length] > 0 && + [[returnContact objectForKey: LDAPContactInfoAttribute] length] > 0 && + [userEntry objectForKey: LDAPContactInfoAttribute] != nil) + [returnContact setObject: [userEntry objectForKey: LDAPContactInfoAttribute] + forKey: LDAPContactInfoAttribute]; } userEntry = [contacts nextObject]; diff --git a/UI/Contacts/UIxContactFoldersView.m b/UI/Contacts/UIxContactFoldersView.m index 7bf95ed38..daedbf9a2 100644 --- a/UI/Contacts/UIxContactFoldersView.m +++ b/UI/Contacts/UIxContactFoldersView.m @@ -1,6 +1,6 @@ /* UIxContactFoldersView.m - this file is part of SOGo * - * Copyright (C) 2006 Inverse inc. + * Copyright (C) 2006-2008 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -140,23 +140,23 @@ withSearchOn: (NSString *) contact return email; } -- (WOResponse *) _responseForResults: (NSArray *) results +- (NSDictionary *) _responseForResults: (NSArray *) results { - WOResponse *response; NSEnumerator *contacts; - NSString *email; + NSString *email, *infoKey, *info; NSDictionary *contact; NSMutableArray *formattedContacts; NSMutableDictionary *formattedContact; + NSUserDefaults *sud; - response = [context response]; + formattedContacts = [NSMutableArray arrayWithCapacity: [results count]]; if ([results count] > 0) { - [response setStatus: 200]; + sud = [NSUserDefaults standardUserDefaults]; + infoKey = [sud stringForKey: @"SOGoLDAPContactInfoAttribute"]; contacts = [results objectEnumerator]; contact = [contacts nextObject]; - formattedContacts = [[NSMutableArray alloc] initWithCapacity: [results count]]; while (contact) { email = [contact objectForKey: @"c_email"]; @@ -169,17 +169,20 @@ withSearchOn: (NSString *) contact forKey: @"name"]; [formattedContact setObject: email forKey: @"email"]; + if ([infoKey length] > 0) + { + info = [contact objectForKey: infoKey]; + if (info != nil) + [formattedContact setObject: info + forKey: @"contactInfo"]; + } [formattedContacts addObject: formattedContact]; } contact = [contacts nextObject]; } - [response appendContentString: [formattedContacts jsonRepresentation]]; - [formattedContacts release]; } - else - [response setStatus: 404]; - return response; + return formattedContacts; } - (id ) allContactSearchAction @@ -187,7 +190,7 @@ withSearchOn: (NSString *) contact id result; id folder; NSString *searchText, *mail; - NSDictionary *contact; + NSDictionary *contact, *data; NSArray *folders, *contacts, *descriptors, *sortedContacts; NSMutableDictionary *uniqueContacts; unsigned int i, j; @@ -214,21 +217,20 @@ withSearchOn: (NSString *) contact if ([mail isNotNull] && [uniqueContacts objectForKey: mail] == nil) [uniqueContacts setObject: contact forKey: [contact objectForKey: @"mail"]]; } - } - - result = [context response]; + } if ([uniqueContacts count] > 0) { // Sort the contacts by display name displayNameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"displayName" ascending:YES] autorelease]; descriptors = [NSArray arrayWithObjects: displayNameDescriptor, nil]; - sortedContacts = [[uniqueContacts allValues] sortedArrayUsingDescriptors: descriptors]; - - [(WOResponse*)result appendContentString: [sortedContacts jsonRepresentation]]; + sortedContacts = [[uniqueContacts allValues] sortedArrayUsingDescriptors: descriptors]; } - else - [(WOResponse*)result setStatus: 404]; + data = [NSDictionary dictionaryWithObjectsAndKeys: searchText, @"searchText", + sortedContacts, @"contacts", + nil]; + result = [context response]; + [(WOResponse*)result appendContentString: [data jsonRepresentation]]; } else result = [NSException exceptionWithHTTPStatus: 400 @@ -239,16 +241,22 @@ withSearchOn: (NSString *) contact - (id ) contactSearchAction { - NSString *contact; + NSDictionary *contacts, *data; + NSString *searchText; id result; LDAPUserManager *um; - contact = [self queryParameterForKey: @"search"]; - if ([contact length] > 0) + searchText = [self queryParameterForKey: @"search"]; + if ([searchText length] > 0) { um = [LDAPUserManager sharedUserManager]; - result - = [self _responseForResults: [um fetchContactsMatching: contact]]; + contacts + = [self _responseForResults: [um fetchContactsMatching: searchText]]; + data = [NSDictionary dictionaryWithObjectsAndKeys: searchText, @"searchText", + contacts, @"contacts", + nil]; + result = [context response]; + [(WOResponse*)result appendContentString: [data jsonRepresentation]]; } else result = [NSException exceptionWithHTTPStatus: 400 diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 96252ceb7..2b47ecc94 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -1,6 +1,6 @@ /* SOGoUserHomePage.m - this file is part of SOGo * - * Copyright (C) 2007 Inverse inc. + * Copyright (C) 2007, 2008 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -45,6 +45,7 @@ #define intervalSeconds 900 /* 15 minutes */ static NSString *defaultModule = nil; +static NSString *LDAPContactInfoAttribute = nil; @interface SOGoUserHomePage : UIxComponent @@ -59,6 +60,7 @@ static NSString *defaultModule = nil; if (!defaultModule) { ud = [NSUserDefaults standardUserDefaults]; + defaultModule = [ud stringForKey: @"SOGoUIxDefaultModule"]; if (defaultModule) { @@ -75,6 +77,9 @@ static NSString *defaultModule = nil; defaultModule = @"Calendar"; [self logWithFormat: @"default module set to '%@'", defaultModule]; [defaultModule retain]; + + LDAPContactInfoAttribute = [ud stringForKey: @"SOGoLDAPContactInfoAttribute"]; + [LDAPContactInfoAttribute retain]; } } @@ -287,9 +292,11 @@ static NSString *defaultModule = nil; folders = [results objectForKey: contact]; foldersString = [self _foldersStringForFolders: [folders objectEnumerator]]; - [responseString appendFormat: @"%@:%@:%@%@\n", uid, + [responseString appendFormat: @"%@:%@:%@:%@%@\n", uid, [contact objectForKey: @"cn"], [contact objectForKey: @"c_email"], + ([LDAPContactInfoAttribute length] > 0 && [contact objectForKey: LDAPContactInfoAttribute] != nil) ? + [contact objectForKey: LDAPContactInfoAttribute] : @"", foldersString]; } [response appendContentString: responseString]; diff --git a/UI/WebServerResources/UIxAttendeesEditor.js b/UI/WebServerResources/UIxAttendeesEditor.js index f30fdd288..1841c361e 100644 --- a/UI/WebServerResources/UIxAttendeesEditor.js +++ b/UI/WebServerResources/UIxAttendeesEditor.js @@ -69,23 +69,23 @@ function onContactKeydown(event) { } else if ($('attendeesMenu').getStyle('visibility') == 'visible') { attendeesEditor.currentField = this; - if (event.keyCode == 38) { // Up arrow + if (event.keyCode == Event.KEY_UP) { // Up arrow if (attendeesEditor.selectedIndex > 0) { var attendees = $('attendeesMenu').select("li"); attendees[attendeesEditor.selectedIndex--].removeClassName("selected"); attendees[attendeesEditor.selectedIndex].addClassName("selected"); - this.value = this.confirmedValue = attendees[attendeesEditor.selectedIndex].firstChild.nodeValue.trim(); + this.value = this.confirmedValue = attendees[attendeesEditor.selectedIndex].readAttribute("address"); this.uid = attendees[attendeesEditor.selectedIndex].uid; } } - else if (event.keyCode == 40) { // Down arrow + else if (event.keyCode == Event.KEY_DOWN) { // Down arrow var attendees = $('attendeesMenu').select("li"); if (attendees.size() - 1 > attendeesEditor.selectedIndex) { if (attendeesEditor.selectedIndex >= 0) attendees[attendeesEditor.selectedIndex].removeClassName("selected"); attendeesEditor.selectedIndex++; attendees[attendeesEditor.selectedIndex].addClassName("selected"); - this.value = this.confirmedValue = attendees[attendeesEditor.selectedIndex].firstChild.nodeValue.trim(); + this.value = this.confirmedValue = attendees[attendeesEditor.selectedIndex].readAttribute("address"); this.uid = attendees[attendeesEditor.selectedIndex].uid; } } @@ -121,19 +121,27 @@ function performSearchCallback(http) { var start = input.value.length; var data = http.responseText.evalJSON(true); - if (data.length > 1) { + if (data.contacts.length > 1) { $(list.childNodesWithTag("li")).each(function(item) { item.remove(); }); // Populate popup menu - for (var i = 0; i < data.length; i++) { - var contact = data[i]; + for (var i = 0; i < data.contacts.length; i++) { + var contact = data.contacts[i]; var completeEmail = contact["name"] + " <" + contact["email"] + ">"; - var node = document.createElement("li"); + var node = new Element('li', { 'address': completeEmail }); + var matchPosition = completeEmail.toLowerCase().indexOf(data.searchText.toLowerCase()); + var matchBefore = completeEmail.substring(0, matchPosition); + var matchText = completeEmail.substring(matchPosition, matchPosition + data.searchText.length); + var matchAfter = completeEmail.substring(matchPosition + data.searchText.length); list.appendChild(node); node.uid = contact["uid"]; - node.appendChild(document.createTextNode(completeEmail)); + node.appendChild(document.createTextNode(matchBefore)); + node.appendChild(new Element('strong').update(matchText)); + node.appendChild(document.createTextNode(matchAfter)); + if (contact["contactInfo"]) + node.appendChild(document.createTextNode(" (" + contact["contactInfo"] + ")")); $(node).observe("mousedown", onAttendeeResultClick); } @@ -145,7 +153,7 @@ function performSearchCallback(http) { var heightDiff = window.height() - offset[1]; var nodeHeight = node.getHeight(); - if ((data.length * nodeHeight) > heightDiff) + if ((data.contacts.length * nodeHeight) > heightDiff) // Limit the size of the popup to the window height, minus 12 pixels height = parseInt(heightDiff/nodeHeight) * nodeHeight - 12 + 'px'; @@ -162,9 +170,9 @@ function performSearchCallback(http) { if (document.currentPopupMenu) hideMenu(document.currentPopupMenu); - if (data.length == 1) { + if (data.contacts.length == 1) { // Single result - var contact = data[0]; + var contact = data.contacts[0]; if (contact["uid"].length > 0) input.uid = contact["uid"]; var completeEmail = contact["name"] + " <" + contact["email"] + ">"; @@ -192,7 +200,7 @@ function performSearchCallback(http) { function onAttendeeResultClick(event) { if (attendeesEditor.currentField) { attendeesEditor.currentField.uid = this.uid; - attendeesEditor.currentField.value = this.firstChild.nodeValue.trim(); + attendeesEditor.currentField.value = $(this).readAttribute("address"); attendeesEditor.currentField.confirmedValue = attendeesEditor.currentField.value; attendeesEditor.currentField.blur(); // triggers checkAttendee function call } diff --git a/UI/WebServerResources/UIxContactsUserFolders.js b/UI/WebServerResources/UIxContactsUserFolders.js index 7fd196b24..e100ac139 100644 --- a/UI/WebServerResources/UIxContactsUserFolders.js +++ b/UI/WebServerResources/UIxContactsUserFolders.js @@ -25,6 +25,8 @@ function addLineToTree(tree, parent, line) { var parentNode = nodes[0]; var userInfos = parentNode.split(":"); var email = userInfos[1] + " <" + userInfos[2] + ">"; + if (!userInfos[3].empty()) + email += " (" + userInfos[3] + ")"; // extra contact info tree.add(parent, 0, email, 0, '#', userInfos[0], 'person', '', '', ResourcesURL + '/abcard.gif', diff --git a/UI/WebServerResources/UIxMailEditor.js b/UI/WebServerResources/UIxMailEditor.js index 8080c1340..4d1486f8f 100644 --- a/UI/WebServerResources/UIxMailEditor.js +++ b/UI/WebServerResources/UIxMailEditor.js @@ -387,7 +387,7 @@ function onContactKeydown(event) { if (MailEditor.selectedIndex > 0) { var contacts = $('contactsMenu').select("li"); contacts[MailEditor.selectedIndex--].removeClassName("selected"); - this.value = contacts[MailEditor.selectedIndex].firstChild.nodeValue.trim(); + this.value = contacts[MailEditor.selectedIndex].readAttribute("address"); contacts[MailEditor.selectedIndex].addClassName("selected"); } } @@ -397,7 +397,7 @@ function onContactKeydown(event) { if (MailEditor.selectedIndex >= 0) contacts[MailEditor.selectedIndex].removeClassName("selected"); MailEditor.selectedIndex++; - this.value = contacts[MailEditor.selectedIndex].firstChild.nodeValue.trim(); + this.value = contacts[MailEditor.selectedIndex].readAttribute("address"); contacts[MailEditor.selectedIndex].addClassName("selected"); } } @@ -431,20 +431,28 @@ function performSearchCallback(http) { if (http.status == 200) { var start = input.value.length; var data = http.responseText.evalJSON(true); - - if (data.length > 1) { + + if (data.contacts.length > 1) { list.select("li").each(function(item) { item.remove(); }); // Populate popup menu - for (var i = 0; i < data.length; i++) { - var contact = data[i]; + for (var i = 0; i < data.contacts.length; i++) { + var contact = data.contacts[i]; var completeEmail = contact["displayName"] + " <" + contact["mail"] + ">"; - var node = document.createElement("li"); + var node = new Element('li', { 'address': completeEmail }); + var matchPosition = completeEmail.toLowerCase().indexOf(data.searchText.toLowerCase()); + var matchBefore = completeEmail.substring(0, matchPosition); + var matchText = completeEmail.substring(matchPosition, matchPosition + data.searchText.length); + var matchAfter = completeEmail.substring(matchPosition + data.searchText.length); list.appendChild(node); node.uid = contact["c_uid"]; - node.appendChild(document.createTextNode(completeEmail)); + node.appendChild(document.createTextNode(matchBefore)); + node.appendChild(new Element('strong').update(matchText)); + node.appendChild(document.createTextNode(matchAfter)); + if (contact["contactInfo"]) + node.appendChild(document.createTextNode(" (" + contact["contactInfo"] + ")")); $(node).observe("mousedown", onAddressResultClick); } @@ -456,7 +464,7 @@ function performSearchCallback(http) { var heightDiff = window.height() - offset[1]; var nodeHeight = node.getHeight(); - if ((data.length * nodeHeight) > heightDiff) + if ((data.contacts.length * nodeHeight) > heightDiff) // Limit the size of the popup to the window height, minus 12 pixels height = parseInt(heightDiff/nodeHeight) * nodeHeight - 12 + 'px'; @@ -473,9 +481,9 @@ function performSearchCallback(http) { if (document.currentPopupMenu) hideMenu(document.currentPopupMenu); - if (data.length == 1) { + if (data.contacts.length == 1) { // Single result - var contact = data[0]; + var contact = data.contacts[0]; if (contact["c_uid"].length > 0) input.uid = contact["c_uid"]; var completeEmail = contact["displayName"] + " <" + contact["mail"] + ">"; @@ -504,7 +512,7 @@ function performSearchCallback(http) { function onAddressResultClick(event) { if (MailEditor.currentField) { MailEditor.currentField.uid = this.uid; - MailEditor.currentField.value = this.firstChild.nodeValue.trim(); + MailEditor.currentField.value = $(this).readAttribute("address"); MailEditor.currentField.confirmedValue = MailEditor.currentField.value; } }