1 /* ***** BEGIN LICENSE BLOCK ***** 2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 * 4 * The contents of this file are subject to the Mozilla Public License Version 5 * 1.1 (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * http://www.mozilla.org/MPL/ 8 * 9 * Software distributed under the License is distributed on an "AS IS" basis, 10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 * for the specific language governing rights and limitations under the 12 * License. 13 * 14 * The Original Code is gContactSync. 15 * 16 * The Initial Developer of the Original Code is 17 * Josh Geenen <gcontactsync@pirules.org>. 18 * Portions created by the Initial Developer are Copyright (C) 2008-2010 19 * the Initial Developer. All Rights Reserved. 20 * 21 * Contributor(s): 22 * 23 * Alternatively, the contents of this file may be used under the terms of 24 * either the GNU General Public License Version 2 or later (the "GPL"), or 25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 26 * in which case the provisions of the GPL or the LGPL are applicable instead 27 * of those above. If you wish to allow use of your version of this file only 28 * under the terms of either the GPL or the LGPL, and not to allow others to 29 * use your version of this file under the terms of the MPL, indicate your 30 * decision by deleting the provisions above and replace them with the notice 31 * and other provisions required by the GPL or the LGPL. If you do not delete 32 * the provisions above, a recipient may use your version of this file under 33 * the terms of any one of the MPL, the GPL or the LGPL. 34 * 35 * ***** END LICENSE BLOCK ***** */ 36 37 if (!com) var com = {}; // A generic wrapper variable 38 // A wrapper for all GCS functions and variables 39 if (!com.gContactSync) com.gContactSync = {}; 40 41 window.addEventListener("load", 42 /** Initializes the ContactConverter class when the window has finished loading */ 43 function gCS_ContactConverterLoadListener(e) { 44 com.gContactSync.ContactConverter.init(); 45 }, 46 false); 47 48 49 /** 50 * Converts contacts between Thunderbird's format (a 'card') and the Atom/XML 51 * representation of a contact. Must be initialized before the first use by 52 * calling the init() function. 53 * NOTE: The first 6 screennames of a contact from Google are stored as: 54 * _AimScreenName, TalkScreenName, ICQScreenName, YahooScreenName, MSNScreenName 55 * and JabberScreenName for compatibility with gContactSync 0.1b1 and the 56 * default type for those textboxes. 57 * @class 58 */ 59 com.gContactSync.ContactConverter = { 60 /** The GD Namespace */ 61 GD: {}, 62 /** The ATOM/XML Namespace */ 63 ATOM: {}, 64 /** The current TBContact being converted into a GContact */ 65 mCurrentCard: {}, 66 /** An array of ContactConverter objects */ 67 mConverterArr: [], 68 /** 69 * Extra attributes added by this extension. Doesn't include GoogleID or any 70 * of the URLs. Should be obtained w/ ContactConverter.getExtraSyncAttributes 71 */ 72 mAddedAttributes: [ 73 "HomeFaxNumber", "OtherNumber", "ThirdEmail", "FourthEmail", 74 "TalkScreenName", "ICQScreenName", "YahooScreenName", "MSNScreenName", 75 "JabberScreenName", "PrimaryEmailType", "SecondEmailType", 76 "ThirdEmailType", "FourthEmailType", "_AimScreenNameType", 77 "TalkScreenNameType", "ICQScreenNameType", "YahooScreenNameType", 78 "MSNScreenNameType", "JabberScreenNameType", "HomePhoneType", 79 "WorkPhoneType", "FaxNumberType", "CellularNumberType", "PagerNumberType", 80 "HomeFaxNumberType", "OtherNumberType", "Relation0", "Relation0Type", 81 "Relation1", "Relation1Type", "Relation2", "Relation2Type", "Relation3", 82 "Relation3Type", "CompanySymbol", "JobDescription", 83 "WebPage1Type", "WebPage2Type" 84 ], 85 /** Stores whether this object has been initialized yet */ 86 mInitialized: false, 87 /** 88 * Initializes this object by populating the array of ConverterElement 89 * objects and the two namespaces most commonly used by this object. 90 */ 91 init: function ContactConverter_init() { 92 this.GD = com.gContactSync.gdata.namespaces.GD; 93 this.ATOM = com.gContactSync.gdata.namespaces.ATOM; 94 var phoneTypes = com.gContactSync.Preferences.mSyncPrefs.phoneTypes.value; 95 // ConverterElement(aElement, aTbName, aIndex, aType) 96 // This array stores info on what tags in Google's feed sync with which 97 // properties in Thunderbird. gdata.contacts has info on these tags 98 this.mConverterArr = [ 99 // Various components of a name 100 new com.gContactSync.ConverterElement("fullName", "DisplayName", 0), 101 new com.gContactSync.ConverterElement("givenName", "FirstName", 0), 102 new com.gContactSync.ConverterElement("familyName", "LastName", 0), 103 new com.gContactSync.ConverterElement("additionalName", "AdditionalName", 0), 104 new com.gContactSync.ConverterElement("namePrefix", "namePrefix", 0), 105 new com.gContactSync.ConverterElement("nameSuffix", "nameSuffix", 0), 106 new com.gContactSync.ConverterElement("nickname", "NickName", 0), 107 // general 108 new com.gContactSync.ConverterElement("notes", "Notes", 0), 109 new com.gContactSync.ConverterElement("id", "GoogleID", 0), 110 // e-mail addresses 111 new com.gContactSync.ConverterElement("email", "PrimaryEmail", 0, "other"), 112 new com.gContactSync.ConverterElement("email", "SecondEmail", 1, "other"), 113 new com.gContactSync.ConverterElement("email", "ThirdEmail", 2, "other"), 114 new com.gContactSync.ConverterElement("email", "FourthEmail", 3, "other"), 115 // IM screennames 116 new com.gContactSync.ConverterElement("im", "_AimScreenName", 0, "AIM"), 117 new com.gContactSync.ConverterElement("im", "TalkScreenName", 1, "GOOGLE_TALK"), 118 new com.gContactSync.ConverterElement("im", "ICQScreenName", 2, "ICQ"), 119 new com.gContactSync.ConverterElement("im", "YahooScreenName", 3, "YAHOO"), 120 new com.gContactSync.ConverterElement("im", "MSNScreenName", 4, "MSN"), 121 new com.gContactSync.ConverterElement("im", "JabberScreenName", 5, "JABBER"), 122 // the phone numbers 123 new com.gContactSync.ConverterElement("phoneNumber", "WorkPhone", 0, "work"), 124 new com.gContactSync.ConverterElement("phoneNumber", "HomePhone", (phoneTypes ? 1 : 0), "home"), 125 new com.gContactSync.ConverterElement("phoneNumber", "FaxNumber", (phoneTypes ? 2 : 0), "work_fax"), 126 new com.gContactSync.ConverterElement("phoneNumber", "CellularNumber", (phoneTypes ? 3 : 0), "mobile"), 127 new com.gContactSync.ConverterElement("phoneNumber", "PagerNumber", (phoneTypes ? 4 : 0), "pager"), 128 new com.gContactSync.ConverterElement("phoneNumber", "HomeFaxNumber", (phoneTypes ? 5 : 0), "home_fax"), 129 new com.gContactSync.ConverterElement("phoneNumber", "OtherNumber", (phoneTypes ? 6 : 0), "other"), 130 // company info 131 new com.gContactSync.ConverterElement("orgTitle", "JobTitle", 0), 132 new com.gContactSync.ConverterElement("orgName", "Company", 0), 133 new com.gContactSync.ConverterElement("orgDepartment", "Department", 0), 134 new com.gContactSync.ConverterElement("orgJobDescription", "JobDescription", 0), 135 new com.gContactSync.ConverterElement("orgSymbol", "CompanySymbol", 0), 136 // the URLs from Google - Photo, Self, and Edit 137 new com.gContactSync.ConverterElement("PhotoURL", "PhotoURL", 0), 138 new com.gContactSync.ConverterElement("SelfURL", "SelfURL", 0), 139 new com.gContactSync.ConverterElement("EditURL", "EditURL", 0), 140 // Relation fields 141 new com.gContactSync.ConverterElement("relation", "Relation0", 0, ""), 142 new com.gContactSync.ConverterElement("relation", "Relation1", 1, ""), 143 new com.gContactSync.ConverterElement("relation", "Relation2", 2, ""), 144 new com.gContactSync.ConverterElement("relation", "Relation3", 3, ""), 145 // Websites 146 new com.gContactSync.ConverterElement("website", "WebPage1", 0, "work"), 147 new com.gContactSync.ConverterElement("website", "WebPage2", 1, "home"), 148 ]; 149 150 // Only synchronize (if possible) postal addresses if the preference was 151 // changed to true 152 if (com.gContactSync.Preferences.mSyncPrefs.syncAddresses.value) { 153 // Home address 154 this.mConverterArr.push(new com.gContactSync.ConverterElement("street", "HomeAddress", 0, "home")); 155 this.mConverterArr.push(new com.gContactSync.ConverterElement("city", "HomeCity", 0, "home")); 156 this.mConverterArr.push(new com.gContactSync.ConverterElement("region", "HomeState", 0, "home")); 157 this.mConverterArr.push(new com.gContactSync.ConverterElement("postcode", "HomeZipCode", 0, "home")); 158 this.mConverterArr.push(new com.gContactSync.ConverterElement("country", "HomeCountry", 0, "home")); 159 // Work address 160 this.mConverterArr.push(new com.gContactSync.ConverterElement("street", "WorkAddress", 0, "work")); 161 this.mConverterArr.push(new com.gContactSync.ConverterElement("city", "WorkCity", 0, "work")); 162 this.mConverterArr.push(new com.gContactSync.ConverterElement("region", "WorkState", 0, "work")); 163 this.mConverterArr.push(new com.gContactSync.ConverterElement("postcode", "WorkZipCode", 0, "work")); 164 this.mConverterArr.push(new com.gContactSync.ConverterElement("country", "WorkCountry", 0, "work")); 165 // full (formatted) addresses at the bottom so they have priority 166 this.mConverterArr.push(new com.gContactSync.ConverterElement("formattedAddress", "FullHomeAddress", 0, "home")); 167 this.mConverterArr.push(new com.gContactSync.ConverterElement("formattedAddress", "FullWorkAddress", 0, "work")); 168 this.mConverterArr.push(new com.gContactSync.ConverterElement("formattedAddress", "OtherAddress", 0, "other")); 169 } 170 this.mInitialized = true; 171 }, 172 /** 173 * Returns an array of all of the extra attributes synced by this extension. 174 * @param aIncludeURLs {boolean} Should be true if the URL-related attributes 175 * should be returned. 176 */ 177 getExtraSyncAttributes: function ContactConverter_getExtraSyncAttributes(aIncludeURLs, aIncludeAddresses) { 178 if (!this.mInitialized) 179 this.init(); 180 var arr = this.mAddedAttributes.slice(); 181 if (aIncludeURLs) 182 arr = arr.concat("PhotoURL", "SelfURL", "EditURL", "GoogleID"); 183 if (aIncludeAddresses) 184 arr = arr.concat("FullHomeAddress", "FullWorkAddress", "OtherAddress"); 185 return arr; 186 }, 187 /** 188 * Updates or creates a GContact object's Atom/XML representation using its 189 * complementary Address Book card. 190 * @param aTBContact {TBContact} The address book card used to update the Atom 191 * feed. Must be in an address book. 192 * @param aGContact {GContact} Optional. The GContact object with the Atom/XML 193 * representation of the contact, if it exists. If 194 * not supplied, a contact and feed will be created. 195 * @returns {GContact} A GContact object with the Atom feed for the contact. 196 */ 197 cardToAtomXML: function ContactConverter_cardToAtomXML(aTBContact, aGContact) { 198 var isNew = !aGContact, 199 ab = aTBContact.mAddressBook, 200 arr = this.mConverterArr, 201 i = 0, 202 obj, 203 value, 204 type; 205 if (!aGContact) 206 aGContact = new com.gContactSync.GContact(); 207 if (!this.mInitialized) 208 this.init(); 209 if (!(aTBContact instanceof com.gContactSync.TBContact)) { 210 throw "Invalid TBContact sent to ContactConverter.cardToAtomXML from " + 211 this.caller; 212 } 213 if (!(ab instanceof com.gContactSync.AddressBook)) { 214 throw "Invalid TBContact (no mAddressBook) sent to " + 215 "ContactConverter.cardToAtomXML from " + this.caller; 216 } 217 this.mCurrentCard = aTBContact; 218 // set the regular properties from the array mConverterArr 219 for (i = 0, length = arr.length; i < length; i++) { 220 // skip the URLs 221 if (arr[i].tbName.indexOf("URL") !== -1 || arr[i].tbName === "GoogleID") 222 continue; 223 obj = arr[i]; 224 com.gContactSync.LOGGER.VERBOSE_LOG(" * " + obj.tbName); 225 value = this.checkValue(aTBContact.getValue(obj.tbName)); 226 // for the type, get the type from the card, or use its default 227 type = aTBContact.getValue(obj.tbName + "Type"); 228 if (!type || type === "") 229 type = obj.type; 230 // see the dummy e-mail note below 231 if (obj.tbName === com.gContactSync.dummyEmailName && 232 com.gContactSync.isDummyEmail(value)) { 233 value = null; 234 type = null; 235 } 236 com.gContactSync.LOGGER.VERBOSE_LOG(" - " + value + " type: " + type); 237 aGContact.setValue(obj.elementName, obj.index, type, value); 238 } 239 // Birthday can be either YYYY-M-D or --M-D for no year. 240 // TB can have all three, just a day/month, or just a year through the UI 241 var birthDay = aTBContact.getValue("BirthDay"), 242 birthMonth = isNaN(parseInt(birthDay, 10)) ? 243 null : aTBContact.getValue("BirthMonth"), 244 birthdayVal = null; 245 // if the contact has a birth month (and birth day) add it to the contact 246 // from Google 247 if (birthMonth && !isNaN(parseInt(birthMonth, 10))) { 248 var birthYear = aTBContact.getValue("BirthYear"); 249 // if the birth year is empty, use '-' 250 if (!birthYear || isNaN(parseInt(birthYear, 10))) { 251 birthYear = "-"; 252 } 253 // otherwise pad it to 4 characters 254 else { 255 birthYear = String(birthYear); 256 while (birthYear.length < 4) { 257 birthYear = "0" + birthYear; 258 } 259 } 260 // Pad the birth month to 2 characters 261 birthMonth = String(birthMonth); 262 while (birthMonth.length < 2) { 263 birthMonth = "0" + birthMonth; 264 } 265 // Pad the birth day to 2 characters 266 birthDay = String(birthDay); 267 while (birthDay.length < 2) { 268 birthDay = "0" + birthDay; 269 } 270 // form the birthday string: year-month-day 271 birthdayVal = birthYear + "-" + birthMonth + "-" + birthDay; 272 } 273 com.gContactSync.LOGGER.VERBOSE_LOG(" * Birthday: " + birthdayVal); 274 aGContact.setValue("birthday", 0, null, birthdayVal); 275 276 // set the extended properties 277 aGContact.removeExtendedProperties(); 278 arr = com.gContactSync.Preferences.mExtendedProperties; 279 var props = {}; 280 for (i = 0, length = arr.length; i < length; i++) { 281 // add this extended property if it isn't a duplicate 282 if (!props[arr[i]]) { 283 props[arr[i]] = true; 284 value = this.checkValue(aTBContact.getValue(arr[i])); 285 aGContact.setExtendedProperty(arr[i], value); 286 } 287 else { 288 com.gContactSync.LOGGER.LOG_WARNING("Found a duplicate extended property: " + 289 arr[i]); 290 } 291 } 292 // If the myContacts pref is set and this contact is new then add the 293 // myContactsName group 294 if (ab.mPrefs.myContacts === "true") { 295 if (isNew && com.gContactSync.Sync.mContactsUrl) { 296 aGContact.setGroups([com.gContactSync.Sync.mContactsUrl]); 297 } 298 } 299 else { 300 // set the groups 301 var groups = [], 302 list; 303 com.gContactSync.LOGGER.VERBOSE_LOG(" * Determining the groups this contact belongs to"); 304 for (i in com.gContactSync.Sync.mLists) { 305 list = com.gContactSync.Sync.mLists[i]; 306 if (list instanceof com.gContactSync.GMailList) { 307 if (list.hasContact(aTBContact)) { 308 com.gContactSync.LOGGER.VERBOSE_LOG(" - " + list.getName()); 309 groups.push(i); 310 } 311 } 312 else { 313 com.gContactSync.LOGGER.LOG_WARNING(" - Found an invalid list: " + i); 314 } 315 } 316 aGContact.setGroups(groups); 317 } 318 // Upload the photo 319 if (com.gContactSync.Preferences.mSyncPrefs.sendPhotos.value) { 320 // Get the profile directory 321 var file = Components.classes["@mozilla.org/file/directory_service;1"] 322 .getService(Components.interfaces.nsIProperties) 323 .get("ProfD", Components.interfaces.nsIFile); 324 // Get (or make) the Photos directory 325 file.append("Photos"); 326 if (!file.exists() || !file.isDirectory()) 327 file.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0777); 328 file.append(aTBContact.getValue("PhotoName")); 329 if (file.exists() && file.isFile()) { 330 aGContact.setPhoto(Components.classes["@mozilla.org/network/io-service;1"] 331 .getService(Components.interfaces.nsIIOService) 332 .newFileURI(file)); 333 } 334 else { 335 aGContact.setPhoto(""); 336 } 337 } 338 return aGContact; 339 }, 340 /** 341 * Converts an GContact's Atom/XML representation of a contact to 342 * Thunderbird's address book card format. 343 * @param aGContact {GContact} A GContact object with the contact to convert. 344 * @param aTBContact {TBContact} An existing card that can be QI'd to 345 * Components.interfaces.nsIAbMDBCard if this is 346 * before 413260 landed. 347 * @returns {TBContact} The updated TBContact. 348 */ 349 makeCard: function ContactConverter_makeCard(aGContact, aTBContact) { 350 if (!aGContact) 351 throw "Invalid aGContact parameter supplied to the 'makeCard' method" + 352 com.gContactSync.StringBundle.getStr("pleaseReport"); 353 if (!this.mInitialized) 354 this.init(); 355 if (!(aTBContact instanceof com.gContactSync.TBContact)) { 356 throw "Invalid TBContact sent to ContactConverter.makeCard from " + 357 this.caller; 358 } 359 var ab = aTBContact.mAddressBook; 360 if (!(ab instanceof com.gContactSync.AddressBook)) { 361 throw "Invalid TBContact (no mAddressBook) sent to " + 362 "ContactConverter.cardToAtomXML from " + this.caller; 363 } 364 var arr = this.mConverterArr, 365 blankProp = new com.gContactSync.Property("", ""); 366 // get the regular properties from the array mConverterArr 367 for (var i = 0, length = arr.length; i < length; i++) { 368 var obj = arr[i], 369 property = aGContact.getValue(obj.elementName, obj.index, obj.type); 370 property = property || blankProp; 371 com.gContactSync.LOGGER.VERBOSE_LOG(obj.tbName + ": '" + property.value + 372 "', type: '" + property.type + "'"); 373 // Thunderbird has problems with contacts who do not have an e-mail addr 374 // and are in Mailing Lists. To avoid problems, use a dummy e-mail addr 375 // that is hidden from the user 376 if (obj.tbName === com.gContactSync.dummyEmailName && !property.value) { 377 property.value = com.gContactSync.makeDummyEmail(aGContact); 378 property.type = "home"; 379 } 380 // don't wipe out structured address info 381 if (property.value || 382 (obj.elementName !== 'street' && obj.elementName !== 'city' && 383 obj.elementName !== 'region' && obj.elementName !== 'postcode' && 384 obj.elementName !== 'country')) { 385 aTBContact.setValue(obj.tbName, property.value); 386 // set the type, if it is an attribute with a type 387 if (property.type) 388 aTBContact.setValue(obj.tbName + "Type", property.type); 389 } 390 else { 391 com.gContactSync.LOGGER.VERBOSE_LOG("Going to avoid wiping out " + obj.tbName); 392 } 393 } 394 // get the extended properties 395 arr = com.gContactSync.Preferences.mExtendedProperties; 396 for (i = 0, length = arr.length; i < length; i++) { 397 var value = aGContact.getExtendedProperty(arr[i]); 398 value = value ? value.value : null; 399 aTBContact.setValue(arr[i], value); 400 } 401 402 // parse the DisplayName into FirstName and LastName 403 if (com.gContactSync.Preferences.mSyncPrefs.parseNames.value) { 404 var name = aTBContact.getValue("DisplayName"), 405 first = aTBContact.getValue("FirstName"), 406 last = aTBContact.getValue("LastName"); 407 // only parse if the contact has a name and there isn't already a first 408 // or last name set 409 if (name && !first && !last) { 410 var nameArr = [], 411 commaIndex; 412 if (name.split && name.indexOf) { 413 // If the name has a comma, it is probably <last>, <first> 414 commaIndex = name.indexOf(","); 415 if (commaIndex !== -1) { 416 name = name.replace(", ", ","); 417 var tmpArr = name.split(","); 418 nameArr.push(tmpArr[1]); 419 nameArr.push(tmpArr[0]); 420 // now fix the DisplayName 421 aTBContact.setValue("DisplayName", tmpArr[1] + " " + tmpArr[0]); 422 } 423 // If are there any DBCS characters in name, That's an asian name, 424 // <last> <first> 425 else if (encodeURI(name).replace(/%../g,"x").length != name.length) { 426 var tmpArr = name.split(" "); 427 if (tmpArr.length > 1) { 428 nameArr.push(tmpArr[1]); 429 nameArr.push(tmpArr[0]); 430 } 431 else 432 nameArr = [name]; 433 } 434 // Otherwise assume it is <first> <last> 435 else 436 nameArr = name.split(" "); 437 } 438 else 439 nameArr = [name]; 440 // take the first part of the name and set it as the first name 441 // then take the last and set it as the last name 442 first = nameArr.shift(); 443 last = nameArr.join(" "); 444 com.gContactSync.LOGGER.VERBOSE_LOG("FirstName\n" + first + "\nLastName\n" + last); 445 aTBContact.setValue("FirstName", first); 446 aTBContact.setValue("LastName", last); 447 } 448 } 449 450 // Get the birthday info 451 var bday = aGContact.getValue("birthday", 0, com.gContactSync.gdata.contacts.types.UNTYPED), 452 year = null, 453 month = null, 454 day = null; 455 // If it has a birthday... 456 if (bday && bday.value) { 457 com.gContactSync.LOGGER.VERBOSE_LOG(" * Found a birthday value of " + bday.value); 458 // If it consists of all three date elements: YYYY-M-D 459 if (bday.value.indexOf("--") === -1) { 460 arr = bday.value.split("-"); 461 year = arr[0]; 462 month = arr[1]; 463 day = arr[2]; 464 } 465 // Else it is just a month and day: --M-D 466 else { 467 arr = bday.value.replace("--", "").split("-"); 468 month = arr[0]; 469 day = arr[1]; 470 } 471 com.gContactSync.LOGGER.VERBOSE_LOG(" - Year: " + year); 472 com.gContactSync.LOGGER.VERBOSE_LOG(" - Month: " + month); 473 com.gContactSync.LOGGER.VERBOSE_LOG(" - Day: " + day); 474 } 475 aTBContact.setValue("BirthYear", year); 476 aTBContact.setValue("BirthMonth", month); 477 aTBContact.setValue("BirthDay", day); 478 479 if (com.gContactSync.Preferences.mSyncPrefs.getPhotos.value) { 480 var info = aGContact.getPhotoInfo(); 481 // If the contact has a photo then save it to a local file and update 482 // the related attributes 483 if (info && info.etag && 484 (file = aGContact.writePhoto(com.gContactSync.Sync.mCurrentAuthToken))) { 485 com.gContactSync.LOGGER.VERBOSE_LOG("Wrote photo...name: " + file.leafName); 486 aTBContact.setValue("PhotoName", file.leafName); 487 aTBContact.setValue("PhotoType", "file"); 488 aTBContact.setValue("PhotoURI", 489 Components.classes["@mozilla.org/network/io-service;1"] 490 .getService(Components.interfaces.nsIIOService) 491 .newFileURI(file) 492 .spec); 493 aTBContact.setValue("PhotoEtag", info.etag); 494 } 495 // If the contact doesn't have a photo then clear the related attributes 496 else { 497 aTBContact.setValue("PhotoName", ""); 498 aTBContact.setValue("PhotoType", ""); 499 aTBContact.setValue("PhotoURI", ""); 500 aTBContact.setValue("PhotoEtag", ""); 501 } 502 } 503 504 aTBContact.update(); 505 if (ab.mPrefs.syncGroups == "true" && ab.mPrefs.myContacts != "true") { 506 // get the groups after updating the card 507 var groups = aGContact.getValue("groupMembershipInfo"), 508 lists = com.gContactSync.Sync.mLists, 509 list, 510 group; 511 for (var i in lists) { 512 group = groups[i]; 513 list = lists[i]; 514 // delete the card from the list, if necessary 515 if (list.hasContact(aTBContact)) { 516 if (!group) { 517 list.deleteContacts([aTBContact]); 518 } 519 aTBContact.update(); 520 } 521 // add the card to the list, if necessary 522 else if (group) { 523 list.addContact(aTBContact); 524 } 525 } 526 } 527 }, 528 /** 529 * Check if the given string is null, of length 0, or consists only of spaces 530 * and return null if any of the listed conditions is true. 531 * This function was added to fix Bug 20389: Values with only spaces should be 532 * treated as empty 533 * @param aValue {string} The string to check. 534 * @returns null - The string is null, of length 0, or consists only of 535 spaces 536 * aValue - The string has at least one character that is not a space 537 */ 538 checkValue: function ContactConverter_checkValue(aValue) { 539 if (!aValue || !aValue.length) return null; 540 for (var i = 0; i < aValue.length; i++) 541 if (aValue[i] != " ") return aValue; 542 return null; 543 } 544 }; 545