mirror of
https://github.com/OpenXE-org/OpenXE.git
synced 2024-12-28 07:30:28 +01:00
536 lines
17 KiB
PHP
536 lines
17 KiB
PHP
<?php
|
|
|
|
namespace Sabre\VObject\Component;
|
|
|
|
use Sabre\VObject;
|
|
use Sabre\Xml;
|
|
|
|
/**
|
|
* The VCard component.
|
|
*
|
|
* This component represents the BEGIN:VCARD and END:VCARD found in every
|
|
* vcard.
|
|
*
|
|
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
|
* @author Evert Pot (http://evertpot.com/)
|
|
* @license http://sabre.io/license/ Modified BSD License
|
|
*/
|
|
class VCard extends VObject\Document
|
|
{
|
|
/**
|
|
* The default name for this component.
|
|
*
|
|
* This should be 'VCALENDAR' or 'VCARD'.
|
|
*
|
|
* @var string
|
|
*/
|
|
public static $defaultName = 'VCARD';
|
|
|
|
/**
|
|
* Caching the version number.
|
|
*
|
|
* @var int
|
|
*/
|
|
private $version = null;
|
|
|
|
/**
|
|
* This is a list of components, and which classes they should map to.
|
|
*
|
|
* @var array
|
|
*/
|
|
public static $componentMap = [
|
|
'VCARD' => 'Sabre\\VObject\\Component\\VCard',
|
|
];
|
|
|
|
/**
|
|
* List of value-types, and which classes they map to.
|
|
*
|
|
* @var array
|
|
*/
|
|
public static $valueMap = [
|
|
'BINARY' => 'Sabre\\VObject\\Property\\Binary',
|
|
'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean',
|
|
'CONTENT-ID' => 'Sabre\\VObject\\Property\\FlatText', // vCard 2.1 only
|
|
'DATE' => 'Sabre\\VObject\\Property\\VCard\\Date',
|
|
'DATE-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateTime',
|
|
'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only
|
|
'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue',
|
|
'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue',
|
|
'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
|
|
'PHONE-NUMBER' => 'Sabre\\VObject\\Property\\VCard\\PhoneNumber', // vCard 3.0 only
|
|
'TIMESTAMP' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
|
|
'TEXT' => 'Sabre\\VObject\\Property\\Text',
|
|
'TIME' => 'Sabre\\VObject\\Property\\Time',
|
|
'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
|
|
'URI' => 'Sabre\\VObject\\Property\\Uri',
|
|
'URL' => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only
|
|
'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset',
|
|
];
|
|
|
|
/**
|
|
* List of properties, and which classes they map to.
|
|
*
|
|
* @var array
|
|
*/
|
|
public static $propertyMap = [
|
|
// vCard 2.1 properties and up
|
|
'N' => 'Sabre\\VObject\\Property\\Text',
|
|
'FN' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'PHOTO' => 'Sabre\\VObject\\Property\\Binary',
|
|
'BDAY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
|
|
'ADR' => 'Sabre\\VObject\\Property\\Text',
|
|
'LABEL' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
|
|
'TEL' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'EMAIL' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
|
|
'GEO' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'TITLE' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'ROLE' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'LOGO' => 'Sabre\\VObject\\Property\\Binary',
|
|
// 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so
|
|
// not supported at the moment
|
|
'ORG' => 'Sabre\\VObject\\Property\\Text',
|
|
'NOTE' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'REV' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
|
|
'SOUND' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'URL' => 'Sabre\\VObject\\Property\\Uri',
|
|
'UID' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'KEY' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'TZ' => 'Sabre\\VObject\\Property\\Text',
|
|
|
|
// vCard 3.0 properties
|
|
'CATEGORIES' => 'Sabre\\VObject\\Property\\Text',
|
|
'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'PRODID' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'NICKNAME' => 'Sabre\\VObject\\Property\\Text',
|
|
'CLASS' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
|
|
|
|
// rfc2739 properties
|
|
'FBURL' => 'Sabre\\VObject\\Property\\Uri',
|
|
'CAPURI' => 'Sabre\\VObject\\Property\\Uri',
|
|
'CALURI' => 'Sabre\\VObject\\Property\\Uri',
|
|
'CALADRURI' => 'Sabre\\VObject\\Property\\Uri',
|
|
|
|
// rfc4770 properties
|
|
'IMPP' => 'Sabre\\VObject\\Property\\Uri',
|
|
|
|
// vCard 4.0 properties
|
|
'SOURCE' => 'Sabre\\VObject\\Property\\Uri',
|
|
'XML' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'ANNIVERSARY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
|
|
'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text',
|
|
'LANG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
|
|
'GENDER' => 'Sabre\\VObject\\Property\\Text',
|
|
'KIND' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'MEMBER' => 'Sabre\\VObject\\Property\\Uri',
|
|
'RELATED' => 'Sabre\\VObject\\Property\\Uri',
|
|
|
|
// rfc6474 properties
|
|
'BIRTHPLACE' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'DEATHPLACE' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'DEATHDATE' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
|
|
|
|
// rfc6715 properties
|
|
'EXPERTISE' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'HOBBY' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'INTEREST' => 'Sabre\\VObject\\Property\\FlatText',
|
|
'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText',
|
|
];
|
|
|
|
/**
|
|
* Returns the current document type.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getDocumentType()
|
|
{
|
|
if (!$this->version) {
|
|
$version = (string) $this->VERSION;
|
|
|
|
switch ($version) {
|
|
case '2.1':
|
|
$this->version = self::VCARD21;
|
|
break;
|
|
case '3.0':
|
|
$this->version = self::VCARD30;
|
|
break;
|
|
case '4.0':
|
|
$this->version = self::VCARD40;
|
|
break;
|
|
default:
|
|
// We don't want to cache the version if it's unknown,
|
|
// because we might get a version property in a bit.
|
|
return self::UNKNOWN;
|
|
}
|
|
}
|
|
|
|
return $this->version;
|
|
}
|
|
|
|
/**
|
|
* Converts the document to a different vcard version.
|
|
*
|
|
* Use one of the VCARD constants for the target. This method will return
|
|
* a copy of the vcard in the new version.
|
|
*
|
|
* At the moment the only supported conversion is from 3.0 to 4.0.
|
|
*
|
|
* If input and output version are identical, a clone is returned.
|
|
*
|
|
* @param int $target
|
|
*
|
|
* @return VCard
|
|
*/
|
|
public function convert($target)
|
|
{
|
|
$converter = new VObject\VCardConverter();
|
|
|
|
return $converter->convert($this, $target);
|
|
}
|
|
|
|
/**
|
|
* VCards with version 2.1, 3.0 and 4.0 are found.
|
|
*
|
|
* If the VCARD doesn't know its version, 2.1 is assumed.
|
|
*/
|
|
const DEFAULT_VERSION = self::VCARD21;
|
|
|
|
/**
|
|
* Validates the node for correctness.
|
|
*
|
|
* The following options are supported:
|
|
* Node::REPAIR - May attempt to automatically repair the problem.
|
|
*
|
|
* This method returns an array with detected problems.
|
|
* Every element has the following properties:
|
|
*
|
|
* * level - problem level.
|
|
* * message - A human-readable string describing the issue.
|
|
* * node - A reference to the problematic node.
|
|
*
|
|
* The level means:
|
|
* 1 - The issue was repaired (only happens if REPAIR was turned on)
|
|
* 2 - An inconsequential issue
|
|
* 3 - A severe issue.
|
|
*
|
|
* @param int $options
|
|
*
|
|
* @return array
|
|
*/
|
|
public function validate($options = 0)
|
|
{
|
|
$warnings = [];
|
|
|
|
$versionMap = [
|
|
self::VCARD21 => '2.1',
|
|
self::VCARD30 => '3.0',
|
|
self::VCARD40 => '4.0',
|
|
];
|
|
|
|
$version = $this->select('VERSION');
|
|
if (1 === count($version)) {
|
|
$version = (string) $this->VERSION;
|
|
if ('2.1' !== $version && '3.0' !== $version && '4.0' !== $version) {
|
|
$warnings[] = [
|
|
'level' => 3,
|
|
'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
|
|
'node' => $this,
|
|
];
|
|
if ($options & self::REPAIR) {
|
|
$this->VERSION = $versionMap[self::DEFAULT_VERSION];
|
|
}
|
|
}
|
|
if ('2.1' === $version && ($options & self::PROFILE_CARDDAV)) {
|
|
$warnings[] = [
|
|
'level' => 3,
|
|
'message' => 'CardDAV servers are not allowed to accept vCard 2.1.',
|
|
'node' => $this,
|
|
];
|
|
}
|
|
}
|
|
$uid = $this->select('UID');
|
|
if (0 === count($uid)) {
|
|
if ($options & self::PROFILE_CARDDAV) {
|
|
// Required for CardDAV
|
|
$warningLevel = 3;
|
|
$message = 'vCards on CardDAV servers MUST have a UID property.';
|
|
} else {
|
|
// Not required for regular vcards
|
|
$warningLevel = 2;
|
|
$message = 'Adding a UID to a vCard property is recommended.';
|
|
}
|
|
if ($options & self::REPAIR) {
|
|
$this->UID = VObject\UUIDUtil::getUUID();
|
|
$warningLevel = 1;
|
|
}
|
|
$warnings[] = [
|
|
'level' => $warningLevel,
|
|
'message' => $message,
|
|
'node' => $this,
|
|
];
|
|
}
|
|
|
|
$fn = $this->select('FN');
|
|
if (1 !== count($fn)) {
|
|
$repaired = false;
|
|
if (($options & self::REPAIR) && 0 === count($fn)) {
|
|
// We're going to try to see if we can use the contents of the
|
|
// N property.
|
|
if (isset($this->N)) {
|
|
$value = explode(';', (string) $this->N);
|
|
if (isset($value[1]) && $value[1]) {
|
|
$this->FN = $value[1].' '.$value[0];
|
|
} else {
|
|
$this->FN = $value[0];
|
|
}
|
|
$repaired = true;
|
|
|
|
// Otherwise, the ORG property may work
|
|
} elseif (isset($this->ORG)) {
|
|
$this->FN = (string) $this->ORG;
|
|
$repaired = true;
|
|
|
|
// Otherwise, the EMAIL property may work
|
|
} elseif (isset($this->EMAIL)) {
|
|
$this->FN = (string) $this->EMAIL;
|
|
$repaired = true;
|
|
}
|
|
}
|
|
$warnings[] = [
|
|
'level' => $repaired ? 1 : 3,
|
|
'message' => 'The FN property must appear in the VCARD component exactly 1 time',
|
|
'node' => $this,
|
|
];
|
|
}
|
|
|
|
return array_merge(
|
|
parent::validate($options),
|
|
$warnings
|
|
);
|
|
}
|
|
|
|
/**
|
|
* A simple list of validation rules.
|
|
*
|
|
* This is simply a list of properties, and how many times they either
|
|
* must or must not appear.
|
|
*
|
|
* Possible values per property:
|
|
* * 0 - Must not appear.
|
|
* * 1 - Must appear exactly once.
|
|
* * + - Must appear at least once.
|
|
* * * - Can appear any number of times.
|
|
* * ? - May appear, but not more than once.
|
|
*
|
|
* @var array
|
|
*/
|
|
public function getValidationRules()
|
|
{
|
|
return [
|
|
'ADR' => '*',
|
|
'ANNIVERSARY' => '?',
|
|
'BDAY' => '?',
|
|
'CALADRURI' => '*',
|
|
'CALURI' => '*',
|
|
'CATEGORIES' => '*',
|
|
'CLIENTPIDMAP' => '*',
|
|
'EMAIL' => '*',
|
|
'FBURL' => '*',
|
|
'IMPP' => '*',
|
|
'GENDER' => '?',
|
|
'GEO' => '*',
|
|
'KEY' => '*',
|
|
'KIND' => '?',
|
|
'LANG' => '*',
|
|
'LOGO' => '*',
|
|
'MEMBER' => '*',
|
|
'N' => '?',
|
|
'NICKNAME' => '*',
|
|
'NOTE' => '*',
|
|
'ORG' => '*',
|
|
'PHOTO' => '*',
|
|
'PRODID' => '?',
|
|
'RELATED' => '*',
|
|
'REV' => '?',
|
|
'ROLE' => '*',
|
|
'SOUND' => '*',
|
|
'SOURCE' => '*',
|
|
'TEL' => '*',
|
|
'TITLE' => '*',
|
|
'TZ' => '*',
|
|
'URL' => '*',
|
|
'VERSION' => '1',
|
|
'XML' => '*',
|
|
|
|
// FN is commented out, because it's already handled by the
|
|
// validate function, which may also try to repair it.
|
|
// 'FN' => '+',
|
|
'UID' => '?',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Returns a preferred field.
|
|
*
|
|
* VCards can indicate wether a field such as ADR, TEL or EMAIL is
|
|
* preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x
|
|
* being a number between 1 and 100).
|
|
*
|
|
* If neither of those parameters are specified, the first is returned, if
|
|
* a field with that name does not exist, null is returned.
|
|
*
|
|
* @param string $fieldName
|
|
*
|
|
* @return VObject\Property|null
|
|
*/
|
|
public function preferred($propertyName)
|
|
{
|
|
$preferred = null;
|
|
$lastPref = 101;
|
|
foreach ($this->select($propertyName) as $field) {
|
|
$pref = 101;
|
|
if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) {
|
|
$pref = 1;
|
|
} elseif (isset($field['PREF'])) {
|
|
$pref = $field['PREF']->getValue();
|
|
}
|
|
|
|
if ($pref < $lastPref || is_null($preferred)) {
|
|
$preferred = $field;
|
|
$lastPref = $pref;
|
|
}
|
|
}
|
|
|
|
return $preferred;
|
|
}
|
|
|
|
/**
|
|
* Returns a property with a specific TYPE value (ADR, TEL, or EMAIL).
|
|
*
|
|
* This function will return null if the property does not exist. If there are
|
|
* multiple properties with the same TYPE value, only one will be returned.
|
|
*
|
|
* @param string $propertyName
|
|
* @param string $type
|
|
*
|
|
* @return VObject\Property|null
|
|
*/
|
|
public function getByType($propertyName, $type)
|
|
{
|
|
foreach ($this->select($propertyName) as $field) {
|
|
if (isset($field['TYPE']) && $field['TYPE']->has($type)) {
|
|
return $field;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method should return a list of default property values.
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getDefaults()
|
|
{
|
|
return [
|
|
'VERSION' => '4.0',
|
|
'PRODID' => '-//Sabre//Sabre VObject '.VObject\Version::VERSION.'//EN',
|
|
'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* This method returns an array, with the representation as it should be
|
|
* encoded in json. This is used to create jCard or jCal documents.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function jsonSerialize()
|
|
{
|
|
// A vcard does not have sub-components, so we're overriding this
|
|
// method to remove that array element.
|
|
$properties = [];
|
|
|
|
foreach ($this->children() as $child) {
|
|
$properties[] = $child->jsonSerialize();
|
|
}
|
|
|
|
return [
|
|
strtolower($this->name),
|
|
$properties,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* This method serializes the data into XML. This is used to create xCard or
|
|
* xCal documents.
|
|
*
|
|
* @param Xml\Writer $writer XML writer
|
|
*/
|
|
public function xmlSerialize(Xml\Writer $writer)
|
|
{
|
|
$propertiesByGroup = [];
|
|
|
|
foreach ($this->children() as $property) {
|
|
$group = $property->group;
|
|
|
|
if (!isset($propertiesByGroup[$group])) {
|
|
$propertiesByGroup[$group] = [];
|
|
}
|
|
|
|
$propertiesByGroup[$group][] = $property;
|
|
}
|
|
|
|
$writer->startElement(strtolower($this->name));
|
|
|
|
foreach ($propertiesByGroup as $group => $properties) {
|
|
if (!empty($group)) {
|
|
$writer->startElement('group');
|
|
$writer->writeAttribute('name', strtolower($group));
|
|
}
|
|
|
|
foreach ($properties as $property) {
|
|
switch ($property->name) {
|
|
case 'VERSION':
|
|
break;
|
|
|
|
case 'XML':
|
|
$value = $property->getParts();
|
|
$fragment = new Xml\Element\XmlFragment($value[0]);
|
|
$writer->write($fragment);
|
|
break;
|
|
|
|
default:
|
|
$property->xmlSerialize($writer);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!empty($group)) {
|
|
$writer->endElement();
|
|
}
|
|
}
|
|
|
|
$writer->endElement();
|
|
}
|
|
|
|
/**
|
|
* Returns the default class for a property name.
|
|
*
|
|
* @param string $propertyName
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getClassNameForPropertyName($propertyName)
|
|
{
|
|
$className = parent::getClassNameForPropertyName($propertyName);
|
|
|
|
// In vCard 4, BINARY no longer exists, and we need URI instead.
|
|
if ('Sabre\\VObject\\Property\\Binary' == $className && self::VCARD40 === $this->getDocumentType()) {
|
|
return 'Sabre\\VObject\\Property\\Uri';
|
|
}
|
|
|
|
return $className;
|
|
}
|
|
}
|