| | |
| | | 'manager' => 'X-MANAGER', |
| | | 'spouse' => 'X-SPOUSE', |
| | | 'edit' => 'X-AB-EDIT', |
| | | 'groups' => 'CATEGORIES', |
| | | ); |
| | | private $typemap = array( |
| | | 'IPHONE' => 'mobile', |
| | |
| | | */ |
| | | public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array()) |
| | | { |
| | | if (!empty($fielmap)) { |
| | | if (!empty($fieldmap)) { |
| | | $this->extend_fieldmap($fieldmap); |
| | | } |
| | | |
| | |
| | | $tmp = $this->email[0]; |
| | | $this->email[0] = $this->email[$pref_index]; |
| | | $this->email[$pref_index] = $tmp; |
| | | } |
| | | |
| | | // fix broken vcards from Outlook that only supply ORG but not the required N or FN properties |
| | | if (!strlen(trim($this->displayname . $this->surname . $this->firstname)) && strlen($this->organization)) { |
| | | $this->displayname = $this->organization; |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | case 'birthday': |
| | | case 'anniversary': |
| | | if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) { |
| | | $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date')); |
| | | if (($val = rcube_utils::anytodatetime($value)) && ($fn = self::$fieldmap[$field])) { |
| | | $this->raw[$fn][] = array(0 => $val->format('Y-m-d'), 'value' => array('date')); |
| | | } |
| | | break; |
| | | |
| | |
| | | default: |
| | | if ($field == 'phone' && $this->phonetypemap[$type_uc]) { |
| | | $type = $this->phonetypemap[$type_uc]; |
| | | } |
| | | } |
| | | |
| | | if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) { |
| | | $index = count($this->raw[$tag]); |
| | |
| | | $vcard_block = ''; |
| | | $in_vcard_block = false; |
| | | |
| | | foreach (preg_split("/[\r\n]+/", $data) as $i => $line) { |
| | | foreach (preg_split("/[\r\n]+/", $data) as $line) { |
| | | if ($in_vcard_block && !empty($line)) { |
| | | $vcard_block .= $line . "\n"; |
| | | } |
| | |
| | | if (preg_match('/^END:VCARD$/i', $line)) { |
| | | // parse vcard |
| | | $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap); |
| | | if (!empty($obj->displayname) || !empty($obj->email)) { |
| | | // FN and N is required by vCard format (RFC 2426) |
| | | // on import we can be less restrictive, let's addressbook decide |
| | | if (!empty($obj->displayname) || !empty($obj->surname) || !empty($obj->firstname) || !empty($obj->email)) { |
| | | $out[] = $obj; |
| | | } |
| | | |
| | |
| | | * |
| | | * @return string Cleaned vcard block |
| | | */ |
| | | private static function cleanup($vcard) |
| | | public static function cleanup($vcard) |
| | | { |
| | | // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;) |
| | | $vcard = preg_replace( |
| | | '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', |
| | | '\2;type=\5\3:\4', |
| | | $vcard); |
| | | |
| | | // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility |
| | | $vcard = preg_replace_callback( |
| | | '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', |
| | | array('self', 'x_abrelatednames_callback'), |
| | | $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); |
| | | // Cleanup |
| | | $vcard = preg_replace(array( |
| | | // convert special types (like Skype) to normal type='skype' classes with this simple regex ;) |
| | | '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', |
| | | '/^item\d*\.X-AB.*$/m', // remove cruft like item1.X-AB* |
| | | '/^item\d*\./m', // remove item1.ADR instead of ADR |
| | | '/\n+/', // remove empty lines |
| | | '/^(N:[^;\R]*)$/m', // if N doesn't have any semicolons, add some |
| | | ), |
| | | array( |
| | | '\2;type=\5\3:\4', |
| | | '', |
| | | '', |
| | | "\n", |
| | | '\1;;;;', |
| | | ), $vcard); |
| | | |
| | | // convert X-WAB-GENDER to X-GENDER |
| | | if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) { |
| | | $value = $matches[1] == '2' ? 'male' : 'female'; |
| | | $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard); |
| | | } |
| | | |
| | | // if N doesn't have any semicolons, add some |
| | | $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard); |
| | | |
| | | return $vcard; |
| | | } |
| | |
| | | $enc = null; |
| | | |
| | | foreach($regs2[1] as $attrid => $attr) { |
| | | $attr = preg_replace('/[\s\t\n\r\0\x0B]/', '', $attr); |
| | | if ((list($key, $value) = explode('=', $attr)) && $value) { |
| | | $value = trim($value); |
| | | if ($key == 'ENCODING') { |
| | | $value = strtoupper($value); |
| | | // add next line(s) to value string if QP line end detected |
| | |
| | | $value[] = $attrvalues; |
| | | } |
| | | else if (is_bool($attrvalues)) { |
| | | // true means just tag, not tag=value, as in PHOTO;BASE64:... |
| | | // true means just a tag, not tag=value, as in PHOTO;BASE64:... |
| | | if ($attrvalues) { |
| | | $attr .= strtoupper(";$attrname"); |
| | | // vCard v3 uses ENCODING=B (#1489183) |
| | | if ($attrname == 'base64') { |
| | | $attr .= ";ENCODING=B"; |
| | | } |
| | | else { |
| | | $attr .= strtoupper(";$attrname"); |
| | | } |
| | | } |
| | | } |
| | | else { |
| | |
| | | * |
| | | * @return string Joined and quoted string |
| | | */ |
| | | private static function vcard_quote($s, $sep = ';') |
| | | public static function vcard_quote($s, $sep = ';') |
| | | { |
| | | if (is_array($s)) { |
| | | foreach($s as $part) { |
| | |
| | | return(implode($sep, (array)$r)); |
| | | } |
| | | |
| | | return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;')); |
| | | return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', $sep => '\\'.$sep)); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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); |
| | | // break string into parts separated by $sep |
| | | if (!empty($sep)) { |
| | | // Handle properly backslash escaping (#1488896) |
| | | $rep1 = array("\\\\" => "\010", "\\$sep" => "\007"); |
| | | $rep2 = array("\007" => "\\$sep", "\010" => "\\\\"); |
| | | |
| | | if (count($parts = explode($sep, strtr($s, $rep1))) > 1) { |
| | | foreach ($parts as $s) { |
| | | $result[] = self::vcard_unquote(strtr($s, $rep2)); |
| | | } |
| | | return $result; |
| | | } |
| | | return $result; |
| | | |
| | | $s = trim(strtr($s, $rep2)); |
| | | } |
| | | |
| | | return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':')); |
| | | // 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", '\,' => ',', '\;' => ';')); |
| | | |
| | | $s = str_replace("\r", '', $s); |
| | | $pos = 0; |
| | | |
| | | while (($pos = strpos($s, '\\', $pos)) !== false) { |
| | | $next = substr($s, $pos + 1, 1); |
| | | if ($next == 'n' || $next == 'N') { |
| | | $s = substr_replace($s, "\n", $pos, 2); |
| | | } |
| | | else { |
| | | $s = substr_replace($s, '', $pos, 1); |
| | | } |
| | | |
| | | $pos += 1; |
| | | } |
| | | |
| | | return $s; |
| | | } |
| | | |
| | | /** |