1/* 2 Copyright (C) 2005-2019 Inverse inc. 3 4 This file is part of SOGo. 5 6 SOGo is free software; you can redistribute it and/or modify it under 7 the terms of the GNU Lesser General Public License as published by the 8 Free Software Foundation; either version 2, or (at your option) any 9 later version. 10 11 SOGo is distributed in the hope that it will be useful, but WITHOUT ANY 12 WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with OGo; see the file COPYING. If not, write to the 18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 19 02111-1307, USA. 20*/ 21 22#import <Foundation/NSURL.h> 23#import <Foundation/NSValue.h> 24 25#import <NGObjWeb/NSException+HTTP.h> 26#import <NGObjWeb/WOResponse.h> 27#import <NGCards/NSArray+NGCards.h> 28#import <NGExtensions/NSString+Ext.h> 29#import <NGExtensions/NSString+misc.h> 30 31#import <SOGo/NSArray+Utilities.h> 32#import <SOGo/NSCalendarDate+SOGo.h> 33#import <SOGo/NSDictionary+Utilities.h> 34#import <SOGo/SOGoSource.h> 35#import <SOGo/SOGoUser.h> 36#import <SOGo/SOGoUserDefaults.h> 37#import <SOGo/SOGoUserManager.h> 38 39#import <Contacts/NGVCard+SOGo.h> 40#import <Contacts/SOGoContactObject.h> 41 42#import "UIxContactView.h" 43 44@implementation UIxContactView 45 46- (id) init 47{ 48 if ((self = [super init])) 49 { 50 photosURL = nil; 51 card = nil; 52 phones = nil; 53 homeAdr = nil; 54 workAdr = nil; 55 } 56 57 return self; 58} 59 60- (void) dealloc 61{ 62 [card release]; 63 [photosURL release]; 64 [super dealloc]; 65} 66 67- (NSArray *) _languageContactsCategories 68{ 69 NSArray *categoryLabels; 70 71 categoryLabels = [[self labelForKey: @"contacts_category_labels"] componentsSeparatedByString: @","]; 72 if (!categoryLabels) 73 categoryLabels = [NSArray array]; 74 75 return [categoryLabels trimmedComponents]; 76} 77 78- (NSArray *) _fetchAndCombineCategoriesList 79{ 80 NSString *ownerLogin; 81 SOGoUserDefaults *ud; 82 NSArray *cats, *newCats, *contactCategories; 83 84 ownerLogin = [[self clientObject] ownerInContext: context]; 85 ud = [[SOGoUser userWithLogin: ownerLogin] userDefaults]; 86 cats = [ud contactsCategories]; 87 if (!cats) 88 cats = [self _languageContactsCategories]; 89 90 contactCategories = [card categories]; 91 if (contactCategories) 92 { 93 newCats = [cats mergedArrayWithArray: [contactCategories trimmedComponents]]; 94 if ([newCats count] != [cats count]) 95 { 96 cats = [newCats sortedArrayUsingSelector: 97 @selector (localizedCaseInsensitiveCompare:)]; 98 [ud setContactsCategories: cats]; 99 [ud synchronize]; 100 } 101 } 102 103 return cats; 104} 105 106- (NSArray *) categories 107{ 108 NSMutableArray *categories; 109 NSArray *values; 110 NSString *category; 111 NSUInteger count, max; 112 113 values = [card categories]; 114 max = [values count]; 115 if (max > 0) 116 { 117 categories = [NSMutableArray arrayWithCapacity: max]; 118 for (count = 0; count < max; count++) 119 { 120 category = [values objectAtIndex: count]; 121 if ([category length] > 0) 122 [categories addObject: [NSDictionary dictionaryWithObject: category forKey: @"value"]]; 123 } 124 } 125 else 126 categories = nil; 127 128 return categories; 129} 130 131- (NSArray *) urls 132{ 133 NSMutableArray *urls; 134 NSMutableDictionary *attrs; 135 NSArray *values; 136 NSString *type, *value; 137 NSURL *url; 138 CardElement *element; 139 NSUInteger count, max; 140 141 values = [card childrenWithTag: @"url"]; 142 max = [values count]; 143 if (max > 0) 144 { 145 urls = [NSMutableArray arrayWithCapacity: max]; 146 for (count = 0; count < max; count++) 147 { 148 attrs = [NSMutableDictionary dictionary]; 149 element = [values objectAtIndex: count]; 150 type = [element value: 0 ofAttribute: @"type"]; 151 if ([type length]) 152 [attrs setObject: type forKey: @"type"]; 153 value = [element flattenedValuesForKey: @""]; 154 url = [NSURL URLWithString: value]; 155 if (![url scheme] && [value length] > 0) 156 url = [NSURL URLWithString: [NSString stringWithFormat: @"http://%@", value]]; 157 if (url) 158 [attrs setObject: [url absoluteString] forKey: @"value"]; 159 160 [urls addObject: attrs]; 161 } 162 } 163 else 164 urls = nil; 165 166 return urls; 167} 168 169- (NSArray *) deliveryAddresses 170{ 171 NSString *type, *postoffice, *street, *street2, *locality, *region, *postalcode, *country; 172 NSMutableDictionary *address; 173 NSMutableArray *addresses; 174 NSArray *elements; 175 CardElement *adr; 176 177 NSUInteger count, max; 178 179 elements = [card childrenWithTag: @"adr"]; 180 max = [elements count]; 181 182 if (max > 0) 183 { 184 addresses = [NSMutableArray arrayWithCapacity: max]; 185 for (count = 0; count < max; count++) 186 { 187 adr = [elements objectAtIndex: count]; 188 type = [adr value: 0 ofAttribute: @"type"]; 189 postoffice = [adr flattenedValueAtIndex: 0 forKey: @""]; 190 street2 = [adr flattenedValueAtIndex: 1 forKey: @""]; 191 street = [adr flattenedValueAtIndex: 2 forKey: @""]; 192 locality = [adr flattenedValueAtIndex: 3 forKey: @""]; 193 region = [adr flattenedValueAtIndex: 4 forKey: @""]; 194 postalcode = [adr flattenedValueAtIndex: 5 forKey: @""]; 195 country = [adr flattenedValueAtIndex: 6 forKey: @""]; 196 197 address = [NSMutableDictionary dictionaryWithObject: type forKey: @"type"]; 198 if (postoffice) [address setObject: postoffice forKey: @"postoffice"]; 199 if (street2) [address setObject: street2 forKey: @"street2"]; 200 if (street) [address setObject: street forKey: @"street"]; 201 if (locality) [address setObject: locality forKey: @"locality"]; 202 if (region) [address setObject: region forKey: @"region"]; 203 if (postalcode) [address setObject: postalcode forKey: @"postalcode"]; 204 if (country) [address setObject: country forKey: @"country"]; 205 if ([[address allKeys] count] > 1) [addresses addObject: address]; 206 } 207 } 208 else 209 addresses = nil; 210 211 return addresses; 212} 213 214/* action */ 215 216- (id <WOActionResults>) defaultAction 217{ 218 card = [[self clientObject] vCard]; 219 if (card) 220 { 221 [card retain]; 222 phones = nil; 223 homeAdr = nil; 224 workAdr = nil; 225 } 226 else 227 return [NSException exceptionWithHTTPStatus: 404 /* Not Found */ 228 reason: @"could not locate contact"]; 229 230 return self; 231} 232 233/** 234 * @api {get} /so/:username/Contacts/:addressbookId/:cardId/view Get card 235 * @apiVersion 1.0.0 236 * @apiName GetData 237 * @apiGroup Contacts 238 * @apiExample {curl} Example usage: 239 * curl -i http://localhost/SOGo/so/sogo1/Contacts/personal/1BC8-52F53F80-1-38C52040.vcf/view 240 * 241 * @apiSuccess (Success 200) {String} id Card ID 242 * @apiSuccess (Success 200) {String} pid Address book ID (card's container) 243 * @apiSuccess (Success 200) {String} c_component Either vcard or vlist 244 * @apiSuccess (Success 200) {String} [c_givenname] Firstname 245 * @apiSuccess (Success 200) {String} [nickname] Nickname 246 * @apiSuccess (Success 200) {String} [c_sn] Lastname 247 * @apiSuccess (Success 200) {String} [c_fn] Fullname 248 * @apiSuccess (Success 200) {String} [title] Title 249 * @apiSuccess (Success 200) {String} [role] Role 250 * @apiSuccess (Success 200) {String} [c_screenname] Screen Name (X-AIM for now) 251 * @apiSuccess (Success 200) {String} [tz] Timezone 252 * @apiSuccess (Success 200) {String} [org] Main organization 253 * @apiSuccess (Success 200) {String[]} [orgs] Additional organizations 254 * @apiSuccess (Success 200) {String[]} [notes] Notes 255 * @apiSuccess (Success 200) {String[]} allCategories All available categories 256 * @apiSuccess (Success 200) {Object[]} [categories] Categories assigned to the card 257 * @apiSuccess (Success 200) {String} categories.value Category name 258 * @apiSuccess (Success 200) {Number} hasCertificate 1 if contact has a mail certificate 259 * @apiSuccess (Success 200) {Object[]} [addresses] Postal addresses 260 * @apiSuccess (Success 200) {String} addresses.type Type (e.g., home or work) 261 * @apiSuccess (Success 200) {String} addresses.postoffice Post office box 262 * @apiSuccess (Success 200) {String} addresses.street Street address 263 * @apiSuccess (Success 200) {String} addresses.street2 Extended address (e.g., apartment or suite number) 264 * @apiSuccess (Success 200) {String} addresses.locality Locality (e.g., city) 265 * @apiSuccess (Success 200) {String} addresses.region Region (e.g., state or province) 266 * @apiSuccess (Success 200) {String} addresses.postalcode Postal code 267 * @apiSuccess (Success 200) {String} addresses.country Country name 268 * @apiSuccess (Success 200) {Object[]} [emails] Email addresses 269 * @apiSuccess (Success 200) {String} emails.type Type (e.g., home or work) 270 * @apiSuccess (Success 200) {String} emails.value Email address 271 * @apiSuccess (Success 200) {Object[]} [phones] Phone numbers 272 * @apiSuccess (Success 200) {String} phones.type Type (e.g., mobile or work) 273 * @apiSuccess (Success 200) {String} phones.value Phone number 274 * @apiSuccess (Success 200) {Object[]} [urls] URLs 275 * @apiSuccess (Success 200) {String} urls.type Type (e.g., personal or work) 276 * @apiSuccess (Success 200) {String} urls.value URL 277 * @apiSuccess (Success 200) {Object[]} customFields Custom fields from Thunderbird 278 */ 279- (id <WOActionResults>) dataAction 280{ 281 NSMutableDictionary *customFields, *data; 282 SOGoObject <SOGoContactObject> *contact; 283 id <WOActionResults> result; 284 NSArray *values; 285 id o; 286 287 contact = [self clientObject]; 288 card = [contact vCard]; 289 if (card) 290 { 291 [card retain]; 292 phones = nil; 293 homeAdr = nil; 294 workAdr = nil; 295 } 296 else 297 return [NSException exceptionWithHTTPStatus: 404 /* Not Found */ 298 reason: @"could not locate contact"]; 299 300 data = [NSMutableDictionary dictionaryWithObjectsAndKeys: 301 [[contact container] nameInContainer], @"pid", 302 [contact nameInContainer], @"id", 303 [[card tag] lowercaseString], @"c_component", 304 nil]; 305 o = [card fn]; 306 if (o) [data setObject: o forKey: @"c_cn"]; 307 o = [card n]; 308 if (o) 309 { 310 NSString *lastName = [o flattenedValueAtIndex: 0 forKey: @""]; 311 NSString *firstName = [o flattenedValueAtIndex: 1 forKey: @""]; 312 if ([lastName length] > 0) 313 [data setObject: lastName forKey: @"c_sn"]; 314 if ([firstName length] > 0) 315 [data setObject: firstName forKey: @"c_givenname"]; 316 } 317 318 o = [[card uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""]; 319 if ([o length]) [data setObject: o forKey: @"c_screenname"]; 320 321 o = [card nickname]; 322 if (o) [data setObject: o forKey: @"nickname"]; 323 o = [card titles]; 324 if ([o count]) 325 [data setObject: [o componentsJoinedByString: @" / "] forKey: @"title"]; 326 o = [card role]; 327 if ([o length] > 0) 328 [data setObject: o forKey: @"role"]; 329 values = [card organizations]; 330 if ([values count]) 331 { 332 [data setObject: [values objectAtIndex: 0] forKey: @"org"]; 333 if ([values count] > 1) 334 [data setObject: [values subarrayWithRange: NSMakeRange(1, [values count] - 1)] forKey: @"orgs"]; 335 } 336 337 o = [card certificate]; 338 if ([o length]) 339 [data setObject: [NSNumber numberWithBool: YES] forKey: @"hasCertificate"]; 340 341 o = [card birthday]; 342 if (o) 343 [data setObject: [o descriptionWithCalendarFormat: @"%Y-%m-%d"] 344 forKey: @"birthday"]; 345 346 o = [card tz]; 347 if (o) [data setObject: o forKey: @"tz"]; 348 349 o = [card childrenWithTag: @"email"]; 350 if ([o count]) [data setObject: o forKey: @"emails"]; 351 o = [card childrenWithTag: @"tel"]; 352 if ([o count]) [data setObject: o forKey: @"phones"]; 353 o = [self categories]; 354 if ([o count]) [data setObject: o forKey: @"categories"]; 355 o = [self deliveryAddresses]; 356 if ([o count] > 0) [data setObject: o forKey: @"addresses"]; 357 o = [self urls]; 358 if ([o count]) [data setObject: o forKey: @"urls"]; 359 360 o = [card notes]; 361 if (o) [data setObject: o forKey: @"notes"]; 362 o = [self _fetchAndCombineCategoriesList]; 363 if (o) [data setObject: o forKey: @"allCategories"]; 364 if ([contact hasPhoto]) 365 [data setObject: [self photoURL] forKey: @"photoURL"]; 366 367 // Custom fields from Thunderbird 368 customFields = [NSMutableDictionary dictionary]; 369 if ((o = [[card uniqueChildWithTag: @"custom1"] flattenedValuesForKey: @""]) && [o length]) 370 [customFields setObject: o forKey: @"1"]; 371 372 if ((o = [[card uniqueChildWithTag: @"custom2"] flattenedValuesForKey: @""]) && [o length]) 373 [customFields setObject: o forKey: @"2"]; 374 375 if ((o = [[card uniqueChildWithTag: @"custom3"] flattenedValuesForKey: @""]) && [o length]) 376 [customFields setObject: o forKey: @"3"]; 377 378 if ((o = [[card uniqueChildWithTag: @"custom4"] flattenedValuesForKey: @""]) && [o length]) 379 [customFields setObject: o forKey: @"4"]; 380 381 if ([customFields count]) 382 [data setObject: customFields forKey: @"customFields"]; 383 384 result = [self responseWithStatus: 200 385 andString: [data jsonRepresentation]]; 386 387 return result; 388} 389 390- (id <WOActionResults>) membersAction 391{ 392 NSArray *allUsers; 393 NSDictionary *dict; 394 NSEnumerator *emails; 395 NSMutableArray *allUsersData, *allUserEmails; 396 NSMutableDictionary *userData; 397 NSString *email; 398 SOGoObject <SOGoContactObject> *contact; 399 SOGoObject <SOGoSource> *source; 400 SOGoUser *user; 401 id <WOActionResults> result; 402 unsigned int i, max; 403 404 result = nil; 405 contact = [self clientObject]; 406 source = [[contact container] source]; 407 dict = [source lookupContactEntryWithUIDorEmail: [contact nameInContainer] 408 inDomain: nil]; 409 410 if ([[dict objectForKey: @"isGroup"] boolValue]) 411 { 412 if ([source conformsToProtocol:@protocol(SOGoMembershipSource)]) 413 { 414 allUsers = [(id<SOGoMembershipSource>)(source) membersForGroupWithUID: [dict objectForKey: @"c_uid"]]; 415 max = [allUsers count]; 416 allUsersData = [NSMutableArray arrayWithCapacity: max]; 417 for (i = 0; i < max; i++) 418 { 419 user = [allUsers objectAtIndex: i]; 420 allUserEmails = [NSMutableArray array]; 421 emails = [[user allEmails] objectEnumerator]; 422 while ((email = [emails nextObject])) { 423 [allUserEmails addObject: [NSDictionary dictionaryWithObjectsAndKeys: 424 email, @"value", @"work", @"type", nil]]; 425 } 426 userData = [NSDictionary dictionaryWithObjectsAndKeys: 427 [user loginInDomain], @"c_uid", 428 [user cn], @"c_cn", 429 allUserEmails, @"emails", nil]; 430 [allUsersData addObject: userData]; 431 } 432 dict = [NSDictionary dictionaryWithObject: allUsersData forKey: @"members"]; 433 result = [self responseWithStatus: 200 434 andString: [dict jsonRepresentation]]; 435 } 436 else 437 { 438 result = [self responseWithStatus: 403 439 andString: @"Group is not expandable"]; 440 } 441 } 442 else 443 { 444 result = [self responseWithStatus: 405 445 andString: @"Contact is not a group"]; 446 } 447 448 return result; 449} 450 451 452- (BOOL) hasPhoto 453{ 454 return [[self clientObject] hasPhoto]; 455} 456 457- (NSString *) photoURL 458{ 459 NSURL *soURL; 460 461 soURL = [[self clientObject] soURL]; 462 463 return [NSString stringWithFormat: @"%@/photo", [soURL absoluteString]]; 464} 465 466@end /* UIxContactView */ 467