sogo/SoObjects/Contacts/SOGoContactGCSFolder.m

563 lines
16 KiB
Objective-C

/*
Copyright (C) 2006-2019 Inverse inc.
This file is part of SOGo.
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with SOGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoObject+SoDAV.h>
#import <NGObjWeb/WOContext.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <SaxObjC/XMLNamespaces.h>
#import <EOControl/EOFetchSpecification.h>
#import <EOControl/EOQualifier.h>
#import <EOControl/EOSortOrdering.h>
#import <NGCards/CardGroup.h>
#import <GDLContentStore/GCSFolder.h>
#import <SOGo/DOMNode+SOGo.h>
#import <SOGo/SOGoCache.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/NSObject+DAV.h>
#import <SOGo/WORequest+SOGo.h>
#import "SOGoContactGCSEntry.h"
#import "SOGoContactGCSList.h"
#import "SOGoContactGCSFolder.h"
static NSArray *folderListingFields = nil;
@implementation SOGoContactGCSFolder
+ (void) initialize
{
if (!folderListingFields)
folderListingFields = [[NSArray alloc] initWithObjects: @"c_name",
@"c_cn", @"c_givenname", @"c_sn",
@"c_screenname", @"c_o",
@"c_mail", @"c_telephonenumber",
@"c_categories",
@"c_component",
@"c_hascertificate", nil];
}
- (id) init
{
if ((self = [super init]))
{
baseCardDAVURL = nil;
basePublicCardDAVURL = nil;
}
return self;
}
- (void) dealloc
{
[baseCardDAVURL release];
[basePublicCardDAVURL release];
[super dealloc];
}
- (NSArray *) searchFields
{
static NSArray *searchFields = nil;
if (!searchFields)
{
// "name" expands to c_sn, c_givenname and c_cn
searchFields = [NSArray arrayWithObjects: @"name", @"c_mail", @"c_categories", @"c_o", nil];
[searchFields retain];
}
return searchFields;
}
- (Class) objectClassForContent: (NSString *) content
{
CardGroup *cardEntry;
NSString *firstTag;
Class objectClass;
objectClass = Nil;
cardEntry = [CardGroup parseSingleFromSource: content];
if (cardEntry)
{
firstTag = [[cardEntry tag] uppercaseString];
if ([firstTag isEqualToString: @"VCARD"])
objectClass = [SOGoContactGCSEntry class];
else if ([firstTag isEqualToString: @"VLIST"])
objectClass = [SOGoContactGCSList class];
}
return objectClass;
}
- (Class) objectClassForComponentName: (NSString *) componentName
{
Class objectClass;
if ([componentName isEqualToString: @"vcard"])
objectClass = [SOGoContactGCSEntry class];
else if ([componentName isEqualToString: @"vlist"])
objectClass = [SOGoContactGCSList class];
else
objectClass = Nil;
return objectClass;
}
- (Class) objectClassForResourceNamed: (NSString *) name
{
EOQualifier *qualifier;
NSArray *records;
NSString *component;
Class objectClass;
qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_name = %@", [name asSafeSQLString]];
records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
matchingQualifier: qualifier];
if ([records count])
{
component = [[records objectAtIndex: 0] valueForKey: @"c_component"];
objectClass = [self objectClassForComponentName: component];
}
else
objectClass = Nil;
return objectClass;
}
- (BOOL) requestNamedIsHandledLater: (NSString *) name
{
return [name isEqualToString: @"OPTIONS"];
}
- (id) lookupName: (NSString *)_key
inContext: (id)_ctx
acquire: (BOOL)_flag
{
id obj;
NSString *url;
BOOL handledLater;
/* first check attributes directly bound to the application */
handledLater = [self requestNamedIsHandledLater: _key];
if (handledLater)
obj = nil;
else
{
obj = [super lookupName:_key inContext:_ctx acquire:NO];
if (!obj)
{
if ([self isValidContentName: _key])
{
url = [[[_ctx request] uri] urlWithoutParameters];
if ([url hasSuffix: @"AsContact"])
obj = [SOGoContactGCSEntry objectWithName: _key
inContainer: self];
else if ([url hasSuffix: @"AsList"])
obj = [SOGoContactGCSList objectWithName: _key
inContainer: self];
[obj setIsNew: YES];
}
}
if (!obj || ([obj isKindOfClass: [SOGoContactGCSList class]] && [[_ctx request] isMacOSXAddressBookApp]))
obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
}
if (obj)
[[SOGoCache sharedCache] registerObject: obj
withName: _key
inContainer: container];
return obj;
}
- (EOQualifier *) qualifierForFilter: (NSString *) filter
onCriteria: (NSArray *) criteria
{
NSEnumerator *criteriaList;
NSMutableArray *filters;
NSString *filterFormat, *currentCriteria, *qs;
EOQualifier *qualifier;
qualifier = nil;
if ([filter length] > 0)
{
filter = [filter asSafeSQLString];
filters = [NSMutableArray array];
filterFormat = [NSString stringWithFormat: @"(%%@ isCaseInsensitiveLike: '%%%%%@%%%%')", filter];
if (criteria)
criteriaList = [criteria objectEnumerator];
else
criteriaList = [[self searchFields] objectEnumerator];
while (( currentCriteria = [criteriaList nextObject] ))
{
if ([currentCriteria isEqualToString: @"name"])
{
[filters addObject: @"c_sn"];
[filters addObject: @"c_givenname"];
[filters addObject: @"c_cn"];
}
else if ([[self searchFields] containsObject: currentCriteria])
[filters addObject: currentCriteria];
}
if ([filters count])
{
qs = [[[filters uniqueObjects] stringsWithFormat: filterFormat] componentsJoinedByString: @" OR "];
qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
}
}
return qualifier;
}
/**
* Normalize keys of dictionary representing a contact.
* @param contactRecord a dictionary with pairs from the quick table
* @see [UIxContactView dataAction]
*/
- (void) fixupContactRecord: (NSMutableDictionary *) contactRecord
{
NSString *data;
// c_categories => categories
data = [contactRecord objectForKey: @"c_categories"];
if ([data length])
[contactRecord setObject: data forKey: @"categories"];
// c_name => id
data = [contactRecord objectForKey: @"c_name"];
if ([data length])
[contactRecord setObject: data forKey: @"id"];
// c_cn
data = [contactRecord objectForKey: @"c_cn"];
if (![data length])
{
data = [contactRecord keysWithFormat: @"%{c_givenname} %{c_sn}"];
if ([data length] > 1)
{
[contactRecord setObject: data forKey: @"c_cn"];
}
else
{
data = [contactRecord objectForKey: @"c_o"];
[contactRecord setObject: data forKey: @"c_cn"];
}
}
// c_screenname
if (![contactRecord objectForKey: @"c_screenname"])
[contactRecord setObject: @"" forKey: @"c_screenname"];
// c_mail => emails[]
data = [contactRecord objectForKey: @"c_mail"];
if ([data length])
{
NSArray *values;
NSDictionary *email;
NSMutableArray *emails;
NSString *type, *value;
int i, max;
values = [data componentsSeparatedByString: @","];
max = [values count];
emails = [NSMutableArray arrayWithCapacity: max];
for (i = 0; i < max; i++)
{
type = (i == 0)? @"pref" : @"home";
value = [values objectAtIndex: i];
email = [NSDictionary dictionaryWithObjectsAndKeys: type, @"type", value, @"value", nil];
[emails addObject: email];
}
[contactRecord setObject: emails forKey: @"emails"];
}
else
{
[contactRecord setObject: @"" forKey: @"c_mail"];
[contactRecord setObject: [NSArray array] forKey: @"emails"];
}
// c_telephonenumber => phones[]
data = [contactRecord objectForKey: @"c_telephonenumber"];
if ([data length])
{
NSDictionary *phonenumber;
phonenumber = [NSDictionary dictionaryWithObjectsAndKeys: @"pref", @"type", data, @"value", nil];
[contactRecord setObject: [NSArray arrayWithObject: phonenumber] forKey: @"phones"];
}
else
{
[contactRecord setObject: @"" forKey: @"c_telephonenumber"];
[contactRecord setObject: [NSArray array] forKey: @"phones"];
}
}
- (NSArray *) _flattenedRecords: (NSArray *) records
{
NSMutableArray *newRecords;
NSEnumerator *oldRecords;
NSDictionary *oldRecord;
NSMutableDictionary *newRecord;
newRecords = [NSMutableArray arrayWithCapacity: [records count]];
oldRecords = [records objectEnumerator];
oldRecord = [oldRecords nextObject];
while (oldRecord)
{
newRecord = [NSMutableDictionary dictionaryWithDictionary: oldRecord];
[self fixupContactRecord: newRecord];
[newRecords addObject: newRecord];
oldRecord = [oldRecords nextObject];
}
return newRecords;
}
/* This method returns the quick entry corresponding to the name passed as
parameter. */
- (NSDictionary *) lookupContactWithName: (NSString *) aName
{
NSArray *dbRecords;
NSMutableDictionary *record;
EOQualifier *qualifier;
NSString *qs;
record = nil;
if (aName && [aName length] > 0)
{
aName = [aName asSafeSQLString];
qs = [NSString stringWithFormat: @"(c_name='%@')", aName];
qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
dbRecords = [[self ocsFolder] fetchFields: folderListingFields
matchingQualifier: qualifier];
if ([dbRecords count] > 0)
{
record = [dbRecords objectAtIndex: 0];
[self fixupContactRecord: record];
}
}
return record;
}
/*
* GCS folder are personal folders and are not associated to a domain.
* The domain is therefore ignored.
*/
- (NSArray *) lookupContactsWithFilter: (NSString *) filter
onCriteria: (NSArray *) criteria
sortBy: (NSString *) sortKey
ordering: (NSComparisonResult) sortOrdering
inDomain: (NSString *) domain
{
NSArray *dbRecords, *records;
EOQualifier *qualifier;
EOSortOrdering *ordering;
qualifier = [self qualifierForFilter: filter onCriteria: criteria];
dbRecords = [[self ocsFolder] fetchFields: folderListingFields
matchingQualifier: qualifier];
if ([dbRecords count] > 0)
{
records = [self _flattenedRecords: dbRecords];
ordering
= [EOSortOrdering sortOrderingWithKey: sortKey
selector: ((sortOrdering == NSOrderedDescending)
? EOCompareCaseInsensitiveDescending
: EOCompareCaseInsensitiveAscending)];
records
= [records sortedArrayUsingKeyOrderArray:
[NSArray arrayWithObject: ordering]];
}
else
records = nil;
[self debugWithFormat:@"fetched %i records.", [records count]];
return records;
}
- (NSArray *) lookupContactsWithQualifier: (EOQualifier *) qualifier
{
return [self lookupContactsFields: folderListingFields
withQualifier: qualifier
andOrderings: nil];
}
- (NSArray *) lookupContactsFields: (NSArray *) fields
withQualifier: (EOQualifier *) qualifier
andOrderings: (NSArray *) orderings
{
NSArray *dbRecords, *records;
EOFetchSpecification *spec;
spec = [EOFetchSpecification fetchSpecificationWithEntityName: [[self ocsFolder] folderName]
qualifier: qualifier
sortOrderings: orderings];
dbRecords = [[self ocsFolder] fetchFields: fields
fetchSpecification: spec
ignoreDeleted: YES];
if ([dbRecords count] > 0 && fields == folderListingFields)
records = [self _flattenedRecords: dbRecords];
else
records = dbRecords;
[self debugWithFormat:@"fetched %i records.", [records count]];
return records;
}
- (NSDictionary *) davSQLFieldsTable
{
static NSMutableDictionary *davSQLFieldsTable = nil;
if (!davSQLFieldsTable)
{
davSQLFieldsTable = [[super davSQLFieldsTable] mutableCopy];
[davSQLFieldsTable setObject: @"c_content" forKey: @"{urn:ietf:params:xml:ns:carddav}address-data"];
}
return davSQLFieldsTable;
}
- (NSString *) groupDavResourceType
{
return @"vcard-collection";
}
- (NSArray *) davResourceType
{
NSMutableArray *resourceType;
NSArray *cardDavCollection;
cardDavCollection
= [NSArray arrayWithObjects: @"addressbook",
@"urn:ietf:params:xml:ns:carddav", nil];
resourceType = [NSMutableArray arrayWithArray: [super davResourceType]];
[resourceType addObject: cardDavCollection];
return resourceType;
}
- (id) davAddressbookMultiget: (id) queryContext
{
return [self performMultigetInContext: queryContext
inNamespace: @"urn:ietf:params:xml:ns:carddav"];
}
/* sorting */
- (NSComparisonResult) compare: (id) otherFolder
{
NSComparisonResult comparison;
if ([NSStringFromClass([otherFolder class])
isEqualToString: @"SOGoContactSourceFolder"])
comparison = NSOrderedAscending;
else
comparison = [super compare: otherFolder];
return comparison;
}
/* folder type */
- (NSString *) folderType
{
return @"Contact";
}
/* TODO: multiget reorg */
- (NSString *) _nodeTagForProperty: (NSString *) property
{
NSString *namespace, *nodeName, *nsRep;
NSRange nsEnd;
nsEnd = [property rangeOfString: @"}"];
namespace
= [property substringFromRange: NSMakeRange (1, nsEnd.location - 1)];
nodeName = [property substringFromIndex: nsEnd.location + 1];
if ([namespace isEqualToString: XMLNS_CARDDAV])
nsRep = @"C";
else
nsRep = @"D";
return [NSString stringWithFormat: @"%@:%@", nsRep, nodeName];
}
- (NSString *) _baseCardDAVURL
{
NSString *davURL;
if (!baseCardDAVURL)
{
davURL = [[self realDavURL] absoluteString];
if ([davURL hasSuffix: @"/"])
baseCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
else
baseCardDAVURL = davURL;
[baseCardDAVURL retain];
}
return baseCardDAVURL;
}
- (NSString *) cardDavURL
{
return [NSString stringWithFormat: @"%@/", [self _baseCardDAVURL]];
}
- (NSString *) _basePublicCardDAVURL
{
NSString *davURL;
if (!basePublicCardDAVURL)
{
davURL = [[self publicDavURL] absoluteString];
if ([davURL hasSuffix: @"/"])
basePublicCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
else
basePublicCardDAVURL = davURL;
[basePublicCardDAVURL retain];
}
return basePublicCardDAVURL;
}
- (NSString *) publicCardDavURL
{
return [NSString stringWithFormat: @"%@/", [self _basePublicCardDAVURL]];
}
@end /* SOGoContactGCSFolder */