<?php
/**
* Filename.......: vCard.php
* Project........: V-webmail
* Last Modified..: $Date: 2006/01/25 00:04:47 $
* CVS Revision...: $Revision: 1.2 $
* Copyright......: 2001-2004 Richard Heyes
*
* Based on vCard PHP <http://vcardphp.sourceforge.net>
*/
class vCard
{
/**
* An associative array where each key is the property name and each value
* is a VCardProperty array of properties which share that property name.
*/
var $_map;
/**
* The raw vCard data as an array of lines
* @var arrray
*/
var $_data;
/**
* Shortcut attributes object
* @var object
*/
var $attributes;
/**
* Constructor
*
* @param string $data The raw vCard text
*/
function vCard($data = '')
{
$this->attributes = new stdClass;
// Unfold any folded lines
$this->_data = preg_replace('/\r?\n( |\t)/', '', $data);
$this->_data = preg_split("/\r?\n/", $this->_data, -1, PREG_SPLIT_NO_EMPTY);
$this->_parse();
}
/**
* Parses a vCard from one or more lines. Lines that are not property
* lines, such as blank lines, are skipped. Returns false if there are
* no more lines to be parsed.
*
* @param string $lines The raw vCard text
*/
function _parse()
{
foreach ($this->_data as $line) {
$tmp = $this->_split(':', $line, 2);
if (count($tmp) == 2) {
$params = $this->_split(';', strtoupper($tmp[0]));
$name = $params[0];
// Not interested in these
if ($name == 'BEGIN' OR $name == 'END') {
continue;
}
$this->attributes->{$name}[] = new stdClass;
$currAttribute = &$this->attributes->{$name}[count($this->attributes->{$name}) - 1];
$currAttribute->value = preg_replace('/(?<!\\\\)\\\\(,|;)/', '\1', $tmp[1]);
$currAttribute->parameters = array();
array_shift($params);
foreach ($params as $param) {
$this->_parseParam($currAttribute, $param);
}
if (@$currAttribute->parameters['ENCODING'][0] == 'QUOTED-PRINTABLE') {
$this->_decodeQuotedPrintable($currAttribute, $this->_data);
}
if (@$currAttribute->parameters['CHARSET'][0] == 'UTF-8') {
$currAttribute->value = utf8_decode($this->value);
}
}
}
}
/**
* Parses a parameter string where the parameter string is either in the
* form "name=value[,value...]" such as "TYPE=WORK,CELL" or is a
* vCard 2.1 parameter value such as "WORK" in which case the parameter
* name is determined from the parameter value.
*
* @param string $currAttribute The attribute to add the params to
* @param string $param The parameter to parse
*/
function _parseParam(&$currAttribute, $param)
{
$tmp = $this->_split('=', $param, 2);
if (count($tmp) == 1) {
$value = $tmp[0];
$name = $this->_getParamNameFromValue($value);
$currAttribute->parameters[$name][] = $value;
} else {
$name = $tmp[0];
$values = $this->_split(',', $tmp[1]);
foreach ($values as $value) {
$currAttribute->parameters[$name][] = $value;
}
}
}
/**
* The vCard 2.1 specification allows parameter values without a name.
* The parameter name is then determined from the unique parameter value.
*
* @param string $value The value of the parameter
* @return string The name of the parameter
*/
function _getParamNameFromValue($value)
{
static $types = array (
'DOM', 'INTL', 'POSTAL', 'PARCEL','HOME', 'WORK',
'PREF', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER',
'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO',
'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD',
'INTERNET', 'IBMMAIL', 'MCIMAIL',
'POWERSHARE', 'PRODIGY', 'TLX', 'X400',
'GIF', 'CGM', 'WMF', 'BMP', 'MET', 'PMB', 'DIB',
'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'QTIME',
'MPEG', 'MPEG2', 'AVI',
'WAVE', 'AIFF', 'PCM',
'X509', 'PGP');
static $values = array ('INLINE', 'URL', 'CID');
static $encodings = array ('7BIT', 'QUOTED-PRINTABLE', 'BASE64');
if (in_array($value, $types)) {
return 'TYPE';
} elseif (in_array($value, $values)) {
return 'VALUE';
} elseif (in_array($value, $encodings)) {
return 'ENCODING';
}
return 'UNKNOWN';
}
/**
* Decodes a quoted printable value spanning multiple lines.
*
* @param object &$currAttribute The attribute currently being parsed
* @param array $lines The lines of the vCard
*/
function _decodeQuotedPrintable(&$currAttribute, $lines)
{
$value = &$currAttribute->value;
while ($value[strlen($value) - 1] == "=") {
$value = substr($value, 0, strlen($value) - 1);
if (!(list(, $line) = each($lines))) {
break;
}
$value .= rtrim($line);
}
$value = quoted_printable_decode($value);
}
/**
* Splits a string based on a delimiter but only
* said delimiter is not quoted.
*
* @param string $delimiter The delimiter to split on
* @param string $input The input string
* @param integer $maxParts Max number of parts to return
* @return array The split parts
*/
function _split($delimiter, $input, $maxParts = 0)
{
$quote = false;
$length = strlen($input);
for ($i = 0; $i < $length AND ($maxParts == 0 OR $maxParts > 1); $i++) {
$char = $input{$i};
if ($char == '"') {
$quote = !$quote;
} elseif (!$quote AND $char == $delimiter) {
$input{$i} = "\x00";
if ($maxParts > 0) {
$maxParts--;
}
}
}
return explode("\x00", $input);
}
/**
* Generic accessor to add an attribute
*
* @param string $name Name of attribute
* @param string $value Value of attribute
* @param array $params Any parameters
*/
function addAttribute($name, $value, $params = array())
{
$obj = new stdClass;
$obj->value = $value;
$obj->parameters = $params;
$this->attributes->{$name}[] = $obj;
}
function clearAttribute($name)
{
if (!empty($this->attributes->$name)) {
unset($this->attributes->$name);
}
}
/**
* Accessors
*/
function addName($surname, $forename, $additional = '', $prefix = '', $suffix = '')
{
$value = sprintf('%s;%s;%s;%s;%s', $surname, $forename, $additional, $prefix, $suffix);
$this->addAttribute('N', $value);
}
function addFormattedName($value)
{
$this->addAttribute('FN', $value);
}
function addNickname($value)
{
$this->addAttribute('NICKNAME', $value);
}
function addTelephone($value, $types = array())
{
$params = array();
if (!empty($types)) {
$params['TYPE'] = $types;
}
$this->addAttribute('TEL', $value, $params);
}
function addEmail($value, $types = array())
{
$params = array();
if (!empty($types)) {
$params['TYPE'] = $types;
}
$this->addAttribute('EMAIL', $value, $params);
}
function addAddress($street, $city, $province, $postcode, $country, $pobox = '', $extended = '')
{
$value = sprintf('%s;%s;%s;%s;%s;%s;%s', $pobox, $extended, $street, $city, $province, $postcode, $country);
$this->addAttribute('ADR', $value);
}
function addNote($value)
{
$params = array();
if (strpos($value, "\r\n") !== false) {
// QP encode the note, though this technically breaks rfc2426
$params['ENCODING'][] = 'QUOTED-PRINTABLE';
}
$this->addAttribute('NOTE', $value, $params);
}
function addCategories($value)
{
$value = implode(',', $value);
$this->addAttribute('CATEGORIES', $value);
}
/**
* Returns number of instances of a given attribute
*
* @param string $name Name of the attribute
* @return integer Number of instances
*/
function numInstances($name)
{
if (!empty($this->attributes->$name)) {
return count($this->attributes->$name);
}
return 0;
}
/**
* Returns the first instance of the specified attribute name or null if
* there are no attributes with that name.
*
* @param string $name Name of the attribute
* @return object The attribute
*/
function getFirstInstanceOf($name)
{
if (!empty($this->attributes->{$name}[0])) {
return $this->attributes->{$name}[0];
}
return null;
}
/**
* Returns the all instances of the specified attribute name or null if
* there are no attributes with that name.
*
* @param string $name Name of the attribute
* @return array The attribute(s)
*/
function getAllInstancesOf($name)
{
if (!empty($this->attributes->{$name})) {
return $this->attributes->{$name};
}
return null;
}
/**
* Returns an array of the categories this vCard is
* assigned to.
*
* @return array The group list
*/
function getCategories()
{
$result = array();
$property = $this->getFirstInstanceOf('CATEGORIES');
if ($property) {
$result = $this->_split(',', $property->value);
}
return $result;
}
/**
* Returns true if the card belongs to the supplied category.
*
* @param string $category The category to check for
* @return bool Whether this vCard is in the category or not
*/
function inCategory($category)
{
$vcCategories = $this->getCategories();
foreach ($categories as $category) {
if (in_array($category, $vcCategories)) {
return true;
}
}
return false;
}
/**
* Splits the value on unescaped delimiter characters.
*
* @param string $input The input string
* @param string $delimiter The delimiter to split on
* @return array The split values
*/
function getValues($input, $delimiter = ';')
{
// Split on unescaped delimiters
$values = preg_split('/(?<!\\\\)' . preg_quote($delimiter) . '/', $input);
// Unescape escaped delimiters
for ($i=0; $i<count($values); $i++) {
$values[$i] = str_replace('\\' . $delimiter, $delimiter, $values[$i]);
}
return $values;
}
/**
* Generates a vCard from the current set
* of attributes
*
* @return string The vCard text
*/
function generateVcard()
{
$lines = array();
$lines[] = 'BEGIN:VCARD';
$lines[] = 'VERSION:3.0';
foreach ($this->attributes as $attName => $attObjs) {
foreach ($attObjs as $attObj) {
if (!empty($attObj->parameters['ENCODING']) AND $attObj->parameters['ENCODING'][0] == 'QUOTED-PRINTABLE') {
$value = preg_replace('/([^\x20\x21-\x3C\x3E-\x7E])/e', 'sprintf("=%02X", ord("\1"))', $attObj->value);
} else {
$value = $attObj->value;
}
$params = array();
foreach ($attObj->parameters as $paramName => $paramValues) {
$params[] = sprintf('%s=%s', $paramName, implode(',', $paramValues));
}
/**
* Escape certain values
*/
if (in($attName, 'NOTE')) {
$value = strtr($value, array(';' => '\\;', ',' => '\\,'));
}
$lines[] = sprintf('%s%s:%s', $attName, (!empty($params) ? ';' . implode(';', $params) : ''), $value);
}
}
$lines[] = 'END:VCARD';
return implode("\r\n", $lines);
}
}
?>