diff program/lib/Roundcube/rcube_csv2vcard.php @ 0:4681f974d28b

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:52:31 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/program/lib/Roundcube/rcube_csv2vcard.php	Thu Jan 04 15:52:31 2018 -0500
@@ -0,0 +1,663 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   CSV to vCard data conversion                                        |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * CSV to vCard data converter
+ *
+ * @package    Framework
+ * @subpackage Addressbook
+ * @author     Aleksander Machniak <alec@alec.pl>
+ */
+class rcube_csv2vcard
+{
+    /**
+     * CSV to vCard fields mapping
+     *
+     * @var array
+     */
+    protected $csv2vcard_map = array(
+        // MS Outlook 2010
+        'anniversary'           => 'anniversary',
+        'assistants_name'       => 'assistant',
+        'assistants_phone'      => 'phone:assistant',
+        'birthday'              => 'birthday',
+        'business_city'         => 'locality:work',
+        'business_countryregion' => 'country:work',
+        'business_fax'          => 'phone:work,fax',
+        'business_phone'        => 'phone:work',
+        'business_phone_2'      => 'phone:work2',
+        'business_postal_code'  => 'zipcode:work',
+        'business_state'        => 'region:work',
+        'business_street'       => 'street:work',
+        //'business_street_2'     => '',
+        //'business_street_3'     => '',
+        'car_phone'             => 'phone:car',
+        'categories'            => 'groups',
+        //'children'              => '',
+        'company'               => 'organization',
+        //'company_main_phone'    => '',
+        'department'            => 'department',
+        'email_2_address'       => 'email:other',
+        //'email_2_type'          => '',
+        'email_3_address'       => 'email:other',
+        //'email_3_type'          => '',
+        'email_address'         => 'email:pref',
+        //'email_type'            => '',
+        'first_name'            => 'firstname',
+        'gender'                => 'gender',
+        'home_city'             => 'locality:home',
+        'home_countryregion'    => 'country:home',
+        'home_fax'              => 'phone:home,fax',
+        'home_phone'            => 'phone:home',
+        'home_phone_2'          => 'phone:home2',
+        'home_postal_code'      => 'zipcode:home',
+        'home_state'            => 'region:home',
+        'home_street'           => 'street:home',
+        //'home_street_2'         => '',
+        //'home_street_3'         => '',
+        //'initials'              => '',
+        //'isdn'                  => '',
+        'job_title'             => 'jobtitle',
+        //'keywords'              => '',
+        //'language'              => '',
+        'last_name'             => 'surname',
+        //'location'              => '',
+        'managers_name'         => 'manager',
+        'middle_name'           => 'middlename',
+        //'mileage'               => '',
+        'mobile_phone'          => 'phone:cell',
+        'notes'                 => 'notes',
+        //'office_location'       => '',
+        'other_city'            => 'locality:other',
+        'other_countryregion'   => 'country:other',
+        'other_fax'             => 'phone:other,fax',
+        'other_phone'           => 'phone:other',
+        'other_postal_code'     => 'zipcode:other',
+        'other_state'           => 'region:other',
+        'other_street'          => 'street:other',
+        //'other_street_2'        => '',
+        //'other_street_3'        => '',
+        'pager'                 => 'phone:pager',
+        'primary_phone'         => 'phone:pref',
+        //'profession'            => '',
+        //'radio_phone'           => '',
+        'spouse'                => 'spouse',
+        'suffix'                => 'suffix',
+        'title'                 => 'title',
+        'web_page'              => 'website:homepage',
+
+        // Thunderbird
+        'birth_day'             => 'birthday-d',
+        'birth_month'           => 'birthday-m',
+        'birth_year'            => 'birthday-y',
+        'display_name'          => 'displayname',
+        'fax_number'            => 'phone:fax',
+        'home_address'          => 'street:home',
+        //'home_address_2'        => '',
+        'home_country'          => 'country:home',
+        'home_zipcode'          => 'zipcode:home',
+        'mobile_number'         => 'phone:cell',
+        'nickname'              => 'nickname',
+        'organization'          => 'organization',
+        'pager_number'          => 'phone:pager',
+        'primary_email'         => 'email:pref',
+        'secondary_email'       => 'email:other',
+        'web_page_1'            => 'website:homepage',
+        'web_page_2'            => 'website:other',
+        'work_phone'            => 'phone:work',
+        'work_address'          => 'street:work',
+        //'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',
+    );
+
+    /**
+     * CSV label to text mapping for English
+     *
+     * @var array
+     */
+    protected $label_map = array(
+        // MS Outlook 2010
+        'anniversary'       => "Anniversary",
+        'assistants_name'   => "Assistant's Name",
+        'assistants_phone'  => "Assistant's Phone",
+        'birthday'          => "Birthday",
+        'business_city'     => "Business City",
+        'business_countryregion' => "Business Country/Region",
+        'business_fax'      => "Business Fax",
+        'business_phone'    => "Business Phone",
+        'business_phone_2'  => "Business Phone 2",
+        'business_postal_code' => "Business Postal Code",
+        'business_state'    => "Business State",
+        'business_street'   => "Business Street",
+        //'business_street_2' => "Business Street 2",
+        //'business_street_3' => "Business Street 3",
+        'car_phone'         => "Car Phone",
+        'categories'        => "Categories",
+        //'children'          => "Children",
+        'company'           => "Company",
+        //'company_main_phone' => "Company Main Phone",
+        'department'        => "Department",
+        //'directory_server'  => "Directory Server",
+        'email_2_address'   => "E-mail 2 Address",
+        //'email_2_type'      => "E-mail 2 Type",
+        'email_3_address'   => "E-mail 3 Address",
+        //'email_3_type'      => "E-mail 3 Type",
+        'email_address'     => "E-mail Address",
+        //'email_type'        => "E-mail Type",
+        'first_name'        => "First Name",
+        'gender'            => "Gender",
+        'home_city'         => "Home City",
+        'home_countryregion' => "Home Country/Region",
+        'home_fax'          => "Home Fax",
+        'home_phone'        => "Home Phone",
+        'home_phone_2'      => "Home Phone 2",
+        'home_postal_code'  => "Home Postal Code",
+        'home_state'        => "Home State",
+        'home_street'       => "Home Street",
+        //'home_street_2'     => "Home Street 2",
+        //'home_street_3'     => "Home Street 3",
+        //'initials'          => "Initials",
+        //'isdn'              => "ISDN",
+        'job_title'         => "Job Title",
+        //'keywords'          => "Keywords",
+        //'language'          => "Language",
+        'last_name'         => "Last Name",
+        //'location'          => "Location",
+        'managers_name'     => "Manager's Name",
+        'middle_name'       => "Middle Name",
+        //'mileage'           => "Mileage",
+        'mobile_phone'      => "Mobile Phone",
+        'notes'             => "Notes",
+        //'office_location'   => "Office Location",
+        'other_city'        => "Other City",
+        'other_countryregion' => "Other Country/Region",
+        'other_fax'         => "Other Fax",
+        'other_phone'       => "Other Phone",
+        'other_postal_code' => "Other Postal Code",
+        'other_state'       => "Other State",
+        'other_street'      => "Other Street",
+        //'other_street_2'    => "Other Street 2",
+        //'other_street_3'    => "Other Street 3",
+        'pager'             => "Pager",
+        'primary_phone'     => "Primary Phone",
+        //'profession'        => "Profession",
+        //'radio_phone'       => "Radio Phone",
+        'spouse'            => "Spouse",
+        'suffix'            => "Suffix",
+        'title'             => "Title",
+        'web_page'          => "Web Page",
+
+        // Thunderbird
+        'birth_day'         => "Birth Day",
+        'birth_month'       => "Birth Month",
+        'birth_year'        => "Birth Year",
+        'display_name'      => "Display Name",
+        'fax_number'        => "Fax Number",
+        'home_address'      => "Home Address",
+        //'home_address_2'    => "Home Address 2",
+        'home_country'      => "Home Country",
+        'home_zipcode'      => "Home ZipCode",
+        'mobile_number'     => "Mobile Number",
+        'nickname'          => "Nickname",
+        'organization'      => "Organization",
+        'pager_number'      => "Pager Namber",
+        'primary_email'     => "Primary Email",
+        'secondary_email'   => "Secondary Email",
+        'web_page_1'        => "Web Page 1",
+        'web_page_2'        => "Web Page 2",
+        '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 $gmail_map       = array();
+
+
+    /**
+     * Class constructor
+     *
+     * @param string $lang File language
+     */
+    public function __construct($lang = 'en_US')
+    {
+        // Localize fields map
+        if ($lang && $lang != 'en_US') {
+            if (file_exists(RCUBE_LOCALIZATION_DIR . "$lang/csv2vcard.inc")) {
+                include RCUBE_LOCALIZATION_DIR . "$lang/csv2vcard.inc";
+            }
+
+            if (!empty($map)) {
+                $this->local_label_map = array_merge($this->label_map, $map);
+            }
+        }
+
+        $this->label_map = array_flip($this->label_map);
+        $this->local_label_map = array_flip($this->local_label_map);
+    }
+
+    /**
+     * Import contacts from CSV file
+     *
+     * @param string $csv Content of the CSV file
+     */
+    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);
+        $csv       = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $csv); // also remove BOM
+        $head      = '';
+        $prev_line = false;
+
+        $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;
+            }
+
+            // Parse header
+            if (empty($this->map)) {
+                $this->parse_header($elements);
+                if (empty($this->map)) {
+                    break;
+                }
+            }
+            // 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;
+            }
+        }
+    }
+
+    /**
+     * Export vCards
+     *
+     * @return array rcube_vcard List of vcards
+     */
+    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;
+    }
+
+    /**
+     * Parse CSV header line, detect fields mapping
+     */
+    protected function parse_header($elements)
+    {
+        $map1 = array();
+        $map2 = array();
+        $size = count($elements);
+
+        // check English labels
+        for ($i = 0; $i < $size; $i++) {
+            $label = $this->label_map[$elements[$i]];
+            if ($label && !empty($this->csv2vcard_map[$label])) {
+                $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];
+                }
+            }
+        }
+
+        $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++;
+            }
+        }
+    }
+
+    /**
+     * Convert CSV data row to vCard
+     */
+    protected function csv_to_vcard($data)
+    {
+        $contact = array();
+        foreach ($this->map as $idx => $name) {
+            $value = $data[$idx];
+            if ($value !== null && $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;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (empty($contact)) {
+            return;
+        }
+
+        // Handle special values
+        if (!empty($contact['birthday-d']) && !empty($contact['birthday-m']) && !empty($contact['birthday-y'])) {
+            $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])) {
+                $date = preg_replace('/[0[:^word:]]/', '', $contact[$key]);
+                if (empty($date)) {
+                    unset($contact[$key]);
+                }
+            }
+        }
+
+        if (!empty($contact['gender']) && ($gender = strtolower($contact['gender']))) {
+            if (!in_array($gender, array('male', 'female'))) {
+                unset($contact['gender']);
+            }
+        }
+
+        // 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);
+            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
+        $this->vcards[] = $vcard;
+    }
+}