|
|
|
@ -89,6 +89,12 @@ class rcube_vcard
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructor
|
|
|
|
|
*
|
|
|
|
|
* @param string $vcard vCard content
|
|
|
|
|
* @param string $charset Charset of string values
|
|
|
|
|
* @param bool $detect True if loading a 'foreign' vcard and extra heuristics
|
|
|
|
|
* for charset detection is required
|
|
|
|
|
* @param array $fieldmap Fields mapping definition
|
|
|
|
|
*/
|
|
|
|
|
public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
|
|
|
|
|
{
|
|
|
|
@ -104,9 +110,10 @@ class rcube_vcard
|
|
|
|
|
/**
|
|
|
|
|
* Load record from (internal, unfolded) vcard 3.0 format
|
|
|
|
|
*
|
|
|
|
|
* @param string vCard string to parse
|
|
|
|
|
* @param string Charset of string values
|
|
|
|
|
* @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required
|
|
|
|
|
* @param string $vcard vCard string to parse
|
|
|
|
|
* @param string $charset Charset of string values
|
|
|
|
|
* @param bool $detect True if loading a 'foreign' vcard and extra heuristics
|
|
|
|
|
* for charset detection is required
|
|
|
|
|
*/
|
|
|
|
|
public function load($vcard, $charset = RCUBE_CHARSET, $detect = false)
|
|
|
|
|
{
|
|
|
|
@ -132,7 +139,7 @@ class rcube_vcard
|
|
|
|
|
$this->middlename = $this->raw['N'][0][2];
|
|
|
|
|
$this->nickname = $this->raw['NICKNAME'][0][0];
|
|
|
|
|
$this->organization = $this->raw['ORG'][0][0];
|
|
|
|
|
$this->business = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
|
|
|
|
|
$this->business = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (implode('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
|
|
|
|
|
|
|
|
|
|
foreach ((array)$this->raw['EMAIL'] as $i => $raw_email) {
|
|
|
|
|
$this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email;
|
|
|
|
@ -169,10 +176,13 @@ class rcube_vcard
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->raw['N'][0][3])
|
|
|
|
|
if ($this->raw['N'][0][3]) {
|
|
|
|
|
$out['prefix'] = $this->raw['N'][0][3];
|
|
|
|
|
if ($this->raw['N'][0][4])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->raw['N'][0][4]) {
|
|
|
|
|
$out['suffix'] = $this->raw['N'][0][4];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// convert from raw vcard data into associative data for Roundcube
|
|
|
|
|
foreach (array_flip(self::$fieldmap) as $tag => $col) {
|
|
|
|
@ -185,7 +195,7 @@ class rcube_vcard
|
|
|
|
|
if (!empty($raw['type'])) {
|
|
|
|
|
$raw['type'] = array_map('strtolower', $raw['type']);
|
|
|
|
|
|
|
|
|
|
$combined = join(',', array_diff($raw['type'], array('internet', 'pref')));
|
|
|
|
|
$combined = implode(',', array_diff($raw['type'], array('internet', 'pref')));
|
|
|
|
|
$combined = strtoupper($combined);
|
|
|
|
|
|
|
|
|
|
if ($typemap[$combined]) {
|
|
|
|
@ -257,6 +267,10 @@ class rcube_vcard
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert the data structure into a vcard 3.0 string
|
|
|
|
|
*
|
|
|
|
|
* @param bool $folder Use RFC2425 folding
|
|
|
|
|
*
|
|
|
|
|
* @return string vCard output
|
|
|
|
|
*/
|
|
|
|
|
public function export($folded = true)
|
|
|
|
|
{
|
|
|
|
@ -272,8 +286,8 @@ class rcube_vcard
|
|
|
|
|
public function reset($fields = null)
|
|
|
|
|
{
|
|
|
|
|
if (!$fields) {
|
|
|
|
|
$fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap),
|
|
|
|
|
array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY'));
|
|
|
|
|
$fields = array('FN', 'N', 'ORG', 'NICKNAME', 'EMAIL', 'ADR', 'BDAY');
|
|
|
|
|
$fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap), $fields);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($fields as $f) {
|
|
|
|
@ -283,6 +297,7 @@ class rcube_vcard
|
|
|
|
|
if (!$this->raw['N']) {
|
|
|
|
|
$this->raw['N'] = array(array('','','','',''));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$this->raw['FN']) {
|
|
|
|
|
$this->raw['FN'] = array();
|
|
|
|
|
}
|
|
|
|
@ -293,9 +308,9 @@ class rcube_vcard
|
|
|
|
|
/**
|
|
|
|
|
* Setter for address record fields
|
|
|
|
|
*
|
|
|
|
|
* @param string Field name
|
|
|
|
|
* @param string Field value
|
|
|
|
|
* @param string Type/section name
|
|
|
|
|
* @param string $field Field name
|
|
|
|
|
* @param mixed $value Field value
|
|
|
|
|
* @param string $type Type/section name
|
|
|
|
|
*/
|
|
|
|
|
public function set($field, $value, $type = 'HOME')
|
|
|
|
|
{
|
|
|
|
@ -374,7 +389,7 @@ class rcube_vcard
|
|
|
|
|
$value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']);
|
|
|
|
|
|
|
|
|
|
// fall through if not empty
|
|
|
|
|
if (!strlen(join('', $value))) {
|
|
|
|
|
if (!strlen(implode('', $value))) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -402,20 +417,21 @@ class rcube_vcard
|
|
|
|
|
/**
|
|
|
|
|
* Setter for individual vcard properties
|
|
|
|
|
*
|
|
|
|
|
* @param string VCard tag name
|
|
|
|
|
* @param array Value-set of this vcard property
|
|
|
|
|
* @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set
|
|
|
|
|
* @param string $tag VCard tag name
|
|
|
|
|
* @param array $value Value-set of this vcard property
|
|
|
|
|
* @param bool $append Set to true if the value-set should be appended
|
|
|
|
|
* instead of replacing any existing value-set
|
|
|
|
|
*/
|
|
|
|
|
public function set_raw($tag, $value, $append = false)
|
|
|
|
|
{
|
|
|
|
|
$index = $append && isset($this->raw[$tag]) ? count($this->raw[$tag]) : 0;
|
|
|
|
|
$this->raw[$tag][$index] = (array)$value;
|
|
|
|
|
$this->raw[$tag][$index] = (array) $value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find index with the '$type' attribute
|
|
|
|
|
*
|
|
|
|
|
* @param string Field name
|
|
|
|
|
* @param string $field Field name
|
|
|
|
|
*
|
|
|
|
|
* @return int Field index having $type set
|
|
|
|
|
*/
|
|
|
|
@ -457,6 +473,8 @@ class rcube_vcard
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extends fieldmap definition
|
|
|
|
|
*
|
|
|
|
|
* @param array $map Field mapping definition
|
|
|
|
|
*/
|
|
|
|
|
public function extend_fieldmap($map)
|
|
|
|
|
{
|
|
|
|
@ -468,9 +486,9 @@ class rcube_vcard
|
|
|
|
|
/**
|
|
|
|
|
* Factory method to import a vcard file
|
|
|
|
|
*
|
|
|
|
|
* @param string vCard file content
|
|
|
|
|
* @param string $data vCard file content
|
|
|
|
|
*
|
|
|
|
|
* @return array List of rcube_vcard objects
|
|
|
|
|
* @return rcube_vcard[] List of rcube_vcard objects
|
|
|
|
|
*/
|
|
|
|
|
public static function import($data)
|
|
|
|
|
{
|
|
|
|
@ -520,7 +538,7 @@ class rcube_vcard
|
|
|
|
|
/**
|
|
|
|
|
* Normalize vcard data for better parsing
|
|
|
|
|
*
|
|
|
|
|
* @param string vCard block
|
|
|
|
|
* @param string $vcard vCard block
|
|
|
|
|
*
|
|
|
|
|
* @return string Cleaned vcard block
|
|
|
|
|
*/
|
|
|
|
@ -558,22 +576,38 @@ class rcube_vcard
|
|
|
|
|
return $vcard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Apple X-ABRELATEDNAMES converter callback
|
|
|
|
|
*
|
|
|
|
|
* @param array $matches Matching entries
|
|
|
|
|
*
|
|
|
|
|
* @return string Replacement string
|
|
|
|
|
*/
|
|
|
|
|
private static function x_abrelatednames_callback($matches)
|
|
|
|
|
{
|
|
|
|
|
return 'X-' . strtoupper($matches[5]) . $matches[3] . ':'. $matches[4];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* RFC2425 folding callback
|
|
|
|
|
*
|
|
|
|
|
* @param array $matches Matching entries
|
|
|
|
|
*
|
|
|
|
|
* @return string Replacement string
|
|
|
|
|
*/
|
|
|
|
|
private static function rfc2425_fold_callback($matches)
|
|
|
|
|
{
|
|
|
|
|
// chunk_split string and avoid lines breaking multibyte characters
|
|
|
|
|
$c = 71;
|
|
|
|
|
$out .= substr($matches[1], 0, $c);
|
|
|
|
|
|
|
|
|
|
for ($n = $c; $c < strlen($matches[1]); $c++) {
|
|
|
|
|
// break if length > 75 or mutlibyte character starts after position 71
|
|
|
|
|
if ($n > 75 || ($n > 71 && ord($matches[1][$c]) >> 6 == 3)) {
|
|
|
|
|
$out .= "\r\n ";
|
|
|
|
|
$n = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$out .= $matches[1][$c];
|
|
|
|
|
$n++;
|
|
|
|
|
}
|
|
|
|
@ -581,16 +615,22 @@ class rcube_vcard
|
|
|
|
|
return $out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Apply RFC2425 folding to a vCard content
|
|
|
|
|
*
|
|
|
|
|
* @param string $val vCard content
|
|
|
|
|
*
|
|
|
|
|
* @retunr string Folded vCard string
|
|
|
|
|
*/
|
|
|
|
|
public static function rfc2425_fold($val)
|
|
|
|
|
{
|
|
|
|
|
return preg_replace_callback('/([^\n]{72,})/', array('self', 'rfc2425_fold_callback'), $val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Decodes a vcard block (vcard 3.0 format, unfolded)
|
|
|
|
|
* into an array structure
|
|
|
|
|
* Decodes a vcard block (vcard 3.0 format, unfolded) into an array structure
|
|
|
|
|
*
|
|
|
|
|
* @param string vCard block to parse
|
|
|
|
|
* @param string $vcard vCard block to parse
|
|
|
|
|
*
|
|
|
|
|
* @return array Raw data structure
|
|
|
|
|
*/
|
|
|
|
@ -691,8 +731,8 @@ class rcube_vcard
|
|
|
|
|
/**
|
|
|
|
|
* 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)
|
|
|
|
|
* @param string $value String to decode
|
|
|
|
|
* @param string $encoding Encoding type (quoted-printable and base64 supported)
|
|
|
|
|
*
|
|
|
|
|
* @return string Decoded 8bit value
|
|
|
|
|
*/
|
|
|
|
@ -716,7 +756,7 @@ class rcube_vcard
|
|
|
|
|
/**
|
|
|
|
|
* Encodes an entry for storage in our database (vcard 3.0 format, unfolded)
|
|
|
|
|
*
|
|
|
|
|
* @param array Raw data structure to encode
|
|
|
|
|
* @param array $data Raw data structure to encode
|
|
|
|
|
*
|
|
|
|
|
* @return string vCard encoded string
|
|
|
|
|
*/
|
|
|
|
@ -782,32 +822,35 @@ class rcube_vcard
|
|
|
|
|
/**
|
|
|
|
|
* Join indexed data array to a vcard quoted string
|
|
|
|
|
*
|
|
|
|
|
* @param array Field data
|
|
|
|
|
* @param string Separator
|
|
|
|
|
* @param array $str Field data
|
|
|
|
|
* @param string $sep Separator
|
|
|
|
|
*
|
|
|
|
|
* @return string Joined and quoted string
|
|
|
|
|
*/
|
|
|
|
|
public static function vcard_quote($s, $sep = ';')
|
|
|
|
|
public static function vcard_quote($str, $sep = ';')
|
|
|
|
|
{
|
|
|
|
|
if (is_array($s)) {
|
|
|
|
|
foreach ($s as $part) {
|
|
|
|
|
if (is_array($str)) {
|
|
|
|
|
$r = array();
|
|
|
|
|
|
|
|
|
|
foreach ($str as $part) {
|
|
|
|
|
$r[] = self::vcard_quote($part, $sep);
|
|
|
|
|
}
|
|
|
|
|
return(implode($sep, (array)$r));
|
|
|
|
|
|
|
|
|
|
return(implode($sep, $r));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', $sep => '\\'.$sep));
|
|
|
|
|
return strtr($str, array("\\" => "\\\\", "\r" => '', "\n" => '\n', $sep => "\\$sep"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Split quoted string
|
|
|
|
|
*
|
|
|
|
|
* @param string vCard string to split
|
|
|
|
|
* @param string Separator char/string
|
|
|
|
|
* @param string $str vCard string to split
|
|
|
|
|
* @param string $sep Separator char/string
|
|
|
|
|
*
|
|
|
|
|
* @return array List with splited values
|
|
|
|
|
*/
|
|
|
|
|
private static function vcard_unquote($s, $sep = ';')
|
|
|
|
|
private static function vcard_unquote($str, $sep = ';')
|
|
|
|
|
{
|
|
|
|
|
// break string into parts separated by $sep
|
|
|
|
|
if (!empty($sep)) {
|
|
|
|
@ -815,36 +858,37 @@ class rcube_vcard
|
|
|
|
|
$rep1 = array("\\\\" => "\010", "\\$sep" => "\007");
|
|
|
|
|
$rep2 = array("\007" => "\\$sep", "\010" => "\\\\");
|
|
|
|
|
|
|
|
|
|
if (count($parts = explode($sep, strtr($s, $rep1))) > 1) {
|
|
|
|
|
if (count($parts = explode($sep, strtr($str, $rep1))) > 1) {
|
|
|
|
|
foreach ($parts as $s) {
|
|
|
|
|
$result[] = self::vcard_unquote(strtr($s, $rep2));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$s = trim(strtr($s, $rep2));
|
|
|
|
|
$str = trim(strtr($str, $rep2));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// some implementations (GMail) use non-standard backslash before colon (#1489085)
|
|
|
|
|
// we will handle properly any backslashed character - removing dummy backslahes
|
|
|
|
|
// return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
|
|
|
|
|
// return strtr($str, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
|
|
|
|
|
|
|
|
|
|
$s = str_replace("\r", '', $s);
|
|
|
|
|
$str = str_replace("\r", '', $str);
|
|
|
|
|
$pos = 0;
|
|
|
|
|
|
|
|
|
|
while (($pos = strpos($s, '\\', $pos)) !== false) {
|
|
|
|
|
$next = substr($s, $pos + 1, 1);
|
|
|
|
|
while (($pos = strpos($str, "\\", $pos)) !== false) {
|
|
|
|
|
$next = substr($str, $pos + 1, 1);
|
|
|
|
|
if ($next == 'n' || $next == 'N') {
|
|
|
|
|
$s = substr_replace($s, "\n", $pos, 2);
|
|
|
|
|
$str = substr_replace($str, "\n", $pos, 2);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
$s = substr_replace($s, '', $pos, 1);
|
|
|
|
|
$str = substr_replace($str, '', $pos, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$pos += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $s;
|
|
|
|
|
return $str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -869,7 +913,7 @@ class rcube_vcard
|
|
|
|
|
/**
|
|
|
|
|
* Returns UNICODE type based on BOM (Byte Order Mark)
|
|
|
|
|
*
|
|
|
|
|
* @param string Input string to test
|
|
|
|
|
* @param string $string Input string to test
|
|
|
|
|
*
|
|
|
|
|
* @return string Detected encoding
|
|
|
|
|
*/
|
|
|
|
|