/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true,
bitwise:true, strict:true, undef:true, curly:true, browser:true,
devel:true, indent:2, maxerr:50, moz:true, newcap:false, moz:true */
/* global mozContact: false */
/**
* Translate objects created from LDIF files to Contacts
*
* @param Array of LDIF objects
* @return Contact with all fields filled
* @throws ValueError for unknown field in the LDIF object
*
* ContactsAPI:
* @see https://wiki.mozilla.org/WebAPI/ContactsAPI
* @spec http://www.w3.org/TR/contacts-manager-api/
*
* Thunderbid LDAP Schema:
* @see https://developer.mozilla.org/docs/Thunderbird/LDAP_Support
* @see https://tools.ietf.org/html/rfc4519
*/
function translateObjectToContact(inRec) {
"use strict";
var contact = {},
year, month, day, curRec = null;
function ValueError(message) {
this.name = "ValueError";
this.message = message || "Unknown Value";
}
ValueError.prototype = new Error();
ValueError.prototype.constructor = ValueError;
const cleanPhoneNoRE = /[^0-9+]*/g;
/**
* Find the proper record (or create new one) in the multi-value
* attribute
*
* @param idx name of the attribute to which the record belongs
* @param subClass Function constructor of the record if there is
* none
* @param subType String with the type of the record
* @return record of the proper class
*
* Uses global variable contact.
*/
function findSubElement(idx, subType) {
var cAddr = null;
function createNewContact(subType) {
var cont = {};
if (subType) {
cont.type = [subType];
}
return cont;
}
// No contact.adr at all
if (!contact.hasOwnProperty(idx)) {
cAddr = createNewContact(subType);
contact[idx] = cAddr;
}
// Single-element property
else if (!Array.isArray(contact[idx])) {
if (contact[idx].type.indexOf(subType) === -1) {
contact[idx] = [contact[idx]];
cAddr = createNewContact(subType);
contact[idx].push(cAddr);
}
else {
cAddr = contact[idx];
}
}
// Array
else {
cAddr = contact[idx].filter(function (addr) {
return addr.type.indexOf(subType) !== -1;
});
if (cAddr.length === 0) {
cAddr = createNewContact(subType);
contact[idx].push(cAddr);
}
else {
cAddr = cAddr[0];
}
}
return cAddr;
}
/**
* Manages squeezing two-line address into one value field
*
* @param type String ['home', 'work']
* @param target String ContactsAddress attribute to be set (e.g.,
* streetAddress)
* @param first String first line index in inRec
* @param second String second line index in inRec
* @return None
* sets variable inRec local to outer function
* deletes both fields from inRec so as to avoid duplication
*/
function secondLineInAddress(type, target, first, second) {
var curRec = findSubElement("adr", type);
if (second in inRec) {
curRec[target] = inRec[first] + "\n" + inRec[second];
}
else if (first in inRec) {
curRec[target] = inRec[first];
}
if (first in inRec) {
delete inRec[first];
}
if (second in inRec) {
delete inRec[second];
}
}
/**
* Firefox OS doesn’t recognize well formatted phone numbers, and
* works best with the plain number-only ones.
*/
function cleanPhoneNumber(inNo) {
return inNo.replace(cleanPhoneNoRE, "");
}
for (var key in inRec) {
if (["birthyear", "birthmonth", "birthday"].indexOf(key) !== -1) {
// We have alternatively either whole date in birthyear field,
// e.g. 19940221, or we have all three properties set.
year = inRec.birthyear || "1970"; // lowest year in Unix time
month = inRec.birthmonth || null;
day = inRec.birthday || null;
if (year.length === 8) {
contact.bday = new Date(parseInt(year.slice(0,4), 10),
parseInt(year.slice(4,6), 10) - 1,
parseInt(year.slice(6,8), 10));
if (inRec.birthday) {
delete inRec.birthday;
}
if (inRec.birthmonth) {
delete inRec.birthmonth;
}
}
else if (month && day) {
contact.bday = new Date(parseInt(year, 10),
parseInt(month, 10) - 1, parseInt(day, 10));
delete inRec.birthday;
delete inRec.birthmonth;
delete inRec.birthyear;
}
else {
throw new ValueError("Wrong value of birthday!");
}
}
else if (key === "c") {
curRec = findSubElement("adr", "work");
curRec.countryName = inRec[key];
}
else if (key === "cn") {
contact.name = [inRec[key]];
}
else if (key === "description") {
contact.note = [inRec[key]];
}
else if (key === "facsimiletelephonenumber") {
curRec = findSubElement("tel", "fax");
curRec.value = cleanPhoneNumber(inRec[key]);
}
else if (key === "givenName") {
contact.givenName = [inRec[key]];
}
else if (key === "homePhone") {
curRec = findSubElement("tel", "home");
curRec.value = cleanPhoneNumber(inRec[key]);
}
else if (key === "l") {
curRec = findSubElement("adr", "work");
curRec.locality = inRec[key];
}
else if (key === "mail") {
curRec = findSubElement("email", "PREF");
curRec.value = inRec[key];
}
else if (key === "mozillaSecondEmail") {
curRec = findSubElement("email");
curRec.value = inRec[key];
}
else if (key === "mobile") {
curRec = findSubElement("tel", "mobile");
curRec.value = cleanPhoneNumber(inRec[key]);
}
else if (key === "mozillaHomeCountryName") {
curRec = findSubElement("adr", "home");
curRec.countryName = inRec[key];
}
else if (key === "mozillaHomeLocalityName") {
curRec = findSubElement("adr", "home");
curRec.locality = inRec[key];
}
else if (key === "mozillaHomePostalCode") {
curRec = findSubElement("adr", "home");
curRec.postalCode = inRec[key];
}
else if (key === "mozillaHomeState") {
curRec = findSubElement("adr", "home");
curRec.region = inRec[key];
}
else if (["mozillaHomeStreet", "mozillaHomeStreet2"].
indexOf(key) !== -1) {
secondLineInAddress("home", "streetAddress",
"mozillaHomeStreet", "mozillaHomeStreet2");
}
else if (key === "mozillaWorkUrl") {
curRec = findSubElement("url", "work");
curRec.value = inRec[key];
}
else if (key === "mozillaHomeUrl") {
curRec = findSubElement("url", "home");
curRec.value = inRec[key];
}
else if (key === "mozillaNickname") {
contact.nickname = [inRec[key]];
}
// Per W3C Contacts API
// http://www.w3.org/TR/contacts-manager-api/#widl-ContactProperties-org
// org of type array of DOMString
// A string or set thereof representing the organization(s)
// the contact belongs to. It maps to vCard's ORG attribute
//
// Per RFC 6350:
// =============
// 6.6.4. ORG
//
// Purpose: To specify the organizational name and units associated
// with the vCard.
//
// Value type: A single structured text value consisting of components
// separated by the SEMICOLON character (U+003B).
//
// Special notes: The property is based on the X.520 Organization Name
// and Organization Unit attributes [CCITT.X520.1988]. The property
// value is a structured type consisting of the organization name,
// followed by zero or more levels of organizational unit names.
//
// Example: A property value consisting of an organizational name,
// organizational unit #1 name, and organizational unit #2 name.
//
// ORG:ABC\, Inc.;North American Division;Marketing
else if (["o", "ou"].indexOf(key) !== -1) {
if ('ou' in inRec) {
if ('o' in inRec) {
contact.org = inRec.o + ";" + inRec.ou;
}
else {
throw new ValueError(
"Organizational unit without an organization!");
}
delete inRec.ou;
}
else {
contact.org = inRec.o;
}
delete inRec.o;
}
else if (key === "postalCode") {
curRec = findSubElement("adr", "work");
curRec.postalCode = inRec[key];
}
else if (key === "sn") {
contact.familyName = [inRec[key]];
}
else if (key === "st") {
curRec = findSubElement("adr", "work");
curRec.region = inRec[key];
}
else if (["street", "mozillaWorkStreet2"].
indexOf(key) !== -1) {
secondLineInAddress("work", "streetAddress",
"street", "mozillaWorkStreet2");
}
else if (key === "telephoneNumber") {
curRec = findSubElement("tel", "work");
curRec.value = cleanPhoneNumber(inRec[key]);
}
else if (key === "title") {
contact.jobTitle = [inRec[key]];
}
// Unknown attribute
else {
throw new ValueError("Unknown attribute " + key +
" with value:\n" + inRec[key]);
}
}
// Per RFC 4519 section 3.12 cn and sn attributes are always
// required
if (! contact.hasOwnProperty('name')) {
if (contact.hasOwnProperty('familyName') &&
contact.hasOwnProperty('givenName')) {
contact.name = contact.givenName + " " + contact.familyName;
}
else if (contact.hasOwnProperty("org")) {
contact.name = contact.org;
}
else if (contact.hasOwnProperty("jobTitle")) {
contact.name = contact.jobTitle;
}
}
if (Object.keys(contact).length > 0) {
var outObj = new mozContact();
outObj.init(contact);
return contact;
}
return null;
}