From 094e5899bc7e877a2fadff84d877b280e266b2cc Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Fri, 30 Dec 2011 19:32:02 +0000 Subject: [PATCH 1/7] Monotone-Parent: ed89880eae2839f1f4714817c94287a44216b46f Monotone-Revision: e9388e969d7f55642fe7910f621ef54be2512a3a Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-12-30T19:32:02 Monotone-Branch: ca.inverse.sogo --- Tools/GNUmakefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tools/GNUmakefile b/Tools/GNUmakefile index 920ad2d82..c1bf7785b 100644 --- a/Tools/GNUmakefile +++ b/Tools/GNUmakefile @@ -4,6 +4,7 @@ include ../config.make include $(GNUSTEP_MAKEFILES)/common.make include ../Version +### SOGO_TOOL = sogo-tool $(SOGO_TOOL)_INSTALL_DIR = $(SOGO_ADMIN_TOOLS) $(SOGO_TOOL)_OBJC_FILES += \ @@ -18,7 +19,9 @@ $(SOGO_TOOL)_OBJC_FILES += \ SOGoToolRenameUser.m \ SOGoToolUserPreferences.m \ SOGoToolExpireAutoReply.m +TOOL_NAME += $(SOGO_TOOL) +### SOGO_SLAPD_SOCKD = sogo-slapd-sockd $(SOGO_SLAPD_SOCKD)_INSTALL_DIR = $(SOGO_ADMIN_TOOLS) $(SOGO_SLAPD_SOCKD)_OBJC_FILES += \ @@ -27,15 +30,17 @@ $(SOGO_SLAPD_SOCKD)_OBJC_FILES += \ SOGoSockD.m \ SOGoSockDScanner.m \ SOGoSockDOperation.m \ +TOOL_NAME += $(SOGO_SLAPD_SOCKD) +### SOGO_EALARMS_NOTIFY = sogo-ealarms-notify $(SOGO_EALARMS_NOTIFY)_INSTALL_DIR = $(SOGO_ADMIN_TOOLS) $(SOGO_EALARMS_NOTIFY)_OBJC_FILES += \ sogo-ealarms-notify.m \ \ SOGoEAlarmsNotifier.m +TOOL_NAME += $(SOGO_EALARMS_NOTIFY) -TOOL_NAME = $(SOGO_TOOL) $(SOGO_SLAPD_SOCKD) $(SOGO_EALARMS_NOTIFY) -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/tool.make From 540237248cc282ebc3ad4bef3c9b1b32422847d7 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Fri, 30 Dec 2011 19:44:42 +0000 Subject: [PATCH 2/7] Monotone-Parent: e9388e969d7f55642fe7910f621ef54be2512a3a Monotone-Revision: 28ce727aaf05b14f23fbcec914f8b827755f3483 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-12-30T19:44:42 Monotone-Branch: ca.inverse.sogo --- SOPE/NGCards/CardElement.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/SOPE/NGCards/CardElement.m b/SOPE/NGCards/CardElement.m index ec4d7404f..7064c81f9 100644 --- a/SOPE/NGCards/CardElement.m +++ b/SOPE/NGCards/CardElement.m @@ -460,8 +460,6 @@ _orderedValuesAreVoid (NSArray *orderedValues) NSMutableArray *orderedValues; NSUInteger count, max; - result = YES; - keys = [values allKeys]; max = [keys count]; for (count = 0; result && count < max; count++) From 843be30a45c905ac54ac900c2dbf4f2edd42b33f Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Fri, 30 Dec 2011 19:45:58 +0000 Subject: [PATCH 3/7] Monotone-Parent: 28ce727aaf05b14f23fbcec914f8b827755f3483 Monotone-Revision: ec02ae62fee05e50774a4da52cc17e4811526340 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-12-30T19:45:58 Monotone-Branch: ca.inverse.sogo --- SOPE/NGCards/CardGroup.h | 5 ++++- SOPE/NGCards/CardGroup.m | 30 ++++++++++++++++++++++++++++++ SOPE/NGCards/ChangeLog | 6 ++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/SOPE/NGCards/CardGroup.h b/SOPE/NGCards/CardGroup.h index 4535f041d..117d00c54 100644 --- a/SOPE/NGCards/CardGroup.h +++ b/SOPE/NGCards/CardGroup.h @@ -46,12 +46,15 @@ - (CardElement *) uniqueChildWithTag: (NSString *) aTag; - (void) setUniqueChild: (CardElement *) aChild; +- (NSMutableArray *) children; + - (void) addChild: (CardElement *) aChild; - (void) addChildren: (NSArray *) someChildren; - (void) removeChild: (CardElement *) aChild; - (void) removeChildren: (NSArray *) someChildren; -- (NSMutableArray *) children; +- (void) cleanupEmptyChildren; + - (CardElement *) firstChildWithTag: (NSString *) aTag; - (NSArray *) childrenWithTag: (NSString *) aTag; - (NSArray *) childrenWithAttribute: (NSString *) anAttribute diff --git a/SOPE/NGCards/CardGroup.m b/SOPE/NGCards/CardGroup.m index c6ef8bb62..d19b1a321 100644 --- a/SOPE/NGCards/CardGroup.m +++ b/SOPE/NGCards/CardGroup.m @@ -142,6 +142,19 @@ static NGCardsSaxHandler *sax = nil; return nil; } +- (BOOL) isVoid +{ + BOOL isVoid = YES; + NSUInteger count, max; + + max = [children count]; + for (count = 0; isVoid && count < max; count++) + if (![[children objectAtIndex: count] isVoid]) + isVoid = NO; + + return isVoid; +} + - (void) addChild: (CardElement *) aChild { Class mappedClass; @@ -366,6 +379,23 @@ static NGCardsSaxHandler *sax = nil; [self addChild: newChild]; } +- (void) cleanupEmptyChildren +{ + NSUInteger max; + NSInteger count; + CardElement *child; + + max = [children count]; + for (count = max - 1; count > -1; count--) + { + child = [children objectAtIndex: count]; + if ([child isKindOfClass: [CardGroup class]]) + [(CardGroup *) child cleanupEmptyChildren]; + if ([child isVoid]) + [children removeObjectAtIndex: count]; + } +} + - (NSString *) description { NSMutableString *str; diff --git a/SOPE/NGCards/ChangeLog b/SOPE/NGCards/ChangeLog index b78570076..5b111eefd 100644 --- a/SOPE/NGCards/ChangeLog +++ b/SOPE/NGCards/ChangeLog @@ -1,3 +1,9 @@ +2011-12-30 Wolfgang Sourdeau + + * CardGroup.m (-isVoid): overriden method. + (-cleanupEmptyChildren): make use of "isVoid" to detect and remove + empty children. + 2011-11-21 Francis Lachapelle * iCalTimeZone.m (+knownTimeZoneNames): ignore files that don't From 4b4af4d32328c5df1e911b0125348b66ca9eca30 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Fri, 30 Dec 2011 19:46:52 +0000 Subject: [PATCH 4/7] Monotone-Parent: ec02ae62fee05e50774a4da52cc17e4811526340 Monotone-Revision: d5e2b98ae15729726ed7a81b845e37f7657dcb8e Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-12-30T19:46:52 Monotone-Branch: ca.inverse.sogo --- SOPE/NGCards/ChangeLog | 2 ++ SOPE/NGCards/NGVCard.m | 2 ++ 2 files changed, 4 insertions(+) diff --git a/SOPE/NGCards/ChangeLog b/SOPE/NGCards/ChangeLog index 5b111eefd..8c8fe0b52 100644 --- a/SOPE/NGCards/ChangeLog +++ b/SOPE/NGCards/ChangeLog @@ -1,5 +1,7 @@ 2011-12-30 Wolfgang Sourdeau + * NGVCard.m (-initWithUid:): initialize "CLASS" and "PROFILE". + * CardGroup.m (-isVoid): overriden method. (-cleanupEmptyChildren): make use of "isVoid" to detect and remove empty children. diff --git a/SOPE/NGCards/NGVCard.m b/SOPE/NGCards/NGVCard.m index 97bcc92a0..db3833c6e 100644 --- a/SOPE/NGCards/NGVCard.m +++ b/SOPE/NGCards/NGVCard.m @@ -50,6 +50,8 @@ [self setTag: @"vcard"]; [self setUid: _uid]; [self setVersion: @"3.0"]; + [self setVClass: @"PUBLIC"]; + [self setProfile: @"VCARD"]; } return self; From 03e49d5985e3eac3c23abcf06b4e3e00be6ee394 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Fri, 30 Dec 2011 20:10:32 +0000 Subject: [PATCH 5/7] Monotone-Parent: d5e2b98ae15729726ed7a81b845e37f7657dcb8e Monotone-Revision: 77e874f5910fd1b3a30173bdae0bfa2544be6c07 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-12-30T20:10:32 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 6 ++++++ SoObjects/Contacts/SOGoFolder+CardDAV.m | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9e05548a2..3d0be3178 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2011-12-30 Wolfgang Sourdeau + + * SoObjects/Contacts/SOGoFolder+CardDAV.m (_isValidFilter:): make + use of the lowercase instance of the string, which was erroneously + ignored previously. + 2011-12-29 Ludovic Marcotte * SoObjects/SOGo/SOGoSQLUserProfile.m (_sqlJsonRepresentation:): diff --git a/SoObjects/Contacts/SOGoFolder+CardDAV.m b/SoObjects/Contacts/SOGoFolder+CardDAV.m index 2f62f1641..dfb761dfa 100644 --- a/SoObjects/Contacts/SOGoFolder+CardDAV.m +++ b/SoObjects/Contacts/SOGoFolder+CardDAV.m @@ -118,10 +118,10 @@ newString = [theString lowercaseString]; - return ([theString isEqualToString: @"sn"] - || [theString isEqualToString: @"givenname"] - || [theString isEqualToString: @"mail"] - || [theString isEqualToString: @"telephonenumber"]); + return ([newString isEqualToString: @"sn"] + || [newString isEqualToString: @"givenname"] + || [newString isEqualToString: @"mail"] + || [newString isEqualToString: @"telephonenumber"]); } - (NSDictionary *) _parseContactFilter: (id ) filterElement From 486f0f7ad17dae3d537649ec2618700cfec0d780 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Fri, 30 Dec 2011 20:29:36 +0000 Subject: [PATCH 6/7] Monotone-Parent: 77e874f5910fd1b3a30173bdae0bfa2544be6c07 Monotone-Revision: 995b68ade85d0d884cdf8f6fc87e55f0c376a80c Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-12-30T20:29:36 Monotone-Branch: ca.inverse.sogo --- Tests/Integration/webdavlib.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tests/Integration/webdavlib.py b/Tests/Integration/webdavlib.py index eee5eb243..46bc6d317 100644 --- a/Tests/Integration/webdavlib.py +++ b/Tests/Integration/webdavlib.py @@ -30,6 +30,7 @@ import sys xmlns_dav = "DAV:" xmlns_caldav = "urn:ietf:params:xml:ns:caldav" +xmlns_carddav = "urn:ietf:params:xml:ns:carddav" xmlns_inversedav = "urn:inverse:params:xml:ns:inverse-dav" url_re = None @@ -401,6 +402,25 @@ class CalDAVCalendarQuery(WebDAVREPORT): filter_node.append(cal_filter_node) self.top_node.append(filter_node) +class CardDAVAddressBookQuery(WebDAVREPORT): + def __init__(self, url, properties, searchProperty = None, searchValue = None): + WebDAVQuery.__init__(self, url) + query_tag = self.ns_mgr.register("addressbook-query", xmlns_carddav) + ns_key = self.ns_mgr.xmlns[xmlns_carddav] + self.top_node = _WD_XMLTreeElement(query_tag) + if properties is not None and len(properties) > 0: + self._initProperties(properties) + + if searchProperty is not None: + filter_node = _WD_XMLTreeElement("%s:filter" % ns_key) + self.top_node.append(filter_node) + propfilter_node = _WD_XMLTreeElement("%s:prop-filter" % ns_key, { "name": searchProperty }) + filter_node.append(propfilter_node) + match_node = _WD_XMLTreeElement("%s:text-match" % ns_key, + { "collation": "i;unicasemap", "match-type": "starts-with" }) + propfilter_node.append(match_node) + match_node.appendSubtree(None, searchValue) + class MailDAVMailQuery(WebDAVREPORT): def __init__(self, url, properties, filters = None, sort = None, ascending = True): From 4ba4c622bc4ea0a8b0ad3e136862e6b407ab56a1 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Fri, 30 Dec 2011 20:39:07 +0000 Subject: [PATCH 7/7] Monotone-Parent: 995b68ade85d0d884cdf8f6fc87e55f0c376a80c Monotone-Revision: 61fe02b6c3bc4f0aa3797d661d014d8c256b0de9 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-12-30T20:39:07 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 94 +++ SoObjects/Contacts/GNUmakefile | 11 +- SoObjects/Contacts/NGVCard+SOGo.h | 6 +- SoObjects/Contacts/NGVCard+SOGo.m | 691 ++++++++++++++--- SoObjects/Contacts/NGVList+SOGo.m | 4 +- SoObjects/Contacts/NSDictionary+LDIF.h | 34 + SoObjects/Contacts/NSDictionary+LDIF.m | 111 +++ SoObjects/Contacts/NSString+LDIF.h | 34 + SoObjects/Contacts/NSString+LDIF.m | 67 ++ SoObjects/Contacts/SOGoContactEntryPhoto.h | 5 - SoObjects/Contacts/SOGoContactEntryPhoto.m | 29 +- SoObjects/Contacts/SOGoContactGCSEntry.m | 42 +- SoObjects/Contacts/SOGoContactGCSFolder.m | 1 + SoObjects/Contacts/SOGoContactLDIFEntry.h | 5 +- SoObjects/Contacts/SOGoContactLDIFEntry.m | 232 ++---- SoObjects/Contacts/SOGoContactObject.h | 21 +- SoObjects/Contacts/SOGoContactSourceFolder.h | 12 +- SoObjects/Contacts/SOGoContactSourceFolder.m | 64 +- SoObjects/SOGo/GNUmakefile | 2 + SoObjects/SOGo/LDAPSource.h | 13 + SoObjects/SOGo/LDAPSource.m | 769 ++++++++++++------- SoObjects/SOGo/NSDictionary+Utilities.h | 3 +- SoObjects/SOGo/NSDictionary+Utilities.m | 82 -- SoObjects/SOGo/NSString+Utilities.h | 2 - SoObjects/SOGo/NSString+Utilities.m | 38 - SoObjects/SOGo/SOGoSource.h | 9 +- SoObjects/SOGo/SQLSource.m | 59 +- Tools/SOGoSockDOperation.m | 9 +- Tools/SOGoToolBackup.m | 11 +- UI/Contacts/UIxContactActions.m | 2 + UI/Contacts/UIxContactEditor.h | 10 +- UI/Contacts/UIxContactEditor.m | 652 +++------------- UI/Contacts/UIxContactFolderActions.m | 4 +- UI/Contacts/UIxContactView.m | 27 +- UI/Contacts/product.plist | 18 +- UI/Templates/ContactsUI/UIxContactEditor.wox | 291 ++++--- UI/WebServerResources/ContactsUI.js | 9 +- UI/WebServerResources/UIxContactEditor.css | 6 + UI/WebServerResources/UIxContactEditor.js | 102 +-- 39 files changed, 2041 insertions(+), 1540 deletions(-) create mode 100644 SoObjects/Contacts/NSDictionary+LDIF.h create mode 100644 SoObjects/Contacts/NSDictionary+LDIF.m create mode 100644 SoObjects/Contacts/NSString+LDIF.h create mode 100644 SoObjects/Contacts/NSString+LDIF.m diff --git a/ChangeLog b/ChangeLog index 3d0be3178..371e3a5c7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,99 @@ 2011-12-30 Wolfgang Sourdeau + * UI/WebServerResources/UIxContactEditor.js + (validateContactEditor): the birth date is validated slightly + differently, by enabling empty and 2-digit years as well as single + digits months and days. + + * UI/Contacts/UIxContactView.m (-defaultAction, -dealloc): retain + and release the "card" ivar. + (_formattedURL:): the url should be displayed only if it is + non-nil AND non-empty. + (-vcardAction): removed useless method. + + * UI/Contacts/UIxContactEditor.m (init): removed the "snapshot", + "preferredEmail", "card", "photosURL" and "contactCategories" ivars. + (-ldifRecord): new getter that proxy the invocation to the client + object, but by taking the contactEmail and contactFN url + parameters. Replaces the "-snapshot" getter, since the LDIF record + is now directly edited. + (-addressBooksList): the client object container class is no + longer taken into account when fetching the current user's permissions. + (-supportCategories, -supportPhotos): new getters that enables the + categories and photo tabs. + (-setJsonContactCategories:, -jsonContactCategories): now make + use of the special "vcardcategories" parameter found in the + contact LDIF record. + + * SoObjects/SOGo/SQLSource.m (_lookupContactEntry:considerEmail:): + enhanced to copy the "c_XX" fields to unprefixed equivalents. + + * SoObjects/Contacts/NSString+LDIF.m (mustEncodeLDIFValue): new + method, replacing "_isLDIFSafe" in a clearly public manner. + + * SoObjects/SOGo/LDAPSource.m (-[NGLdapEntry _asDictionary]): new + utility method to convert an entry into a "SOGo LDIF record". + (-[NGLdapAttribute _asArrayOrString]): new utility method to + convert an LDAP attribute into a string or an array of strings + when the attribute has one or more values. + (-initFromUDSource:inDomain:): handle the new "modifiers", + "mapping" and "objectClasses" configuration keys, used when the + source instance is used as an addressbook. All LDAP fields are now + converted to lowercase. + (_searchAttributes): removed method as the special "*" attribute + is not costly enough to justify its existence, thereby reducing + code complexity. + (-lookupContactEntry:, -lookupContactEntryWithUIDorEmail:) + (-lookupLoginByDN:): merged common code in the new + -_lookupLDAPEntry: method, that accepts an EOQualifier as argument. + (--addContactEntry:withID:, -updateContactEntry: and + removeContactEntryWithID:): new methods for editing addressbook + sources. + + * SoObjects/Contacts/SOGoContactSourceFolder.m (-source): new + getter for the "source" ivar. + (-lookupName:inContext:acquire:): accept the creation of new LDIF + entries via web methods ending with "AsContact". + (-saveLDIFEntry:, -deleteLDIFEntry:): new proxy methods for the + new SOGoSource -addContactEntry:withID:, -updateContactEntry: and + removeContactEntryWithID: methods, enabling the creation, + modification and deletion of LDAP contacts. + (-aclsForUser:): implemented method based on the array returned by + -[ modifiers]. + + * SoObjects/Contacts/SOGoContactLDIFEntry.m (-vCard): the vcard is + now generated automatically from the LDIF record of the entry, + using the new method provided by NGVCard+SOGo. + (-aclsForUser:): new overriden method similar to the + implementation from SOGoContentObject. + + * SoObjects/Contacts/SOGoContactGCSEntry.m (-setLDIFRecord) + (-ldifRecord, -hasPhoto): new accessors required by the + SOGoContactObject protocol. + (-lookupName:inContext:acquire:): return the only photo element + when the "photo" key is requested, if present in the card. + (-save): now returns an NSException, instead of void. + + * SoObjects/Contacts/SOGoContactEntryPhoto.m + (+entryPhotoWithID:inContainer:, -setPhotoID:): removed methods, + since there can only be one PHOTO element per contact. + (-photo): simplified thanks to the constraint mentionned above. + + * SoObjects/Contacts/NSDictionary+LDIF.m (-ldifRecordAsString): + new method implementing the code previously found in + SOGo/NSDictionary+Utilities.m, in order to produce a textual + representation of an LDIF record. + + * SoObjects/Contacts/NSDictionary+LDIF.[hm]: new category module. + + * SoObjects/Contacts/NGVCard+SOGo.m (-asLDIFRecord): new method + implementing the conversion code previously found in + UIxContactEditor, in order to produce an "LDIF record" in the form + of an NSDictionary matching the inetOrgPerson and + mozillaAbPersonAlpha object classes. + (-updateFromLDIFRecord:) reciprocal method to "-asLDIFRecord", + with conversion code moved from UIxContactEditor. + * SoObjects/Contacts/SOGoFolder+CardDAV.m (_isValidFilter:): make use of the lowercase instance of the string, which was erroneously ignored previously. diff --git a/SoObjects/Contacts/GNUmakefile b/SoObjects/Contacts/GNUmakefile index 3e3ac2ed2..af5ed91f5 100644 --- a/SoObjects/Contacts/GNUmakefile +++ b/SoObjects/Contacts/GNUmakefile @@ -9,17 +9,20 @@ Contacts_PRINCIPAL_CLASS = SOGoContactsProduct Contacts_OBJC_FILES = \ Product.m \ - NGVCard+SOGo.m \ - NGVList+SOGo.m \ + NGVCard+SOGo.m \ + NGVList+SOGo.m \ SOGoFolder+CardDAV.m \ SOGoContactFolders.m \ SOGoContactGCSEntry.m \ SOGoContactGCSList.m \ SOGoContactGCSFolder.m \ SOGoContactLDIFEntry.m \ - SOGoContactSourceFolder.m \ - SOGoUserFolder+Contacts.m \ + SOGoContactSourceFolder.m \ + SOGoUserFolder+Contacts.m \ SOGoContactEntryPhoto.m \ + \ + NSDictionary+LDIF.m \ + NSString+LDIF.m Contacts_RESOURCE_FILES += \ product.plist \ diff --git a/SoObjects/Contacts/NGVCard+SOGo.h b/SoObjects/Contacts/NGVCard+SOGo.h index a758d86c8..49dcd55d0 100644 --- a/SoObjects/Contacts/NGVCard+SOGo.h +++ b/SoObjects/Contacts/NGVCard+SOGo.h @@ -25,9 +25,13 @@ #import +@class NSDictionary; +@class NSMutableDictionary; + @interface NGVCard (SOGoExtensions) -- (NSString *) ldifString; +- (void) updateFromLDIFRecord: (NSDictionary *) ldifRecord; +- (NSMutableDictionary *) asLDIFRecord; @end diff --git a/SoObjects/Contacts/NGVCard+SOGo.m b/SoObjects/Contacts/NGVCard+SOGo.m index 811ade06d..da86be3fa 100644 --- a/SoObjects/Contacts/NGVCard+SOGo.m +++ b/SoObjects/Contacts/NGVCard+SOGo.m @@ -21,153 +21,610 @@ */ #import +#import #import #import #import -#import +#import +#import + +#import "NSDictionary+LDIF.h" #import "NGVCard+SOGo.h" +/* +objectclass ( 2.5.6.6 NAME 'person' + DESC 'RFC2256: a person' + SUP top STRUCTURAL + MUST ( sn $ cn ) + MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) ) + +objectclass ( 2.5.6.7 NAME 'organizationalPerson' + DESC 'RFC2256: an organizational person' + SUP person STRUCTURAL + MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $ + preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ + telephoneNumber $ internationaliSDNNumber $ + facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ + postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) ) + +objectclass ( 2.16.840.1.113730.3.2.2 + NAME 'inetOrgPerson' + DESC 'RFC2798: Internet Organizational Person' + SUP organizationalPerson + STRUCTURAL + MAY ( + audio $ businessCategory $ carLicense $ departmentNumber $ + displayName $ employeeNumber $ employeeType $ givenName $ + homePhone $ homePostalAddress $ initials $ jpegPhoto $ + labeledURI $ mail $ manager $ mobile $ o $ pager $ + photo $ roomNumber $ secretary $ uid $ userCertificate $ + x500uniqueIdentifier $ preferredLanguage $ + userSMIMECertificate $ userPKCS12 ) + ) + +objectclass ( 1.3.6.1.4.1.13769.9.1 NAME 'mozillaAbPersonAlpha' + SUP top AUXILIARY + MUST ( cn ) + MAY( c $ + description $ + displayName $ + facsimileTelephoneNumber $ + givenName $ + homePhone $ + l $ + mail $ + mobile $ + mozillaCustom1 $ + mozillaCustom2 $ + mozillaCustom3 $ + mozillaCustom4 $ + mozillaHomeCountryName $ + mozillaHomeLocalityName $ + mozillaHomePostalCode $ + mozillaHomeState $ + mozillaHomeStreet $ + mozillaHomeStreet2 $ + mozillaHomeUrl $ + mozillaNickname $ + mozillaSecondEmail $ + mozillaUseHtmlMail $ + mozillaWorkStreet2 $ + mozillaWorkUrl $ + nsAIMid $ + o $ + ou $ + pager $ + postalCode $ + postOfficeBox $ + sn $ + st $ + street $ + telephoneNumber $ + title ) ) + + additional vcard fields: +"vcardCategories" + +test contact (export from tb): + +dn:: Y249UHLDqW5vbSBOb20sbWFpbD1hZHIxQGVsZS5jb20= +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +objectclass: mozillaAbPersonAlpha +givenName:: UHLDqW5vbQ== +sn: Nom +cn:: UHLDqW5vbSBOb20= +mozillaNickname: Surnom +mail: adr1@ele.com +mozillaSecondEmail: adralt@ele.com +nsAIMid: pseudo aim +modifytimestamp: 1324509379 +telephoneNumber: travail +homePhone: dom +facsimiletelephonenumber: fax +pager: pager +mobile: port +mozillaHomeStreet:: YWRyMSBwcml2w6ll +mozillaHomeStreet2:: YWRyMiBwcml2w6ll +mozillaHomeLocalityName: ville/locallite +mozillaHomeState:: w6l0YXQvcHJvdg== +mozillaHomePostalCode: codepos +mozillaHomeCountryName: pays +street: adr1 pro +mozillaWorkStreet2: adr2 pro +l: ville pro +st: etat pro +postalCode: codepro +c: payspro +title: fonction pro +ou: service pro +o: soc pro +mozillaWorkUrl: webpro +mozillaHomeUrl: web +birthyear: 1946 +birthmonth: 12 +birthday: 04 +mozillaCustom1: d1 +mozillaCustom2: d2 +mozillaCustom3: d3 +mozillaCustom4: d4 +description: notes + +convention: +- our "LDIF records" are inetOrgPerson + mozillaAbPersonAlpha + a few custom + fields (categories) +- all keys are lowercase + + */ + @implementation NGVCard (SOGoExtensions) -- (NSString *) ldifString +/* LDIF -> VCARD */ +- (CardElement *) _elementWithTag: (NSString *) elementTag + ofType: (NSString *) type { - NSMutableString *rc; - NSString *buffer; - NSArray *array; - NSMutableArray *marray; - NSMutableDictionary *entry; + NSArray *elements; CardElement *element; - id tmp; - entry = [NSMutableDictionary dictionary]; + elements = [self childrenWithTag: elementTag + andAttribute: @"type" havingValue: type]; + if ([elements count] > 0) + element = [elements objectAtIndex: 0]; + else + { + element = [CardElement elementWithTag: elementTag]; + [element addType: type]; + [self addChild: element]; + } - [entry setObject: [NSString stringWithFormat: @"cn=%@,mail=%@", - [self fn], [self preferredEMail]] - forKey: @"dn"]; - [entry setObject: [NSArray arrayWithObjects: @"top", @"person", - @"organizationalPerson", @"inetOrgPerson", - @"mozillaAbPersonObsolete", nil] - forKey: @"objectclass"]; + return element; +} + +- (void) _setPhoneValues: (NSDictionary *) ldifRecord +{ + CardElement *phone; + + phone = [self _elementWithTag: @"tel" ofType: @"work"]; + [phone setSingleValue: [ldifRecord objectForKey: @"telephonenumber"] forKey: @""]; + phone = [self _elementWithTag: @"tel" ofType: @"home"]; + [phone setSingleValue: [ldifRecord objectForKey: @"homephone"] forKey: @""]; + phone = [self _elementWithTag: @"tel" ofType: @"cell"]; + [phone setSingleValue: [ldifRecord objectForKey: @"mobile"] forKey: @""]; + phone = [self _elementWithTag: @"tel" ofType: @"fax"]; + [phone setSingleValue: [ldifRecord objectForKey: @"facsimiletelephonenumber"] + forKey: @""]; + phone = [self _elementWithTag: @"tel" ofType: @"pager"]; + [phone setSingleValue: [ldifRecord objectForKey: @"pager"] forKey: @""]; +} + +- (void) _setEmails: (NSDictionary *) ldifRecord +{ + CardElement *mail, *homeMail; + + mail = [self _elementWithTag: @"email" ofType: @"work"]; + [mail setSingleValue: [ldifRecord objectForKey: @"mail"] forKey: @""]; + homeMail = [self _elementWithTag: @"email" ofType: @"home"]; + [homeMail setSingleValue: [ldifRecord objectForKey: @"mozillasecondemail"] forKey: @""]; + [[self uniqueChildWithTag: @"x-mozilla-html"] + setSingleValue: [ldifRecord objectForKey: @"mozillausehtmlmail"] + forKey: @""]; +} + +- (void) updateFromLDIFRecord: (NSDictionary *) ldifRecord +{ + CardElement *element; + NSArray *units; + NSInteger year, yearOfToday, month, day; + NSCalendarDate *now; + NSString *ou; + + [self setNWithFamily: [ldifRecord objectForKey: @"sn"] + given: [ldifRecord objectForKey: @"givenname"] + additional: nil prefixes: nil suffixes: nil]; + [self setNickname: [ldifRecord objectForKey: @"mozillanickname"]]; + [self setFn: [ldifRecord objectForKey: @"displayname"]]; + [self setTitle: [ldifRecord objectForKey: @"title"]]; + + element = [self _elementWithTag: @"adr" ofType: @"home"]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestreet2"] + atIndex: 1 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestreet"] + atIndex: 2 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomelocalityname"] + atIndex: 3 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestate"] + atIndex: 4 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomepostalcode"] + atIndex: 5 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillahomecountryname"] + atIndex: 6 forKey: @""]; + + element = [self _elementWithTag: @"adr" ofType: @"work"]; + [element setSingleValue: [ldifRecord objectForKey: @"mozillaworkstreet2"] + atIndex: 1 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"street"] + atIndex: 2 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"l"] + atIndex: 3 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"st"] + atIndex: 4 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"postalcode"] + atIndex: 5 forKey: @""]; + [element setSingleValue: [ldifRecord objectForKey: @"c"] + atIndex: 6 forKey: @""]; + + ou = [ldifRecord objectForKey: @"ou"]; + if (ou) + units = [NSArray arrayWithObject: ou]; + else + units = nil; + [self setOrg: [ldifRecord objectForKey: @"o"] + units: units]; + + [self _setPhoneValues: ldifRecord]; + [self _setEmails: ldifRecord]; + [[self _elementWithTag: @"url" ofType: @"home"] + setSingleValue: [ldifRecord objectForKey: @"mozillahomeurl"] forKey: @""]; + [[self _elementWithTag: @"url" ofType: @"work"] + setSingleValue: [ldifRecord objectForKey: @"mozillaworkurl"] forKey: @""]; + + [[self uniqueChildWithTag: @"x-aim"] + setSingleValue: [ldifRecord objectForKey: @"nsaimid"] + forKey: @""]; + + now = [NSCalendarDate date]; + year = [[ldifRecord objectForKey: @"birthyear"] intValue]; + if (year < 100) + { + yearOfToday = [now yearOfCommonEra]; + if (year == 0) + year = yearOfToday; + else if (yearOfToday < (year + 2000)) + year += 1900; + else + year += 2000; + } + month = [[ldifRecord objectForKey: @"birthmonth"] intValue]; + day = [[ldifRecord objectForKey: @"birthday"] intValue]; + + if (year && month && day) + [self setBday: [NSString stringWithFormat: @"%.4d-%.2d-%.2d", + year, month, day]]; + else + [self setBday: @""]; + + [self setNote: [ldifRecord objectForKey: @"description"]]; + [self setCategories: [ldifRecord objectForKey: @"vcardcategories"]]; + + [self cleanupEmptyChildren]; +} + +/* VCARD -> LDIF */ +- (NSString *) _simpleValueForType: (NSString *) aType + inArray: (NSArray *) anArray + excluding: (NSString *) aTypeToExclude +{ + NSArray *elements; + NSString *value; + + elements = [anArray cardElementsWithAttribute: @"type" + havingValue: aType]; + value = nil; + + if ([elements count] > 0) + { + CardElement *ce; + int i; + + for (i = 0; i < [elements count]; i++) + { + ce = [elements objectAtIndex: i]; + value = [ce flattenedValuesForKey: @""]; + + if (!aTypeToExclude) + break; + + if (![ce hasAttribute: @"type" havingValue: aTypeToExclude]) + break; + + value = nil; + } + } + + return value; +} + +- (void) _setValue: (NSString *) key + to: (NSString *) aValue + inLDIFRecord: (NSMutableDictionary *) ldifRecord +{ + if (!aValue) + aValue = @""; + + [ldifRecord setObject: aValue forKey: key]; +} + +- (void) _setupEmailFieldsInLDIFRecord: (NSMutableDictionary *) ldifRecord +{ + NSArray *elements; + NSString *workMail, *homeMail, *potential; + unsigned int max; + + elements = [self childrenWithTag: @"email"]; + max = [elements count]; + workMail = [self _simpleValueForType: @"work" + inArray: elements excluding: nil]; + homeMail = [self _simpleValueForType: @"home" + inArray: elements excluding: nil]; + + if (max > 0) + { + potential = [[elements objectAtIndex: 0] flattenedValuesForKey: @""]; + if (!workMail) + { + if (homeMail && homeMail == potential) + { + if (max > 1) + workMail = [[elements objectAtIndex: 1] flattenedValuesForKey: @""]; + } + else + workMail = potential; + } + if (!homeMail && max > 1) + { + if (workMail && workMail == potential) + homeMail = [[elements objectAtIndex: 1] flattenedValuesForKey: @""]; + else + homeMail = potential; + } + } + + [self _setValue: @"mail" to: workMail inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillasecondemail" to: homeMail inLDIFRecord: ldifRecord]; + + [self _setValue: @"mozillausehtmlmail" + to: [[self uniqueChildWithTag: @"x-mozilla-html"] + flattenedValuesForKey: @""] + inLDIFRecord: ldifRecord]; +} + +- (void) _setupOrgFieldsInLDIFRecord: (NSMutableDictionary *) ldifRecord +{ + NSMutableArray *orgServices; + CardElement *org; + NSString *service; + NSUInteger count, max; + + org = [self org]; + [self _setValue: @"o" + to: [org flattenedValueAtIndex: 0 forKey: @""] + inLDIFRecord: ldifRecord]; + max = [[org valuesForKey: @""] count]; + if (max > 1) + { + orgServices = [NSMutableArray arrayWithCapacity: max]; + for (count = 1; count < max; count++) + { + service = [org flattenedValueAtIndex: count forKey: @""]; + if ([service length] > 0) + [orgServices addObject: service]; + } + + [self _setValue: @"ou" + to: [orgServices componentsJoinedByString: @", "] + inLDIFRecord: ldifRecord]; + } +} + +- (NSMutableDictionary *) asLDIFRecord +{ + NSArray *elements, *categories; + CardElement *element; + NSMutableDictionary *ldifRecord; + NSCalendarDate *birthDay; + NSString *dn, *stringValue, *stringValue2; + + ldifRecord = [NSMutableDictionary dictionaryWithCapacity: 32]; + [ldifRecord setObject: [NSArray arrayWithObjects: @"top", @"inetOrgPerson", + @"mozillaAbPersonAlpha", nil] + forKey: @"objectClass"]; element = [self n]; - tmp = [element flattenedValueAtIndex: 1 forKey: @""]; - if ([tmp length] > 0) - [entry setObject: tmp forKey: @"givenName"]; - - tmp = [element flattenedValueAtIndex: 0 forKey: @""]; - if ([tmp length] > 0) - [entry setObject: tmp forKey: @"sn"]; + [self _setValue: @"sn" + to: [element flattenedValueAtIndex: 0 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"givenname" + to: [element flattenedValueAtIndex: 1 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"displayname" to: [self fn] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillanickname" to: [self nickname] + inLDIFRecord: ldifRecord]; - tmp = [self fn]; - if (tmp) - [entry setObject: tmp forKey: @"cn"]; - - tmp = [self preferredEMail]; - if (tmp) - [entry setObject: tmp forKey: @"mail"]; - - [entry setObject: @"0Z" forKey: @"modifytimestamp"]; + elements = [self childrenWithTag: @"tel"]; + // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX + // one if we do see the FAX field BEFORE the WORK number. + [self _setValue: @"telephonenumber" + to: [self _simpleValueForType: @"work" inArray: elements + excluding: @"fax"] + inLDIFRecord: ldifRecord]; + [self _setValue: @"homephone" + to: [self _simpleValueForType: @"home" inArray: elements + excluding: @"fax"] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mobile" + to: [self _simpleValueForType: @"cell" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; + [self _setValue: @"facsimiletelephonenumber" + to: [self _simpleValueForType: @"fax" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; + [self _setValue: @"pager" + to: [self _simpleValueForType: @"pager" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; - buffer = [self nickname]; - if (buffer && [buffer length] > 0) - [entry setObject: buffer forKey: @"mozillaNickname"]; + // If we don't have a "home" and "work" phone number but + // we have a "voice" one defined, we set it to the "work" value + // This can happen when we have : + // VERSION:2.1 + // N:name;surname;;;; + // TEL;VOICE;HOME: + // TEL;VOICE;WORK: + // TEL;PAGER: + // TEL;FAX;WORK: + // TEL;CELL:514 123 1234 + // TEL;VOICE:450 456 6789 + // ADR;HOME:;;;;;; + // ADR;WORK:;;;;;; + // ADR:;;;;;; + if ([[ldifRecord objectForKey: @"telephonenumber"] length] == 0 && + [[ldifRecord objectForKey: @"homephone"] length] == 0 && + [elements count] > 0) + [self _setValue: @"telephonenumber" + to: [self _simpleValueForType: @"voice" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; - marray = [NSMutableArray arrayWithArray: [self childrenWithTag: @"email"]]; - [marray removeObjectsInArray: [self childrenWithTag: @"email" - andAttribute: @"type" - havingValue: @"pref"]]; - if ([marray count]) + [self _setupEmailFieldsInLDIFRecord: ldifRecord]; + + [self _setValue: @"nsaimid" + to: [[self uniqueChildWithTag: @"x-aim"] + flattenedValuesForKey: @""] + inLDIFRecord: ldifRecord]; + + elements = [self childrenWithTag: @"adr" + andAttribute: @"type" havingValue: @"work"]; + if (elements && [elements count] > 0) { - buffer = [[marray objectAtIndex: [marray count]-1] - flattenedValuesForKey: @""]; - - if ([buffer caseInsensitiveCompare: [self preferredEMail]] != NSOrderedSame) - [entry setObject: buffer forKey: @"mozillaSecondEmail"]; + element = [elements objectAtIndex: 0]; + [self _setValue: @"mozillaworkstreet2" + to: [element flattenedValueAtIndex: 1 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"street" + to: [element flattenedValueAtIndex: 2 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"l" + to: [element flattenedValueAtIndex: 3 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"st" + to: [element flattenedValueAtIndex: 4 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"postalcode" + to: [element flattenedValueAtIndex: 5 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"c" + to: [element flattenedValueAtIndex: 6 forKey: @""] + inLDIFRecord: ldifRecord]; } - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"home"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"homePhone"]; - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"fax"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"fax"]; - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"cell"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"mobile"]; - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"pager"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"pager"]; - - array = [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"home"]; - if ([array count]) + elements = [self childrenWithTag: @"adr" + andAttribute: @"type" havingValue: @"home"]; + if (elements && [elements count] > 0) { - tmp = [array objectAtIndex: 0]; - [entry setObject: [tmp flattenedValueAtIndex: 1 forKey: @""] - forKey: @"mozillaHomeStreet2"]; - [entry setObject: [tmp flattenedValueAtIndex: 2 forKey: @""] - forKey: @"homeStreet"]; - [entry setObject: [tmp flattenedValueAtIndex: 3 forKey: @""] - forKey: @"mozillaHomeLocalityName"]; - [entry setObject: [tmp flattenedValueAtIndex: 4 forKey: @""] - forKey: @"mozillaHomeState"]; - [entry setObject: [tmp flattenedValueAtIndex: 5 forKey: @""] - forKey: @"mozillaHomePostalCode"]; - [entry setObject: [tmp flattenedValueAtIndex: 6 forKey: @""] - forKey: @"mozillaHomeCountryName"]; + element = [elements objectAtIndex: 0]; + [self _setValue: @"mozillahomestreet2" + to: [element flattenedValueAtIndex: 1 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomestreet" + to: [element flattenedValueAtIndex: 2 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomelocalityname" + to: [element flattenedValueAtIndex: 3 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomestate" + to: [element flattenedValueAtIndex: 4 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomepostalcode" + to: [element flattenedValueAtIndex: 5 forKey: @""] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomecountryname" + to: [element flattenedValueAtIndex: 6 forKey: @""] + inLDIFRecord: ldifRecord]; } - element = [self org]; - tmp = [element flattenedValueAtIndex: 0 forKey: @""]; - if ([tmp length] > 0) - [entry setObject: tmp forKey: @"o"]; - - array = [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"work"]; - if ([array count]) + elements = [self childrenWithTag: @"url"]; + [self _setValue: @"mozillaworkurl" + to: [self _simpleValueForType: @"work" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; + [self _setValue: @"mozillahomeurl" + to: [self _simpleValueForType: @"home" inArray: elements + excluding: nil] + inLDIFRecord: ldifRecord]; + + // If we don't have a "work" or "home" URL but we still have + // an URL field present, let's add it to the "home" value + if ([[ldifRecord objectForKey: @"mozillaworkurl"] length] == 0 && + [[ldifRecord objectForKey: @"mozillahomeurl"] length] == 0 && + [elements count] > 0) + [self _setValue: @"mozillahomeurl" + to: [[elements objectAtIndex: 0] + flattenedValuesForKey: @""] + inLDIFRecord: ldifRecord]; + // If we do have a "work" URL but no "home" URL but two + // values URLs present, let's add the second one as the home URL + else if ([[ldifRecord objectForKey: @"mozillaworkurl"] length] > 0 && + [[ldifRecord objectForKey: @"mozillahomeurl"] length] == 0 && + [elements count] > 1) { - tmp = [array objectAtIndex: 0]; - [entry setObject: [tmp flattenedValueAtIndex: 1 forKey: @""] - forKey: @"mozillaWorkStreet2"]; - [entry setObject: [tmp flattenedValueAtIndex: 2 forKey: @""] - forKey: @"street"]; - [entry setObject: [tmp flattenedValueAtIndex: 3 forKey: @""] - forKey: @"l"]; - [entry setObject: [tmp flattenedValueAtIndex: 4 forKey: @""] - forKey: @"st"]; - [entry setObject: [tmp flattenedValueAtIndex: 5 forKey: @""] - forKey: @"postalCode"]; - [entry setObject: [tmp flattenedValueAtIndex: 6 forKey: @""] - forKey: @"c"]; + int i; + + for (i = 0; i < [elements count]; i++) + { + if ([[[elements objectAtIndex: i] flattenedValuesForKey: @""] + caseInsensitiveCompare: [ldifRecord objectForKey: @"mozillaworkurl"]] != NSOrderedSame) + { + [self _setValue: @"mozillahomeurl" + to: [[elements objectAtIndex: i] + flattenedValuesForKey: @""] + inLDIFRecord: ldifRecord]; + break; + } + } } + + [self _setValue: @"title" to: [self title] inLDIFRecord: ldifRecord]; + [self _setupOrgFieldsInLDIFRecord: ldifRecord]; - array = [self childrenWithTag: @"tel" andAttribute: @"type" havingValue: @"work"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"telephoneNumber"]; + categories = [self categories]; + if ([categories count] > 0) + [ldifRecord setValue: categories forKey: @"vcardcategories"]; - array = [self childrenWithTag: @"url" andAttribute: @"type" havingValue: @"work"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"workurl"]; + birthDay = [[self bday] asCalendarDate]; + if (birthDay) + { + stringValue = [NSString stringWithFormat: @"%.4d", [birthDay yearOfCommonEra]]; + [self _setValue: @"birthyear" to: stringValue inLDIFRecord: ldifRecord]; + stringValue = [NSString stringWithFormat: @"%.2d", [birthDay monthOfYear]]; + [self _setValue: @"birthmonth" to: stringValue inLDIFRecord: ldifRecord]; + stringValue = [NSString stringWithFormat: @"%.2d", [birthDay dayOfMonth]]; + [self _setValue: @"birthday" to: stringValue inLDIFRecord: ldifRecord]; + } + [self _setValue: @"description" to: [self note] inLDIFRecord: ldifRecord]; - array = [self childrenWithTag: @"url" andAttribute: @"type" havingValue: @"home"]; - if ([array count]) - [entry setObject: [[array objectAtIndex: 0] flattenedValuesForKey: @""] - forKey: @"homeurl"]; + stringValue = [ldifRecord objectForKey: @"displayname"]; + stringValue2 = [ldifRecord objectForKey: @"mail"]; + if ([stringValue length] > 0) + { + if ([stringValue2 length] > 0) + dn = [NSString stringWithFormat: @"cn=%@,mail=%@", + stringValue, stringValue2]; + else + dn = [NSString stringWithFormat: @"cn=%@", stringValue]; + } + else if ([stringValue2 length] > 0) + dn = [NSString stringWithFormat: @"mail=%@", stringValue2]; + else + dn = @""; + [ldifRecord setObject: dn forKey: @"dn"]; - tmp = [self note]; - if (tmp && [tmp length]) - [entry setObject: tmp forKey: @"description"]; - - rc = [NSMutableString stringWithString: [entry userRecordAsLDIFEntry]]; - [rc appendFormat: @"\n"]; - - return rc; + return ldifRecord; } @end /* NGVCard */ diff --git a/SoObjects/Contacts/NGVList+SOGo.m b/SoObjects/Contacts/NGVList+SOGo.m index f0bc5fd46..4e6fd7c78 100644 --- a/SoObjects/Contacts/NGVList+SOGo.m +++ b/SoObjects/Contacts/NGVList+SOGo.m @@ -27,7 +27,7 @@ #import -#import +#import "NSDictionary+LDIF.h" #import "NGVList+SOGo.h" @@ -65,7 +65,7 @@ } [entry setObject: members forKey: @"member"]; - rc = [NSMutableString stringWithString: [entry userRecordAsLDIFEntry]]; + rc = [NSMutableString stringWithString: [entry ldifRecordAsString]]; [rc appendFormat: @"\n"]; return rc; diff --git a/SoObjects/Contacts/NSDictionary+LDIF.h b/SoObjects/Contacts/NSDictionary+LDIF.h new file mode 100644 index 000000000..1fcae50dc --- /dev/null +++ b/SoObjects/Contacts/NSDictionary+LDIF.h @@ -0,0 +1,34 @@ +/* NSDictionary+LDIF.h - this file is part of SOGo + * + * Copyright (C) 2011 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * 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 NSDICTIONARY_LDIF_H +#define NSDICTIONARY_LDIF_H + +#import + +@interface NSDictionary (SOGoLDIF) + +- (NSString *) ldifRecordAsString; + +@end + +#endif /* NSDICTIONARY_LDIF_H */ diff --git a/SoObjects/Contacts/NSDictionary+LDIF.m b/SoObjects/Contacts/NSDictionary+LDIF.m new file mode 100644 index 000000000..f475c0fc5 --- /dev/null +++ b/SoObjects/Contacts/NSDictionary+LDIF.m @@ -0,0 +1,111 @@ +/* NSDictionary+LDIF.m - this file is part of SOGo + * + * Copyright (C) 2011 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * 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 +#import +#import + +#import "NSString+LDIF.h" + +#import "NSDictionary+LDIF.h" + +@implementation NSDictionary (SOGoLDIF) + +- (void) _appendLDIFKey: (NSString *) key + value: (NSString *) value + toString: (NSMutableString *) ldifString +{ + if ([value isKindOfClass: [NSString class]] && [value length] > 0) + { + if ([value mustEncodeLDIFValue]) + [ldifString appendFormat: @"%@:: %@\n", + key, [value stringByEncodingBase64]]; + else + [ldifString appendFormat: @"%@: %@\n", key, value]; + } +} + +- (void) _appendLDIFKey: (NSString *) key + toString: (NSMutableString *) ldifString +{ + id value; + int count, max; + + value = [self objectForKey: key]; + if ([value isKindOfClass: [NSArray class]]) + { + max = [value count]; + for (count = 0; count < max; count++) + [self _appendLDIFKey: key value: [value objectAtIndex: count] + toString: ldifString]; + } + else + [self _appendLDIFKey: key value: [self objectForKey: key] + toString: ldifString]; +} + +- (void) _appendObjectClassesToString: (NSMutableString *) ldifString +{ + NSEnumerator *classes; + NSString *currentClass; + NSArray *objectClass; + + objectClass = [self objectForKey: @"objectClass"]; + if ([objectClass isKindOfClass: [NSString class]]) + [self _appendLDIFKey: @"objectClass" value: (NSString *) objectClass + toString: ldifString]; + else + { + classes = [objectClass objectEnumerator]; + while ((currentClass = [classes nextObject])) + [self _appendLDIFKey: @"objectClass" value: currentClass + toString: ldifString]; + } +} + +- (NSString *) ldifRecordAsString +{ + NSArray *keys; + NSMutableString *ldifString; + NSUInteger count, max; + NSString *currentKey; + +// {CalendarAccess = YES; MailAccess = YES; c_cn = "Wolfgang Sourdeau"; c_emails = ("wolfgang@test.com"); c_name = "wolfgang@test.com"; c_uid = "wolfgang@test.com"; cn = "wolfgang@test.com"; displayName = "Wolfgang Sourdeau"; dn = "cn=wolfgang@test.com,ou=evariste,o=inverse.ca"; givenName = Wolfgang; mail = "wolfgang@test.com"; objectClass = organizationalPerson; sn = Sourdeau; } + + ldifString = [NSMutableString string]; + [self _appendLDIFKey: @"dn" toString: ldifString]; + [self _appendObjectClassesToString: ldifString]; + + keys = [self allKeys]; + max = [keys count]; + for (count = 0; count < max; count++) + { + currentKey = [keys objectAtIndex: count]; + if (!([currentKey hasPrefix: @"objectClass"] + || [currentKey isEqualToString: @"dn"])) + [self _appendLDIFKey: currentKey toString: ldifString]; + } + + return ldifString; +} + +@end diff --git a/SoObjects/Contacts/NSString+LDIF.h b/SoObjects/Contacts/NSString+LDIF.h new file mode 100644 index 000000000..5f6c0f1da --- /dev/null +++ b/SoObjects/Contacts/NSString+LDIF.h @@ -0,0 +1,34 @@ +/* NSString+LDIF.h - this file is part of SOGo + * + * Copyright (C) 2011 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * 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 NSSTRING_LDIF_H +#define NSSTRING_LDIF_H + +#import + +@interface NSString (SOGoLDIF) + +- (BOOL) mustEncodeLDIFValue; + +@end + +#endif /* NSSTRING_LDIF_H */ diff --git a/SoObjects/Contacts/NSString+LDIF.m b/SoObjects/Contacts/NSString+LDIF.m new file mode 100644 index 000000000..91593a446 --- /dev/null +++ b/SoObjects/Contacts/NSString+LDIF.m @@ -0,0 +1,67 @@ +/* NSString+LDIF.m - this file is part of SOGo + * + * Copyright (C) 2011 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * 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 + +#import "NSString+LDIF.h" + +@implementation NSString (SOGoLDIF) + +static NSMutableCharacterSet *safeLDIFChars = nil; +static NSMutableCharacterSet *safeLDIFStartChars = nil; + +- (void) _initSafeLDIFChars +{ + safeLDIFChars = [NSMutableCharacterSet new]; + [safeLDIFChars addCharactersInRange: NSMakeRange (0x01, 9)]; + [safeLDIFChars addCharactersInRange: NSMakeRange (0x0b, 2)]; + [safeLDIFChars addCharactersInRange: NSMakeRange (0x0e, 114)]; + + safeLDIFStartChars = [safeLDIFChars mutableCopy]; + [safeLDIFStartChars removeCharactersInString: @" :<"]; +} + +- (BOOL) mustEncodeLDIFValue +{ + int count, max; + BOOL rc; + + if (!safeLDIFChars) + [self _initSafeLDIFChars]; + + rc = NO; + + max = [self length]; + if (max > 0) + { + if ([safeLDIFStartChars characterIsMember: [self characterAtIndex: 0]]) + for (count = 1; !rc && count < max; count++) + rc = ![safeLDIFChars + characterIsMember: [self characterAtIndex: count]]; + else + rc = YES; + } + + return rc; +} + +@end diff --git a/SoObjects/Contacts/SOGoContactEntryPhoto.h b/SoObjects/Contacts/SOGoContactEntryPhoto.h index e35eb2cd7..6c44d0757 100644 --- a/SoObjects/Contacts/SOGoContactEntryPhoto.h +++ b/SoObjects/Contacts/SOGoContactEntryPhoto.h @@ -30,11 +30,6 @@ int photoID; } -+ (id) entryPhotoWithID: (int) photoId - inContainer: (id) container; - -- (void) setPhotoID: (int) newPhotoID; - - (NSString *) davContentType; @end diff --git a/SoObjects/Contacts/SOGoContactEntryPhoto.m b/SoObjects/Contacts/SOGoContactEntryPhoto.m index d90750460..7f571ca98 100644 --- a/SoObjects/Contacts/SOGoContactEntryPhoto.m +++ b/SoObjects/Contacts/SOGoContactEntryPhoto.m @@ -35,36 +35,9 @@ @implementation SOGoContactEntryPhoto -+ (id) entryPhotoWithID: (int) photoID - inContainer: (id) container -{ - id photo; - - photo - = [super objectWithName: [NSString stringWithFormat: @"photo%d", photoID] - inContainer: container]; - [photo setPhotoID: photoID]; - - return photo; -} - -- (void) setPhotoID: (int) newPhotoID -{ - photoID = newPhotoID; -} - - (NGVCardPhoto *) photo { - NGVCardPhoto *photo; - NSArray *photoElements; - - photoElements = [[container vCard] childrenWithTag: @"photo"]; - if ([photoElements count] > photoID) - photo = [photoElements objectAtIndex: photoID]; - else - photo = nil; - - return photo; + return (NGVCardPhoto *) [[container vCard] firstChildWithTag: @"photo"]; } - (id) GETAction: (WOContext *) localContext diff --git a/SoObjects/Contacts/SOGoContactGCSEntry.m b/SoObjects/Contacts/SOGoContactGCSEntry.m index 61cb32708..09c18ebbd 100644 --- a/SoObjects/Contacts/SOGoContactGCSEntry.m +++ b/SoObjects/Contacts/SOGoContactGCSEntry.m @@ -19,11 +19,15 @@ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ + #import +#import #import #import +#import +#import "NGVCard+SOGo.h" #import "SOGoContactEntryPhoto.h" #import "SOGoContactGCSEntry.h" @@ -62,6 +66,21 @@ return card; } +- (void) setLDIFRecord: (NSDictionary *) newLDIFRecord +{ + [[self vCard] updateFromLDIFRecord: newLDIFRecord]; +} + +- (NSDictionary *) ldifRecord +{ + return [[self vCard] asLDIFRecord]; +} + +- (BOOL) hasPhoto +{ + return ([[self vCard] firstChildWithTag: @"photo"] != nil); +} + /* actions */ - (id) lookupName: (NSString *) lookupName @@ -69,16 +88,12 @@ acquire: (BOOL) acquire { id obj; - int photoIndex; - NSArray *photoElements; - if ([lookupName hasPrefix: @"photo"]) + if ([lookupName isEqualToString: @"photo"]) { - photoElements = [[self vCard] childrenWithTag: @"photo"]; - photoIndex = [[lookupName substringFromIndex: 5] intValue]; - if (photoIndex > -1 && photoIndex < [photoElements count]) - obj = [SOGoContactEntryPhoto entryPhotoWithID: photoIndex - inContainer: self]; + if ([self hasPhoto]) + obj = [SOGoContactEntryPhoto objectWithName: lookupName + inContainer: self]; else obj = nil; } @@ -127,13 +142,16 @@ /* specialized actions */ -- (void) save +- (NSException *) save { - NGVCard *vcard; + NSException *result; - vcard = [self vCard]; + if (card) + result = [self saveContentString: [card versitString]]; + else + result = nil; /* TODO: we should probably return an exception instead */ - [self saveContentString: [vcard versitString]]; + return result; } - (NSException *) saveContentString: (NSString *) newContent diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.m b/SoObjects/Contacts/SOGoContactGCSFolder.m index 73114b811..9fa758e75 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.m +++ b/SoObjects/Contacts/SOGoContactGCSFolder.m @@ -45,6 +45,7 @@ #import #import #import +#import #import "SOGoContactGCSEntry.h" #import "SOGoContactGCSList.h" diff --git a/SoObjects/Contacts/SOGoContactLDIFEntry.h b/SoObjects/Contacts/SOGoContactLDIFEntry.h index d89b13d30..e2625e665 100644 --- a/SoObjects/Contacts/SOGoContactLDIFEntry.h +++ b/SoObjects/Contacts/SOGoContactLDIFEntry.h @@ -31,8 +31,8 @@ @interface SOGoContactLDIFEntry : SOGoObject { + BOOL isNew; NSDictionary *ldifEntry; - NGVCard *vcard; } + (SOGoContactLDIFEntry *) contactEntryWithName: (NSString *) newName @@ -42,6 +42,9 @@ withLDIFEntry: (NSDictionary *) newEntry inContainer: (id) newContainer; +- (BOOL) isNew; +- (void) setIsNew: (BOOL) newIsNew; + - (NSString *) davEntityTag; @end diff --git a/SoObjects/Contacts/SOGoContactLDIFEntry.m b/SoObjects/Contacts/SOGoContactLDIFEntry.m index 17d562709..f4b09e800 100644 --- a/SoObjects/Contacts/SOGoContactLDIFEntry.m +++ b/SoObjects/Contacts/SOGoContactLDIFEntry.m @@ -29,9 +29,13 @@ #import #import +#import +#import +#import "NGVCard+SOGo.h" #import "SOGoContactGCSEntry.h" #import "SOGoContactLDIFEntry.h" +#import "SOGoContactSourceFolder.h" @implementation SOGoContactLDIFEntry @@ -42,8 +46,8 @@ SOGoContactLDIFEntry *entry; entry = [[self alloc] initWithName: newName - withLDIFEntry: newEntry - inContainer: newContainer]; + withLDIFEntry: newEntry + inContainer: newContainer]; [entry autorelease]; return entry; @@ -56,7 +60,7 @@ if ((self = [self initWithName: newName inContainer: newContainer])) { ASSIGN (ldifEntry, newEntry); - vcard = nil; + isNew = NO; } return self; @@ -64,169 +68,34 @@ - (void) dealloc { - [vcard release]; [ldifEntry release]; [super dealloc]; } +- (BOOL) isNew +{ + return isNew; +} + +- (void) setIsNew: (BOOL) newIsNew +{ + isNew = newIsNew; +} + - (NSString *) contentAsString { return [[self vCard] versitString]; } -- (void) _setPhonesOfVCard: (NGVCard *) vCard -{ - NSString *info; - - info = [ldifEntry objectForKey: @"telephonenumber"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"work", @"voice", @"pref", nil]]; - info = [ldifEntry objectForKey: @"homephone"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"home", @"voice", nil]]; - info = [ldifEntry objectForKey: @"fax"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"work", @"fax", nil]]; - info = [ldifEntry objectForKey: @"pager"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"pager", nil]]; - info = [ldifEntry objectForKey: @"mobile"]; - if (info) - [vCard addTel: info - types: [NSArray arrayWithObjects: @"cell", @"voice", nil]]; - -// telephoneNumber: work phone -// homePhone: home phone -// fax: fax phone -// pager: page phone -// mobile: mobile phone - -} - - (NGVCard *) vCard { - NSString *info, *surname, *streetAddress, *location, *region, *postalCode, *country, *org, *orgunit; - CardElement *element; - unsigned int count; + NGVCard *vcard; - if (!vcard) - { - vcard = [[NGVCard alloc] initWithUid: [self nameInContainer]]; - [vcard setVClass: @"PUBLIC"]; - [vcard setProdID: [NSString - stringWithFormat: @"-//Inverse inc./SOGo %@//EN", - SOGoVersion]]; - [vcard setProfile: @"VCARD"]; - info = [ldifEntry objectForKey: @"c_cn"]; - if (![info length]) - { - info = [ldifEntry objectForKey: @"displayname"]; - if (![info length]) - info = [ldifEntry objectForKey: @"cn"]; - } - [vcard setFn: info]; - surname = [ldifEntry objectForKey: @"sn"]; - if (!surname) - surname = [ldifEntry objectForKey: @"surname"]; - [vcard setNWithFamily: surname - given: [ldifEntry objectForKey: @"givenname"] - additional: nil - prefixes: nil - suffixes: nil]; - info = [ldifEntry objectForKey: @"title"]; - if (info) - [vcard setTitle: info]; - info = [ldifEntry objectForKey: @"mozillanickname"]; - if (info) - [vcard setNickname: info]; - - /* If "c_info" is defined, we set as the NOTE value in order for - Thunderbird (or any other CardDAV client) to display it. */ - info = [ldifEntry objectForKey: @"c_info"]; - if (![info length]) - info = [ldifEntry objectForKey: @"description"]; - if ([info length]) - [vcard setNote: info]; - - info = [ldifEntry objectForKey: @"mail"]; - if (info) - [vcard addEmail: info - types: [NSArray arrayWithObjects: @"internet", @"pref", nil]]; - [self _setPhonesOfVCard: vcard]; - - streetAddress = [ldifEntry objectForKey: @"street"]; - if (!streetAddress) - streetAddress = [ldifEntry objectForKey: @"streetaddress"]; - - location = [ldifEntry objectForKey: @"l"]; - if (!location) - location = [ldifEntry objectForKey: @"locality"]; - - region = [ldifEntry objectForKey: @"st"]; - if (!region) - region = [ldifEntry objectForKey: @"region"]; - - postalCode = [ldifEntry objectForKey: @"postalcode"]; - if (!postalCode) - postalCode = [ldifEntry objectForKey: @"zip"]; - - country = [ldifEntry objectForKey: @"c"]; - if (!country) - country = [ldifEntry objectForKey: @"countryname"]; - - element = [CardElement elementWithTag: @"adr"]; - [element setValue: 0 ofAttribute: @"type" to: @"work"]; - - if (streetAddress) - [element setSingleValue: streetAddress atIndex: 2 forKey: @""]; - if (location) - [element setSingleValue: location atIndex: 3 forKey: @""]; - if (region) - [element setSingleValue: region atIndex: 4 forKey: @""]; - if (postalCode) - [element setSingleValue: postalCode atIndex: 5 forKey: @""]; - if (country) - [element setSingleValue: country atIndex: 6 forKey: @""]; - - if (streetAddress || location || region || postalCode || country) - [vcard addChild: element]; - - // We handle the org/orgunit stuff - element = [CardElement elementWithTag: @"org"]; - org = [ldifEntry objectForKey: @"o"]; - orgunit = [ldifEntry objectForKey: @"ou"]; - if (!orgunit) - orgunit = [ldifEntry objectForKey: @"orgunit"]; - - if (org) - [element setSingleValue: org atIndex: 0 forKey: @""]; - if (orgunit) - [element setSingleValue: orgunit atIndex: 1 forKey: @""]; - - if (org || orgunit) - [vcard addChild: element]; - - info = [ldifEntry objectForKey: @"calFBURL"]; - if (info) - [vcard addChildWithTag: @"FBURL" - types: nil - singleValue: info]; - for (count = 1; count < 5; count++) - { - info = [ldifEntry objectForKey: - [NSString stringWithFormat: @"mozillacustom%d", - count]]; - if (info) - [vcard addChildWithTag: [NSString stringWithFormat: @"CUSTOM%d", - count] - types: nil - singleValue: info]; - } - } + vcard = [NGVCard cardWithUid: [self nameInContainer]]; + [vcard setProdID: [NSString + stringWithFormat: @"-//Inverse inc./SOGo %@//EN", + SOGoVersion]]; + [vcard updateFromLDIFRecord: [self ldifRecord]]; return vcard; } @@ -236,6 +105,21 @@ return NO; } +- (void) setLDIFRecord: (NSDictionary *) newLDIFRecord +{ + ASSIGN (ldifEntry, newLDIFRecord); +} + +- (NSDictionary *) ldifRecord +{ + return ldifEntry; +} + +- (BOOL) hasPhoto +{ + return NO; +} + - (NSString *) davEntityTag { unsigned int hash; @@ -251,13 +135,47 @@ return @"text/x-vcard"; } -- (NSArray *) aclsForUser: (NSString *) uid +- (NSException *) save { - return nil; + return [(SOGoContactSourceFolder *) container saveLDIFEntry: self]; } -- (void) save +- (NSException *) delete { + return [(SOGoContactSourceFolder *) container deleteLDIFEntry: self]; +} + +/* acl */ + +- (NSArray *) aclsForUser: (NSString *) uid +{ + NSMutableArray *acls; + NSArray *containerAcls; + + acls = [NSMutableArray array]; + /* this is unused... */ +// ownAcls = [container aclsForUser: uid +// forObjectAtPath: [self pathArrayToSOGoObject]]; +// [acls addObjectsFromArray: ownAcls]; + containerAcls = [container aclsForUser: uid]; + if ([containerAcls count] > 0) + { + [acls addObjectsFromArray: containerAcls]; + /* The creation of an object is actually a "modification" to an + unexisting object. When the object is new, we give the + "ObjectCreator" the "ObjectModifier" role temporarily while we + disallow the "ObjectModifier" users to modify them, unless they are + ObjectCreators too. */ + if (isNew) + { + if ([containerAcls containsObject: SOGoRole_ObjectCreator]) + [acls addObject: SOGoRole_ObjectEditor]; + else + [acls removeObject: SOGoRole_ObjectEditor]; + } + } + + return acls; } /* DAV */ diff --git a/SoObjects/Contacts/SOGoContactObject.h b/SoObjects/Contacts/SOGoContactObject.h index 74b911540..6b718eefa 100644 --- a/SoObjects/Contacts/SOGoContactObject.h +++ b/SoObjects/Contacts/SOGoContactObject.h @@ -22,25 +22,20 @@ #ifndef __Contacts_SOGoContactObject_H__ #define __Contacts_SOGoContactObject_H__ -/* - SOGoContactObject - - Represents a single contact. This SOPE controller object manages all the - attendee storages (that is, it might store into multiple folders for meeting - appointments!). - - Note: SOGoContactObject do not need to exist yet. They can also be "new" - appointments with an externally generated unique key. -*/ - @class NSDictionary; -@class NSString; @class NGVCard; @protocol SOGoContactObject - (NGVCard *) vCard; -- (void) save; +- (BOOL) hasPhoto; + +/* web editing */ +- (void) setLDIFRecord: (NSDictionary *) newLDIFRecord; +- (NSDictionary *) ldifRecord; + +- (NSException *) save; +- (NSException *) delete; @end diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.h b/SoObjects/Contacts/SOGoContactSourceFolder.h index 5cde076b7..09effd47d 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.h +++ b/SoObjects/Contacts/SOGoContactSourceFolder.h @@ -26,13 +26,14 @@ #import "SOGoContactFolder.h" #import "SOGoFolder+CardDAV.h" -@class NSMutableDictionary; +#import -#import "../SOGo/SOGoSource.h" +@class NSMutableDictionary; +@class SOGoContactLDIFEntry; @interface SOGoContactSourceFolder : SOGoFolder { - id source; + id source; NSMutableDictionary *childRecords; } @@ -42,7 +43,10 @@ - (id) initWithName: (NSString *) newName andDisplayName: (NSString *) newDisplayName inContainer: (id) newContainer; -- (void) setSource: (id) newSource; +- (void) setSource: (id ) newSource; + +- (NSException *) saveLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry; +- (NSException *) deleteLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry; @end diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.m b/SoObjects/Contacts/SOGoContactSourceFolder.m index cc25d0d76..ba576aa8f 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.m +++ b/SoObjects/Contacts/SOGoContactSourceFolder.m @@ -38,6 +38,7 @@ #import #import +#import #import #import #import @@ -96,11 +97,16 @@ [super dealloc]; } -- (void) setSource: (id) newSource +- (void) setSource: (id ) newSource { ASSIGN (source, newSource); } +- (id ) source +{ + return source; +} + - (NSString *) groupDavResourceType { return @"vcard-collection"; @@ -126,7 +132,9 @@ acquire: (BOOL) acquire { NSDictionary *ldifEntry; - id obj; + SOGoContactLDIFEntry *obj; + NSString *url; + BOOL isNew = NO; /* first check attributes directly bound to the application */ obj = [super lookupName: objectName inContext: lookupContext acquire: NO]; @@ -139,11 +147,24 @@ ldifEntry = [source lookupContactEntry: objectName]; if (ldifEntry) [childRecords setObject: ldifEntry forKey: objectName]; + else if ([self isValidContentName: objectName]) + { + url = [[[lookupContext request] uri] urlWithoutParameters]; + if ([url hasSuffix: @"AsContact"]) + { + ldifEntry = [NSMutableDictionary dictionary]; + isNew = YES; + } + } } if (ldifEntry) - obj = [SOGoContactLDIFEntry contactEntryWithName: objectName - withLDIFEntry: ldifEntry - inContainer: self]; + { + obj = [SOGoContactLDIFEntry contactEntryWithName: objectName + withLDIFEntry: ldifEntry + inContainer: self]; + if (isNew) + [obj setIsNew: YES]; + } else obj = [NSException exceptionWithHTTPStatus: 404]; } @@ -156,6 +177,19 @@ return [source allEntryIDs]; } +- (NSException *) saveLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry +{ + return (([ldifEntry isNew]) + ? [source addContactEntry: [ldifEntry ldifRecord] + withID: [ldifEntry nameInContainer]] + : [source updateContactEntry: [ldifEntry ldifRecord]]); +} + +- (NSException *) deleteLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry +{ + return [source removeContactEntryWithID: [ldifEntry nameInContainer]]; +} + - (NSDictionary *) _flattenedRecord: (NSDictionary *) oldRecord { NSMutableDictionary *newRecord; @@ -324,10 +358,26 @@ return [NSArray arrayWithObject: SoRole_Authenticated]; } -/* TODO: this might change one day when we support LDAP acls */ - (NSArray *) aclsForUser: (NSString *) uid { - return nil; + NSArray *acls, *modifiers; + static NSArray *modifierRoles = nil; + + if (!modifierRoles) + modifierRoles = [[NSArray alloc] initWithObjects: @"Owner", + @"ObjectViewer", + @"ObjectEditor", @"ObjectCreator", + @"ObjectEraser", nil]; + + modifiers = [source modifiers]; + if ([modifiers containsObject: uid]) + acls = [modifierRoles copy]; + else + acls = [NSArray new]; + + [acls autorelease]; + + return acls; } @end diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 5a3a05a79..c099e3c49 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -28,6 +28,7 @@ SOGo_HEADER_FILES = \ \ SOGoUserManager.h \ LDAPSource.h \ + LDAPSourceSchema.h \ SQLSource.h \ SOGoUserProfile.h \ SOGoDateFormatter.h \ @@ -97,6 +98,7 @@ SOGo_OBJC_FILES = \ SOGoStartupLogger.m \ SOGoUserManager.m \ LDAPSource.m \ + LDAPSourceSchema.m \ SQLSource.m \ SOGoUserProfile.m \ SOGoSQLUserProfile.m \ diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 0f7929527..64fd9c02d 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -30,6 +30,7 @@ #include "SOGoSource.h" #include "SOGoConstants.h" +@class LDAPSourceSchema; @class NSDictionary; @class NSString; @class NGLdapConnection; @@ -52,6 +53,7 @@ NSString *_scope; NSString *baseDN; + LDAPSourceSchema *schema; NSString *IDField; // the first part of a user DN NSString *CNField; NSString *UIDField; @@ -63,6 +65,9 @@ NSString *domain; NSString *contactInfoAttribute; + NSDictionary *contactMapping; + NSArray *contactObjectClasses; + NSDictionary *modulesConstraints; NSMutableArray *searchAttributes; @@ -74,6 +79,9 @@ /* resources handling */ NSString *kindField; NSString *multipleBookingsField; + + /* ACL */ + NSArray *modifiers; } - (void) setBindDN: (NSString *) newBindDN @@ -95,6 +103,11 @@ kindField: (NSString *) newKindField andMultipleBookingsField: (NSString *) newMultipleBookingsField; +/* This enable the convertion of a contact entry with inetOrgPerson and mozillaAbPerson + to and from an LDAP record */ +- (void) setContactMapping: (NSDictionary *) newMapping + andObjectClasses: (NSArray *) newObjectClasses; + - (NGLdapEntry *) lookupGroupEntryByUID: (NSString *) theUID; - (NGLdapEntry *) lookupGroupEntryByEmail: (NSString *) theEmail; - (NGLdapEntry *) lookupGroupEntryByAttribute: (NSString *) theAttribute diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index abb45190e..27ee22fe8 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -35,6 +35,7 @@ #import #import +#import "LDAPSourceSchema.h" #import "NSArray+Utilities.h" #import "NSString+Utilities.h" #import "SOGoDomainDefaults.h" @@ -44,89 +45,76 @@ #import "../../Main/SOGo.h" +static Class NSStringK; + #define SafeLDAPCriteria(x) [[[x stringByReplacingString: @"\\" withString: @"\\\\"] \ stringByReplacingString: @"'" withString: @"\\'"] \ stringByReplacingString: @"%" withString: @"%%"] -static NSArray *commonSearchFields; + +@interface NGLdapAttribute (SOGoLDAP) + +- (id) _asArrayOrString; + +@end + +@implementation NGLdapAttribute (SOGoLDAP) + +- (id) _asArrayOrString +{ + id value; + NSArray *arrayValue; + + arrayValue = [self allStringValues]; + if ([arrayValue count] == 1) + value = [arrayValue objectAtIndex: 0]; + else + value = arrayValue; + + return value; +} + +@end + +@interface NGLdapEntry (SOGoLDAP) + +- (NSMutableDictionary *) _asDictionary; + +@end + +@implementation NGLdapEntry (SOGoLDAP) + +- (NSMutableDictionary *) _asDictionary +{ + NSMutableDictionary *ldapRecord; + NSDictionary *ldapAttributes; + NSArray *keys; + NSString *key; + NSUInteger count, max; + id value; + + ldapAttributes = [self attributes]; + keys = [ldapAttributes allKeys]; + max = [keys count]; + + ldapRecord = [NSMutableDictionary dictionaryWithCapacity: max]; + for (count = 0; count < max; count++) + { + key = [keys objectAtIndex: count]; + value = [[ldapAttributes objectForKey: key] _asArrayOrString]; + if (value) + [ldapRecord setObject: value forKey: [key lowercaseString]]; + } + + return ldapRecord; +} + +@end @implementation LDAPSource + (void) initialize { - if (!commonSearchFields) - { - commonSearchFields = [NSArray arrayWithObjects: - @"title", - @"company", - @"o", - @"displayname", - @"modifytimestamp", - @"mozillahomestate", - @"mozillahomeurl", - @"homeurl", - @"st", - @"region", - @"mozillacustom2", - @"custom2", - @"mozillahomecountryname", - @"description", - @"notes", - @"department", - @"departmentnumber", - @"ou", - @"orgunit", - @"mobile", - @"cellphone", - @"carphone", - @"mozillacustom1", - @"custom1", - @"mozillanickname", - @"xmozillanickname", - @"mozillaworkurl", - @"workurl", - @"fax", - @"facsimiletelephonenumber", - @"telephonenumber", - @"mozillahomestreet", - @"mozillasecondemail", - @"xmozillasecondemail", - @"mozillacustom4", - @"custom4", - @"nsaimid", - @"nscpaimscreenname", - @"street", - @"streetaddress", - @"postofficebox", - @"homephone", - @"cn", - @"commonname", - @"givenname", - @"mozillahomepostalcode", - @"mozillahomelocalityname", - @"mozillaworkstreet2", - @"mozillausehtmlmail", - @"xmozillausehtmlmail", - @"mozillahomestreet2", - @"postalcode", - @"zip", - @"c", - @"countryname", - @"pager", - @"pagerphone", - @"mail", - @"sn", - @"surname", - @"mozillacustom3", - @"custom3", - @"l", - @"locality", - @"birthyear", - @"serialnumber", - @"calfburl", - @"proxyaddresses", - nil]; - [commonSearchFields retain]; - } + NSStringK = [NSString class]; } // @@ -162,11 +150,13 @@ static NSArray *commonSearchFields; domain = nil; baseDN = nil; + schema = nil; IDField = @"cn"; /* the first part of a user DN */ CNField = @"cn"; UIDField = @"uid"; mailFields = [NSArray arrayWithObject: @"mail"]; [mailFields retain]; + contactMapping = nil; searchFields = [NSArray arrayWithObjects: @"sn", @"displayname", @"telephonenumber", nil]; [searchFields retain]; IMAPHostField = nil; @@ -182,6 +172,7 @@ static NSArray *commonSearchFields; multipleBookingsField = nil; _dnCache = [[NSMutableDictionary alloc] init]; + modifiers = nil; } return self; @@ -192,6 +183,7 @@ static NSArray *commonSearchFields; // - (void) dealloc { + [schema release]; [bindDN release]; [password release]; [sourceBindDN release]; @@ -202,6 +194,7 @@ static NSArray *commonSearchFields; [IDField release]; [CNField release]; [UIDField release]; + [contactMapping release]; [mailFields release]; [searchFields release]; [IMAPHostField release]; @@ -216,6 +209,7 @@ static NSArray *commonSearchFields; [_dnCache release]; [kindField release]; [multipleBookingsField release]; + [modifiers release]; [super dealloc]; } @@ -251,6 +245,9 @@ static NSArray *commonSearchFields; kindField: [udSource objectForKey: @"KindFieldName"] andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]]; + [self setContactMapping: [udSource objectForKey: @"mapping"] + andObjectClasses: [udSource objectForKey: @"objectClasses"]]; + if ([sourceDomain length]) { dd = [SOGoDomainDefaults defaultsForDomain: sourceDomain]; @@ -286,6 +283,8 @@ static NSArray *commonSearchFields; if ([udSource objectForKey: @"passwordPolicy"]) passwordPolicy = [[udSource objectForKey: @"passwordPolicy"] boolValue]; + + ASSIGN (modifiers, [udSource objectForKey: @"modifiers"]); } return self; @@ -345,15 +344,15 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField { ASSIGN(baseDN, [newBaseDN lowercaseString]); if (newIDField) - ASSIGN(IDField, newIDField); + ASSIGN(IDField, [newIDField lowercaseString]); if (newCNField) - ASSIGN(CNField, newCNField); + ASSIGN(CNField, [newCNField lowercaseString]); if (newUIDField) - ASSIGN(UIDField, newUIDField); + ASSIGN(UIDField, [newUIDField lowercaseString]); if (newIMAPHostField) - ASSIGN(IMAPHostField, newIMAPHostField); + ASSIGN(IMAPHostField, [newIMAPHostField lowercaseString]); if (newIMAPLoginField) - ASSIGN(IMAPLoginField, newIMAPLoginField); + ASSIGN(IMAPLoginField, [newIMAPLoginField lowercaseString]); if (newMailFields) ASSIGN(mailFields, newMailFields); if (newSearchFields) @@ -382,9 +381,16 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField } } if (newKindField) - ASSIGN(kindField, newKindField); + ASSIGN(kindField, [newKindField lowercaseString]); if (newMultipleBookingsField) - ASSIGN(multipleBookingsField, newMultipleBookingsField); + ASSIGN(multipleBookingsField, [newMultipleBookingsField lowercaseString]); +} + +- (void) setContactMapping: (NSDictionary *) newMapping + andObjectClasses: (NSArray *) newObjectClasses +{ + ASSIGN (contactMapping, newMapping); + ASSIGN (contactObjectClasses, newObjectClasses); } // @@ -438,7 +444,9 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField } NS_HANDLER { - NSLog(@"Could not bind to the LDAP server %@ (%d) using the bind DN: %@", hostname, port, bindDN); + [self errorWithFormat: @"Could not bind to the LDAP server %@ (%d)" + @" using the bind DN: %@", + hostname, port, bindDN]; ldapConnection = nil; } NS_ENDHANDLER; @@ -738,44 +746,6 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField return fields; } -- (NSArray *) _searchAttributes -{ - if (!searchAttributes) - { - searchAttributes = [NSMutableArray new]; - [searchAttributes addObject: @"objectClass"]; - if (CNField) - [searchAttributes addObject: CNField]; - if (UIDField) - [searchAttributes addObject: UIDField]; - [searchAttributes addObjectsFromArray: mailFields]; - [searchAttributes addObjectsFromArray: [self _constraintsFields]]; - [searchAttributes addObjectsFromArray: commonSearchFields]; - [searchAttributes addObjectUniquely: IDField]; - - // Add SOGoLDAPContactInfoAttribute from user defaults - if ([contactInfoAttribute length]) - [searchAttributes addObjectUniquely: contactInfoAttribute]; - - // Add IMAP hostname from user defaults - if ([IMAPHostField length]) - [searchAttributes addObjectUniquely: IMAPHostField]; - - // Add IMAP login from user defaults - if ([IMAPLoginField length]) - [searchAttributes addObjectUniquely: IMAPLoginField]; - - // Add the resources handling attributes - if ([kindField length]) - [searchAttributes addObjectUniquely: kindField]; - - if ([multipleBookingsField length]) - [searchAttributes addObjectUniquely: multipleBookingsField]; - } - - return searchAttributes; -} - - (NSArray *) allEntryIDs { NSEnumerator *entries; @@ -814,7 +784,7 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField } - (void) _fillEmailsOfEntry: (NGLdapEntry *) ldapEntry - intoContactEntry: (NSMutableDictionary *) contactEntry + intoLDIFRecord: (NSMutableDictionary *) ldifRecord { NSEnumerator *emailFields; NSString *currentFieldName, *ldapValue; @@ -829,27 +799,27 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField allStringValues]; [emails addObjectsFromArray: allValues]; } - [contactEntry setObject: emails forKey: @"c_emails"]; + [ldifRecord setObject: emails forKey: @"c_emails"]; [emails release]; if (IMAPHostField) { ldapValue = [[ldapEntry attributeWithName: IMAPHostField] stringValueAtIndex: 0]; if ([ldapValue length] > 0) - [contactEntry setObject: ldapValue forKey: @"c_imaphostname"]; + [ldifRecord setObject: ldapValue forKey: @"c_imaphostname"]; } if (IMAPLoginField) { ldapValue = [[ldapEntry attributeWithName: IMAPLoginField] stringValueAtIndex: 0]; if ([ldapValue length] > 0) - [contactEntry setObject: ldapValue forKey: @"c_imaplogin"]; + [ldifRecord setObject: ldapValue forKey: @"c_imaplogin"]; } } - (void) _fillConstraints: (NGLdapEntry *) ldapEntry forModule: (NSString *) module - intoContactEntry: (NSMutableDictionary *) contactEntry + intoLDIFRecord: (NSMutableDictionary *) ldifRecord { NSDictionary *constraints; NSEnumerator *matches; @@ -875,22 +845,95 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField } } - [contactEntry setObject: [NSNumber numberWithBool: result] - forKey: [NSString stringWithFormat: @"%@Access", module]]; + [ldifRecord setObject: [NSNumber numberWithBool: result] + forKey: [NSString stringWithFormat: @"%@Access", module]]; +} + +/* conversion LDAP -> SOGo inetOrgPerson entry */ +- (void) _applyContactMappingToResult: (NSMutableDictionary *) ldifRecord +{ + NSArray *sourceFields; + NSArray *keys; + NSString *key, *field, *value; + NSUInteger count, max, fieldCount, fieldMax; + BOOL filled; + + keys = [contactMapping allKeys]; + max = [keys count]; + for (count = 0; count < max; count++) + { + key = [keys objectAtIndex: count]; + sourceFields = [contactMapping objectForKey: key]; + if ([sourceFields isKindOfClass: NSStringK]) + sourceFields = [NSArray arrayWithObject: sourceFields]; + fieldMax = [sourceFields count]; + filled = NO; + for (fieldCount = 0; + !filled && fieldCount < fieldMax; + fieldCount++) + { + field = [[sourceFields objectAtIndex: fieldCount] lowercaseString]; + value = [ldifRecord objectForKey: field]; + if (value) + { + [ldifRecord setObject: value forKey: [key lowercaseString]]; + filled = YES; + } + } + } +} + +/* conversion SOGo inetOrgPerson entry -> LDAP */ +- (void) _applyContactMappingToOutput: (NSMutableDictionary *) ldifRecord +{ + NSArray *sourceFields; + NSArray *keys; + NSString *key, *lowerKey, *field, *value; + NSUInteger count, max, fieldCount, fieldMax; + + if (contactObjectClasses) + [ldifRecord setObject: contactObjectClasses + forKey: @"objectclass"]; + + keys = [contactMapping allKeys]; + max = [keys count]; + for (count = 0; count < max; count++) + { + key = [keys objectAtIndex: count]; + lowerKey = [key lowercaseString]; + value = [ldifRecord objectForKey: lowerKey]; + if ([value length] > 0) + { + sourceFields = [contactMapping objectForKey: key]; + if ([sourceFields isKindOfClass: NSStringK]) + sourceFields = [NSArray arrayWithObject: sourceFields]; + + fieldMax = [sourceFields count]; + for (fieldCount = 0; fieldCount < fieldMax; fieldCount++) + { + field = [[sourceFields objectAtIndex: fieldCount] + lowercaseString]; + [ldifRecord setObject: value forKey: field]; + } + } + } } - (NSDictionary *) _convertLDAPEntryToContact: (NGLdapEntry *) ldapEntry { - NSMutableDictionary *contactEntry; - NSEnumerator *attributes; - NSString *currentAttribute, *value; + NSMutableDictionary *ldifRecord; + NSString *value; + static NSArray *resourceKinds = nil; NSMutableArray *classes; id o; - contactEntry = [NSMutableDictionary dictionary]; - [contactEntry setObject: [ldapEntry dn] forKey: @"dn"]; - attributes = [[self _searchAttributes] objectEnumerator]; + if (!resourceKinds) + resourceKinds = [[NSArray alloc] initWithObjects: @"location", @"thing", + @"group", nil]; + ldifRecord = [ldapEntry _asDictionary]; + [ldifRecord setObject: [ldapEntry dn] forKey: @"dn"]; + // We get our objectClass attribute values. We lowercase // everything for ease of search after. o = [ldapEntry objectClasses]; @@ -909,9 +952,6 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField if (classes) { - [contactEntry setObject: classes - forKey: @"objectclasses"]; - // We check if our entry is a group. If so, we set the // 'isGroup' custom attribute. if ([classes containsObject: @"group"] || @@ -919,63 +959,48 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField [classes containsObject: @"groupofuniquenames"] || [classes containsObject: @"posixgroup"]) { - [contactEntry setObject: [NSNumber numberWithInt: 1] - forKey: @"isGroup"]; + [ldifRecord setObject: [NSNumber numberWithInt: 1] + forKey: @"isGroup"]; } // We check if our entry is a resource. We also support // determining resources based on the KindFieldName attribute // value - see below. else if ([classes containsObject: @"calendarresource"]) { - [contactEntry setObject: [NSNumber numberWithInt: 1] - forKey: @"isResource"]; + [ldifRecord setObject: [NSNumber numberWithInt: 1] + forKey: @"isResource"]; } } - while ((currentAttribute = [attributes nextObject])) + // We check if that entry corresponds to a resource. For this, + // kindField must be defined and it must hold one of those values + // + // location + // thing + // group + // + if ([kindField length] > 0) { - value = [[ldapEntry attributeWithName: currentAttribute] - stringValueAtIndex: 0]; + value = [ldifRecord objectForKey: [kindField lowercaseString]]; + if ([value isKindOfClass: NSStringK] + && [resourceKinds containsObject: value]) + [ldifRecord setObject: [NSNumber numberWithInt: 1] + forKey: @"isResource"]; + } - // It's important here to set our attributes' key in lowercase. - if (value) - { - currentAttribute = [currentAttribute lowercaseString]; - [contactEntry setObject: value forKey: currentAttribute]; - - // We check if that entry corresponds to a resource. For this, - // kindField must be defined and it must hold one of those values - // - // location - // thing - // group - // - if (kindField && - [kindField caseInsensitiveCompare: currentAttribute] == NSOrderedSame) - { - if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame || - [value caseInsensitiveCompare: @"thing"] == NSOrderedSame || - [value caseInsensitiveCompare: @"group"] == NSOrderedSame) - { - [contactEntry setObject: [NSNumber numberWithInt: 1] - forKey: @"isResource"]; - } - } - // We check for the number of simultanous bookings that is allowed. - // A value of 0 means that there's no limit. - if (multipleBookingsField && - [multipleBookingsField caseInsensitiveCompare: currentAttribute] == NSOrderedSame) - { - [contactEntry setObject: [NSNumber numberWithInt: [value intValue]] - forKey: @"numberOfSimultaneousBookings"]; - } - } + // We check for the number of simultanous bookings that is allowed. + // A value of 0 means that there's no limit. + if ([multipleBookingsField length] > 0) + { + value = [ldifRecord objectForKey: [multipleBookingsField lowercaseString]]; + [ldifRecord setObject: [NSNumber numberWithInt: [value intValue]] + forKey: @"numberOfSimultaneousBookings"]; } value = [[ldapEntry attributeWithName: IDField] stringValueAtIndex: 0]; if (!value) value = @""; - [contactEntry setObject: value forKey: @"c_name"]; + [ldifRecord setObject: value forKey: @"c_name"]; value = [[ldapEntry attributeWithName: UIDField] stringValueAtIndex: 0]; if (!value) value = @""; @@ -984,11 +1009,14 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField // Eventually, we could check at this point if the entry is a group // and prefix the UID with a "@" // } - [contactEntry setObject: value forKey: @"c_uid"]; + [ldifRecord setObject: value forKey: @"c_uid"]; value = [[ldapEntry attributeWithName: CNField] stringValueAtIndex: 0]; if (!value) value = @""; - [contactEntry setObject: value forKey: @"c_cn"]; + [ldifRecord setObject: value forKey: @"c_cn"]; + /* if "displayName" is not set, we use CNField because it must exist */ + if (![ldifRecord objectForKey: @"displayname"]) + [ldifRecord setObject: value forKey: @"displayname"]; if (contactInfoAttribute) { @@ -999,21 +1027,24 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField } else value = @""; - [contactEntry setObject: value forKey: @"c_info"]; + [ldifRecord setObject: value forKey: @"c_info"]; if (domain) value = domain; else value = @""; - [contactEntry setObject: value forKey: @"c_domain"]; + [ldifRecord setObject: value forKey: @"c_domain"]; - [self _fillEmailsOfEntry: ldapEntry intoContactEntry: contactEntry]; + [self _fillEmailsOfEntry: ldapEntry intoLDIFRecord: ldifRecord]; [self _fillConstraints: ldapEntry forModule: @"Calendar" - intoContactEntry: (NSMutableDictionary *) contactEntry]; + intoLDIFRecord: (NSMutableDictionary *) ldifRecord]; [self _fillConstraints: ldapEntry forModule: @"Mail" - intoContactEntry: (NSMutableDictionary *) contactEntry]; + intoLDIFRecord: (NSMutableDictionary *) ldifRecord]; - return contactEntry; + if (contactMapping) + [self _applyContactMappingToResult: ldifRecord]; + + return ldifRecord; } - (NSArray *) fetchContactsMatching: (NSString *) match @@ -1031,7 +1062,8 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField { ldapConnection = [self _ldapConnection]; qualifier = [self _qualifierForFilter: match]; - attributes = [self _searchAttributes]; + // attributes = [self _searchAttributes]; + attributes = [NSArray arrayWithObject: @"*"]; if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) entries = [ldapConnection baseSearchAtBaseDN: baseDN @@ -1053,83 +1085,76 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField return contacts; } +- (NGLdapEntry *) _lookupLDAPEntry: (EOQualifier *) qualifier +{ + NGLdapConnection *ldapConnection; + NSArray *attributes; + NSEnumerator *entries; + + // attributes = [self _searchAttributes]; + ldapConnection = [self _ldapConnection]; + if (!schema) + { + schema = [LDAPSourceSchema new]; + [schema readSchemaFromConnection: ldapConnection]; + } + attributes = [NSArray arrayWithObject: @"*"]; + + if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) + entries = [ldapConnection baseSearchAtBaseDN: baseDN + qualifier: qualifier + attributes: attributes]; + else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) + entries = [ldapConnection flatSearchAtBaseDN: baseDN + qualifier: qualifier + attributes: attributes]; + else + entries = [ldapConnection deepSearchAtBaseDN: baseDN + qualifier: qualifier + attributes: attributes]; + + return [entries nextObject]; +} + - (NSDictionary *) lookupContactEntry: (NSString *) theID { NGLdapEntry *ldapEntry; - NGLdapConnection *ldapConnection; - NSEnumerator *entries; EOQualifier *qualifier; - NSArray *attributes; NSString *s; - NSDictionary *contactEntry; + NSDictionary *ldifRecord; - contactEntry = nil; + ldifRecord = nil; if ([theID length] > 0) { - ldapConnection = [self _ldapConnection]; s = [NSString stringWithFormat: @"(%@='%@')", IDField, SafeLDAPCriteria(theID)]; qualifier = [EOQualifier qualifierWithQualifierFormat: s]; - attributes = [self _searchAttributes]; - - if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) - entries = [ldapConnection baseSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) - entries = [ldapConnection flatSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else - entries = [ldapConnection deepSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - - ldapEntry = [entries nextObject]; + ldapEntry = [self _lookupLDAPEntry: qualifier]; if (ldapEntry) - contactEntry = [self _convertLDAPEntryToContact: ldapEntry]; + ldifRecord = [self _convertLDAPEntryToContact: ldapEntry]; } - return contactEntry; + return ldifRecord; } - (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) uid { - NGLdapConnection *ldapConnection; NGLdapEntry *ldapEntry; - NSEnumerator *entries; EOQualifier *qualifier; - NSArray *attributes; - NSDictionary *contactEntry; + NSDictionary *ldifRecord; - contactEntry = nil; + ldifRecord = nil; if ([uid length] > 0) { - ldapConnection = [self _ldapConnection]; qualifier = [self _qualifierForUIDFilter: uid]; - attributes = [self _searchAttributes]; - - if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) - entries = [ldapConnection baseSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) - entries = [ldapConnection flatSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else - entries = [ldapConnection deepSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - - ldapEntry = [entries nextObject]; + ldapEntry = [self _lookupLDAPEntry: qualifier]; if (ldapEntry) - contactEntry = [self _convertLDAPEntryToContact: ldapEntry]; + ldifRecord = [self _convertLDAPEntryToContact: ldapEntry]; } - return contactEntry; + return ldifRecord; } - (NSString *) lookupLoginByDN: (NSString *) theDN @@ -1171,43 +1196,24 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField - (NGLdapEntry *) lookupGroupEntryByAttribute: (NSString *) theAttribute andValue: (NSString *) theValue { - NSMutableArray *attributes; - NSEnumerator *entries; EOQualifier *qualifier; NSString *s; - NGLdapConnection *ldapConnection; NGLdapEntry *ldapEntry; if ([theValue length] > 0) { - ldapConnection = [self _ldapConnection]; - s = [NSString stringWithFormat: @"(%@='%@')", theAttribute, SafeLDAPCriteria(theValue)]; qualifier = [EOQualifier qualifierWithQualifierFormat: s]; // We look for additional attributes - the ones related to group // membership - attributes = [NSMutableArray arrayWithArray: [self _searchAttributes]]; - [attributes addObject: @"member"]; - [attributes addObject: @"uniqueMember"]; - [attributes addObject: @"memberUid"]; - [attributes addObject: @"memberOf"]; - - if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) - entries = [ldapConnection baseSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) - entries = [ldapConnection flatSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - else - entries = [ldapConnection deepSearchAtBaseDN: baseDN - qualifier: qualifier - attributes: attributes]; - - ldapEntry = [entries nextObject]; + // attributes = [NSMutableArray arrayWithArray: [self _searchAttributes]]; + // [attributes addObject: @"member"]; + // [attributes addObject: @"uniqueMember"]; + // [attributes addObject: @"memberUid"]; + // [attributes addObject: @"memberOf"]; + ldapEntry = [self _lookupLDAPEntry: qualifier]; } else ldapEntry = nil; @@ -1225,4 +1231,247 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField return baseDN; } +- (NSArray *) modifiers +{ + return modifiers; +} + +static NSArray * +_convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifRecord) +{ + /* convert resulting record to NGLdapEntry: + - ignore fields with empty values + - ignore extra fields + - use correct case for LDAP attribute matching classes */ + NSMutableArray *attributes; + NGLdapAttribute *attribute; + NSArray *classes, *fields, *values; + NSString *field, *lowerField, *value; + NSUInteger count, max, valueCount, valueMax; + + attributes = [NSMutableArray new]; + + classes = [ldifRecord objectForKey: @"objectclass"]; + if ([classes isKindOfClass: NSStringK]) + classes = [NSArray arrayWithObject: classes]; + fields = [schema fieldsForClasses: classes]; + + max = [fields count]; + for (count = 0; count < max; count++) + { + attribute = nil; + field = [fields objectAtIndex: count]; + lowerField = [field lowercaseString]; + if (![lowerField isEqualToString: @"dn"]) + { + values = [ldifRecord objectForKey: lowerField]; + if ([values isKindOfClass: NSStringK]) + values = [NSArray arrayWithObject: values]; + valueMax = [values count]; + for (valueCount = 0; valueCount < valueMax; valueCount++) + { + value = [values objectAtIndex: valueCount]; + if ([value length] > 0) + { + if (!attribute) + { + attribute = [[NGLdapAttribute alloc] + initWithAttributeName: field]; + [attributes addObject: attribute]; + [attribute release]; + } + [attribute addStringValue: value]; + } + } + } + } + + return attributes; +} + +- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord + withID: (NSString *) aId +{ + NSException *result = nil; + NGLdapEntry *newEntry; + NSMutableDictionary *ldifRecord; + NSArray *attributes; + NSString *dn, *cnValue; + NGLdapConnection *ldapConnection; + + if ([aId length] > 0) + { + ldapConnection = [self _ldapConnection]; + if (!schema) + { + schema = [LDAPSourceSchema new]; + [schema readSchemaFromConnection: ldapConnection]; + } + + ldifRecord = [roLdifRecord mutableCopy]; + [ldifRecord autorelease]; + [ldifRecord setObject: aId forKey: UIDField]; + + /* if CN is not set, we use aId because it must exist */ + if (![ldifRecord objectForKey: CNField]) + { + cnValue = [ldifRecord objectForKey: @"displayname"]; + if ([cnValue length] == 0) + cnValue = aId; + [ldifRecord setObject: aId forKey: @"cn"]; + } + + [self _applyContactMappingToOutput: ldifRecord]; + + /* since the id might have changed due to the mapping above, we + reload the record ID */ + aId = [ldifRecord objectForKey: UIDField]; + dn = [NSString stringWithFormat: @"%@=%@,%@", IDField, aId, baseDN]; + attributes = _convertRecordToLDAPAttributes (schema, ldifRecord); + + newEntry = [[NGLdapEntry alloc] initWithDN: dn + attributes: attributes]; + [newEntry autorelease]; + [attributes release]; + NS_DURING + { + [ldapConnection addEntry: newEntry]; + } + NS_HANDLER + { + result = localException; + } + NS_ENDHANDLER; + } + else + [self errorWithFormat: @"no value for id field '%@'", IDField]; + + return result; +} + +static NSArray * +_makeLDAPChanges (NGLdapConnection *ldapConnection, + NSString *dn, NSArray *attributes) +{ + NSMutableArray *changes, *attributeNames, *origAttributeNames; + NGLdapEntry *origEntry; + NSArray *values; + NGLdapAttribute *attribute, *origAttribute; + NSString *name; + NSDictionary *origAttributes; + NSUInteger count, max, valueCount, valueMax; + BOOL allStrings; + + /* additions and modifications */ + origEntry = [ldapConnection entryAtDN: dn + attributes: [NSArray arrayWithObject: @"*"]]; + origAttributes = [origEntry attributes]; + + max = [attributes count]; + changes = [NSMutableArray arrayWithCapacity: max]; + attributeNames = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + attribute = [attributes objectAtIndex: count]; + name = [attribute attributeName]; + [attributeNames addObject: name]; + origAttribute = [origAttributes objectForKey: name]; + if (origAttribute) + { + if (![origAttribute isEqual: attribute]) + [changes + addObject: [NGLdapModification replaceModification: attribute]]; + } + else + [changes addObject: [NGLdapModification addModification: attribute]]; + } + + /* deletions */ + origAttributeNames = [[origAttributes allKeys] mutableCopy]; + [origAttributeNames autorelease]; + [origAttributeNames removeObjectsInArray: attributeNames]; + max = [origAttributeNames count]; + for (count = 0; count < max; count++) + { + name = [origAttributeNames objectAtIndex: count]; + origAttribute = [origAttributes objectForKey: name]; + /* the attribute must only have string values, otherwise it will anyway + be missing from the new record */ + allStrings = YES; + values = [origAttribute allValues]; + valueMax = [values count]; + for (valueCount = 0; allStrings && valueCount < valueMax; valueCount++) + if (![[values objectAtIndex: valueCount] isKindOfClass: NSStringK]) + allStrings = NO; + if (allStrings) + [changes + addObject: [NGLdapModification deleteModification: origAttribute]]; + } + + return changes; +} + +- (NSException *) updateContactEntry: (NSDictionary *) roLdifRecord +{ + NSException *result = nil; + NSString *dn; + NSMutableDictionary *ldifRecord; + NSArray *attributes, *changes; + NGLdapConnection *ldapConnection; + + dn = [roLdifRecord objectForKey: @"dn"]; + if ([dn length] > 0) + { + ldapConnection = [self _ldapConnection]; + if (!schema) + { + schema = [LDAPSourceSchema new]; + [schema readSchemaFromConnection: ldapConnection]; + } + + ldifRecord = [roLdifRecord mutableCopy]; + [ldifRecord autorelease]; + [self _applyContactMappingToOutput: ldifRecord]; + attributes = _convertRecordToLDAPAttributes (schema, ldifRecord); + + changes = _makeLDAPChanges (ldapConnection, dn, attributes); + + NS_DURING + { + [ldapConnection modifyEntryWithDN: dn + changes: changes]; + } + NS_HANDLER + { + result = localException; + } + NS_ENDHANDLER; + } + else + [self errorWithFormat: @"expected dn for modified record"]; + + return result; +} + +- (NSException *) removeContactEntryWithID: (NSString *) aId +{ + NSException *result = nil; + NGLdapConnection *ldapConnection; + NSString *dn; + + ldapConnection = [self _ldapConnection]; + dn = [NSString stringWithFormat: @"%@=%@,%@", IDField, aId, baseDN]; + NS_DURING + { + [ldapConnection removeEntryWithDN: dn]; + } + NS_HANDLER + { + result = localException; + } + NS_ENDHANDLER; + + return result; +} + @end diff --git a/SoObjects/SOGo/NSDictionary+Utilities.h b/SoObjects/SOGo/NSDictionary+Utilities.h index c8e08ea7c..92fe50f34 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.h +++ b/SoObjects/SOGo/NSDictionary+Utilities.h @@ -35,8 +35,7 @@ - (NSString *) jsonRepresentation; - (NSString *) keysWithFormat: (NSString *) keyFormat; -// LDIF methods -- (NSString *) userRecordAsLDIFEntry; +- (NSComparisonResult) caseInsensitiveDisplayNameCompare: (NSDictionary *) theDictionary; @end diff --git a/SoObjects/SOGo/NSDictionary+Utilities.m b/SoObjects/SOGo/NSDictionary+Utilities.m index c38dce316..79e0c6c6d 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.m +++ b/SoObjects/SOGo/NSDictionary+Utilities.m @@ -26,12 +26,8 @@ #import #import #import -#import - -#import #import "NSArray+Utilities.h" -#import "NSObject+Utilities.h" #import "NSString+Utilities.h" #import "NSDictionary+Utilities.h" @@ -109,84 +105,6 @@ return [[self objectForKey: @"cn"] caseInsensitiveCompare: [theDictionary objectForKey: @"cn"]]; } -// LDIF Methods -#warning We might want to support more than just strings here -- (void) _appendLDIFKey: (NSString *) key - value: (NSString *) value - toString: (NSMutableString *) ldifString -{ - if ([value isKindOfClass: [NSString class]]) - { - if ([value _isLDIFSafe]) - [ldifString appendFormat: @"%@: %@\n", key, value]; - else - [ldifString appendFormat: @"%@:: %@\n", - key, [value stringByEncodingBase64]]; - } -} - -- (void) _appendLDIFKey: (NSString *) key - toString: (NSMutableString *) ldifString -{ - id value; - int count, max; - - value = [self objectForKey: key]; - if ([value isKindOfClass: [NSArray class]]) - { - max = [value count]; - for (count = 0; count < max; count++) - [self _appendLDIFKey: key value: [value objectAtIndex: count] - toString: ldifString]; - } - else - [self _appendLDIFKey: key value: [self objectForKey: key] - toString: ldifString]; -} - -- (void) _appendObjectClassesToString: (NSMutableString *) ldifString -{ - NSEnumerator *classes; - NSString *currentClass; - - classes = [[self objectForKey: @"objectClasses"] objectEnumerator]; - while ((currentClass = [classes nextObject])) - [self _appendLDIFKey: @"objectClass" value: currentClass - toString: ldifString]; -} - -- (NSString *) userRecordAsLDIFEntry -{ - NSMutableString *ldifString; - NSEnumerator *keys; - NSString *currentKey; - -// {CalendarAccess = YES; MailAccess = YES; c_cn = "Wolfgang Sourdeau"; c_emails = ("wolfgang@test.com"); c_name = "wolfgang@test.com"; c_uid = "wolfgang@test.com"; cn = "wolfgang@test.com"; displayName = "Wolfgang Sourdeau"; dn = "cn=wolfgang@test.com,ou=evariste,o=inverse.ca"; givenName = Wolfgang; mail = "wolfgang@test.com"; objectClass = organizationalPerson; sn = Sourdeau; } - - ldifString = [NSMutableString string]; - [self _appendLDIFKey: @"dn" toString: ldifString]; - [self _appendObjectClassesToString: ldifString]; - - keys = [[self allKeys] objectEnumerator]; - while ((currentKey = [keys nextObject])) - { - if (!([currentKey isEqualToString: @"CalendarAccess"] - || [currentKey isEqualToString: @"MailAccess"] - || [currentKey isEqualToString: @"ContactAccess"] - || [currentKey hasPrefix: @"objectClass"] - || [currentKey hasPrefix: @"c_"] - || [currentKey isEqualToString: @"dn"] - || [currentKey isEqualToString: @"isGroup"] - || [currentKey isEqualToString: @"isResource"] - || [currentKey isEqualToString: @"numberOfSimultaneousBookings"] - || [currentKey isEqualToString: @"canAuthenticate"])) - [self _appendLDIFKey: currentKey toString: ldifString]; - } - - return ldifString; -} - - @end @implementation NSMutableDictionary (SOGoDictionaryUtilities) diff --git a/SoObjects/SOGo/NSString+Utilities.h b/SoObjects/SOGo/NSString+Utilities.h index 78af47b7b..53bf190b9 100644 --- a/SoObjects/SOGo/NSString+Utilities.h +++ b/SoObjects/SOGo/NSString+Utilities.h @@ -62,8 +62,6 @@ - (int) timeValue; -- (BOOL) _isLDIFSafe; - - (BOOL) isJSONString; - (id) objectFromJSONString; diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index 094dd595d..c9079c4bd 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -486,44 +486,6 @@ static int cssEscapingCount; return time; } -static NSMutableCharacterSet *safeLDIFChars = nil; -static NSMutableCharacterSet *safeLDIFStartChars = nil; - -- (void) _initSafeLDIFChars -{ - safeLDIFChars = [NSMutableCharacterSet new]; - [safeLDIFChars addCharactersInRange: NSMakeRange (0x01, 9)]; - [safeLDIFChars addCharactersInRange: NSMakeRange (0x0b, 2)]; - [safeLDIFChars addCharactersInRange: NSMakeRange (0x0e, 114)]; - - safeLDIFStartChars = [safeLDIFChars mutableCopy]; - [safeLDIFStartChars removeCharactersInString: @" :<"]; -} - -- (BOOL) _isLDIFSafe -{ - int count, max; - BOOL rc; - - if (!safeLDIFChars) - [self _initSafeLDIFChars]; - - rc = YES; - - max = [self length]; - if (max > 0) - { - if ([safeLDIFStartChars characterIsMember: [self characterAtIndex: 0]]) - for (count = 1; rc && count < max; count++) - rc = [safeLDIFChars - characterIsMember: [self characterAtIndex: count]]; - else - rc = NO; - } - - return rc; -} - - (BOOL) isJSONString { NSDictionary *jsonData; diff --git a/SoObjects/SOGo/SOGoSource.h b/SoObjects/SOGo/SOGoSource.h index 7fd04fd46..3c8f90380 100644 --- a/SoObjects/SOGo/SOGoSource.h +++ b/SoObjects/SOGo/SOGoSource.h @@ -28,9 +28,10 @@ #import "SOGoConstants.h" @class NSDictionary; +@class NSException; @class NSString; -@protocol SOGoSource +@protocol SOGoSource + (id) sourceFromUDSource: (NSDictionary *) udSource inDomain: (NSString *) domain; @@ -57,6 +58,12 @@ - (NSArray *) allEntryIDs; - (NSArray *) fetchContactsMatching: (NSString *) filter; - (NSString *) sourceID; +- (NSArray *) modifiers; + +- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord + withID: (NSString *) aId; +- (NSException *) updateContactEntry: (NSDictionary *) ldifRecord; +- (NSException *) removeContactEntryWithID: (NSString *) aId; @end diff --git a/SoObjects/SOGo/SQLSource.m b/SoObjects/SOGo/SQLSource.m index 84777c8ab..6950e2d7e 100644 --- a/SoObjects/SOGo/SQLSource.m +++ b/SoObjects/SOGo/SQLSource.m @@ -22,8 +22,9 @@ */ #import -#import #import +#import +#import #import #import #import @@ -379,6 +380,7 @@ { NSMutableDictionary *response; NSMutableArray *qualifiers; + NSArray *fieldNames; EOAdaptorChannel *channel; EOQualifier *loginQualifier, *qualifier; GCSChannelManager *cm; @@ -463,6 +465,16 @@ [response autorelease]; [channel cancelFetch]; + /* Convert all c_ fields to obtain their ldif equivalent */ + fieldNames = [response allKeys]; + for (i = 0; i < [fieldNames count]; i++) + { + field = [fieldNames objectAtIndex: i]; + if ([field hasPrefix: @"c_"]) + [response setObject: [response objectForKey: field] + forKey: [field substringFromIndex: 2]]; + } + // We have to do this here since we do not manage modules // constraints right now over a SQL backend. [response setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"]; @@ -684,4 +696,49 @@ return _sourceID; } +- (NSArray *) modifiers +{ + return nil; +} + +- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord + withID: (NSString *) aId +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) updateContactEntry: (NSDictionary *) roLdifRecord +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) removeContactEntryWithID: (NSString *) aId +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + @end diff --git a/Tools/SOGoSockDOperation.m b/Tools/SOGoSockDOperation.m index 6e7057a34..62d4d8226 100644 --- a/Tools/SOGoSockDOperation.m +++ b/Tools/SOGoSockDOperation.m @@ -29,6 +29,7 @@ #import #import #import +#import #import #import #import @@ -120,13 +121,13 @@ Class SOGoContactSourceFolderKlass = Nil; value = [entry objectForKey: key]; if ([value isKindOfClass: [NSString class]] && [value length] > 0) { - if ([value _isLDIFSafe]) - [result appendFormat: @"%@: %@\n", - [key substringFromIndex: 2], value]; - else + if ([value mustEncodeLDIFValue]) [result appendFormat: @"%@:: %@\n", [key substringFromIndex: 2], [value stringByEncodingBase64]]; + else + [result appendFormat: @"%@: %@\n", + [key substringFromIndex: 2], value]; } } [result appendString: @"\n"]; diff --git a/Tools/SOGoToolBackup.m b/Tools/SOGoToolBackup.m index 5b2c6588e..8dd7ea04a 100644 --- a/Tools/SOGoToolBackup.m +++ b/Tools/SOGoToolBackup.m @@ -37,11 +37,12 @@ #import #import #import -#import +#import #import #import #import #import +#import #import "SOGoTool.h" @@ -61,6 +62,12 @@ @implementation SOGoToolBackup ++ (void) initialize +{ + [[SOGoProductLoader productLoader] + loadProducts: [NSArray arrayWithObject: @"Contacts.SOGo"]]; +} + + (NSString *) command { return @"backup"; @@ -376,7 +383,7 @@ userEntry = [currentSource lookupContactEntry: uid]; if (userEntry) { - [userRecord setObject: [userEntry userRecordAsLDIFEntry] + [userRecord setObject: [userEntry ldifRecordAsString] forKey: @"ldif_record"]; done = YES; } diff --git a/UI/Contacts/UIxContactActions.m b/UI/Contacts/UIxContactActions.m index 03f5f6a07..aa4cc7138 100644 --- a/UI/Contacts/UIxContactActions.m +++ b/UI/Contacts/UIxContactActions.m @@ -52,6 +52,8 @@ categories = [[self categories] mutableCopy]; [categories autorelease]; + if (!categories) + categories = [NSMutableArray array]; if (set) { if (![categories containsObject: category]) diff --git a/UI/Contacts/UIxContactEditor.h b/UI/Contacts/UIxContactEditor.h index f0998f2f2..e01c07767 100644 --- a/UI/Contacts/UIxContactEditor.h +++ b/UI/Contacts/UIxContactEditor.h @@ -27,22 +27,18 @@ @class NSString; @class NSMutableDictionary; -@class NGVCard; - @class SOGoContactFolder; @interface UIxContactEditor : UIxComponent { id addressBookItem; - NSString *preferredEmail; NSString *item; - NGVCard *card; - NSMutableArray *photosURL; - NSMutableDictionary *snapshot; /* contains the values for editing */ + NSMutableDictionary *ldifRecord; /* contains the values for editing */ SOGoContactFolder *componentAddressBook; - NSArray *contactCategories; } +- (NSMutableDictionary *) ldifRecord; + - (void) setAddressBookItem: (id) _item; - (id) addressBookItem; diff --git a/UI/Contacts/UIxContactEditor.m b/UI/Contacts/UIxContactEditor.m index dfbc48644..73a88d448 100644 --- a/UI/Contacts/UIxContactEditor.m +++ b/UI/Contacts/UIxContactEditor.m @@ -34,8 +34,6 @@ #import #import -#import -#import #import #import @@ -50,20 +48,23 @@ #import "UIxContactEditor.h" +static Class SOGoContactGCSEntryK = Nil; + @implementation UIxContactEditor ++ (void) initialize +{ + SOGoContactGCSEntryK = [SOGoContactGCSEntry class]; +} + - (id) init { if ((self = [super init])) { - snapshot = [[NSMutableDictionary alloc] initWithCapacity: 16]; - preferredEmail = nil; - photosURL = nil; + ldifRecord = nil; addressBookItem = nil; item = nil; - card = nil; componentAddressBook = nil; - contactCategories = nil; } return self; @@ -71,18 +72,35 @@ - (void) dealloc { - [snapshot release]; - [preferredEmail release]; - [photosURL release]; + [ldifRecord release]; [addressBookItem release]; [item release]; [componentAddressBook release]; - [contactCategories release]; [super dealloc]; } /* accessors */ +- (NSMutableDictionary *) ldifRecord +{ + NSDictionary *clientLDIFRecord; + NSString *queryValue; + + if (!ldifRecord) + { + clientLDIFRecord = [[self clientObject] ldifRecord]; + ldifRecord = [clientLDIFRecord mutableCopy]; + queryValue = [self queryParameterForKey: @"contactEmail"]; + if ([queryValue length] > 0) + [ldifRecord setObject: queryValue forKey: @"mail"]; + queryValue = [self queryParameterForKey: @"contactFN"]; + if ([queryValue length] > 0) + [ldifRecord setObject: queryValue forKey: @"displayname"]; + } + + return ldifRecord; +} + - (void) setAddressBookItem: (id) _item { ASSIGN (addressBookItem, _item); @@ -130,27 +148,6 @@ /* load/store content format */ -// - (void) _fixupSnapshot -// { -// NSString *currentKey, *currentString; -// NSMutableString *newString; -// NSArray *keys; -// unsigned int count, max; - -// keys = [snapshot allKeys]; -// max = [keys count]; -// for (count = 0; count < max; count++) -// { -// currentKey = [keys objectAtIndex: count]; -// currentString = [snapshot objectForKey: currentKey]; -// newString = [currentString mutableCopy]; -// [newString autorelease]; -// [newString replaceString: @";" withString: @"\\;"]; -// if (![newString isEqualToString: currentString]) -// [snapshot setObject: newString forKey: currentKey]; -// } -// } - /* helper */ - (NSString *) _completeURIForMethod: (NSString *) _method @@ -179,12 +176,7 @@ - (BOOL) isNew { - id co; - - co = [self clientObject]; - - return ([co isKindOfClass: [SOGoContentObject class]] - && [co isNew]); + return ([[self clientObject] isNew]); } - (NSArray *) addressBooksList @@ -205,14 +197,13 @@ while (currentFolder) { if ([currentFolder isEqual: folder] || - ([currentFolder isKindOfClass: [SOGoContactGCSFolder class]] && - ![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles - onObject: currentFolder - inContext: context])) + ![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles + onObject: currentFolder + inContext: context]) [addressBooksList addObject: currentFolder]; currentFolder = [folders nextObject]; } - + return addressBooksList; } @@ -245,26 +236,30 @@ return fDisplayName; } -- (void) setContactCategories: (NSString *) jsonCategories +- (BOOL) supportCategories +{ + return [[self clientObject] isKindOfClass: SOGoContactGCSEntryK]; +} + +- (void) setJsonContactCategories: (NSString *) jsonCategories { NSArray *newCategories; newCategories = [jsonCategories objectFromJSONString]; if ([newCategories isKindOfClass: [NSArray class]]) - ASSIGN (contactCategories, newCategories); + [[self ldifRecord] setObject: newCategories + forKey: @"vcardcategories"]; + else + [[self ldifRecord] removeObjectForKey: @"vcardcategories"]; } -- (NSString *) contactCategories +- (NSString *) jsonContactCategories { - NSString *jsonCats; + NSArray *categories; - if (!contactCategories) - ASSIGN (contactCategories, [card categories]); - jsonCats = [contactCategories jsonRepresentation]; - if (!jsonCats) - jsonCats = @"[]"; + categories = [[self ldifRecord] objectForKey: @"vcardcategories"]; - return jsonCats; + return [categories jsonRepresentation]; } - (NSArray *) _languageContactsCategories @@ -279,11 +274,11 @@ return [categoryLabels trimmedComponents]; } -- (NSArray *) _fetchAndCombineCategoriesList: (NSArray *) contactCats +- (NSArray *) _fetchAndCombineCategoriesList { NSString *ownerLogin; SOGoUserDefaults *ud; - NSArray *cats, *newCats; + NSArray *cats, *newCats, *contactCategories; ownerLogin = [[self clientObject] ownerInContext: context]; ud = [[SOGoUser userWithLogin: ownerLogin] userDefaults]; @@ -291,9 +286,10 @@ if (!cats) cats = [self _languageContactsCategories]; - if (contactCats) + contactCategories = [[self ldifRecord] objectForKey: @"vcardcategories"]; + if (contactCategories) { - newCats = [cats mergedArrayWithArray: contactCats]; + newCats = [cats mergedArrayWithArray: contactCategories]; if ([newCats count] != [cats count]) { cats = [newCats sortedArrayUsingSelector: @@ -311,7 +307,7 @@ NSArray *cats; NSString *list; - cats = [self _fetchAndCombineCategoriesList: [card categories]]; + cats = [self _fetchAndCombineCategoriesList]; list = [cats jsonRepresentation]; if (!list) list = @"[]"; @@ -328,311 +324,7 @@ actionName = [[request requestHandlerPath] lastPathComponent]; - return ([[self clientObject] isKindOfClass: [SOGoContactGCSEntry class]] - && [actionName hasPrefix: @"save"]); -} - -- (void) _setSnapshotValue: (NSString *) key - to: (NSString *) aValue -{ - if (!aValue) - aValue = @""; - - [snapshot setObject: aValue forKey: key]; -} - -- (NSMutableDictionary *) snapshot -{ - return snapshot; -} - -- (NSString *) _simpleValueForType: (NSString *) aType - inArray: (NSArray *) anArray - excluding: (NSString *) aTypeToExclude -{ - NSArray *elements; - NSString *value; - - elements = [anArray cardElementsWithAttribute: @"type" - havingValue: aType]; - - value = nil; - - if ([elements count] > 0) - { - CardElement *ce; - int i; - - for (i = 0; i < [elements count]; i++) - { - ce = [elements objectAtIndex: i]; - value = [ce flattenedValuesForKey: @""]; - - if (!aTypeToExclude) - break; - - if (![ce hasAttribute: @"type" havingValue: aTypeToExclude]) - break; - - value = nil; - } - } - - return value; -} - -- (void) _setupEmailFields -{ - NSArray *elements; - NSString *workMail, *homeMail, *prefMail, *potential; - unsigned int max; - - elements = [card childrenWithTag: @"email"]; - max = [elements count]; - workMail = [self _simpleValueForType: @"work" - inArray: elements excluding: nil]; - homeMail = [self _simpleValueForType: @"home" - inArray: elements excluding: nil]; - prefMail = [self _simpleValueForType: @"pref" - inArray: elements excluding: nil]; - - if (max > 0) - { - potential = [[elements objectAtIndex: 0] flattenedValuesForKey: @""]; - if (!workMail) - { - if (homeMail && homeMail == potential) - { - if (max > 1) - workMail = [[elements objectAtIndex: 1] flattenedValuesForKey: @""]; - } - else - workMail = potential; - } - if (!homeMail && max > 1) - { - if (workMail && workMail == potential) - homeMail = [[elements objectAtIndex: 1] flattenedValuesForKey: @""]; - else - homeMail = potential; - } - - if (prefMail) - { - if (prefMail == workMail) - preferredEmail = @"work"; - else if (prefMail == homeMail) - preferredEmail = @"home"; - } - } - - [self _setSnapshotValue: @"workMail" to: workMail]; - [self _setSnapshotValue: @"homeMail" to: homeMail]; - - [self _setSnapshotValue: @"mozillaUseHtmlMail" - to: [[card uniqueChildWithTag: @"x-mozilla-html"] flattenedValuesForKey: @""]]; -} - -- (void) _setupOrgFields -{ - NSMutableArray *orgServices; - CardElement *org; - NSString *service; - NSUInteger count, max; - - org = [card org]; - [self _setSnapshotValue: @"workCompany" - to: [org flattenedValueAtIndex: 0 forKey: @""]]; - max = [[org valuesForKey: @""] count]; - if (max > 1) - { - orgServices = [NSMutableArray arrayWithCapacity: max]; - for (count = 1; count < max; count++) - { - service = [org flattenedValueAtIndex: count forKey: @""]; - if ([service length] > 0) - [orgServices addObject: service]; - } - - [self _setSnapshotValue: @"workService" - to: [orgServices componentsJoinedByString: @", "]]; - } -} - -- (NSString *) preferredEmail -{ - return preferredEmail; -} - -- (void) setPreferredEmail: (NSString *) aString -{ - preferredEmail = aString; -} - -- (void) _retrieveQueryParameter: (NSString *) queryKey - intoSnapshotValue: (NSString *) snapshotKey -{ - NSString *queryValue; - - queryValue = [self queryParameterForKey: queryKey]; - if (queryValue && [queryValue length] > 0) - [self _setSnapshotValue: snapshotKey to: queryValue]; -} - -- (void) initSnapshot -{ - NSArray *elements; - CardElement *element; - - element = [card n]; - [self _setSnapshotValue: @"sn" - to: [element flattenedValueAtIndex: 0 forKey: @""]]; - [self _setSnapshotValue: @"givenName" - to: [element flattenedValueAtIndex: 1 forKey: @""]]; - [self _setSnapshotValue: @"fn" to: [card fn]]; - [self _setSnapshotValue: @"nickname" to: [card nickname]]; - - elements = [card childrenWithTag: @"tel"]; - // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX - // one if we do see the FAX field BEFORE the WORK number. - [self _setSnapshotValue: @"telephoneNumber" - to: [self _simpleValueForType: @"work" inArray: elements excluding: @"fax"]]; - [self _setSnapshotValue: @"homeTelephoneNumber" - to: [self _simpleValueForType: @"home" inArray: elements excluding: @"fax"]]; - [self _setSnapshotValue: @"mobile" - to: [self _simpleValueForType: @"cell" inArray: elements excluding: nil]]; - [self _setSnapshotValue: @"facsimileTelephoneNumber" - to: [self _simpleValueForType: @"fax" inArray: elements excluding: nil]]; - [self _setSnapshotValue: @"pager" - to: [self _simpleValueForType: @"pager" inArray: elements excluding: nil]]; - - // If we don't have a "home" and "work" phone number but - // we have a "voice" one defined, we set it to the "work" value - // This can happen when we have : - // VERSION:2.1 - // N:name;surname;;;; - // TEL;VOICE;HOME: - // TEL;VOICE;WORK: - // TEL;PAGER: - // TEL;FAX;WORK: - // TEL;CELL:514 123 1234 - // TEL;VOICE:450 456 6789 - // ADR;HOME:;;;;;; - // ADR;WORK:;;;;;; - // ADR:;;;;;; - if ([[snapshot objectForKey: @"telephoneNumber"] length] == 0 && - [[snapshot objectForKey: @"homeTelephoneNumber"] length] == 0 && - [elements count] > 0) - { - [self _setSnapshotValue: @"telephoneNumber" - to: [self _simpleValueForType: @"voice" inArray: elements excluding: nil]]; - } - - [self _setupEmailFields]; - - [self _setSnapshotValue: @"screenName" - to: [[card uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""]]; - - elements = [card childrenWithTag: @"adr" - andAttribute: @"type" havingValue: @"work"]; - if (elements && [elements count] > 0) - { - element = [elements objectAtIndex: 0]; - [self _setSnapshotValue: @"workExtendedAddress" - to: [element flattenedValueAtIndex: 1 forKey: @""]]; - [self _setSnapshotValue: @"workStreetAddress" - to: [element flattenedValueAtIndex: 2 forKey: @""]]; - [self _setSnapshotValue: @"workCity" - to: [element flattenedValueAtIndex: 3 forKey: @""]]; - [self _setSnapshotValue: @"workState" - to: [element flattenedValueAtIndex: 4 forKey: @""]]; - [self _setSnapshotValue: @"workPostalCode" - to: [element flattenedValueAtIndex: 5 forKey: @""]]; - [self _setSnapshotValue: @"workCountry" - to: [element flattenedValueAtIndex: 6 forKey: @""]]; - } - - elements = [card childrenWithTag: @"adr" - andAttribute: @"type" havingValue: @"home"]; - if (elements && [elements count] > 0) - { - element = [elements objectAtIndex: 0]; - [self _setSnapshotValue: @"homeExtendedAddress" - to: [element flattenedValueAtIndex: 1 forKey: @""]]; - [self _setSnapshotValue: @"homeStreetAddress" - to: [element flattenedValueAtIndex: 2 forKey: @""]]; - [self _setSnapshotValue: @"homeCity" - to: [element flattenedValueAtIndex: 3 forKey: @""]]; - [self _setSnapshotValue: @"homeState" - to: [element flattenedValueAtIndex: 4 forKey: @""]]; - [self _setSnapshotValue: @"homePostalCode" - to: [element flattenedValueAtIndex: 5 forKey: @""]]; - [self _setSnapshotValue: @"homeCountry" - to: [element flattenedValueAtIndex: 6 forKey: @""]]; - } - - elements = [card childrenWithTag: @"url"]; - [self _setSnapshotValue: @"workURL" - to: [self _simpleValueForType: @"work" inArray: elements excluding: nil]]; - [self _setSnapshotValue: @"homeURL" - to: [self _simpleValueForType: @"home" inArray: elements excluding: nil]]; - - // If we don't have a "work" or "home" URL but we still have - // an URL field present, let's add it to the "home" value - if ([[snapshot objectForKey: @"workURL"] length] == 0 && - [[snapshot objectForKey: @"homeURL"] length] == 0 && - [elements count] > 0) - { - [self _setSnapshotValue: @"homeURL" - to: [[elements objectAtIndex: 0] flattenedValuesForKey: @""]]; - } - // If we do have a "work" URL but no "home" URL but two - // values URLs present, let's add the second one as the home URL - else if ([[snapshot objectForKey: @"workURL"] length] > 0 && - [[snapshot objectForKey: @"homeURL"] length] == 0 && - [elements count] > 1) - { - int i; - - for (i = 0; i < [elements count]; i++) - { - if ([[[elements objectAtIndex: i] flattenedValuesForKey: @""] - caseInsensitiveCompare: [snapshot objectForKey: @"workURL"]] != NSOrderedSame) - { - [self _setSnapshotValue: @"homeURL" - to: [[elements objectAtIndex: i] flattenedValuesForKey: @""]]; - break; - } - } - } - - - [self _setSnapshotValue: @"calFBURL" - to: [[card uniqueChildWithTag: @"FBURL"] flattenedValuesForKey: @""]]; - - [self _setSnapshotValue: @"title" to: [card title]]; - [self _setupOrgFields]; - - [self _setSnapshotValue: @"bday" to: [card bday]]; - [self _setSnapshotValue: @"tz" to: [card tz]]; - [self _setSnapshotValue: @"note" to: [card note]]; - - [self _retrieveQueryParameter: @"contactEmail" - intoSnapshotValue: @"workMail"]; - [self _retrieveQueryParameter: @"contactFN" - intoSnapshotValue: @"fn"]; -} - -- (id ) defaultAction -{ - card = [[self clientObject] vCard]; - if (card) - [self initSnapshot]; - else - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason: @"could not open contact"]; - - return self; + return ([actionName hasPrefix: @"save"]); } - (NSString *) viewActionName @@ -647,216 +339,60 @@ return @"editAsContact"; } -- (BOOL) canCreateOrModify +- (BOOL) supportPhotos { - SOGoObject *co; - - co = [self clientObject]; - - return ([co isKindOfClass: [SOGoContentObject class]] - && [super canCreateOrModify]); + return [[self clientObject] isKindOfClass: SOGoContactGCSEntryK]; } -- (NSArray *) photosURL +- (BOOL) hasPhoto +{ + return [[self clientObject] hasPhoto]; +} + +- (NSString *) photoURL { - NSArray *photoElements; NSURL *soURL; - NSString *baseInlineURL, *photoURL; - NGVCardPhoto *photo; - int count, max; - if (!photosURL) - { - soURL = [[self clientObject] soURL]; - baseInlineURL = [soURL absoluteString]; - photoElements = [card childrenWithTag: @"photo"]; - max = [photoElements count]; - photosURL = [[NSMutableArray alloc] initWithCapacity: max]; - for (count = 0; count < max; count++) - { - photo = [photoElements objectAtIndex: count]; - if ([photo isInline]) - photoURL = [NSString stringWithFormat: @"%@/photo%d", - baseInlineURL, count]; - else - photoURL = [photo flattenedValuesForKey: @""]; - [photosURL addObject: photoURL]; - } - } + soURL = [[self clientObject] soURL]; - return photosURL; -} - -- (CardElement *) _elementWithTag: (NSString *) tag - ofType: (NSString *) type -{ - NSArray *elements; - CardElement *element; - - elements = [card childrenWithTag: tag - andAttribute: @"type" havingValue: type]; - if ([elements count] > 0) - element = [elements objectAtIndex: 0]; - else - { - element = [CardElement new]; - [element autorelease]; - [element setTag: tag]; - [element addType: type]; - [card addChild: element]; - } - - return element; -} - -- (void) _savePhoneValues -{ - CardElement *phone; - - phone = [self _elementWithTag: @"tel" ofType: @"work"]; - [phone setSingleValue: [snapshot objectForKey: @"telephoneNumber"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"home"]; - [phone setSingleValue: [snapshot objectForKey: @"homeTelephoneNumber"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"cell"]; - [phone setSingleValue: [snapshot objectForKey: @"mobile"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"fax"]; - [phone setSingleValue: [snapshot objectForKey: @"facsimileTelephoneNumber"] - forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"pager"]; - [phone setSingleValue: [snapshot objectForKey: @"pager"] forKey: @""]; -} - -- (void) _saveEmails -{ - CardElement *workMail, *homeMail; - - workMail = [self _elementWithTag: @"email" ofType: @"work"]; - [workMail setSingleValue: [snapshot objectForKey: @"workMail"] forKey: @""]; - homeMail = [self _elementWithTag: @"email" ofType: @"home"]; - [homeMail setSingleValue: [snapshot objectForKey: @"homeMail"] forKey: @""]; - if (preferredEmail) - { - if ([preferredEmail isEqualToString: @"work"]) - [card setPreferred: workMail]; - else - [card setPreferred: homeMail]; - } - - [[card uniqueChildWithTag: @"x-mozilla-html"] - setSingleValue: [snapshot objectForKey: @"mozillaUseHtmlMail"] - forKey: @""]; -} - -- (void) _saveSnapshot -{ - CardElement *element; - NSArray *units; - - [card setNWithFamily: [snapshot objectForKey: @"sn"] - given: [snapshot objectForKey: @"givenName"] - additional: nil - prefixes: nil - suffixes: nil]; - [card setNickname: [snapshot objectForKey: @"nickname"]]; - [card setFn: [snapshot objectForKey: @"fn"]]; - [card setTitle: [snapshot objectForKey: @"title"]]; - [card setBday: [snapshot objectForKey: @"bday"]]; - [card setNote: [snapshot objectForKey: @"note"]]; - [card setTz: [snapshot objectForKey: @"tz"]]; - - element = [self _elementWithTag: @"adr" ofType: @"home"]; - [element setSingleValue: [snapshot objectForKey: @"homeExtendedAddress"] - atIndex: 1 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homeStreetAddress"] - atIndex: 2 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homeCity"] - atIndex: 3 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homeState"] - atIndex: 4 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homePostalCode"] - atIndex: 5 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"homeCountry"] - atIndex: 6 forKey: @""]; - - element = [self _elementWithTag: @"adr" ofType: @"work"]; - [element setSingleValue: [snapshot objectForKey: @"workExtendedAddress"] - atIndex: 1 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workStreetAddress"] - atIndex: 2 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workCity"] - atIndex: 3 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workState"] - atIndex: 4 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workPostalCode"] - atIndex: 5 forKey: @""]; - [element setSingleValue: [snapshot objectForKey: @"workCountry"] - atIndex: 6 forKey: @""]; - - element = [CardElement simpleElementWithTag: @"fburl" - value: [snapshot objectForKey: @"calFBURL"]]; - [card setUniqueChild: element]; - - units = [NSArray arrayWithObject: [snapshot objectForKey: @"workService"]]; - [card setOrg: [snapshot objectForKey: @"workCompany"] - units: units]; - - [self _savePhoneValues]; - [self _saveEmails]; - [[self _elementWithTag: @"url" ofType: @"home"] - setSingleValue: [snapshot objectForKey: @"homeURL"] forKey: @""]; - [[self _elementWithTag: @"url" ofType: @"work"] - setSingleValue: [snapshot objectForKey: @"workURL"] forKey: @""]; - - [[card uniqueChildWithTag: @"x-aim"] - setSingleValue: [snapshot objectForKey: @"screenName"] - forKey: @""]; + return [NSString stringWithFormat: @"%@/photo", [soURL absoluteString]]; } - (id ) saveAction { - SOGoContactGCSEntry *contact; + SOGoObject *contact; id result; NSString *jsRefreshMethod; SoSecurityManager *sm; contact = [self clientObject]; - card = [contact vCard]; - if (card) - { -// [self _fixupSnapshot]; - [self _saveSnapshot]; - [card setCategories: contactCategories]; - [self _fetchAndCombineCategoriesList: contactCategories]; - [contact save]; + [contact setLDIFRecord: ldifRecord]; + [self _fetchAndCombineCategoriesList]; + [contact save]; - if (componentAddressBook && componentAddressBook != [self componentAddressBook]) - { - sm = [SoSecurityManager sharedSecurityManager]; - if (![sm validatePermission: SoPerm_DeleteObjects - onObject: componentAddressBook - inContext: context]) - { - if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles - onObject: componentAddressBook - inContext: context]) - [contact moveToFolder: (SOGoGCSFolder *)componentAddressBook]; // TODO: handle exception - } - } - - if ([[[[self context] request] formValueForKey: @"nojs"] intValue]) - result = [self redirectToLocation: [self modulePath]]; - else + if (componentAddressBook && componentAddressBook != [self componentAddressBook]) + { + sm = [SoSecurityManager sharedSecurityManager]; + if (![sm validatePermission: SoPerm_DeleteObjects + onObject: componentAddressBook + inContext: context]) { - jsRefreshMethod - = [NSString stringWithFormat: @"refreshContacts(\"%@\")", - [contact nameInContainer]]; - result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; + if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles + onObject: componentAddressBook + inContext: context]) + [contact moveToFolder: (SOGoGCSFolder *)componentAddressBook]; // TODO: handle exception } } + + if ([[[[self context] request] formValueForKey: @"nojs"] intValue]) + result = [self redirectToLocation: [self modulePath]]; else - result = [NSException exceptionWithHTTPStatus: 400 /* Bad Request */ - reason: @"method cannot be invoked on " - @"the specified object"]; + { + jsRefreshMethod + = [NSString stringWithFormat: @"refreshContacts(\"%@\")", + [contact nameInContainer]]; + result = [self jsCloseWithRefreshMethod: jsRefreshMethod]; + } return result; } @@ -866,17 +402,15 @@ NSString *email, *cn, *url; NSMutableString *address; - card = [[self clientObject] vCard]; - [self initSnapshot]; - if ([preferredEmail isEqualToString: @"home"]) - email = [snapshot objectForKey: @"homeMail"]; - else - email = [snapshot objectForKey: @"workMail"]; + [self ldifRecord]; + email = [ldifRecord objectForKey: @"mail"]; + if ([email length] == 0) + email = [ldifRecord objectForKey: @"mozillasecondemail"]; if (email) { address = [NSMutableString string]; - cn = [card fn]; + cn = [ldifRecord objectForKey: @"cn"]; if ([cn length] > 0) [address appendFormat: @"%@ <%@>", cn, email]; else diff --git a/UI/Contacts/UIxContactFolderActions.m b/UI/Contacts/UIxContactFolderActions.m index 2dd503615..8adbcaa2b 100644 --- a/UI/Contacts/UIxContactFolderActions.m +++ b/UI/Contacts/UIxContactFolderActions.m @@ -35,6 +35,7 @@ #import #import #import +#import #import #import @@ -76,9 +77,10 @@ inContext: [self context] acquire: NO]; if ([currentChild respondsToSelector: @selector (vCard)]) - [content appendFormat: [[currentChild vCard] ldifString]]; + [content appendFormat: [[currentChild ldifRecord] ldifRecordAsString]]; else if ([currentChild respondsToSelector: @selector (vList)]) [content appendFormat: [[currentChild vList] ldifString]]; + [content appendString: @"\n"]; } response = [context response]; diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m index 498bf2e91..3d3bf53c2 100644 --- a/UI/Contacts/UIxContactView.m +++ b/UI/Contacts/UIxContactView.m @@ -52,6 +52,7 @@ - (void) dealloc { + [card release]; [photosURL release]; [super dealloc]; } @@ -377,9 +378,7 @@ { NSString *data; - data = nil; - - if (url) + if ([url length] > 0) { if (![[url lowercaseString] rangeOfString: @"://"].length) url = [NSString stringWithFormat: @"http://%@", url]; @@ -388,6 +387,8 @@ @"%@", url, url]; } + else + data = nil; return [self _cardStringWithLabel: nil value: data]; } @@ -646,30 +647,12 @@ /* action */ -- (id ) vcardAction -{ -#warning this method is unused - WOResponse *response; - - card = [[self clientObject] vCard]; - if (card) - { - response = [context response]; - [response setHeader: @"text/vcard" forKey: @"Content-type"]; - [response appendContentString: [card versitString]]; - } - else - return [NSException exceptionWithHTTPStatus: 404 /* Not Found */ - reason:@"could not locate contact"]; - - return response; -} - - (id ) defaultAction { card = [[self clientObject] vCard]; if (card) { + [card retain]; phones = nil; homeAdr = nil; workAdr = nil; diff --git a/UI/Contacts/product.plist b/UI/Contacts/product.plist index e88d39ca5..ca66751fe 100644 --- a/UI/Contacts/product.plist +++ b/UI/Contacts/product.plist @@ -234,13 +234,27 @@ SOGoContactLDIFEntry = { methods = { view = { - protectedBy = ""; + protectedBy = "Access Contents Information"; pageName = "UIxContactView"; }; edit = { - protectedBy = ""; + protectedBy = "Access Contents Information"; pageName = "UIxContactEditor"; }; + editAsContact = { + protectedBy = "Access Contents Information"; + pageName = "UIxContactEditor"; + }; + save = { + protectedBy = "Change Images And Files"; + pageName = "UIxContactEditor"; + actionName = "save"; + }; + saveAsContact = { + protectedBy = "Change Images And Files"; + pageName = "UIxContactEditor"; + actionName = "save"; + }; write = { protectedBy = ""; pageName = "UIxContactEditor"; diff --git a/UI/Templates/ContactsUI/UIxContactEditor.wox b/UI/Templates/ContactsUI/UIxContactEditor.wox index ebb3eb41c..89b59cb8f 100644 --- a/UI/Templates/ContactsUI/UIxContactEditor.wox +++ b/UI/Templates/ContactsUI/UIxContactEditor.wox @@ -1,4 +1,4 @@ - + -
  • -
  • +
  • +
  • -
  • -
  • +
  • +
  • @@ -46,34 +50,34 @@ + + var:value="ldifRecord.sn" + /> + + @@ -82,26 +86,26 @@ + + + @@ -111,9 +115,8 @@ - + selection="ldifRecord.mozillausehtmlmail" + /> @@ -123,9 +126,9 @@ @@ -133,24 +136,19 @@ + name="homephone" id="homephone" + var:value="ldifRecord.homephone" + /> + name="facsimiletelephonenumber" + id="facsimiletelephonenumber" + var:value="ldifRecord.facsimiletelephonenumber" + /> @@ -159,38 +157,37 @@ - + var:value="ldifRecord.pager" + /> + var:value="ldifRecord.mobile" + /> -
    -
    -
    -
    +
    +
    + - - -
    + + +
    @@ -198,64 +195,64 @@ + + + + + + + @@ -267,128 +264,116 @@ - + var:value="ldifRecord.title" + /> + + + + + + + + +
    -
    - -
    -
    +
    + +
    - - - - - - diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index e38f37028..89f070d94 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -512,9 +512,6 @@ function onToolbarDeleteSelectedContactsConfirm(dialogId) { var contactsList = $('contactsList'); var rows = contactsList.getSelectedRowsId(); for (var i = 0; i < rows.length; i++) { - var row = $(rows[i]); - row.deselect(); - row.hide(); delete cachedContacts[Contact.currentAddressBook + "/" + rows[i]]; var urlstr = (URLForFolderID(Contact.currentAddressBook) + "/" + rows[i] + "/delete"); @@ -532,6 +529,7 @@ function onContactDeleteEventCallback(http) { $("contactView").update(); Contact.currentContact = null; } + Contact.deleteContactsRequestCount--; if (Contact.deleteContactsRequestCount == 0) { var nextRow = row.next("tr"); @@ -543,7 +541,10 @@ function onContactDeleteEventCallback(http) { loadContact(Contact.currentContact); } } - row.parentNode.removeChild(row); + if (row) { + row.deselect(); + row.parentNode.removeChild(row); + } } else if (parseInt(http.status) == 403) { var row = $(http.callbackData); diff --git a/UI/WebServerResources/UIxContactEditor.css b/UI/WebServerResources/UIxContactEditor.css index 58112a94d..26f3c8ba6 100644 --- a/UI/WebServerResources/UIxContactEditor.css +++ b/UI/WebServerResources/UIxContactEditor.css @@ -69,3 +69,9 @@ INPUT.comboBoxField, #emptyCategory #otherInfos TEXTAREA { width: 70%; } + +#birthday, #birthmonth +{ width: 18px; } + +#birthyear +{ width: 36px; } diff --git a/UI/WebServerResources/UIxContactEditor.js b/UI/WebServerResources/UIxContactEditor.js index f9f3a53bd..1db214b58 100644 --- a/UI/WebServerResources/UIxContactEditor.js +++ b/UI/WebServerResources/UIxContactEditor.js @@ -22,9 +22,9 @@ 02111-1307, USA. */ -var dateRegex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/; +var dateRegex = /^(([0-9]{2})?[0-9])?[0-9]-[0-9]?[0-9]-[0-9]?[0-9]$/; -var displayNameChanged = false; +var displaynameChanged = false; var tabIndex = 0; @@ -37,43 +37,43 @@ function unescapeCallbackParameter(s) { } function copyContact(type, email, uid, sn, - cn, givenName, telephoneNumber, facsimileTelephoneNumber, - mobile, postalAddress, homePostalAddress, - departmentNumber, l) + displayname, givenname, telephonenumber, facsimiletelephonenumber, + mobile, postalAddress, homePostalAddress, + departmentnumber, l) { // var type = arguments[0]; // var email = arguments[1]; // var uid = arguments[2]; // var sn = arguments[3]; - // var givenName = arguments[4]; - // var telephoneNumber = arguments[5]; - // var facsimileTelephoneNumber = arguments[6]; + // var givenname = arguments[4]; + // var telephonenumber = arguments[5]; + // var facsimiletelephonenumber = arguments[6]; // var mobile = arguments[7]; - // var postalAddress = arguments[8]; - // var homePostalAddress = arguments[9]; - // var departmentNumber = arguments[10]; + // var postaladdress = arguments[8]; + // var homepostaladdress = arguments[9]; + // var departmentnumber = arguments[10]; // var l = arguments[11]; var e; - e = $('cn'); - e.setAttribute('value', unescapeCallbackParameter(cn)); + e = $('displayname'); + e.setAttribute('value', unescapeCallbackParameter(displayname)); e = $('email'); e.setAttribute('value', email); e = $('sn'); e.setAttribute('value', unescapeCallbackParameter(sn)); - e = $('givenName'); - e.setAttribute('value', unescapeCallbackParameter(givenName)); - e = $('telephoneNumber'); - e.setAttribute('value', telephoneNumber); - e = $('facsimileTelephoneNumber'); - e.setAttribute('value', facsimileTelephoneNumber); + e = $('givenname'); + e.setAttribute('value', unescapeCallbackParameter(givenname)); + e = $('telephonenumber'); + e.setAttribute('value', telephonenumber); + e = $('facsimiletelephonenumber'); + e.setAttribute('value', facsimileTelephonenumber); e = $('mobile'); e.setAttribute('value', mobile); - e = $('postalAddress'); + e = $('postaladdress'); e.setAttribute('value', unescapeCallbackParameter(postalAddress)); - e = $('homePostalAddress'); + e = $('homepostaladdress'); e.setAttribute('value', unescapeCallbackParameter(homePostalAddress)); - e = $('departmentNumber'); - e.setAttribute('value', unescapeCallbackParameter(departmentNumber)); + e = $('departmentnumber'); + e.setAttribute('value', unescapeCallbackParameter(departmentnumber)); e = $('l'); e.setAttribute('value', unescapeCallbackParameter(l)); }; @@ -81,23 +81,25 @@ function copyContact(type, email, uid, sn, function validateContactEditor() { var rc = true; - var e = $('workMail'); + var e = $('mail'); if (e.value.length > 0 && !emailRE.test(e.value)) { alert(_("invalidemailwarn")); rc = false; } - e = $('homeMail'); + e = $('mozillasecondemail'); if (e.value.length > 0 && !emailRE.test(e.value)) { alert(_("invalidemailwarn")); rc = false; } - e = $('birthday'); - if (e.value.length > 0 - && !dateRegex.test(e.value)) { + var byear = $('birthyear'); + var bmonth = $('birthmonth'); + var bday = $('birthday'); + var bdayValue = byear.value + "-" + bmonth.value + "-" + bday.value; + if (bdayValue != "--" && !dateRegex.test(bdayValue)) { alert(_("invaliddatewarn")); rc = false; } @@ -105,25 +107,25 @@ function validateContactEditor() { return rc; } -function onFnKeyDown() { - var fn = $("fn"); +function onDisplaynameKeyDown() { + var fn = $("displayname"); fn.onkeydown = null; - displayNameChanged = true; + displaynameChanged = true; return true; } -function onFnNewValue(event) { - if (!displayNameChanged) { +function onDisplaynameNewValue(event) { + if (!displaynameChanged) { var sn = $("sn").value.trim(); - var givenName = $("givenName").value.trim(); + var givenname = $("givenname").value.trim(); - var fullName = givenName; - if (fullName && sn) - fullName += ' '; - fullName += sn; + var fullname = givenname; + if (fullname && sn) + fullname += ' '; + fullname += sn; - $("fn").value = fullName; + $("displayname").value = fullname; } return true; @@ -144,7 +146,7 @@ function onEditorSubmitClick(event) { function saveCategories() { var container = $("categoryContainer"); - var catsInput = $("contactCategories"); + var catsInput = $("jsonContactCategories"); if (container && catsInput) { var newCategories = $([]); var inputs = container.select("INPUT"); @@ -164,8 +166,8 @@ function onDocumentKeydown(event) { var target = Event.element(event); if (target.tagName == "INPUT" || target.tagName == "SELECT") { if (event.keyCode == Event.KEY_RETURN) { - var fcn = onEditorSubmitClick.bind($("submitButton")); - fcn(); + var fdisplayname = onEditorSubmitClick.bind($("submitButton")); + fdisplayname(); Event.stop(event); } } @@ -267,10 +269,10 @@ function initEditorForm() { var controller = new SOGoTabsController(); controller.attachToTabsContainer(tabsContainer); - displayNameChanged = ($("fn").value.length > 0); - $("fn").onkeydown = onFnKeyDown; - $("sn").onkeyup = onFnNewValue; - $("givenName").onkeyup = onFnNewValue; + displaynameChanged = ($("displayname").value.length > 0); + $("displayname").onkeydown = onDisplaynameKeyDown; + $("sn").onkeyup = onDisplaynameNewValue; + $("givenname").onkeyup = onDisplaynameNewValue; $("cancelButton").observe("click", onEditorCancelClick); var submitButton = $("submitButton"); @@ -280,8 +282,10 @@ function initEditorForm() { Event.observe(document, "keydown", onDocumentKeydown); - regenerateCategoriesMenu(); - var catsInput = $("contactCategories"); + if (typeof(gCategories) != "undefined") { + regenerateCategoriesMenu(); + } + var catsInput = $("jsonContactCategories"); if (catsInput && catsInput.value.length > 0) { var contactCats = $(catsInput.value.evalJSON(false)); for (var i = 0; i < contactCats.length; i++) { @@ -294,6 +298,6 @@ function initEditorForm() { emptyCategory.tabIndex = 10000; emptyCategory.observe("click", onEmptyCategoryClick); } -} +} document.observe("dom:loaded", initEditorForm);
    - + + - + + - - - - + var:value="ldifRecord.birthday"/>
    - -
    +