This commit is contained in:
xesmyd committed 2025-08-14 22:41:49 +02:00
1 parent 2de81ccc46
commit 8ce45119b6
39774 files changed
+4309466

No files matched your search

+1000
View File
@@ -0,0 +1,1000 @@
<?php
namespace JeroenDesloovere\VCard;
/*
* This file is part of the VCard PHP Class from Jeroen Desloovere.
*
* For the full copyright and license information, please view the license
* file that was distributed with this source code.
*/
use Behat\Transliterator\Transliterator;
/**
* VCard PHP Class to generate .vcard files and save them to a file or output as a download.
*/
class VCard
{
/**
* definedElements
*
* @var array
*/
private $definedElements;
/**
* Filename
*
* @var string
*/
private $filename;
/**
* Save Path
*
* @var string
*/
private $savePath = null;
/**
* Multiple properties for element allowed
*
* @var array
*/
private $multiplePropertiesForElementAllowed = [
'email',
'address',
'phoneNumber',
'url',
'label'
];
/**
* Properties
*
* @var array
*/
private $properties;
/**
* Default Charset
*
* @var string
*/
public $charset = 'utf-8';
/**
* Add address
*
* @param string [optional] $name
* @param string [optional] $extended
* @param string [optional] $street
* @param string [optional] $city
* @param string [optional] $region
* @param string [optional] $zip
* @param string [optional] $country
* @param string [optional] $type
* $type may be DOM | INTL | POSTAL | PARCEL | HOME | WORK
* or any combination of these: e.g. "WORK;PARCEL;POSTAL"
* @return $this
*/
public function addAddress(
$name = '',
$extended = '',
$street = '',
$city = '',
$region = '',
$zip = '',
$country = '',
$type = 'WORK;POSTAL'
) {
// init value
$value = $name . ';' . $extended . ';' . $street . ';' . $city . ';' . $region . ';' . $zip . ';' . $country;
// set property
$this->setProperty(
'address',
'ADR' . (($type != '') ? ';' . $type : '') . $this->getCharsetString(),
$value
);
return $this;
}
/**
* Add birthday
*
* @param string $date Format is YYYY-MM-DD
* @return $this
*/
public function addBirthday($date)
{
$this->setProperty(
'birthday',
'BDAY',
$date
);
return $this;
}
/**
* Add company
*
* @param string $company
* @param string $department
* @return $this
*/
public function addCompany($company, $department = '')
{
$this->setProperty(
'company',
'ORG' . $this->getCharsetString(),
$company
. ($department != '' ? ';' . $department : '')
);
// if filename is empty, add to filename
if ($this->filename === null) {
$this->setFilename($company);
}
return $this;
}
/**
* Add email
*
* @param string $address The e-mail address
* @param string [optional] $type The type of the email address
* $type may be PREF | WORK | HOME
* or any combination of these: e.g. "PREF;WORK"
* @return $this
*/
public function addEmail($address, $type = '')
{
$this->setProperty(
'email',
'EMAIL;INTERNET' . (($type != '') ? ';' . $type : ''),
$address
);
return $this;
}
/**
* Add jobtitle
*
* @param string $jobtitle The jobtitle for the person.
* @return $this
*/
public function addJobtitle($jobtitle)
{
$this->setProperty(
'jobtitle',
'TITLE' . $this->getCharsetString(),
$jobtitle
);
return $this;
}
/**
* Add a label
*
* @param string $label
* @param string $type
*
* @return $this
*/
public function addLabel($label, $type = '')
{
$this->setProperty(
'label',
'LABEL' . ($type !== '' ? ';' . $type : '') . $this->getCharsetString(),
$label
);
return $this;
}
/**
* Add role
*
* @param string $role The role for the person.
* @return $this
*/
public function addRole($role)
{
$this->setProperty(
'role',
'ROLE' . $this->getCharsetString(),
$role
);
return $this;
}
/**
* Add a photo or logo (depending on property name)
*
* @param string $property LOGO|PHOTO
* @param string $url image url or filename
* @param bool $include Do we include the image in our vcard or not?
* @param string $element The name of the element to set
* @throws VCardException
*/
private function addMedia($property, $url, $element, $include = true)
{
$mimeType = null;
//Is this URL for a remote resource?
if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
$headers = get_headers($url, 1);
if (array_key_exists('Content-Type', $headers)) {
$mimeType = $headers['Content-Type'];
if (is_array($mimeType)) {
$mimeType = end($mimeType);
}
}
} else {
//Local file, so inspect it directly
$mimeType = mime_content_type($url);
}
if (strpos($mimeType, ';') !== false) {
$mimeType = strstr($mimeType, ';', true);
}
if (!is_string($mimeType) || substr($mimeType, 0, 6) !== 'image/') {
throw VCardException::invalidImage();
}
$fileType = strtoupper(substr($mimeType, 6));
if ($include) {
if ((bool) ini_get('allow_url_fopen') === true) {
$value = file_get_contents($url);
} else {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$value = curl_exec($curl);
curl_close($curl);
}
if (!$value) {
throw VCardException::emptyURL();
}
$value = base64_encode($value);
$property .= ";ENCODING=b;TYPE=" . $fileType;
} else {
if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
$propertySuffix = ';VALUE=URL';
$propertySuffix .= ';TYPE=' . strtoupper($fileType);
$property = $property . $propertySuffix;
$value = $url;
} else {
$value = $url;
}
}
$this->setProperty(
$element,
$property,
$value
);
}
/**
* Add a photo or logo (depending on property name)
*
* @param string $property LOGO|PHOTO
* @param string $content image content
* @param string $element The name of the element to set
*/
private function addMediaContent($property, $content, $element)
{
$finfo = new \finfo();
$mimeType = $finfo->buffer($content, FILEINFO_MIME_TYPE);
if (strpos($mimeType, ';') !== false) {
$mimeType = strstr($mimeType, ';', true);
}
if (!is_string($mimeType) || substr($mimeType, 0, 6) !== 'image/') {
throw VCardException::invalidImage();
}
$fileType = strtoupper(substr($mimeType, 6));
$content = base64_encode($content);
$property .= ";ENCODING=b;TYPE=" . $fileType;
$this->setProperty(
$element,
$property,
$content
);
}
/**
* Add name
*
* @param string [optional] $lastName
* @param string [optional] $firstName
* @param string [optional] $additional
* @param string [optional] $prefix
* @param string [optional] $suffix
* @return $this
*/
public function addName(
$lastName = '',
$firstName = '',
$additional = '',
$prefix = '',
$suffix = ''
) {
// define values with non-empty values
$values = array_filter([
$prefix,
$firstName,
$additional,
$lastName,
$suffix,
]);
// define filename
$this->setFilename($values);
// set property
$property = $lastName . ';' . $firstName . ';' . $additional . ';' . $prefix . ';' . $suffix;
$this->setProperty(
'name',
'N' . $this->getCharsetString(),
$property
);
// is property FN set?
if (!$this->hasProperty('FN')) {
// set property
$this->setProperty(
'fullname',
'FN' . $this->getCharsetString(),
trim(implode(' ', $values))
);
}
return $this;
}
/**
* Add note
*
* @param string $note
* @return $this
*/
public function addNote($note)
{
$this->setProperty(
'note',
'NOTE' . $this->getCharsetString(),
$note
);
return $this;
}
/**
* Add categories
*
* @param array $categories
* @return $this
*/
public function addCategories($categories)
{
$this->setProperty(
'categories',
'CATEGORIES' . $this->getCharsetString(),
trim(implode(',', $categories))
);
return $this;
}
/**
* Add phone number
*
* @param string $number
* @param string [optional] $type
* Type may be PREF | WORK | HOME | VOICE | FAX | MSG |
* CELL | PAGER | BBS | CAR | MODEM | ISDN | VIDEO
* or any senseful combination, e.g. "PREF;WORK;VOICE"
* @return $this
*/
public function addPhoneNumber($number, $type = '')
{
$this->setProperty(
'phoneNumber',
'TEL' . (($type != '') ? ';' . $type : ''),
$number
);
return $this;
}
/**
* Add Logo
*
* @param string $url image url or filename
* @param bool $include Include the image in our vcard?
* @return $this
*/
public function addLogo($url, $include = true)
{
$this->addMedia(
'LOGO',
$url,
'logo',
$include
);
return $this;
}
/**
* Add Logo content
*
* @param string $content image content
* @return $this
*/
public function addLogoContent($content)
{
$this->addMediaContent(
'LOGO',
$content,
'logo'
);
return $this;
}
/**
* Add Photo
*
* @param string $url image url or filename
* @param bool $include Include the image in our vcard?
* @return $this
*/
public function addPhoto($url, $include = true)
{
$this->addMedia(
'PHOTO',
$url,
'photo',
$include
);
return $this;
}
/**
* Add Photo content
*
* @param string $content image content
* @return $this
*/
public function addPhotoContent($content)
{
$this->addMediaContent(
'PHOTO',
$content,
'photo'
);
return $this;
}
/**
* Add URL
*
* @param string $url
* @param string [optional] $type Type may be WORK | HOME
* @return $this
*/
public function addURL($url, $type = '')
{
$this->setProperty(
'url',
'URL' . (($type != '') ? ';' . $type : ''),
$url
);
return $this;
}
/**
* Build VCard (.vcf)
*
* @return string
*/
public function buildVCard()
{
// init string
$string = "BEGIN:VCARD\r\n";
$string .= "VERSION:3.0\r\n";
$string .= "REV:" . date("Y-m-d") . "T" . date("H:i:s") . "Z\r\n";
// loop all properties
$properties = $this->getProperties();
foreach ($properties as $property) {
// add to string
$string .= $this->fold($property['key'] . ':' . $this->escape($property['value']) . "\r\n");
}
// add to string
$string .= "END:VCARD\r\n";
// return
return $string;
}
/**
* Build VCalender (.ics) - Safari (< iOS 8) can not open .vcf files, so we have build a workaround.
*
* @return string
*/
public function buildVCalendar()
{
// init dates
$dtstart = date("Ymd") . "T" . date("Hi") . "00";
$dtend = date("Ymd") . "T" . date("Hi") . "01";
// init string
$string = "BEGIN:VCALENDAR\n";
$string .= "VERSION:2.0\n";
$string .= "BEGIN:VEVENT\n";
$string .= "DTSTART;TZID=Europe/London:" . $dtstart . "\n";
$string .= "DTEND;TZID=Europe/London:" . $dtend . "\n";
$string .= "SUMMARY:Click attached contact below to save to your contacts\n";
$string .= "DTSTAMP:" . $dtstart . "Z\n";
$string .= "ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/directory;\n";
$string .= " X-APPLE-FILENAME=" . $this->getFilename() . "." . $this->getFileExtension() . ":\n";
// base64 encode it so that it can be used as an attachemnt to the "dummy" calendar appointment
$b64vcard = base64_encode($this->buildVCard());
// chunk the single long line of b64 text in accordance with RFC2045
// (and the exact line length determined from the original .ics file exported from Apple calendar
$b64mline = chunk_split($b64vcard, 74, "\n");
// need to indent all the lines by 1 space for the iphone (yes really?!!)
$b64final = preg_replace('/(.+)/', ' $1', $b64mline);
$string .= $b64final;
// output the correctly formatted encoded text
$string .= "END:VEVENT\n";
$string .= "END:VCALENDAR\n";
// return
return $string;
}
/**
* Returns the browser user agent string.
*
* @return string
*/
protected function getUserAgent()
{
if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
$browser = strtolower($_SERVER['HTTP_USER_AGENT']);
} else {
$browser = 'unknown';
}
return $browser;
}
/**
* Decode
*
* @param string $value The value to decode
* @return string decoded
*/
private function decode($value)
{
// convert cyrlic, greek or other caracters to ASCII characters
return Transliterator::transliterate($value);
}
/**
* Download a vcard or vcal file to the browser.
*/
public function download()
{
// define output
$output = $this->getOutput();
foreach ($this->getHeaders(false) as $header) {
header($header);
}
// echo the output and it will be a download
echo $output;
}
/**
* Fold a line according to RFC2425 section 5.8.1.
*
* @link http://tools.ietf.org/html/rfc2425#section-5.8.1
* @param string $text
* @return mixed
*/
protected function fold($text)
{
if (strlen($text) <= 75) {
return $text;
}
// The chunk_split_unicode creates a huge memory footprint when used on long strings (EG photos are base64 10MB results in > 1GB memory usage)
// So check if the string is ASCII (7 bit) and if it is use the built in way RE: https://github.com/jeroendesloovere/vcard/issues/153
if ($this->is_ascii($text)) {
return substr(chunk_split($text, 75, "\r\n "), 0, -3);
}
// split, wrap and trim trailing separator
return substr($this->chunk_split_unicode($text, 75, "\r\n "), 0, -3);
}
/**
* Determine if string is pure 7bit ascii
* @link https://pageconfig.com/post/how-to-validate-ascii-text-in-php
*
* @param string $string
* @return bool
*/
protected function is_ascii($string = '' ) {
$num = 0;
while( isset( $string[$num] ) ) {
if( ord( $string[$num] ) & 0x80 ) {
return false;
}
$num++;
}
return true;
}
/**
* multibyte word chunk split
* @link http://php.net/manual/en/function.chunk-split.php#107711
*
* @param string $body The string to be chunked.
* @param integer $chunklen The chunk length.
* @param string $end The line ending sequence.
* @return string Chunked string
*/
protected function chunk_split_unicode($body, $chunklen = 76, $end = "\r\n")
{
$array = array_chunk(
preg_split("//u", $body, -1, PREG_SPLIT_NO_EMPTY), $chunklen);
$body = "";
foreach ($array as $item) {
$body .= join("", $item) . $end;
}
return $body;
}
/**
* Escape newline characters according to RFC2425 section 5.8.4.
*
* @link http://tools.ietf.org/html/rfc2425#section-5.8.4
* @param string $text
* @return string
*/
protected function escape($text)
{
if ($text === null) {
return null;
}
$text = str_replace("\r\n", "\\n", $text);
$text = str_replace("\n", "\\n", $text);
return $text;
}
/**
* Get output as string
* @deprecated in the future
*
* @return string
*/
public function get()
{
return $this->getOutput();
}
/**
* Get charset
*
* @return string
*/
public function getCharset()
{
return $this->charset;
}
/**
* Get charset string
*
* @return string
*/
public function getCharsetString()
{
return ';CHARSET=' . $this->charset;
}
/**
* Get content type
*
* @return string
*/
public function getContentType()
{
return ($this->isIOS7()) ?
'text/x-vcalendar' : 'text/x-vcard';
}
/**
* Get filename
*
* @return string
*/
public function getFilename()
{
if (!$this->filename) {
return 'unknown';
}
return $this->filename;
}
/**
* Get file extension
*
* @return string
*/
public function getFileExtension()
{
return ($this->isIOS7()) ?
'ics' : 'vcf';
}
/**
* Get headers
*
* @param bool $asAssociative
* @return array
*/
public function getHeaders($asAssociative)
{
$contentType = $this->getContentType() . '; charset=' . $this->getCharset();
$contentDisposition = 'attachment; filename=' . $this->getFilename() . '.' . $this->getFileExtension();
$contentLength = mb_strlen($this->getOutput(), '8bit');
$connection = 'close';
if ((bool)$asAssociative) {
return [
'Content-type' => $contentType,
'Content-Disposition' => $contentDisposition,
'Content-Length' => $contentLength,
'Connection' => $connection,
];
}
return [
'Content-type: ' . $contentType,
'Content-Disposition: ' . $contentDisposition,
'Content-Length: ' . $contentLength,
'Connection: ' . $connection,
];
}
/**
* Get output as string
* iOS devices (and safari < iOS 8 in particular) can not read .vcf (= vcard) files.
* So I build a workaround to build a .ics (= vcalender) file.
*
* @return string
*/
public function getOutput()
{
$output = ($this->isIOS7()) ?
$this->buildVCalendar() : $this->buildVCard();
return $output;
}
/**
* Get properties
*
* @return array
*/
public function getProperties()
{
return $this->properties;
}
/**
* Has property
*
* @param string $key
* @return bool
*/
public function hasProperty($key)
{
$properties = $this->getProperties();
foreach ($properties as $property) {
if ($property['key'] === $key && $property['value'] !== '') {
return true;
}
}
return false;
}
/**
* Is iOS - Check if the user is using an iOS-device
*
* @return bool
*/
public function isIOS()
{
// get user agent
$browser = $this->getUserAgent();
return (strpos($browser, 'iphone') || strpos($browser, 'ipod') || strpos($browser, 'ipad'));
}
/**
* Is iOS less than 7 (should cal wrapper be returned)
*
* @return bool
*/
public function isIOS7()
{
return ($this->isIOS() && $this->shouldAttachmentBeCal());
}
/**
* Save to a file
*
* @return void
*/
public function save()
{
$file = $this->getFilename() . '.' . $this->getFileExtension();
// Add save path if given
if (null !== $this->savePath) {
$file = $this->savePath . $file;
}
file_put_contents(
$file,
$this->getOutput()
);
}
/**
* Set charset
*
* @param mixed $charset
* @return void
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Set filename
*
* @param mixed $value
* @param bool $overwrite [optional] Default overwrite is true
* @param string $separator [optional] Default separator is an underscore '_'
* @return void
*/
public function setFilename($value, $overwrite = true, $separator = '_')
{
// recast to string if $value is array
if (is_array($value)) {
$value = implode($separator, $value);
}
// trim unneeded values
$value = trim($value, $separator);
// remove all spaces
$value = preg_replace('/\s+/', $separator, $value);
// if value is empty, stop here
if (empty($value)) {
return;
}
// decode value + lowercase the string
$value = strtolower($this->decode($value));
// urlize this part
$value = Transliterator::urlize($value);
// overwrite filename or add to filename using a prefix in between
$this->filename = ($overwrite) ?
$value : $this->filename . $separator . $value;
}
/**
* Set the save path directory
*
* @param string $savePath Save Path
* @throws VCardException
*/
public function setSavePath($savePath)
{
if (!is_dir($savePath)) {
throw VCardException::outputDirectoryNotExists();
}
// Add trailing directory separator the save path
if (substr($savePath, -1) != DIRECTORY_SEPARATOR) {
$savePath .= DIRECTORY_SEPARATOR;
}
$this->savePath = $savePath;
}
/**
* Set property
*
* @param string $element The element name you want to set, f.e.: name, email, phoneNumber, ...
* @param string $key
* @param string $value
* @throws VCardException
*/
private function setProperty($element, $key, $value)
{
if (!in_array($element, $this->multiplePropertiesForElementAllowed)
&& isset($this->definedElements[$element])
) {
throw VCardException::elementAlreadyExists($element);
}
// we define that we set this element
$this->definedElements[$element] = true;
// adding property
$this->properties[] = [
'key' => $key,
'value' => $value
];
}
/**
* Checks if we should return vcard in cal wrapper
*
* @return bool
*/
protected function shouldAttachmentBeCal()
{
$browser = $this->getUserAgent();
$matches = [];
preg_match('/os (\d+)_(\d+)\s+/', $browser, $matches);
$version = isset($matches[1]) ? ((int)$matches[1]) : 999;
return ($version < 8);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace JeroenDesloovere\VCard;
/*
* This file is part of the VCard PHP Class from Jeroen Desloovere.
*
* For the full copyright and license information, please view the license
* file that was distributed with this source code.
*/
/**
* VCard Exception PHP Class.
*/
class VCardException extends \Exception
{
public static function elementAlreadyExists($element)
{
return new self('You can only set "' . $element . '" once.');
}
public static function emptyURL()
{
return new self('Nothing returned from URL.');
}
public static function invalidImage()
{
return new self('Returned data is not an image.');
}
public static function outputDirectoryNotExists()
{
return new self('Output directory does not exist.');
}
}
+341
View File
@@ -0,0 +1,341 @@
<?php
namespace JeroenDesloovere\VCard;
/*
* This file is part of the VCard PHP Class from Jeroen Desloovere.
*
* For the full copyright and license information, please view the license
* file that was distributed with this source code.
*/
use Iterator;
use OutOfBoundsException;
use RuntimeException;
use stdClass;
/**
* VCard PHP Class to parse .vcard files.
*
* This class is heavily based on the Zendvcard project (seemingly abandoned),
* which is licensed under the Apache 2.0 license.
* More information can be found at https://code.google.com/archive/p/zendvcard/
*/
class VCardParser implements Iterator
{
/**
* The raw VCard content.
*
* @var string
*/
protected $content;
/**
* The VCard data objects.
*
* @var array
*/
protected $vcardObjects;
/**
* The iterator position.
*
* @var int
*/
protected $position;
/**
* Helper function to parse a file directly.
*
* @param string $filename
* @return self
*/
public static function parseFromFile($filename)
{
if (file_exists($filename) && is_readable($filename)) {
return new self(file_get_contents($filename));
} else {
throw new \RuntimeException(sprintf("File %s is not readable, or doesn't exist.", $filename));
}
}
public function __construct($content)
{
$this->content = $content;
$this->vcardObjects = [];
$this->rewind();
$this->parse();
}
public function rewind(): void
{
$this->position = 0;
}
public function current(): \stdClass
{
if (! $this->valid()) {
throw new RuntimeException('invalid');
}
return $this->getCardAtIndex($this->position);
}
public function key(): int
{
return $this->position;
}
public function next(): void
{
$this->position++;
}
public function valid(): bool
{
return !empty($this->vcardObjects[$this->position]);
}
/**
* Fetch all the imported VCards.
*
* @return array
* A list of VCard card data objects.
*/
public function getCards(): array
{
return $this->vcardObjects;
}
/**
* Fetch the imported VCard at the specified index.
*
* @throws OutOfBoundsException
*
* @param int $i
*
* @return stdClass
* The card data object.
*/
public function getCardAtIndex($i): stdClass
{
if (isset($this->vcardObjects[$i])) {
return $this->vcardObjects[$i];
}
throw new \OutOfBoundsException();
}
/**
* Start the parsing process.
*
* This method will populate the data object.
*/
protected function parse()
{
// Normalize new lines.
$this->content = str_replace(["\r\n", "\r"], "\n", $this->content);
// RFC2425 5.8.1. Line delimiting and folding
// Unfolding is accomplished by regarding CRLF immediately followed by
// a white space character (namely HTAB ASCII decimal 9 or. SPACE ASCII
// decimal 32) as equivalent to no characters at all (i.e., the CRLF
// and single white space character are removed).
$this->content = preg_replace("/\n(?:[ \t])/", "", $this->content);
$lines = explode("\n", $this->content);
// Parse the VCard, line by line.
foreach ($lines as $line) {
$line = trim($line);
if (strtoupper($line) == "BEGIN:VCARD") {
$cardData = new \stdClass();
} elseif (strtoupper($line) == "END:VCARD") {
$this->vcardObjects[] = $cardData;
} elseif (!empty($line)) {
// Strip grouping information. We don't use the group names. We
// simply use a list for entries that have multiple values.
// As per RFC, group names are alphanumerical, and end with a
// period (.).
$line = preg_replace('/^\w+\./', '', $line);
$type = '';
$value = '';
@list($type, $value) = explode(':', $line, 2);
$types = explode(';', $type);
$element = strtoupper($types[0]);
array_shift($types);
// Normalize types. A type can either be a type-param directly,
// or can be prefixed with "type=". E.g.: "INTERNET" or
// "type=INTERNET".
if (!empty($types)) {
$types = array_map(function($type) {
return preg_replace('/^type=/i', '', $type);
}, $types);
}
$i = 0;
$rawValue = false;
foreach ($types as $type) {
if (preg_match('/base64/', strtolower($type))) {
$value = base64_decode($value);
unset($types[$i]);
$rawValue = true;
} elseif (preg_match('/encoding=b/', strtolower($type))) {
$value = base64_decode($value);
unset($types[$i]);
$rawValue = true;
} elseif (preg_match('/quoted-printable/', strtolower($type))) {
$value = quoted_printable_decode($value);
unset($types[$i]);
$rawValue = true;
} elseif (strpos(strtolower($type), 'charset=') === 0) {
try {
$value = mb_convert_encoding($value, "UTF-8", substr($type, 8));
} catch (\Exception $e) {
}
unset($types[$i]);
}
$i++;
}
switch (strtoupper($element)) {
case 'FN':
$cardData->fullname = $value;
break;
case 'N':
foreach ($this->parseName($value) as $key => $val) {
$cardData->{$key} = $val;
}
break;
case 'BDAY':
$cardData->birthday = $this->parseBirthday($value);
break;
case 'ADR':
if (!isset($cardData->address)) {
$cardData->address = [];
}
$key = !empty($types) ? implode(';', $types) : 'WORK;POSTAL';
$cardData->address[$key][] = $this->parseAddress($value);
break;
case 'TEL':
if (!isset($cardData->phone)) {
$cardData->phone = [];
}
$key = !empty($types) ? implode(';', $types) : 'default';
$cardData->phone[$key][] = $value;
break;
case 'EMAIL':
if (!isset($cardData->email)) {
$cardData->email = [];
}
$key = !empty($types) ? implode(';', $types) : 'default';
$cardData->email[$key][] = $value;
break;
case 'REV':
$cardData->revision = $value;
break;
case 'VERSION':
$cardData->version = $value;
break;
case 'ORG':
$cardData->organization = $value;
break;
case 'URL':
if (!isset($cardData->url)) {
$cardData->url = [];
}
$key = !empty($types) ? implode(';', $types) : 'default';
$cardData->url[$key][] = $value;
break;
case 'TITLE':
$cardData->title = $value;
break;
case 'PHOTO':
if ($rawValue) {
$cardData->rawPhoto = $value;
} else {
$cardData->photo = $value;
}
break;
case 'LOGO':
if ($rawValue) {
$cardData->rawLogo = $value;
} else {
$cardData->logo = $value;
}
break;
case 'NOTE':
$cardData->note = $this->unescape($value);
break;
case 'CATEGORIES':
$cardData->categories = array_map('trim', explode(',', $value));
break;
case 'LABEL':
$cardData->label = $value;
break;
}
}
}
}
protected function parseName($value)
{
@list(
$lastname,
$firstname,
$additional,
$prefix,
$suffix
) = explode(';', $value);
return (object) [
'lastname' => $lastname,
'firstname' => $firstname,
'additional' => $additional,
'prefix' => $prefix,
'suffix' => $suffix,
];
}
protected function parseBirthday($value)
{
return new \DateTime($value);
}
protected function parseAddress($value)
{
@list(
$name,
$extended,
$street,
$city,
$region,
$zip,
$country,
) = explode(';', $value);
return (object) [
'name' => $name,
'extended' => $extended,
'street' => $street,
'city' => $city,
'region' => $region,
'zip' => $zip,
'country' => $country,
];
}
/**
* Unescape newline characters according to RFC2425 section 5.8.4.
* This function will replace escaped line breaks with PHP_EOL.
*
* @link http://tools.ietf.org/html/rfc2425#section-5.8.4
* @param string $text
* @return string
*/
protected function unescape($text)
{
return str_replace("\\n", PHP_EOL, $text);
}
}