You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1147 lines
35 KiB
1147 lines
35 KiB
<?php |
|
|
|
/* |
|
+-----------------------------------------------------------------------+ |
|
| vCard to LDIF/CSV Converter Class | |
|
| extends the PEAR Contact_Vcard_Parse Class | |
|
| | |
|
| Copyright (C) 2006-2013, Thomas Bruederli - Switzerland | |
|
| Licensed under the GNU GPL | |
|
| | |
|
+-----------------------------------------------------------------------+ |
|
| Author: Thomas Bruederli <thomas@brotherli.ch> | |
|
+-----------------------------------------------------------------------+ |
|
|
|
*/ |
|
|
|
// version 1.31 required |
|
require_once('Contact_Vcard_Parse.php'); |
|
|
|
|
|
/** |
|
* Typedef of a vCard object |
|
*/ |
|
class vCard |
|
{ |
|
var $version; |
|
var $displayname; |
|
var $surname; |
|
var $firstname; |
|
var $middlename; |
|
var $nickname; |
|
var $title; |
|
var $birthday; |
|
var $organization; |
|
var $department; |
|
var $jobtitle; |
|
var $home = array(); |
|
var $work = array(); |
|
var $countrycode; |
|
var $relatedname; |
|
var $email; |
|
var $email2; |
|
var $email3; |
|
var $pager; |
|
var $mobile; |
|
var $im = array(); |
|
var $notes; |
|
var $categories; |
|
var $uid; |
|
var $photo; |
|
} |
|
|
|
|
|
/** |
|
* vCard to LDIF/CSV Converter Class |
|
*/ |
|
class vcard_convert extends Contact_Vcard_Parse |
|
{ |
|
// define mapping for newline values |
|
static $NEWLINE_MAP = array( |
|
'\n' => "\n", |
|
'n' => "\n", |
|
'lf' => "\n", |
|
'\r' => "\r", |
|
'r' => "\r", |
|
'cr' => "\r", |
|
'rn' => "\r\n", |
|
'\r\n' => "\r\n", |
|
'crlf' => "\r\n", |
|
); |
|
|
|
var $parsed = array(); |
|
var $vcards = array(); |
|
var $file_charset = 'ISO-8859-1'; |
|
var $charset = 'ISO-8859-1'; |
|
var $export_count = 0; |
|
var $mailonly = false; |
|
var $phoneonly = false; |
|
var $accesscode = null; |
|
|
|
|
|
/** |
|
* Constructor taking a list of converter properties |
|
*/ |
|
function __construct($p = array()) |
|
{ |
|
foreach ($p as $prop => $value) |
|
$this->$prop = $value; |
|
} |
|
|
|
|
|
/** |
|
* Read a file and parse it |
|
* |
|
* @override |
|
*/ |
|
function fromFile($filename, $decode_qp = true) |
|
{ |
|
if (!filesize($filename) || ($text = $this->fileGetContents($filename)) === false) |
|
return false; |
|
|
|
// dump to, and get return from, the fromText() method. |
|
return $this->fromText($text, $decode_qp); |
|
} |
|
|
|
/** |
|
* Parse a given string for vCards |
|
* |
|
* @override |
|
*/ |
|
function fromText($text, $decode_qp = true) |
|
{ |
|
// check if charsets are specified (usually vcard version < 3.0 but this is not reliable) |
|
if (preg_match('/charset=/i', substr($text, 0, 2048))) |
|
$this->charset = null; |
|
// try to detect charset of the whole file |
|
else if ($encoding = vcard_convert::get_charset($text)) |
|
$this->charset = $this->file_charset = $encoding; |
|
|
|
// convert document to UTF-8 |
|
if (isset($this->charset) && $this->charset != 'UTF-8' && $this->charset != 'ISO-8859-1') |
|
{ |
|
$text = $this->utf8_convert($text); |
|
$this->charset = 'UTF-8'; |
|
} |
|
|
|
$this->parsed = parent::fromText($text, $decode_qp); |
|
if (!empty($this->parsed)) |
|
{ |
|
$this->normalize(); |
|
|
|
// after normalize() all values should be UTF-8 |
|
if (!isset($this->charset)) |
|
$this->charset = 'UTF-8'; |
|
|
|
return count($this->cards); |
|
} |
|
else |
|
return false; |
|
} |
|
|
|
|
|
/** |
|
* Convert the abstract vCard structure into address objects |
|
* |
|
* @access private |
|
*/ |
|
function normalize() |
|
{ |
|
$this->cards = array(); |
|
foreach($this->parsed as $i => $card) |
|
{ |
|
$vcard = new vCard; |
|
$vcard->version = (float)$card['VERSION'][0]['value'][0][0]; |
|
|
|
// convert all values to UTF-8 according to their charset param |
|
if (!isset($this->charset)) |
|
$card = $this->card2utf8($card); |
|
|
|
// extract names |
|
$names = $card['N'][0]['value']; |
|
$vcard->surname = trim($names[0][0]); |
|
$vcard->firstname = trim($names[1][0]); |
|
$vcard->middlename = trim($names[2][0]); |
|
$vcard->title = trim($names[3][0]); |
|
|
|
if (empty($vcard->title) && isset($card['TITLE'])) |
|
$vcard->title = trim($card['TITLE'][0]['value'][0][0]); |
|
|
|
$vcard->displayname = isset($card['FN']) ? trim($card['FN'][0]['value'][0][0]) : ''; |
|
$vcard->nickname = isset($card['NICKNAME']) ? trim($card['NICKNAME'][0]['value'][0][0]) : ''; |
|
|
|
// extract notes |
|
$vcard->notes = isset($card['NOTE']) ? ltrim($card['NOTE'][0]['value'][0][0]) : ''; |
|
|
|
// extract birthday and anniversary |
|
foreach (array('BDAY' => 'birthday', 'ANNIVERSARY' => 'anniversary', 'X-ANNIVERSARY' => 'anniversary') as $vcf => $propname) |
|
{ |
|
if (is_array($card[$vcf])) |
|
{ |
|
$temp = preg_replace('/[\-\.\/]/', '', $card[$vcf][0]['value'][0][0]); |
|
$vcard->$propname = array( |
|
'y' => substr($temp,0,4), |
|
'm' => substr($temp,4,2), |
|
'd' => substr($temp,6,2)); |
|
} |
|
} |
|
|
|
if (is_array($card['GENDER'])) |
|
$vcard->gender = $card['GENDER'][0]['value'][0][0]; |
|
else if (is_array($card['X-GENDER'])) |
|
$vcard->gender = $card['X-GENDER'][0]['value'][0][0]; |
|
|
|
if (!empty($vcard->gender)) |
|
$vcard->gender = strtoupper($vcard->gender[0]); |
|
|
|
// extract job_title |
|
if (is_array($card['TITLE'])) |
|
$vcard->jobtitle = $card['TITLE'][0]['value'][0][0]; |
|
|
|
// extract UID |
|
if (is_array($card['UID'])) |
|
$vcard->uid = $card['UID'][0]['value'][0][0]; |
|
|
|
// extract org and dep |
|
if (is_array($card['ORG']) && ($temp = $card['ORG'][0]['value'])) |
|
{ |
|
$vcard->organization = trim($temp[0][0]); |
|
$vcard->department = trim($temp[1][0]); |
|
} |
|
|
|
// extract urls |
|
if (is_array($card['URL'])) |
|
$this->parse_url($card['URL'], $vcard); |
|
|
|
// extract addresses |
|
if (is_array($card['ADR'])) |
|
$this->parse_adr($card['ADR'], $vcard); |
|
|
|
// extract phones |
|
if (is_array($card['TEL'])) |
|
$this->parse_tel($card['TEL'], $vcard); |
|
|
|
// read Apple Address Book proprietary fields |
|
for ($n = 1; $n <= 9; $n++) |
|
{ |
|
$prefix = 'ITEM'.$n; |
|
if (is_array($card["$prefix.TEL"])) { |
|
$this->parse_tel($card["$prefix.TEL"], $vcard); |
|
} |
|
if (is_array($card["$prefix.URL"])) { |
|
$this->parse_url($card["$prefix.URL"], $vcard); |
|
} |
|
if (is_array($card["$prefix.ADR"])) { |
|
$this->parse_adr($card["$prefix.ADR"], $vcard); |
|
} |
|
if (is_array($card["$prefix.X-ABADR"])) { |
|
$this->parse_cc($card["$prefix.X-ABADR"], $vcard); |
|
} |
|
if (is_array($card["$prefix.X-ABDATE"])) { |
|
$this->parse_abdate($card["$prefix.X-ABDATE"], $vcard, $card["$prefix.X-ABLABEL"][0]); |
|
} |
|
if (is_array($card["$prefix.X-ABRELATEDNAMES"])) { |
|
$this->parse_rn($card["$prefix.X-ABRELATEDNAMES"], $vcard /*, $card["$prefix.X-ABLABEL"][0]*/); |
|
} |
|
} |
|
|
|
// extract e-mail addresses |
|
$a_email = array(); |
|
$n = 0; |
|
if (is_array($card['EMAIL'])) { |
|
while (isset($card['EMAIL'][$n])) { |
|
$a_email[] = $card['EMAIL'][$n]['value'][0][0]; |
|
$n++; |
|
} |
|
} |
|
if ($n < 2) { //as only 3 e-mail address will be exported we don't need to search for more |
|
for ($n = 1; $n <= 9; $n++) { |
|
if (is_array($card["ITEM$n.EMAIL"])) |
|
{ |
|
$a_email[] = $card["ITEM$n.EMAIL"][0]['value'][0][0]; |
|
if (isset($card["ITEM$n.EMAIL"][1])) |
|
$a_email[] = $card["ITEM$n.EMAIL"][1]['value'][0][0]; |
|
} |
|
} |
|
} |
|
|
|
if (count($a_email)) |
|
$vcard->email = $a_email[0]; |
|
if (!empty($a_email[1])) |
|
$vcard->email2 = $a_email[1]; |
|
if (!empty($a_email[2])) |
|
$vcard->email3 = $a_email[2]; |
|
|
|
// find IM entries |
|
if (is_array($card['X-AIM'])) |
|
$vcard->im['aim'] = $card['X-AIM'][0]['value'][0][0]; |
|
if (is_array($card['X-IQC'])) |
|
$vcard->im['icq'] = $card['X-ICQ'][0]['value'][0][0]; |
|
if (is_array($card['X-MSN'])) |
|
$vcard->im['msn'] = $card['X-MSN'][0]['value'][0][0]; |
|
if (is_array($card['X-JABBER'])) |
|
$vcard->im['jabber'] = $card['X-JABBER'][0]['value'][0][0]; |
|
|
|
if (is_array($card['PHOTO'][0])) |
|
$vcard->photo = array('data' => $card['PHOTO'][0]['value'][0][0], 'encoding' => $card['PHOTO'][0]['param']['ENCODING'][0], 'type' => $card['PHOTO'][0]['param']['TYPE'][0]); |
|
|
|
$vcard->categories = join(',', (array)$card['CATEGORIES'][0]['value'][0]); |
|
|
|
$this->cards[] = $vcard; |
|
} |
|
} |
|
|
|
/** |
|
* Helper method to parse an URL node |
|
* |
|
* @access private |
|
*/ |
|
function parse_url(&$node, &$vcard) |
|
{ |
|
foreach($node as $url) |
|
{ |
|
if (empty($url['param']['TYPE'][0]) || in_array_nc("WORK", $url['param']['TYPE']) || in_array_nc("PREF", $url['param']['TYPE'])) |
|
$vcard->work['url'] = $url['value'][0][0]; |
|
if (in_array_nc("HOME", $url['param']['TYPE'])) |
|
$vcard->home['url'] = $url['value'][0][0]; |
|
} |
|
} |
|
|
|
/** |
|
* Helper method to parse first or preferred related name node (when available) |
|
* |
|
* @access private |
|
*/ |
|
function parse_rn(&$node, &$vcard, $ablabel = null) |
|
{ |
|
$key = $ablabel ? strtolower(trim($ablabel['value'][0][0], '_$!<>')) : null; |
|
if (empty($key)) |
|
$key = 'relatedname'; |
|
|
|
foreach($node as $rn) |
|
{ |
|
if (empty($vcard->{$key}) || in_array_nc("PREF", $rn['param']['TYPE'])) |
|
$vcard->{$key} = $rn['value'][0][0]; |
|
} |
|
} |
|
|
|
/** |
|
* Helper method to parse first or preferred country code (when available) |
|
* |
|
* @access private |
|
*/ |
|
function parse_cc(&$node, &$vcard) |
|
{ |
|
foreach($node as $cc) |
|
{ |
|
if (empty($vcard->countrycode) || in_array_nc("PREF", $cc['param']['TYPE'])) |
|
$vcard->countrycode = $cc['value'][0][0]; |
|
} |
|
} |
|
|
|
/** |
|
* Helper method to parse an address node |
|
* |
|
* @access private |
|
*/ |
|
function parse_adr(&$node, &$vcard) |
|
{ |
|
$home = array(); |
|
$work = array(); |
|
|
|
foreach($node as $adr) |
|
{ |
|
if (empty($adr['param']['TYPE'][0]) || in_array_nc("HOME", $adr['param']['TYPE'])) |
|
$home = $adr['value']; |
|
if (in_array_nc("WORK", $adr['param']['TYPE'])) |
|
$work = $adr['value']; |
|
} |
|
|
|
// values not splitted by Contact_Vcard_Parse if key is like item1.ADR |
|
if (strstr($home[0][0], ';')) |
|
{ |
|
$temp = explode(';', $home[0][0]); |
|
$vcard->home += array( |
|
'addr1' => $temp[2], |
|
'city' => $temp[3], |
|
'state' => $temp[4], |
|
'zipcode' => $temp[5], |
|
'country' => $temp[6]); |
|
} |
|
else if (sizeof($home)>6) |
|
{ |
|
$vcard->home += array( |
|
'addr1' => $home[2][0], |
|
'city' => $home[3][0], |
|
'state' => $home[4][0], |
|
'zipcode' => $home[5][0], |
|
'country' => $home[6][0]); |
|
} |
|
|
|
// values not splitted by Contact_Vcard_Parse if key is like item1.ADR |
|
if (strstr($work[0][0], ';')) |
|
{ |
|
$temp = explode(';', $work[0][0]); |
|
$vcard->work += array( |
|
'office' => $temp[1], |
|
'addr1' => $temp[2], |
|
'city' => $temp[3], |
|
'state' => $temp[4], |
|
'zipcode' => $temp[5], |
|
'country' => $temp[6]); |
|
} |
|
else if (sizeof($work)>6) |
|
{ |
|
$vcard->work += array( |
|
'addr1' => $work[2][0], |
|
'city' => $work[3][0], |
|
'state' => $work[4][0], |
|
'zipcode' => $work[5][0], |
|
'country' => $work[6][0]); |
|
} |
|
} |
|
|
|
/** |
|
* Helper method to parse an phone number node |
|
* |
|
* @access private |
|
*/ |
|
function parse_tel(&$node, &$vcard) |
|
{ |
|
foreach($node as $tel) |
|
{ |
|
if (in_array_nc("PAGER", $tel['param']['TYPE'])) |
|
$vcard->pager = $tel['value'][0][0]; |
|
else if (in_array_nc("CELL", $tel['param']['TYPE'])) |
|
$vcard->mobile = $tel['value'][0][0]; |
|
else if (in_array_nc("HOME", $tel['param']['TYPE']) || in_array_nc("PREF", $tel['param']['TYPE'])) |
|
{ |
|
if (in_array_nc("FAX", $tel['param']['TYPE'])) |
|
$vcard->home['fax'] = $tel['value'][0][0]; |
|
else |
|
$vcard->home['phone'] = $tel['value'][0][0]; |
|
} |
|
else if (in_array_nc("WORK", $tel['param']['TYPE'])) |
|
{ |
|
if(in_array_nc("FAX", $tel['param']['TYPE'])) |
|
$vcard->work['fax'] = $tel['value'][0][0]; |
|
else |
|
$vcard->work['phone'] = $tel['value'][0][0]; |
|
} |
|
else if (in_array_nc("VOICE", $tel['param']['TYPE'])) |
|
{ |
|
if (!isset($vcard->home['phone'])) |
|
$vcard->home['phone'] = $tel['value'][0][0]; |
|
else if (!isset($vcard->work['phone'])) |
|
$vcard->work['phone'] = $tel['value'][0][0]; |
|
else if (!isset($vcard->mobile)) |
|
$vcard->mobile = $tel['value'][0][0]; |
|
} |
|
|
|
if (in_array_nc("FAX", $tel['param']['TYPE'])) |
|
$vcard->fax = $tel['value'][0][0]; |
|
} |
|
} |
|
|
|
/** |
|
* Helper method to parse X-ABDATE nodes |
|
* |
|
* @access private |
|
*/ |
|
function parse_abdate(&$node, &$vcard, $ablabel = null) |
|
{ |
|
$key = $ablabel ? strtolower(trim($ablabel['value'][0][0], '_$!<>')) : null; |
|
if (empty($key)) |
|
$key = '_x_abdate'; |
|
|
|
foreach($node as $abdate) |
|
{ |
|
if (empty($vcard->{$key}) && preg_match('/^(\d{4})-(\d{2})-(\d{2})/', $abdate['value'][0][0], $m)) |
|
{ |
|
$vcard->{$key} = array( |
|
'y' => intval($m[1]), |
|
'm' => intval($m[2]), |
|
'd' => intval($m[3]), |
|
); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Convert the parsed vCard data into CSV format |
|
*/ |
|
function toCSV($delm="\t", $add_title=true, $encoding=null, $newlines="\n") |
|
{ |
|
$out = ''; |
|
$eol = isset(self::$NEWLINE_MAP[$newlines]) ? self::$NEWLINE_MAP[$newlines] : $newlines; |
|
$this->export_count = 0; |
|
|
|
if ($add_title) |
|
{ |
|
$out .= 'First Name'.$delm.'Last Name'.$delm.'Display Name'.$delm.'Nickname'.$delm.'E-mail Address'.$delm.'E-mail 2 Address'.$delm.'E-mail 3 Address'.$delm; |
|
$out .= 'Home Phone'.$delm.'Business Phone'.$delm.'Home Fax'.$delm.'Business Fax'.$delm.'Pager'.$delm.'Mobile Phone'.$delm; |
|
$out .= 'Home Street'.$delm.'Home Address 2'.$delm.'Home City'.$delm.'Home State'.$delm.'Home Postal Code'.$delm.'Home Country'.$delm; |
|
$out .= 'Business Address'.$delm.'Business Address 2'.$delm.'Business City'.$delm.'Business State'.$delm.'Business Postal Code'.$delm; |
|
$out .= 'Business Country'.$delm.'Country Code'.$delm.'Related name'.$delm.'Job Title'.$delm.'Department'.$delm.'Organization'.$delm.'Notes'.$delm; |
|
$out .= 'Birthday'.$delm.'Anniversary'.$delm.'Gender'.$delm; |
|
$out .= 'Web Page'.$delm.'Web Page 2'.$delm.'Categories'; |
|
$out .= $eol; |
|
} |
|
|
|
foreach ($this->cards as $card) |
|
{ |
|
if ($this->mailonly && empty($card->email) && empty($card->email2) && empty($card->email3)) |
|
continue; |
|
if ($this->phoneonly && empty($card->home['phone']) && empty($card->work['phone']) && empty($card->mobile)) |
|
continue; |
|
|
|
$out .= $this->csv_encode($card->firstname, $delm); |
|
$out .= $this->csv_encode($card->surname, $delm); |
|
$out .= $this->csv_encode($card->displayname, $delm); |
|
$out .= $this->csv_encode($card->nickname, $delm); |
|
$out .= $this->csv_encode($card->email, $delm); |
|
$out .= $this->csv_encode($card->email2, $delm); |
|
$out .= $this->csv_encode($card->email3, $delm); |
|
$out .= $this->csv_encode($this->normalize_phone($card->home['phone']), $delm); |
|
$out .= $this->csv_encode($this->normalize_phone($card->work['phone']), $delm); |
|
$out .= $this->csv_encode($this->normalize_phone($card->home['fax']), $delm); |
|
$out .= $this->csv_encode($this->normalize_phone($card->work['fax']), $delm); |
|
$out .= $this->csv_encode($this->normalize_phone($card->pager), $delm); |
|
$out .= $this->csv_encode($this->normalize_phone($card->mobile), $delm); |
|
$out .= $this->csv_encode($card->home['addr1'], $delm); |
|
$out .= $this->csv_encode($card->home['addr2'], $delm); |
|
$out .= $this->csv_encode($card->home['city'], $delm); |
|
$out .= $this->csv_encode($card->home['state'], $delm); |
|
$out .= $this->csv_encode($card->home['zipcode'], $delm); |
|
$out .= $this->csv_encode($card->home['country'], $delm); |
|
$out .= $this->csv_encode($card->work['addr1'], $delm); |
|
$out .= $this->csv_encode($card->work['addr2'], $delm); |
|
$out .= $this->csv_encode($card->work['city'], $delm); |
|
$out .= $this->csv_encode($card->work['state'], $delm); |
|
$out .= $this->csv_encode($card->work['zipcode'], $delm); |
|
$out .= $this->csv_encode($card->work['country'], $delm); |
|
$out .= $this->csv_encode($card->countrycode, $delm); |
|
$out .= $this->csv_encode($card->relatedname, $delm); |
|
$out .= $this->csv_encode($card->jobtitle, $delm); |
|
$out .= $this->csv_encode($card->department, $delm); |
|
$out .= $this->csv_encode($card->organization, $delm); |
|
$out .= $this->csv_encode($card->notes, $delm); |
|
$out .= !empty($card->birthday) ? $this->csv_encode(sprintf('%04d-%02d-%02d 00:00:00', $card->birthday['y'], $card->birthday['m'], $card->birthday['d']), $delm) : $delm; |
|
$out .= !empty($card->anniversary) ? $this->csv_encode(sprintf('%04d-%02d-%02d', $card->anniversary['y'], $card->anniversary['m'], $card->anniversary['d']), $delm) : $delm; |
|
$out .= $this->csv_encode($card->gender, $delm); |
|
$out .= $this->csv_encode($card->work['url'], $delm); |
|
$out .= $this->csv_encode($card->home['url'], $delm); |
|
$out .= $this->csv_encode($card->categories, $delm, false); |
|
|
|
$out .= $eol; |
|
$this->export_count++; |
|
} |
|
|
|
return $this->charset_convert($out, $encoding); |
|
} |
|
|
|
/** |
|
* New GMail export function |
|
* |
|
* @author Thomas Bruederli |
|
* @author Max Plischke <plischke@gmail.com> |
|
*/ |
|
function toGmail() |
|
{ |
|
$delm = ','; |
|
$this->export_count = 0; |
|
$out = "Name,E-mail,Notes,Section 1 - Description,Section 1 - Email,". |
|
"Section 1 - IM,Section 1 - Phone,Section 1 - Mobile,". |
|
"Section 1 - Pager,Section 1 - Fax,Section 1 - Company,". |
|
"Section 1 - Title,Section 1 - Other,Section 1 - Address,". |
|
"Section 2 - Description,Section 2 - Email,Section 2 - IM,". |
|
"Section 2 - Phone,Section 2 - Mobile,Section 2 - Pager,". |
|
"Section 2 - Fax,Section 2 - Company,Section 2 - Title,". |
|
"Section 2 - Other,Section 2 - Address\n"; |
|
|
|
foreach ($this->cards as $card) |
|
{ |
|
if ($this->mailonly && empty($card->email) && empty($card->email2)) |
|
continue; |
|
if ($this->phoneonly && empty($card->home['phone']) && empty($card->work['phone']) && empty($card->mobile)) |
|
continue; |
|
|
|
$home = array($card->home['addr1'], $card->home['city']); |
|
if ($card->home['state']) $home[] = $card->home['state']; |
|
if ($card->home['zipcode']) $home[] = $card->home['zipcode']; |
|
if ($card->home['country']) $home[] = $card->home['country']; |
|
|
|
$work = array($card->work['addr1'], $card->work['city']); |
|
if ($card->work['state']) $work[] = $card->work['state']; |
|
if ($card->work['zipcode']) $work[] = $card->work['zipcode']; |
|
if ($card->work['country']) $work[] = $card->work['country']; |
|
|
|
$im = array_values($card->im); |
|
|
|
$out .= $this->csv_encode($card->displayname, $delm); |
|
$out .= $this->csv_encode($card->email, $delm); // main |
|
$out .= $this->csv_encode($card->notes, $delm); // Notes |
|
|
|
$out .= $this->csv_encode('Home', $delm); |
|
$out .= $this->csv_encode('', $delm); // home email ? |
|
$out .= $this->csv_encode($im[0], $delm); // IM |
|
$out .= $this->csv_encode($this->normalize_phone($card->home['phone']), $delm); |
|
$out .= $this->csv_encode($this->normalize_phone($card->mobile), $delm); |
|
$out .= $this->csv_encode($this->normalize_phone($card->pager), $delm); |
|
$out .= $this->csv_encode($this->normalize_phone($card->home['fax']), $delm); |
|
$out .= $this->csv_encode('', $delm); // |
|
$out .= /* $card['title'] . */ $delm; |
|
$out .= $this->csv_encode('', $delm); // other |
|
$out .= $this->csv_encode(join(' ', $home), $delm); |
|
|
|
$out .= $this->csv_encode('Work', $delm); |
|
$out .= $this->csv_encode($card->email2, $delm); // work email |
|
$out .= $this->csv_encode($im[1], $delm); // IM |
|
$out .= $this->csv_encode($this->normalize_phone($card->work['phone']), $delm); |
|
$out .= $this->csv_encode('', $delm); // |
|
$out .= $this->csv_encode('', $delm); // |
|
$out .= $this->csv_encode($this->normalize_phone($card->work['fax']), $delm); // work fax |
|
$out .= $this->csv_encode($card->organization, $delm); |
|
$out .= $this->csv_encode($card->jobtitle, $delm); // title |
|
$out .= $this->csv_encode($card->department, $delm); |
|
$out .= $this->csv_encode(join(' ', $work), $delm); |
|
|
|
//$out .= $this->csv_encode($card->nick, $delm); |
|
//$out .= $this->csv_encode($card->home['url'], $delm); |
|
//$out .= $this->csv_encode($card->work['url'], $delm, FALSE); |
|
|
|
$out .= "\n"; |
|
$this->export_count++; |
|
} |
|
|
|
return $out; |
|
} |
|
|
|
/** |
|
* Convert the parsed vCard data into libdlusb format |
|
* |
|
* @author Kevin Clement <donkjunk@softhome.net> |
|
*/ |
|
function toLibdlusb() |
|
{ |
|
$delm="; "; |
|
$out = ''; |
|
$this->export_count = 0; |
|
|
|
foreach ($this->cards as $card) |
|
{ |
|
if ($this->mailonly && empty($card->email) && empty($card->email2)) |
|
continue; |
|
if ($this->phoneonly && empty($card->home['phone']) && empty($card->work['phone']) && empty($card->mobile)) |
|
continue; |
|
|
|
// a little ugly but this filters out files that only have incompatible data to prevent "blank" files |
|
if (empty($card->home['phone']) && empty($card->work['phone']) && empty($card->email) && empty($card->mobile)) |
|
continue; |
|
|
|
// having determined there is data that needs exporting this |
|
// makes certain we don't have holes to save watch memory |
|
$out .= $this->csv_encode($card->displayname, $delm); |
|
if ($card->home['phone'] != '') |
|
{ |
|
$out .= 'Home = '; |
|
$out .= $this->csv_encode($this->normalize_phone($card->home['phone']), $delm); |
|
} |
|
if ($card->work['phone'] != '') |
|
{ |
|
$out .= 'Work = '; |
|
$out .= $this->csv_encode($this->normalize_phone($card->work['phone']), $delm); |
|
} |
|
if ($card->email != '') |
|
{ |
|
$out .= 'Email = '; |
|
$out .= $this->csv_encode($card->email, $delm); |
|
} |
|
if($card->mobile != '') |
|
{ |
|
$out .= 'Mobile = '; |
|
$out .= $this->csv_encode($this->normalize_phone($card->mobile), $delm); |
|
} |
|
|
|
$out .= "\n"; |
|
$this->export_count++; |
|
} |
|
|
|
// convert to ISO-8859-1 |
|
//if ($encoding == 'ISO-8859-1' && $this->charset == 'UTF-8' && function_exists('utf8_decode')) |
|
// $out = utf8_decode($out); |
|
|
|
return $out; |
|
} |
|
|
|
|
|
/** |
|
* Export cards as Ldif/LDAP Ldif |
|
*/ |
|
function toLdif($identifier="", $attrib=null, $encoding="UTF-8") |
|
{ |
|
$out = ''; |
|
$this->export_count = 0; |
|
|
|
foreach($this->cards as $card) |
|
{ |
|
if ($this->mailonly && empty($card->email) && empty($card->email2)) |
|
continue; |
|
if ($this->phoneonly && empty($card->home['phone']) && empty($card->work['phone']) && empty($card->mobile) && empty($card->fax)) |
|
continue; |
|
|
|
// If we will export LDIF for an LDAP server, some checks and tweaks are needed |
|
if ($identifier != "") |
|
{ |
|
$card->displayname = trim($card->displayname); |
|
|
|
// Generate a random UID for LDAP Ldif if not present |
|
if (empty($card->uid)) |
|
$card->uid = uniqid('card-id-'); |
|
|
|
if (empty($card->displayname)) |
|
$card->displayname = trim($card->firstname.' '.$card->surname); |
|
|
|
if (empty($card->displayname)) |
|
$card->displayname = trim($card->nickname); |
|
|
|
if (empty($card->displayname)) |
|
$card->displayname = trim($card->organization); |
|
|
|
if (empty($card->displayname)) |
|
$card->displayname = $card->uid; |
|
|
|
if (empty($card->surname)) |
|
$card->surname = $card->displayname; |
|
} |
|
|
|
$a_out = array(); |
|
|
|
if ($identifier == "") |
|
$a_out['dn'] = sprintf("cn=%s,mail=%s", $card->displayname, $card->email); |
|
else |
|
$a_out['dn'] = sprintf("uid=%s,%s", $card->uid, $identifier); |
|
|
|
$a_out['objectclass'] = array('top', 'person', 'organizationalPerson', 'inetOrgPerson', 'mozillaAbPersonAlpha'); |
|
|
|
$a_out['cn'] = $card->displayname; |
|
$a_out['sn'] = $card->surname; |
|
|
|
if ($card->uid) |
|
$a_out['uid'] = $card->uid; |
|
if ($card->firstname) |
|
$a_out['givenName'] = $card->firstname; |
|
if ($card->title) |
|
$a_out['title'] = $card->title; |
|
if ($card->jobtitle) |
|
$a_out['employeeType'] = $card->jobtitle; |
|
if ($card->email) |
|
$a_out['mail'] = $card->email; |
|
// Get the binary for the photo of type jpeg |
|
// FIXME: ? According to the specs, only JFIF formats should be used |
|
// but some clients read even PNG from the jpegPhoto attr. Should we be |
|
// so restrictive here? |
|
if ($card->photo && strtolower($card->photo['type']) == "jpeg") |
|
$a_out['jpegPhoto'] = base64_decode(preg_replace('/\s+/', '', $card->photo['data'])); |
|
if ($card->nickname) |
|
$a_out['mozillaNickname'] = $card->nickname; |
|
if ($card->email2) |
|
$a_out['mozillaSecondEmail'] = $card->email2; |
|
if ($card->home['phone']) |
|
$a_out['homePhone'] = $this->normalize_phone($card->home['phone']); |
|
if ($card->mobile) |
|
$a_out['mobile'] = $this->normalize_phone($card->mobile); |
|
if ($card->fax) |
|
$a_out['fax'] = $this->normalize_phone($card->fax); |
|
if ($card->pager) |
|
$a_out['pager'] = $this->normalize_phone($card->pager); |
|
if ($card->home['addr1']) |
|
$a_out['mozillaHomeStreet'] = $card->home['addr1']; |
|
if ($card->home['city']) |
|
$a_out['mozillaHomeLocalityName'] = $card->home['city']; |
|
if ($card->home['state']) |
|
$a_out['mozillaHomeState'] = $card->home['state']; |
|
if ($card->home['zipcode']) |
|
$a_out['mozillaHomePostalCode'] = $card->home['zipcode']; |
|
if ($card->home['country']) |
|
$a_out['mozillaHomeCountryName'] = $card->home['country']; |
|
if ($card->organization) |
|
$a_out['o'] = $card->organization; |
|
if ($card->department) |
|
$a_out['departmentNumber'] = $card->department; |
|
if ($card->work['addr1']) |
|
$a_out['street'] = $card->work['addr1']; |
|
if ($card->work['city']) |
|
$a_out['l'] = $card->work['city']; |
|
if ($card->work['state']) |
|
$a_out['st'] = $card->work['state']; |
|
if ($card->work['zipcode']) |
|
$a_out['postalCode'] = $card->work['zipcode']; |
|
if ($card->work['country'] && strlen($card->work['country']) == 2) |
|
$a_out['c'] = $card->work['country']; |
|
if ($card->work['phone']) |
|
$a_out['telephoneNumber'] = $this->normalize_phone($card->work['phone']); |
|
if ($card->work['fax']) |
|
$a_out['fax'] = $this->normalize_phone($card->work['fax']); |
|
else if ($card->home['fax']) |
|
$a_out['fax'] = $this->normalize_phone($card->home['fax']); |
|
if ($card->work['url']) |
|
$a_out['mozillaWorkUrl'] = $card->work['url']; |
|
if ($card->home['url']) |
|
$a_out['mozillaHomeUrl'] = $card->home['url']; |
|
if ($card->notes) |
|
$a_out['description'] = $card->notes; |
|
if ($card->birthday) { |
|
$a_out['birthyear'] = $card->birthday['y']; |
|
$a_out['mozillaCustom1'] = sprintf("%04d-%02d-%02d", $card->birthday['y'], $card->birthday['m'], $card->birthday['d']); |
|
} |
|
|
|
// only return a single attribute (e.g. dn) |
|
if (!empty($attrib)) |
|
{ |
|
if (!isset($a_out[$attrib])) |
|
continue; |
|
$val = is_array($a_out[$attrib]) ? $a_out[$attrib][0] : $a_out[$attrib]; |
|
$out .= trim($this->ldif_encode($val, $enc, true)); |
|
} |
|
else |
|
{ |
|
// compose ldif output |
|
foreach ($a_out as $key => $val) |
|
{ |
|
$enc = $key == 'dn' ? 'UTF-8' : $encoding; |
|
if (is_array($val)) |
|
foreach ($val as $i => $val2) |
|
$out .= sprintf("%s: %s\n", $key, $this->ldif_encode($val2, $enc)); |
|
else |
|
$out .= sprintf("%s:%s\n", $key, $this->ldif_encode($val, $enc)); |
|
} |
|
} |
|
|
|
$out .= "\n"; |
|
$this->export_count++; |
|
} |
|
|
|
return $out; |
|
} |
|
|
|
/** |
|
* Convert the parsed vCard data into CSV format for FritzBox |
|
* |
|
* @author Thomas Bruederli |
|
* @author Gerd Mueller <gerd@zeltnerweg9.ch> |
|
*/ |
|
function toFritzBox() |
|
{ |
|
$delm=";"; |
|
$out = 'sep='.$delm."\r\n"; |
|
$this->export_count = 0; |
|
|
|
$out .= 'Name'.$delm. |
|
'TelNumHome'.$delm.'VanityHome'.$delm.'KurzWahlHome'.$delm. |
|
'TelNumWork'.$delm.'VanityWork'.$delm.'KurzWahlWork'.$delm. |
|
'TelNumMobile'.$delm.'VanityMobile'.$delm.'KurzWahlMobile'.$delm. |
|
'Kommentar'.$delm.'Firma'.$delm.'Bild'.$delm.'Kategorie'.$delm.'ImageUrl'.$delm. |
|
'Prio'.$delm.'Email'.$delm.'RingTone'.$delm.'RingVol'. |
|
"\r\n"; |
|
|
|
foreach ($this->cards as $card) |
|
{ |
|
if ($this->mailonly && empty($card->email) && empty($card->email2)) |
|
continue; |
|
if ($this->phoneonly && empty($card->home['phone']) && empty($card->work['phone']) && empty($card->mobile)) |
|
continue; |
|
|
|
$name=array(); |
|
$firstname = $this->csv_encode($card->firstname, $delm, false); |
|
$surname = $this->csv_encode($card->surname, $delm, false); |
|
$organization = $this->csv_encode($card->organization, $delm, false); |
|
|
|
if (strlen($surname)) $name[] = $surname; |
|
if (strlen($firstname)) $name[] = $firstname; |
|
if (count($name)) |
|
{ |
|
$out .= implode(' ',$name) . $delm; |
|
} else |
|
{ |
|
$out .= $organization.$delm; |
|
} |
|
|
|
$out .= $this->csv_encode($this->normalize_phone($card->home['phone']), $delm); |
|
$out .= $delm; # Vanity |
|
$out .= $delm; # Kurzwahl |
|
$out .= $this->csv_encode($this->normalize_phone($card->work['phone']), $delm); |
|
$out .= $delm; # Vanity |
|
$out .= $delm; # Kurzwahl |
|
$out .= $this->csv_encode($this->normalize_phone($card->mobile), $delm); |
|
$out .= $delm; # Vanity |
|
$out .= $delm; # Kurzwahl |
|
$out .= $this->csv_encode($card->notes, $delm); |
|
$out .= $organization.$delm; |
|
$out .= $delm; # Bild |
|
$out .= $delm; # Kategorie |
|
$out .= $delm; # ImageUrl |
|
$out .= '1'.$delm; #Prio |
|
$out .= $this->csv_encode((empty($card->email)) ? $card->email2 : $card->email, $delm); |
|
$out .= $delm; # RingTone |
|
$out .= $delm; # RingVol |
|
|
|
$out .= "\r\n"; |
|
$this->export_count++; |
|
} |
|
|
|
// convert to ISO-8859-1 |
|
if ($this->charset == 'UTF-8' && function_exists('utf8_decode')) |
|
$out = utf8_decode($out); |
|
|
|
return $out; |
|
} |
|
|
|
/** |
|
* Export all cards images |
|
*/ |
|
function toImages($tmpdir) |
|
{ |
|
$this->export_count = 0; |
|
|
|
foreach($this->cards as $card) |
|
{ |
|
if ($card->photo) |
|
{ |
|
$ext = strtolower($card->photo['type']); |
|
if ($ext == "jpeg" || $ext == "png") |
|
{ |
|
if ($ext == "jpeg") |
|
$ext = "jpg"; |
|
|
|
// Try to guess the displayname of the card if it is empty |
|
$card->displayname = trim($card->displayname); |
|
|
|
if (empty($card->displayname)) |
|
$card->displayname = trim($card->firstname.' '.$card->surname); |
|
if (empty($card->displayname)) |
|
$card->displayname = trim($card->nickname); |
|
if (empty($card->displayname)) |
|
$card->displayname = trim($card->organization); |
|
|
|
// A FIX: Since some cards may give no (or identical) filenames |
|
// after the cleanup by asciiwords, always generate a random UID |
|
// for card's file name |
|
$fn = asciiwords(strtolower($card->displayname)); |
|
if (empty($fn) || preg_match("/^[_ -]+$/",$fn) || preg_match("/^-/",$fn)) |
|
$fn = uniqid('card-id-'); |
|
else |
|
$fn .= uniqid('-card-id-'); |
|
|
|
$operation = file_put_contents($tmpdir.'/'.$fn.'.'.$ext, base64_decode(preg_replace('/\s+/', '', $card->photo['data']))); |
|
if ($operation !== False) |
|
$this->export_count++; |
|
} |
|
} |
|
} |
|
|
|
return $this->export_count; |
|
} |
|
|
|
/** |
|
* Encode one col string for CSV export |
|
* |
|
* @access private |
|
*/ |
|
function csv_encode($str, $delm, $add_delm=true) |
|
{ |
|
if (strpos($str, $delm)) |
|
$str = '"'.$str.'"'; |
|
return preg_replace('/\r?\n/', ' ', $str) . ($add_delm ? $delm : ''); |
|
} |
|
|
|
|
|
/** |
|
* Encode one col string for Ldif export |
|
* |
|
* @access private |
|
*/ |
|
function ldif_encode($str, $encoding, $nowrap = false) |
|
{ |
|
// base64-encode all values that contain non-ascii chars |
|
// $str is already UTF-encoded after the VCard is read in $card |
|
if (preg_match('/[^\x09\x20-\x7E]/', $str)) |
|
$str = ': ' . base64_encode($this->charset_convert($str, $encoding)); |
|
else |
|
$str = ' ' . $str; |
|
|
|
// Make long lines splited according to LDIF specs to a new line starting with [:space:] |
|
return $nowrap ? $str : preg_replace('/\n $/', '', chunk_split($str, 76, "\n ")); |
|
} |
|
|
|
|
|
/** |
|
* Strip Access Code |
|
* |
|
* @access private |
|
*/ |
|
function normalize_phone($phone) |
|
{ |
|
if (strlen($this->accesscode)) |
|
$phone = preg_replace('/^[\+|00]+' . $this->accesscode . '[- ]*(\d+)/', '0\1', $phone); |
|
return $phone; |
|
} |
|
|
|
|
|
/** |
|
* Convert a whole vcard (array) to UTF-8. |
|
* Each member value that has a charset parameter will be converted. |
|
* |
|
* @access private |
|
*/ |
|
function card2utf8($card) |
|
{ |
|
foreach ($card as $key => $node) |
|
{ |
|
foreach ($node as $i => $subnode) |
|
{ |
|
if ($subnode['param']['CHARSET'] && ($charset = strtoupper($subnode['param']['CHARSET'][0]))) |
|
{ |
|
$card[$key][$i]['value'] = $this->utf8_convert($subnode['value'], $charset); |
|
unset($card[$key][$i]['param']['CHARSET']); |
|
} |
|
} |
|
} |
|
|
|
return $card; |
|
} |
|
|
|
/** |
|
* Convert the given input to UTF-8. |
|
* If it's an array, all values will be converted recursively. |
|
* |
|
* @access private |
|
*/ |
|
function utf8_convert($in, $from=null) |
|
{ |
|
// Sometimes the charset in $from is in quotes, so clean it up |
|
$from = trim($from,'"'); |
|
|
|
if (!$from) |
|
$from = $this->charset; |
|
|
|
// recursively convert all array values |
|
if (is_array($in)) |
|
{ |
|
foreach ($in as $key => $value) |
|
$in[$key] = $this->utf8_convert($value, $from); |
|
return $in; |
|
} |
|
else |
|
$str = $in; |
|
|
|
// try to convert to UTF-8 |
|
if ($from != 'UTF-8') |
|
{ |
|
if ($from == 'ISO-8859-1' && function_exists('utf8_encode')) |
|
$str = utf8_encode($str); |
|
else if (function_exists('mb_convert_encoding')) |
|
{ |
|
$str = mb_convert_encoding($str, 'UTF-8', $from); |
|
if (strlen($str) == 0) |
|
error_log("Vcfconvert error: mbstring failed to convert the text!"); |
|
} |
|
else if (function_exists('iconv')) |
|
{ |
|
$str = iconv($from, 'UTF-8', $str); |
|
if (strlen($str) == 0) |
|
error_log("Vcfconvert error: iconv failed to convert the text!"); |
|
} |
|
else |
|
error_log("Vcfconvert warning: the vcard is not in UTF-8."); |
|
} |
|
|
|
// strip BOM if it is still there |
|
return ltrim($str, "\xFE\xFF\xEF\xBB\xBF\0"); |
|
} |
|
|
|
|
|
/** |
|
* Convert the given string from internal charset to the target encoding |
|
*/ |
|
function charset_convert($str, $encoding) |
|
{ |
|
// convert to ISO-8859-1 |
|
if ($encoding == 'ISO-8859-1' && $this->charset == 'UTF-8' && function_exists('utf8_decode')) |
|
return utf8_decode($str); |
|
|
|
// convert to any other charset |
|
if (!empty($encoding) && $encoding !== $this->charset && function_exists('iconv')) |
|
return iconv($this->charset, $encoding.'//IGNORE', $str); |
|
|
|
return $str; |
|
} |
|
|
|
|
|
/** |
|
* Returns UNICODE type based on BOM (Byte Order Mark) or default value on no match |
|
* |
|
* @author Clemens Wacha <clemens.wacha@gmx.net> |
|
* @access private |
|
* @static |
|
*/ |
|
function get_charset($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 |
|
if (vcard_convert::is_utf8($string)) return 'UTF-8'; |
|
|
|
// heuristics |
|
if ($string[0] == "\0" && $string[1] == "\0" && $string[2] == "\0" && $string[3] != "\0") return 'UTF-32BE'; |
|
if ($string[0] != "\0" && $string[1] == "\0" && $string[2] == "\0" && $string[3] == "\0") return 'UTF-32LE'; |
|
if ($string[0] == "\0" && $string[1] != "\0" && $string[2] == "\0" && $string[3] != "\0") return 'UTF-16BE'; |
|
if ($string[0] != "\0" && $string[1] == "\0" && $string[2] != "\0" && $string[3] == "\0") return 'UTF-16LE'; |
|
|
|
return false; |
|
} |
|
|
|
|
|
/** |
|
* Returns true if $string is valid UTF-8 and false otherwise. |
|
* From http://w3.org/International/questions/qa-forms-utf-8.html |
|
* |
|
* @access private |
|
* @static |
|
*/ |
|
function is_utf8($string) |
|
{ |
|
return 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)); |
|
} |
|
|
|
} // end class vcard_convert |
|
|
|
|
|
/** |
|
* Checks if a value exists in an array non-case-sensitive |
|
*/ |
|
function in_array_nc($needle, $haystack, $strict = false) |
|
{ |
|
foreach ((array)$haystack as $key => $value) |
|
{ |
|
if (strtolower($needle) == strtolower($value) && ($strict || gettype($needle) == gettype($value))) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
?>
|
|
|