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 = parseInt(aTBContact.getValue("BirthYear"), 10); 249 // if the birth year is NaN or 0, use '-' 250 if (!birthYear) { 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 339 // Add the phonetic first and last names 340 if (com.gContactSync.Preferences.mSyncPrefs.syncPhoneticNames.value) { 341 aGContact.setAttribute("givenName", 342 com.gContactSync.gdata.namespaces.GD.url, 343 0, 344 "yomi", 345 aTBContact.getValue("PhoneticFirstName")); 346 aGContact.setAttribute("familyName", 347 com.gContactSync.gdata.namespaces.GD.url, 348 0, 349 "yomi", 350 aTBContact.getValue("PhoneticLastName")); 351 } 352 353 return aGContact; 354 }, 355 /** 356 * Converts an GContact's Atom/XML representation of a contact to 357 * Thunderbird's address book card format. 358 * @param aGContact {GContact} A GContact object with the contact to convert. 359 * @param aTBContact {TBContact} An existing card that can be QI'd to 360 * Components.interfaces.nsIAbMDBCard if this is 361 * before 413260 landed. 362 * @returns {TBContact} The updated TBContact. 363 */ 364 makeCard: function ContactConverter_makeCard(aGContact, aTBContact) { 365 if (!aGContact) 366 throw "Invalid aGContact parameter supplied to the 'makeCard' method" + 367 com.gContactSync.StringBundle.getStr("pleaseReport"); 368 if (!this.mInitialized) 369 this.init(); 370 if (!(aTBContact instanceof com.gContactSync.TBContact)) { 371 throw "Invalid TBContact sent to ContactConverter.makeCard from " + 372 this.caller; 373 } 374 var ab = aTBContact.mAddressBook; 375 if (!(ab instanceof com.gContactSync.AddressBook)) { 376 throw "Invalid TBContact (no mAddressBook) sent to " + 377 "ContactConverter.cardToAtomXML from " + this.caller; 378 } 379 var arr = this.mConverterArr, 380 blankProp = new com.gContactSync.Property("", ""); 381 // get the regular properties from the array mConverterArr 382 for (var i = 0, length = arr.length; i < length; i++) { 383 var obj = arr[i], 384 property = aGContact.getValue(obj.elementName, obj.index, obj.type); 385 property = property || blankProp; 386 com.gContactSync.LOGGER.VERBOSE_LOG(obj.tbName + ": '" + property.value + 387 "', type: '" + property.type + "'"); 388 // Thunderbird has problems with contacts who do not have an e-mail addr 389 // and are in Mailing Lists. To avoid problems, use a dummy e-mail addr 390 // that is hidden from the user 391 if (obj.tbName === com.gContactSync.dummyEmailName && !property.value) { 392 property.value = com.gContactSync.makeDummyEmail(aGContact); 393 property.type = "home"; 394 } 395 // don't wipe out structured address info 396 if (property.value || 397 (obj.elementName !== 'street' && obj.elementName !== 'city' && 398 obj.elementName !== 'region' && obj.elementName !== 'postcode' && 399 obj.elementName !== 'country')) { 400 aTBContact.setValue(obj.tbName, property.value); 401 // set the type, if it is an attribute with a type 402 if (property.type) 403 aTBContact.setValue(obj.tbName + "Type", property.type); 404 } 405 else { 406 com.gContactSync.LOGGER.VERBOSE_LOG("Going to avoid wiping out " + obj.tbName); 407 } 408 } 409 // get the extended properties 410 arr = com.gContactSync.Preferences.mExtendedProperties; 411 for (i = 0, length = arr.length; i < length; i++) { 412 var value = aGContact.getExtendedProperty(arr[i]); 413 value = value ? value.value : null; 414 aTBContact.setValue(arr[i], value); 415 } 416 417 // Get the birthday info 418 var bday = aGContact.getValue("birthday", 0, com.gContactSync.gdata.contacts.types.UNTYPED), 419 year = null, 420 month = null, 421 day = null; 422 // If it has a birthday... 423 if (bday && bday.value) { 424 com.gContactSync.LOGGER.VERBOSE_LOG(" * Found a birthday value of " + bday.value); 425 // If it consists of all three date elements: YYYY-M-D 426 if (bday.value.indexOf("--") === -1) { 427 arr = bday.value.split("-"); 428 year = arr[0]; 429 month = arr[1]; 430 day = arr[2]; 431 } 432 // Else it is just a month and day: --M-D 433 else { 434 arr = bday.value.replace("--", "").split("-"); 435 month = arr[0]; 436 day = arr[1]; 437 } 438 com.gContactSync.LOGGER.VERBOSE_LOG(" - Year: " + year); 439 com.gContactSync.LOGGER.VERBOSE_LOG(" - Month: " + month); 440 com.gContactSync.LOGGER.VERBOSE_LOG(" - Day: " + day); 441 } 442 aTBContact.setValue("BirthYear", year); 443 aTBContact.setValue("BirthMonth", month); 444 aTBContact.setValue("BirthDay", day); 445 446 if (com.gContactSync.Preferences.mSyncPrefs.getPhotos.value) { 447 var info = aGContact.getPhotoInfo(); 448 // If the contact has a photo then save it to a local file and update 449 // the related attributes 450 if (info && info.etag && 451 (file = aGContact.writePhoto(com.gContactSync.Sync.mCurrentAuthToken))) { 452 com.gContactSync.LOGGER.VERBOSE_LOG("Wrote photo...name: " + file.leafName); 453 aTBContact.setValue("PhotoName", file.leafName); 454 aTBContact.setValue("PhotoType", "file"); 455 aTBContact.setValue("PhotoURI", 456 Components.classes["@mozilla.org/network/io-service;1"] 457 .getService(Components.interfaces.nsIIOService) 458 .newFileURI(file) 459 .spec); 460 aTBContact.setValue("PhotoEtag", info.etag); 461 } 462 // If the contact doesn't have a photo then clear the related attributes 463 else { 464 aTBContact.setValue("PhotoName", ""); 465 aTBContact.setValue("PhotoType", ""); 466 aTBContact.setValue("PhotoURI", ""); 467 aTBContact.setValue("PhotoEtag", ""); 468 } 469 } 470 471 // Add the phonetic first and last names 472 if (com.gContactSync.Preferences.mSyncPrefs.syncPhoneticNames.value) { 473 aTBContact.setValue("PhoneticFirstName", 474 aGContact.getAttribute("givenName", 475 com.gContactSync.gdata.namespaces.GD.url, 476 0, 477 "yomi")); 478 aTBContact.setValue("PhoneticLastName", 479 aGContact.getAttribute("familyName", 480 com.gContactSync.gdata.namespaces.GD.url, 481 0, 482 "yomi")); 483 } 484 485 aTBContact.update(); 486 if (ab.mPrefs.syncGroups == "true" && ab.mPrefs.myContacts != "true") { 487 // get the groups after updating the card 488 var groups = aGContact.getValue("groupMembershipInfo"), 489 lists = com.gContactSync.Sync.mLists, 490 list, 491 group; 492 for (var i in lists) { 493 group = groups[i]; 494 list = lists[i]; 495 // delete the card from the list, if necessary 496 if (list.hasContact(aTBContact)) { 497 if (!group) { 498 list.deleteContacts([aTBContact]); 499 } 500 aTBContact.update(); 501 } 502 // add the card to the list, if necessary 503 else if (group) { 504 list.addContact(aTBContact); 505 } 506 } 507 } 508 }, 509 /** 510 * Check if the given string is null, of length 0, or consists only of spaces 511 * and return null if any of the listed conditions is true. 512 * This function was added to fix Bug 20389: Values with only spaces should be 513 * treated as empty 514 * @param aValue {string} The string to check. 515 * @returns null - The string is null, of length 0, or consists only of 516 spaces 517 * aValue - The string has at least one character that is not a space 518 */ 519 checkValue: function ContactConverter_checkValue(aValue) { 520 if (!aValue || !aValue.length) return null; 521 for (var i = 0; i < aValue.length; i++) 522 if (aValue[i] != " ") return aValue; 523 return null; 524 } 525 }; 526