Aleksander Machniak
2015-06-17 3f4521bcf4b538b6ac54817cfad22b51e347546d
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,17 +399,24 @@
    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) {
        foreach (preg_split("/[\r\n]+/", $csv) as $line) {
            if (!empty($prev_line)) {
                $line = '"' . $line;
            }
            $elements = $this->parse_line($line);
            if (empty($elements)) {
                continue;
            }
@@ -285,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;
            }
        }
    }
@@ -343,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];
                }
@@ -354,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++;
            }
        }
    }
    /**
@@ -365,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;
                        }
                    }
                }
            }
        }
@@ -378,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]);
                }
            }
        }
@@ -403,7 +643,14 @@
        $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