From 2965a981b7ec22866fbdf2d567d87e2d068d3617 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Fri, 31 Jul 2015 16:04:08 -0400
Subject: [PATCH] Allow to search and import missing PGP pubkeys from keyservers using Publickey.js

---
 program/lib/Roundcube/rcube_csv2vcard.php |  325 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 302 insertions(+), 23 deletions(-)

diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php
index 9c28a3b..4b6e4fd 100644
--- a/program/lib/Roundcube/rcube_csv2vcard.php
+++ b/program/lib/Roundcube/rcube_csv2vcard.php
@@ -47,16 +47,16 @@
         //'business_street_2'     => '',
         //'business_street_3'     => '',
         'car_phone'             => 'phone:car',
-        'categories'            => 'categories',
+        'categories'            => 'groups',
         //'children'              => '',
         'company'               => 'organization',
         //'company_main_phone'    => '',
         'department'            => 'department',
-        //'email_2_address'       => '', //@TODO
+        'email_2_address'       => 'email:other',
         //'email_2_type'          => '',
-        //'email_3_address'       => '', //@TODO
+        'email_3_address'       => 'email:other',
         //'email_3_type'          => '',
-        'email_address'         => 'email:main',
+        'email_address'         => 'email:pref',
         //'email_type'            => '',
         'first_name'            => 'firstname',
         'gender'                => 'gender',
@@ -124,6 +124,38 @@
         //'work_address_2'        => '',
         'work_country'          => 'country:work',
         'work_zipcode'          => 'zipcode:work',
+        'last'                  => 'surname',
+        'first'                 => 'firstname',
+        'work_city'             => 'locality:work',
+        'work_state'            => 'region:work',
+        'home_city_short'       => 'locality:home',
+        'home_state_short'      => 'region:home',
+
+        // Atmail
+        'date_of_birth'         => 'birthday',
+        'email'                 => 'email:pref',
+        'home_mobile'           => 'phone:cell',
+        'home_zip'              => 'zipcode:home',
+        'info'                  => 'notes',
+        'user_photo'            => 'photo',
+        'url'                   => 'website:homepage',
+        'work_company'          => 'organization',
+        'work_dept'             => 'departament',
+        'work_fax'              => 'phone:work,fax',
+        'work_mobile'           => 'phone:work,cell',
+        'work_title'            => 'jobtitle',
+        'work_zip'              => 'zipcode:work',
+        'group'                 => 'groups',
+
+        // GMail
+        'groups'                => 'groups',
+        'group_membership'      => 'groups',
+        'given_name'            => 'firstname',
+        'additional_name'       => 'middlename',
+        'family_name'           => 'surname',
+        'name'                  => 'displayname',
+        'name_prefix'           => 'prefix',
+        'name_suffix'           => 'suffix',
     );
 
     /**
@@ -154,9 +186,9 @@
         //'company_main_phone' => "Company Main Phone",
         'department'        => "Department",
         //'directory_server'  => "Directory Server",
-        //'email_2_address'   => "E-mail 2 Address",
+        'email_2_address'   => "E-mail 2 Address",
         //'email_2_type'      => "E-mail 2 Type",
-        //'email_3_address'   => "E-mail 3 Address",
+        'email_3_address'   => "E-mail 3 Address",
         //'email_3_type'      => "E-mail 3 Type",
         'email_address'     => "E-mail Address",
         //'email_type'        => "E-mail Type",
@@ -224,13 +256,119 @@
         'work_phone'        => "Work Phone",
         'work_address'      => "Work Address",
         //'work_address_2'    => "Work Address 2",
+        'work_city'         => "Work City",
         'work_country'      => "Work Country",
+        'work_state'        => "Work State",
         'work_zipcode'      => "Work ZipCode",
+
+        // Atmail
+        'date_of_birth'     => "Date of Birth",
+        'email'             => "Email",
+        //'email_2'         => "Email2",
+        //'email_3'         => "Email3",
+        //'email_4'         => "Email4",
+        //'email_5'         => "Email5",
+        'home_mobile'       => "Home Mobile",
+        'home_zip'          => "Home Zip",
+        'info'              => "Info",
+        'user_photo'        => "User Photo",
+        'url'               => "URL",
+        'work_company'      => "Work Company",
+        'work_dept'         => "Work Dept",
+        'work_fax'          => "Work Fax",
+        'work_mobile'       => "Work Mobile",
+        'work_title'        => "Work Title",
+        'work_zip'          => "Work Zip",
+        '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',
+                '*'    => 'email:other',
+            ),
+        ),
+        '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();
 
 
     /**
@@ -261,22 +399,23 @@
     public function import($csv)
     {
         // convert to UTF-8
-        $head     = substr($csv, 0, 4096);
-        $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1?
-        $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 $i => $line) {
-            $line = trim($line);
-            if (empty($line)) {
-                continue;
+        foreach (preg_split("/[\r\n]+/", $csv) as $line) {
+            if (!empty($prev_line)) {
+                $line = '"' . $line;
             }
 
-            $elements = rcube_utils::explode_quoted_string(',', $line);
+            $elements = $this->parse_line($line);
 
             if (empty($elements)) {
                 continue;
@@ -291,7 +430,28 @@
             }
             // 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;
             }
         }
     }
@@ -302,6 +462,35 @@
     public function export()
     {
         return $this->vcards;
+    }
+
+    /**
+     * Parse CSV file line
+     */
+    protected function parse_line($line)
+    {
+        $line = trim($line);
+        if (empty($line)) {
+            return null;
+        }
+
+        $fields = rcube_utils::explode_quoted_string(',', $line);
+
+        // remove quotes if needed
+        if (!empty($fields)) {
+            foreach ($fields as $idx => $value) {
+                if (($len = strlen($value)) > 1 && $value[0] == '"' && $value[$len-1] == '"') {
+                    // remove surrounding quotes
+                    $value = substr($value, 1, -1);
+                    // replace doubled quotes inside the string with single quote
+                    $value = str_replace('""', '"', $value);
+
+                    $fields[$idx] = $value;
+                }
+            }
+        }
+
+        return $fields;
     }
 
     /**
@@ -320,10 +509,17 @@
                 $map1[$i] = $this->csv2vcard_map[$label];
             }
         }
+
         // check localized labels
         if (!empty($this->local_label_map)) {
             for ($i = 0; $i < $size; $i++) {
                 $label = $this->local_label_map[$elements[$i]];
+
+                // special localization label
+                if ($label && $label[0] == '_') {
+                    $label = substr($label, 1);
+                }
+
                 if ($label && !empty($this->csv2vcard_map[$label])) {
                     $map2[$i] = $this->csv2vcard_map[$label];
                 }
@@ -331,6 +527,22 @@
         }
 
         $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++;
+            }
+        }
     }
 
     /**
@@ -342,7 +554,41 @@
         foreach ($this->map as $idx => $name) {
             $value = $data[$idx];
             if ($value !== null && $value !== '') {
-                $contact[$name] = $value;
+                if (!empty($contact[$name])) {
+                    $contact[$name]   = (array) $contact[$name];
+                    $contact[$name][] = $value;
+                }
+                else {
+                   $contact[$name] = $value;
+                }
+            }
+        }
+
+        // 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 !== '') {
+                    foreach (array($type, '*') as $_type) {
+                        if ($data_idx = $this->gmail_label_map[$key][$item_key][$_type]) {
+                            $value = explode(' ::: ', $value);
+
+                            if (!empty($contact[$data_idx])) {
+                                $contact[$data_idx]   = array_merge((array) $contact[$data_idx], $value);
+                            }
+                            else {
+                                $contact[$data_idx] = $value;
+                            }
+                            break;
+                        }
+                    }
+                }
             }
         }
 
@@ -355,9 +601,26 @@
             $contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d'];
         }
 
+        if (!empty($contact['groups'])) {
+            // categories/groups separator in vCard is ',' not ';'
+            $contact['groups'] = str_replace(',', '', $contact['groups']);
+            $contact['groups'] = str_replace(';', ',', $contact['groups']);
+
+            if (!empty($this->gmail_map)) {
+                // remove "* " added by GMail
+                $contact['groups'] = str_replace('* ', '', $contact['groups']);
+                // replace strange delimiter
+                $contact['groups'] = str_replace(' ::: ', ',', $contact['groups']);
+            }
+        }
+
+        // Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00"
         foreach (array('birthday', 'anniversary') as $key) {
-            if (!empty($contact[$key]) && $contact[$key] == '0/0/00') { // @TODO: localization?
-                unset($contact[$key]);
+            if (!empty($contact[$key])) {
+                $date = preg_replace('/[0[:^word:]]/', '', $contact[$key]);
+                if (empty($date)) {
+                    unset($contact[$key]);
+                }
             }
         }
 
@@ -367,11 +630,27 @@
             }
         }
 
+        // Convert address(es) to rcube_vcard data
+        foreach ($contact as $idx => $value) {
+            $name = explode(':', $idx);
+            if (in_array($name[0], array('street', 'locality', 'region', 'zipcode', 'country'))) {
+                $contact['address:'.$name[1]][$name[0]] = $value;
+                unset($contact[$idx]);
+            }
+        }
+
         // Create vcard object
         $vcard = new rcube_vcard();
         foreach ($contact as $name => $value) {
             $name = explode(':', $name);
-            $vcard->set($name[0], $value, $name[1]);
+            if (is_array($value) && $name[0] != 'address') {
+                foreach ((array) $value as $val) {
+                    $vcard->set($name[0], $val, $name[1]);
+                }
+            }
+            else {
+                $vcard->set($name[0], $value, $name[1]);
+            }
         }
 
         // add to the list

--
Gitblit v1.9.1