| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- <?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' => VCard::class,
- ];
- /**
- * List of value-types, and which classes they map to.
- *
- * @var array
- */
- public static $valueMap = [
- 'BINARY' => VObject\Property\Binary::class,
- 'BOOLEAN' => VObject\Property\Boolean::class,
- 'CONTENT-ID' => VObject\Property\FlatText::class, // vCard 2.1 only
- 'DATE' => VObject\Property\VCard\Date::class,
- 'DATE-TIME' => VObject\Property\VCard\DateTime::class,
- 'DATE-AND-OR-TIME' => VObject\Property\VCard\DateAndOrTime::class, // vCard only
- 'FLOAT' => VObject\Property\FloatValue::class,
- 'INTEGER' => VObject\Property\IntegerValue::class,
- 'LANGUAGE-TAG' => VObject\Property\VCard\LanguageTag::class,
- 'PHONE-NUMBER' => VObject\Property\VCard\PhoneNumber::class, // vCard 3.0 only
- 'TIMESTAMP' => VObject\Property\VCard\TimeStamp::class,
- 'TEXT' => VObject\Property\Text::class,
- 'TIME' => VObject\Property\Time::class,
- 'UNKNOWN' => VObject\Property\Unknown::class, // jCard / jCal-only.
- 'URI' => VObject\Property\Uri::class,
- 'URL' => VObject\Property\Uri::class, // vCard 2.1 only
- 'UTC-OFFSET' => VObject\Property\UtcOffset::class,
- ];
- /**
- * List of properties, and which classes they map to.
- *
- * @var array
- */
- public static $propertyMap = [
- // vCard 2.1 properties and up
- 'N' => VObject\Property\Text::class,
- 'FN' => VObject\Property\FlatText::class,
- 'PHOTO' => VObject\Property\Binary::class,
- 'BDAY' => VObject\Property\VCard\DateAndOrTime::class,
- 'ADR' => VObject\Property\Text::class,
- 'LABEL' => VObject\Property\FlatText::class, // Removed in vCard 4.0
- 'TEL' => VObject\Property\FlatText::class,
- 'EMAIL' => VObject\Property\FlatText::class,
- 'MAILER' => VObject\Property\FlatText::class, // Removed in vCard 4.0
- 'GEO' => VObject\Property\FlatText::class,
- 'TITLE' => VObject\Property\FlatText::class,
- 'ROLE' => VObject\Property\FlatText::class,
- 'LOGO' => VObject\Property\Binary::class,
- // 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so
- // not supported at the moment
- 'ORG' => VObject\Property\Text::class,
- 'NOTE' => VObject\Property\FlatText::class,
- 'REV' => VObject\Property\VCard\TimeStamp::class,
- 'SOUND' => VObject\Property\FlatText::class,
- 'URL' => VObject\Property\Uri::class,
- 'UID' => VObject\Property\FlatText::class,
- 'VERSION' => VObject\Property\FlatText::class,
- 'KEY' => VObject\Property\FlatText::class,
- 'TZ' => VObject\Property\Text::class,
- // vCard 3.0 properties
- 'CATEGORIES' => VObject\Property\Text::class,
- 'SORT-STRING' => VObject\Property\FlatText::class,
- 'PRODID' => VObject\Property\FlatText::class,
- 'NICKNAME' => VObject\Property\Text::class,
- 'CLASS' => VObject\Property\FlatText::class, // Removed in vCard 4.0
- // rfc2739 properties
- 'FBURL' => VObject\Property\Uri::class,
- 'CAPURI' => VObject\Property\Uri::class,
- 'CALURI' => VObject\Property\Uri::class,
- 'CALADRURI' => VObject\Property\Uri::class,
- // rfc4770 properties
- 'IMPP' => VObject\Property\Uri::class,
- // vCard 4.0 properties
- 'SOURCE' => VObject\Property\Uri::class,
- 'XML' => VObject\Property\FlatText::class,
- 'ANNIVERSARY' => VObject\Property\VCard\DateAndOrTime::class,
- 'CLIENTPIDMAP' => VObject\Property\Text::class,
- 'LANG' => VObject\Property\VCard\LanguageTag::class,
- 'GENDER' => VObject\Property\Text::class,
- 'KIND' => VObject\Property\FlatText::class,
- 'MEMBER' => VObject\Property\Uri::class,
- 'RELATED' => VObject\Property\Uri::class,
- // rfc6474 properties
- 'BIRTHPLACE' => VObject\Property\FlatText::class,
- 'DEATHPLACE' => VObject\Property\FlatText::class,
- 'DEATHDATE' => VObject\Property\VCard\DateAndOrTime::class,
- // rfc6715 properties
- 'EXPERTISE' => VObject\Property\FlatText::class,
- 'HOBBY' => VObject\Property\FlatText::class,
- 'INTEREST' => VObject\Property\FlatText::class,
- 'ORG-DIRECTORY' => VObject\Property\FlatText::class,
- ];
- /**
- * 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 NICKNAME property may work
- } elseif (isset($this->NICKNAME)) {
- $this->FN = (string) $this->NICKNAME;
- $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 whether 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
- */
- #[\ReturnTypeWillChange]
- 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): void
- {
- $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 (VObject\Property\Binary::class == $className && self::VCARD40 === $this->getDocumentType()) {
- return VObject\Property\Uri::class;
- }
- return $className;
- }
- }
|