Page MenuHomePhorge

No OneTemporary

diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
index 3ad47a5cb..8cc390c7a 100644
--- a/program/include/rcube_vcard.php
+++ b/program/include/rcube_vcard.php
@@ -1,389 +1,418 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_vcard.php |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2008, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Logical representation of a vcard address record |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id: $
*/
/**
* Logical representation of a vcard-based address record
* Provides functions to parse and export vCard data format
*
* @package Addressbook
* @author Thomas Bruederli <roundcube@gmail.com>
*/
class rcube_vcard
{
private $raw = array(
'FN' => array(),
'N' => array(array('','','','','')),
);
public $business = false;
public $displayname;
public $surname;
public $firstname;
public $middlename;
public $nickname;
public $organization;
public $notes;
public $email = array();
/**
* Constructor
*/
public function __construct($vcard = null)
{
if (!empty($vcard))
$this->load($vcard);
}
/**
* Load record from (internal, unfolded) vcard 3.0 format
*
* @param string vCard string to parse
*/
public function load($vcard)
{
$this->raw = self::vcard_decode($vcard);
// find well-known address fields
$this->displayname = $this->raw['FN'][0];
$this->surname = $this->raw['N'][0][0];
$this->firstname = $this->raw['N'][0][1];
$this->middlename = $this->raw['N'][0][2];
$this->nickname = $this->raw['NICKNAME'][0];
$this->organization = $this->raw['ORG'][0];
$this->business = ($this->raw['X-ABShowAs'][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
foreach ((array)$this->raw['EMAIL'] as $i => $raw_email)
- $this->email[$i] = $raw_email[0];
+ $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email;
// make the pref e-mail address the first entry in $this->email
$pref_index = $this->get_type_index('EMAIL', 'pref');
if ($pref_index > 0) {
$tmp = $this->email[0];
$this->email[0] = $this->email[$pref_index];
$this->email[$pref_index] = $tmp;
}
}
/**
* Convert the data structure into a vcard 3.0 string
*/
public function export()
{
return self::rfc2425_fold(self::vcard_encode($this->raw));
}
/**
* Setter for address record fields
*
* @param string Field name
* @param string Field value
* @param string Section name
*/
public function set($field, $value, $section = 'HOME')
{
switch ($field) {
case 'name':
case 'displayname':
$this->raw['FN'][0] = $value;
break;
case 'firstname':
$this->raw['N'][0][1] = $value;
break;
case 'surname':
$this->raw['N'][0][0] = $value;
break;
case 'nickname':
$this->raw['NICKNAME'][0] = $value;
break;
case 'organization':
$this->raw['ORG'][0] = $value;
break;
case 'email':
$index = $this->get_type_index('EMAIL', $section);
if (!is_array($this->raw['EMAIL'][$index])) {
$this->raw['EMAIL'][$index] = array(0 => $value, 'type' => array('INTERNET', $section, 'pref'));
}
else {
$this->raw['EMAIL'][$index][0] = $value;
}
break;
}
}
/**
* Find index with the '$type' attribute
*
* @param string Field name
* @return int Field index having $type set
*/
private function get_type_index($field, $type = 'pref')
{
$result = 0;
if ($this->raw[$field]) {
foreach ($this->raw[$field] as $i => $data) {
if (is_array($data['type']) && in_array_nocase('pref', $data['type']))
$result = $i;
}
}
return $result;
}
/**
* Factory method to import a vcard file
*
* @param string vCard file content
* @return array List of rcube_vcard objects
*/
public static function import($data)
{
$out = array();
// detect charset and convert to utf-8
$encoding = self::detect_encoding($data);
if ($encoding && $encoding != RCMAIL_CHARSET) {
$data = rcube_charset_convert($data, $encoding);
}
$vcard_block = '';
$in_vcard_block = false;
foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
if ($in_vcard_block && !empty($line))
$vcard_block .= $line . "\n";
if (trim($line) == 'END:VCARD') {
// parse vcard
$obj = new rcube_vcard(self::cleanup($vcard_block));
if (!empty($obj->displayname))
$out[] = $obj;
$in_vcard_block = false;
}
else if (trim($line) == 'BEGIN:VCARD') {
$vcard_block = $line . "\n";
$in_vcard_block = true;
}
}
return $out;
}
/**
* Normalize vcard data for better parsing
*
* @param string vCard block
* @return string Cleaned vcard block
*/
private static function cleanup($vcard)
{
// Convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
$vcard = preg_replace(
'/item(\d+)\.(TEL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
'\2;type=\5\3:\4',
$vcard);
// Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines
$vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard);
// remove vcard 2.1 charset definitions
- $vcard = preg_replace('/;CHARSET=[^:]+/', '', $vcard);
+ $vcard = preg_replace('/;CHARSET=[^:;]+/', '', $vcard);
return $vcard;
}
private static function rfc2425_fold($val)
{
return preg_replace('/:([^\n]{72,})/e', '":\n ".rtrim(chunk_split("\\1", 72, "\n "))', $val) . "\n";
}
/**
* Decodes a vcard block (vcard 3.0 format, unfolded)
* into an array structure
*
* @param string vCard block to parse
* @return array Raw data structure
*/
private static function vcard_decode($vcard)
{
// Perform RFC2425 line unfolding
$vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard);
$data = array();
if (preg_match_all('/^([^\\:]*):(.+)$/m', $vcard, $regs, PREG_SET_ORDER)) {
foreach($regs as $line) {
- // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet,home:"
- if(($data['VERSION'][0] == "2.1") && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2) && !preg_match('/^TYPE=/i', $regs2[2])) {
- $line[1] = $regs2[1] . ";TYPE=" . strtr($regs2[2], array(";" => ","));
+ // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:"
+ if (($data['VERSION'][0] == "2.1") && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2) && !preg_match('/^TYPE=/i', $regs2[2])) {
+ $line[1] = $regs2[1];
+ foreach (explode(';', $regs2[2]) as $prop)
+ $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
}
if (!preg_match('/^(BEGIN|END)$/', $line[1]) && preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
$entry = array(self::vcard_unquote($line[2]));
foreach($regs2[1] as $attrid => $attr) {
- if ((list($key, $value) = explode('=', $attr)) && $value)
- $entry[strtolower($key)] = array_merge((array)$entry[strtolower($key)], (array)self::vcard_unquote($value, ','));
- elseif ($attrid > 0)
+ if ((list($key, $value) = explode('=', $attr)) && $value) {
+ if ($key == 'ENCODING')
+ $entry[0] = self::decode_value($entry[0], $value);
+ else
+ $entry[strtolower($key)] = array_merge((array)$entry[strtolower($key)], (array)self::vcard_unquote($value, ','));
+ }
+ else if ($attrid > 0) {
$entry[$key] = true; # true means attr without =value
+ }
}
$data[$regs2[1][0]][] = count($entry) > 1 ? $entry : $entry[0];
}
}
unset($data['VERSION']);
}
return $data;
}
/**
* Split quoted string
*
* @param string vCard string to split
* @param string Separator char/string
* @return array List with splitted values
*/
private static function vcard_unquote($s, $sep = ';')
{
// break string into parts separated by $sep, but leave escaped $sep alone
if (count($parts = explode($sep, strtr($s, array("\\$sep" => "\007")))) > 1) {
foreach($parts as $s) {
$result[] = self::vcard_unquote(strtr($s, array("\007" => "\\$sep")), $sep);
}
return $result;
}
else {
return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\,' => ',', '\;' => ';', '\:' => ':'));
}
}
+ /**
+ * Decode a given string with the encoding rule from ENCODING attributes
+ *
+ * @param string String to decode
+ * @param string Encoding type (quoted-printable and base64 supported)
+ * @return string Decoded 8bit value
+ */
+ private static function decode_value($value, $encoding)
+ {
+ switch (strtolower($encoding)) {
+ case 'quoted-printable':
+ return quoted_printable_decode($value);
+
+ case 'base64':
+ return base64_decode($value);
+
+ default:
+ return $value;
+ }
+ }
+
+
/**
* Encodes an entry for storage in our database (vcard 3.0 format, unfolded)
*
* @param array Raw data structure to encode
* @return string vCard encoded string
*/
static function vcard_encode($data)
{
foreach((array)$data as $type => $entries) {
/* valid N has 5 properties */
while ($type == "N" && count($entries[0]) < 5)
$entries[0][] = "";
foreach((array)$entries as $entry) {
$attr = '';
if (is_array($entry)) {
$value = array();
foreach($entry as $attrname => $attrvalues) {
if (is_int($attrname))
$value[] = $attrvalues;
elseif ($attrvalues === true)
$attr .= ";$attrname"; # true means just tag, not tag=value, as in PHOTO;BASE64:...
else {
foreach((array)$attrvalues as $attrvalue)
$attr .= ";$attrname=" . self::vcard_quote($attrvalue, ',');
}
}
}
else {
$value = $entry;
}
$vcard .= self::vcard_quote($type) . $attr . ':' . self::vcard_quote($value) . "\n";
}
}
return "BEGIN:VCARD\nVERSION:3.0\n{$vcard}END:VCARD";
}
/**
* Join indexed data array to a vcard quoted string
*
* @param array Field data
* @param string Separator
* @return string Joined and quoted string
*/
private static function vcard_quote($s, $sep = ';')
{
if (is_array($s)) {
foreach($s as $part) {
$r[] = self::vcard_quote($part, $sep);
}
return(implode($sep, (array)$r));
}
else {
return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ';' => '\;', ':' => '\:'));
}
}
/**
* Returns UNICODE type based on BOM (Byte Order Mark)
*
* @param string Input string to test
* @return string Detected encoding
*/
private static function detect_encoding($string)
{
if (substr($string, 0, 4) == "\0\0\xFE\xFF") return 'UTF-32BE'; // Big Endian
if (substr($string, 0, 4) == "\xFF\xFE\0\0") return 'UTF-32LE'; // Little Endian
if (substr($string, 0, 2) == "\xFE\xFF") return 'UTF-16BE'; // Big Endian
if (substr($string, 0, 2) == "\xFF\xFE") return 'UTF-16LE'; // Little Endian
if (substr($string, 0, 3) == "\xEF\xBB\xBF") return 'UTF-8';
// No match, check for UTF-8
// from http://w3.org/International/questions/qa-forms-utf-8.html
if (preg_match('/\A(
[\x09\x0A\x0D\x20-\x7E]
| [\xC2-\xDF][\x80-\xBF]
| \xE0[\xA0-\xBF][\x80-\xBF]
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
| \xED[\x80-\x9F][\x80-\xBF]
| \xF0[\x90-\xBF][\x80-\xBF]{2}
| [\xF1-\xF3][\x80-\xBF]{3}
| \xF4[\x80-\x8F][\x80-\xBF]{2}
)*\z/xs', substr($string, 0, 2048)))
return 'UTF-8';
- return null;
+ return 'ISO-8859-1'; # fallback to Latin-1
}
}
diff --git a/program/steps/addressbook/import.inc b/program/steps/addressbook/import.inc
index a0786e214..93452eccd 100644
--- a/program/steps/addressbook/import.inc
+++ b/program/steps/addressbook/import.inc
@@ -1,184 +1,185 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/addressbook/import.inc |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2008, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Import contacts from a vCard or CSV file |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id: $
*/
/**
* Handler function to display the import/upload form
*/
function rcmail_import_form($attrib)
{
global $RCMAIL, $OUTPUT;
$attrib += array('id' => "rcmImportForm");
$upload = new html_inputfield(array('type' => 'file', 'name' => '_file', 'id' => 'rcmimportfile', 'size' => 40));
$form = html::p(null, html::label('rcmimportfile', rcube_label('importfromfile')) . html::br() . $upload->show());
$check_replace = new html_checkbox(array('name' => '_replace', 'value' => 1, 'id' => 'rcmimportreplace'));
$form .= html::p(null, $check_replace->show(get_input_value('_replace', RCUBE_INPUT_GPC)) .
html::label('rcmimportreplace', rcube_label('importreplace')));
$OUTPUT->add_label('selectimportfile','importwait');
$OUTPUT->add_gui_object('importform', $attrib['id']);
$out = html::p(null, Q(rcube_label('importtext'), 'show'));
$out .= $OUTPUT->form_tag(array(
'action' => $RCMAIL->url('import'),
'method' => 'post',
'enctype' => 'multipart/form-data') + $attrib,
$form);
return $out;
}
/**
* Render the confirmation page for the import process
*/
function rcmail_import_confirm($attrib)
{
global $IMPORT_STATS;
$vars = get_object_vars($IMPORT_STATS);
$vars['names'] = join(', ', $IMPORT_STATS->names);
return html::p($attrib, Q(rcube_label(array(
'name' => 'importconfirm',
'nr' => $IMORT_STATS->inserted,
'vars' => $vars,
)), 'show'));
}
/**
* Create navigation buttons for the current import step
*/
function rcmail_import_buttons($attrib)
{
global $IMPORT_STATS, $OUTPUT;
$attrib += array('type' => "input");
unset($attrib['name']);
if (is_object($IMPORT_STATS)) {
$attrib['class'] = trim($attrib['class'] . ' mainaction');
$out = $OUTPUT->button(array('command' => "list", 'label' => "done") + $attrib);
}
else {
$out = $OUTPUT->button(array('command' => "list", 'label' => "cancel") + $attrib);
$out .= '&nbsp;';
$attrib['class'] = trim($attrib['class'] . ' mainaction');
$out .= $OUTPUT->button(array('command' => "import", 'label' => "import") + $attrib);
}
return $out;
}
/** The import process **/
$importstep = 'rcmail_import_form';
if ($_FILES['_file']['tmp_name'] && is_uploaded_file($_FILES['_file']['tmp_name'])) {
$replace = (bool)get_input_value('_replace', RCUBE_INPUT_GPC);
$CONTACTS = $RCMAIL->get_address_book(null, true);
// let rcube_vcard do the hard work :-)
$vcards = rcube_vcard::import(file_get_contents($_FILES['_file']['tmp_name']));
// no vcards detected
if (!count($vcards)) {
$OUTPUT->show_message('importerror', 'error');
}
else if ($CONTACTS->readonly) {
$OUTPUT->show_message('addresswriterror', 'error');
}
else {
$IMPORT_STATS = new stdClass;
$IMPORT_STATS->names = array();
$IMPORT_STATS->count = count($vcards);
$IMPORT_STATS->inserted = $IMPORT_STATS->skipped = $IMPORT_STATS->nomail = $IMPORT_STATS->errors = 0;
if ($replace)
$CONTACTS->delete_all();
foreach ($vcards as $vcard) {
$email = $vcard->email[0];
+ // skip entries without an e-mail address
+ if (empty($email)) {
+ $IMPORT_STATS->nomail++;
+ continue;
+ }
+
if (!$replace) {
// compare e-mail address
$existing = $CONTACTS->search('email', $email, false, false);
if (!$existing->count) { // compare display name
$existing = $CONTACTS->search('name', $vcard->displayname, false, false);
}
if ($existing->count) {
$IMPORT_STATS->skipped++;
continue;
}
}
- // skip entries without an e-mail address
- if (empty($email)) {
- $IMPORT_STATS->nomail++;
- continue;
- }
$success = $CONTACTS->insert(array(
'name' => $vcard->displayname,
'firstname' => $vcard->firstname,
'surname' => $vcard->surname,
'email' => $email,
'vcard' => $vcard->export(),
));
if ($success) {
$IMPORT_STATS->inserted++;
$IMPORT_STATS->names[] = $vcard->displayname;
}
else {
$IMPORT_STATS->errors++;
}
}
$importstep = 'rcmail_import_confirm';
}
}
else if ($err = $_FILES['_file']['error']) {
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
$OUTPUT->show_message('filesizeerror', 'error', array('size' => show_bytes(parse_bytes(ini_get('upload_max_filesize')))));
}
else {
$OUTPUT->show_message('fileuploaderror', 'error');
}
}
$OUTPUT->set_pagetitle(rcube_label('importcontacts'));
$OUTPUT->add_handlers(array(
'importstep' => $importstep,
'importnav' => 'rcmail_import_buttons',
));
// render page
$OUTPUT->send('importcontacts');
?>
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sat, Mar 1, 10:24 AM (11 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
166672
Default Alt Text
(19 KB)

Event Timeline