Support contacts import in GMail CSV format

pull/212/head
Aleksander Machniak 10 years ago
parent 0fddf7a6a2
commit 5f17657e82

@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- Support contacts import in GMail CSV format
- Added namespace filter in Folder Manager
- Added folder searching in Folder Manager
- Added config option 'imap_log_session' to enable Roundcube <-> IMAP session ID logging

@ -149,6 +149,13 @@ class rcube_csv2vcard
// GMail
'groups' => 'groups',
'group_membership' => 'groups',
'given_name' => 'firstname',
'additional_name' => 'middlename',
'family_name' => 'surname',
'name' => 'displayname',
'name_prefix' => 'prefix',
'name_suffix' => 'suffix',
);
/**
@ -272,12 +279,95 @@ class rcube_csv2vcard
'work_mobile' => "Work Mobile",
'work_title' => "Work Title",
'work_zip' => "Work Zip",
'groups' => "Group",
'group' => "Group",
// GMail
'groups' => "Groups",
'group_membership' => "Group Membership",
'given_name' => "Given Name",
'additional_name' => "Additional Name",
'family_name' => "Family Name",
'name' => "Name",
'name_prefix' => "Name Prefix",
'name_suffix' => "Name Suffix",
);
/**
* Special fields map for GMail format
*
* @var array
*/
protected $gmail_label_map = array(
'E-mail' => array(
'Value' => array(
'home' => 'email:home',
'work' => 'email:work',
),
),
'Phone' => array(
'Value' => array(
'home' => 'phone:home',
'homefax' => 'phone:homefax',
'main' => 'phone:pref',
'pager' => 'phone:pager',
'mobile' => 'phone:cell',
'work' => 'phone:work',
'workfax' => 'phone:workfax',
),
),
'Relation' => array(
'Value' => array(
'spouse' => 'spouse',
),
),
'Website' => array(
'Value' => array(
'profile' => 'website:profile',
'blog' => 'website:blog',
'homepage' => 'website:homepage',
'work' => 'website:work',
),
),
'Address' => array(
'Street' => array(
'home' => 'street:home',
'work' => 'street:work',
),
'City' => array(
'home' => 'locality:home',
'work' => 'locality:work',
),
'Region' => array(
'home' => 'region:home',
'work' => 'region:work',
),
'Postal Code' => array(
'home' => 'zipcode:home',
'work' => 'zipcode:work',
),
'Country' => array(
'home' => 'country:home',
'work' => 'country:work',
),
),
'Organization' => array(
'Name' => array(
'' => 'organization',
),
'Title' => array(
'' => 'jobtitle',
),
'Department' => array(
'' => 'department',
),
),
);
protected $local_label_map = array();
protected $vcards = array();
protected $map = array();
protected $vcards = array();
protected $map = array();
protected $gmail_map = array();
/**
@ -308,16 +398,24 @@ class rcube_csv2vcard
public function import($csv)
{
// convert to UTF-8
$head = substr($csv, 0, 4096);
$charset = rcube_charset::detect($head, RCUBE_CHARSET);
$csv = rcube_charset::convert($csv, $charset);
$head = '';
$head = substr($csv, 0, 4096);
$charset = rcube_charset::detect($head, RCUBE_CHARSET);
$csv = rcube_charset::convert($csv, $charset);
$csv = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $csv); // also remove BOM
$head = '';
$prev_line = false;
$this->map = array();
$this->map = array();
$this->gmail_map = array();
// Parse file
foreach (preg_split("/[\r\n]+/", $csv) as $line) {
if (!empty($prev_line)) {
$line = '"' . $line;
}
$elements = $this->parse_line($line);
if (empty($elements)) {
continue;
}
@ -331,7 +429,28 @@ class rcube_csv2vcard
}
// Parse data row
else {
// handle multiline elements (e.g. Gmail)
if (!empty($prev_line)) {
$first = array_shift($elements);
if ($first[0] == '"') {
$prev_line[count($prev_line)-1] = '"' . $prev_line[count($prev_line)-1] . "\n" . substr($first, 1);
}
else {
$prev_line[count($prev_line)-1] .= "\n" . $first;
}
$elements = array_merge($prev_line, $elements);
}
$last_element = $elements[count($elements)-1];
if ($last_element[0] == '"') {
$elements[count($elements)-1] = substr($last_element, 1);
$prev_line = $elements;
continue;
}
$this->csv_to_vcard($elements);
$prev_line = false;
}
}
}
@ -389,6 +508,7 @@ class rcube_csv2vcard
$map1[$i] = $this->csv2vcard_map[$label];
}
}
// check localized labels
if (!empty($this->local_label_map)) {
for ($i = 0; $i < $size; $i++) {
@ -406,6 +526,22 @@ class rcube_csv2vcard
}
$this->map = count($map1) >= count($map2) ? $map1 : $map2;
// support special Gmail format
foreach ($this->gmail_label_map as $key => $items) {
$num = 1;
while (($_key = "$key $num - Type") && ($found = array_search($_key, $elements)) !== false) {
$this->gmail_map["$key:$num"] = array('_key' => $key, '_idx' => $found);
foreach (array_keys($items) as $item_key) {
$_key = "$key $num - $item_key";
if (($found = array_search($_key, $elements)) !== false) {
$this->gmail_map["$key:$num"][$item_key] = $found;
}
}
$num++;
}
}
}
/**
@ -421,6 +557,22 @@ class rcube_csv2vcard
}
}
// Gmail format support
foreach ($this->gmail_map as $idx => $item) {
$type = preg_replace('/[^a-z]/', '', strtolower($data[$item['_idx']]));
$key = $item['_key'];
unset($item['_idx']);
unset($item['_key']);
foreach ($item as $item_key => $item_idx) {
$value = $data[$item_idx];
if ($value !== null && $value !== '' && ($data_idx = $this->gmail_label_map[$key][$item_key][$type])) {
$contact[$data_idx] = $value;
}
}
}
if (empty($contact)) {
return;
}
@ -430,9 +582,14 @@ class rcube_csv2vcard
$contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d'];
}
// categories/groups separator in vCard is ',' not ';'
if (!empty($contact['groups'])) {
// categories/groups separator in vCard is ',' not ';'
$contact['groups'] = str_replace(';', ',', $contact['groups']);
// remove "* " added by GMail
if (!empty($this->gmail_map)) {
$contact['groups'] = str_replace('* ', '', $contact['groups']);
}
}
// Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00"

@ -55,4 +55,22 @@ class Framework_Csv2vcard extends PHPUnit_Framework_TestCase
$vcard = trim(str_replace("\r\n", "\n", $vcard));
$this->assertEquals($vcf_text, $vcard);
}
function test_import_gmail()
{
$csv_text = file_get_contents(TESTS_DIR . '/src/Csv2vcard/gmail.csv');
$vcf_text = file_get_contents(TESTS_DIR . '/src/Csv2vcard/gmail.vcf');
$csv = new rcube_csv2vcard;
$csv->import($csv_text);
$result = $csv->export();
$vcard = $result[0]->export(false);
$this->assertCount(1, $result);
$vcf_text = trim(str_replace("\r\n", "\n", $vcf_text));
$vcard = trim(str_replace("\r\n", "\n", $vcard));
$this->assertEquals($vcf_text, $vcard);
}
}

Binary file not shown.
1 Name Given Name Additional Name Family Name Yomi Name Given Name Yomi Additional Name Yomi Family Name Yomi Name Prefix Name Suffix Initials Nickname Short Name Maiden Name Birthday Gender Location Billing Information Directory Server Mileage Occupation Hobby Sensitivity Priority Subject Notes Group Membership E-mail 1 - Type E-mail 1 - Value E-mail 2 - Type E-mail 2 - Value Phone 1 - Type Phone 1 - Value Phone 2 - Type Phone 2 - Value Phone 3 - Type Phone 3 - Value Phone 4 - Type Phone 4 - Value Phone 5 - Type Phone 5 - Value Phone 6 - Type Phone 6 - Value Phone 7 - Type Phone 7 - Value Address 1 - Type Address 1 - Formatted Address 1 - Street Address 1 - City Address 1 - PO Box Address 1 - Region Address 1 - Postal Code Address 1 - Country Address 1 - Extended Address Address 2 - Type Address 2 - Formatted Address 2 - Street Address 2 - City Address 2 - PO Box Address 2 - Region Address 2 - Postal Code Address 2 - Country Address 2 - Extended Address Organization 1 - Type Organization 1 - Name Organization 1 - Yomi Name Organization 1 - Title Organization 1 - Department Organization 1 - Symbol Organization 1 - Location Organization 1 - Job Description Relation 1 - Type Relation 1 - Value Website 1 - Type Website 1 - Value Website 2 - Type Website 2 - Value
2 Prefix Firstname Middle Lastname Suffix Firstname Middle Lastname Prefix Suffix nick 1975-12-12 note"note * My Contacts * Home home@aaa.pl Work work@email.pl Pager pager Main mainphone Home homephone Home Fax homefax Mobile mobile Work workphone Work Fax workfax Home home_street home_pobox home_city, home_state home_zip home_country home_street home_city home_pobox home_state home_zip home_country Work work_street work_city work_zip work_country work_street work_city work_zip work_country company jobtitle Spouse spouse Profile test.com Home Page home.page.com

@ -0,0 +1,25 @@
BEGIN:VCARD
VERSION:3.0
FN:Prefix Firstname Middle Lastname Suffix
N:Lastname;Firstname;Middle;Prefix;Suffix
NICKNAME:nick
BDAY;VALUE=date:1975-12-12
NOTE:note"note
CATEGORIES:My Contacts
EMAIL;TYPE=INTERNET;TYPE=HOME:home@aaa.pl
EMAIL;TYPE=INTERNET;TYPE=WORK:work@email.pl
TEL;TYPE=pager:pager
TEL;TYPE=pref:mainphone
TEL;TYPE=home:homephone
TEL;TYPE=homefax:homefax
TEL;TYPE=cell:mobile
TEL;TYPE=work:workphone
TEL;TYPE=workfax:workfax
X-SPOUSE:spouse
URL;TYPE=profile:test.com
URL;TYPE=homepage:home.page.com
ORG:company
TITLE:jobtitle
ADR;TYPE=home:;;home_street;home_city;home_state;home_zip;home_country
ADR;TYPE=work:;;work_street;work_city;;work_zip;work_country
END:VCARD
Loading…
Cancel
Save