vendor/sabre/vobject/lib/Component/VCard.php line 149

Open in your IDE?
  1. <?php
  2. namespace Sabre\VObject\Component;
  3. use Sabre\VObject;
  4. use Sabre\Xml;
  5. /**
  6.  * The VCard component.
  7.  *
  8.  * This component represents the BEGIN:VCARD and END:VCARD found in every
  9.  * vcard.
  10.  *
  11.  * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
  12.  * @author Evert Pot (http://evertpot.com/)
  13.  * @license http://sabre.io/license/ Modified BSD License
  14.  */
  15. class VCard extends VObject\Document
  16. {
  17.     /**
  18.      * The default name for this component.
  19.      *
  20.      * This should be 'VCALENDAR' or 'VCARD'.
  21.      *
  22.      * @var string
  23.      */
  24.     public static $defaultName 'VCARD';
  25.     /**
  26.      * Caching the version number.
  27.      *
  28.      * @var int
  29.      */
  30.     private $version null;
  31.     /**
  32.      * This is a list of components, and which classes they should map to.
  33.      *
  34.      * @var array
  35.      */
  36.     public static $componentMap = [
  37.         'VCARD' => VCard::class,
  38.     ];
  39.     /**
  40.      * List of value-types, and which classes they map to.
  41.      *
  42.      * @var array
  43.      */
  44.     public static $valueMap = [
  45.         'BINARY' => VObject\Property\Binary::class,
  46.         'BOOLEAN' => VObject\Property\Boolean::class,
  47.         'CONTENT-ID' => VObject\Property\FlatText::class,   // vCard 2.1 only
  48.         'DATE' => VObject\Property\VCard\Date::class,
  49.         'DATE-TIME' => VObject\Property\VCard\DateTime::class,
  50.         'DATE-AND-OR-TIME' => VObject\Property\VCard\DateAndOrTime::class, // vCard only
  51.         'FLOAT' => VObject\Property\FloatValue::class,
  52.         'INTEGER' => VObject\Property\IntegerValue::class,
  53.         'LANGUAGE-TAG' => VObject\Property\VCard\LanguageTag::class,
  54.         'PHONE-NUMBER' => VObject\Property\VCard\PhoneNumber::class, // vCard 3.0 only
  55.         'TIMESTAMP' => VObject\Property\VCard\TimeStamp::class,
  56.         'TEXT' => VObject\Property\Text::class,
  57.         'TIME' => VObject\Property\Time::class,
  58.         'UNKNOWN' => VObject\Property\Unknown::class, // jCard / jCal-only.
  59.         'URI' => VObject\Property\Uri::class,
  60.         'URL' => VObject\Property\Uri::class, // vCard 2.1 only
  61.         'UTC-OFFSET' => VObject\Property\UtcOffset::class,
  62.     ];
  63.     /**
  64.      * List of properties, and which classes they map to.
  65.      *
  66.      * @var array
  67.      */
  68.     public static $propertyMap = [
  69.         // vCard 2.1 properties and up
  70.         'N' => VObject\Property\Text::class,
  71.         'FN' => VObject\Property\FlatText::class,
  72.         'PHOTO' => VObject\Property\Binary::class,
  73.         'BDAY' => VObject\Property\VCard\DateAndOrTime::class,
  74.         'ADR' => VObject\Property\Text::class,
  75.         'LABEL' => VObject\Property\FlatText::class, // Removed in vCard 4.0
  76.         'TEL' => VObject\Property\FlatText::class,
  77.         'EMAIL' => VObject\Property\FlatText::class,
  78.         'MAILER' => VObject\Property\FlatText::class, // Removed in vCard 4.0
  79.         'GEO' => VObject\Property\FlatText::class,
  80.         'TITLE' => VObject\Property\FlatText::class,
  81.         'ROLE' => VObject\Property\FlatText::class,
  82.         'LOGO' => VObject\Property\Binary::class,
  83.         // 'AGENT'   => 'Sabre\\VObject\\Property\\',      // Todo: is an embedded vCard. Probably rare, so
  84.                                  // not supported at the moment
  85.         'ORG' => VObject\Property\Text::class,
  86.         'NOTE' => VObject\Property\FlatText::class,
  87.         'REV' => VObject\Property\VCard\TimeStamp::class,
  88.         'SOUND' => VObject\Property\FlatText::class,
  89.         'URL' => VObject\Property\Uri::class,
  90.         'UID' => VObject\Property\FlatText::class,
  91.         'VERSION' => VObject\Property\FlatText::class,
  92.         'KEY' => VObject\Property\FlatText::class,
  93.         'TZ' => VObject\Property\Text::class,
  94.         // vCard 3.0 properties
  95.         'CATEGORIES' => VObject\Property\Text::class,
  96.         'SORT-STRING' => VObject\Property\FlatText::class,
  97.         'PRODID' => VObject\Property\FlatText::class,
  98.         'NICKNAME' => VObject\Property\Text::class,
  99.         'CLASS' => VObject\Property\FlatText::class, // Removed in vCard 4.0
  100.         // rfc2739 properties
  101.         'FBURL' => VObject\Property\Uri::class,
  102.         'CAPURI' => VObject\Property\Uri::class,
  103.         'CALURI' => VObject\Property\Uri::class,
  104.         'CALADRURI' => VObject\Property\Uri::class,
  105.         // rfc4770 properties
  106.         'IMPP' => VObject\Property\Uri::class,
  107.         // vCard 4.0 properties
  108.         'SOURCE' => VObject\Property\Uri::class,
  109.         'XML' => VObject\Property\FlatText::class,
  110.         'ANNIVERSARY' => VObject\Property\VCard\DateAndOrTime::class,
  111.         'CLIENTPIDMAP' => VObject\Property\Text::class,
  112.         'LANG' => VObject\Property\VCard\LanguageTag::class,
  113.         'GENDER' => VObject\Property\Text::class,
  114.         'KIND' => VObject\Property\FlatText::class,
  115.         'MEMBER' => VObject\Property\Uri::class,
  116.         'RELATED' => VObject\Property\Uri::class,
  117.         // rfc6474 properties
  118.         'BIRTHPLACE' => VObject\Property\FlatText::class,
  119.         'DEATHPLACE' => VObject\Property\FlatText::class,
  120.         'DEATHDATE' => VObject\Property\VCard\DateAndOrTime::class,
  121.         // rfc6715 properties
  122.         'EXPERTISE' => VObject\Property\FlatText::class,
  123.         'HOBBY' => VObject\Property\FlatText::class,
  124.         'INTEREST' => VObject\Property\FlatText::class,
  125.         'ORG-DIRECTORY' => VObject\Property\FlatText::class,
  126.     ];
  127.     /**
  128.      * Returns the current document type.
  129.      *
  130.      * @return int
  131.      */
  132.     public function getDocumentType()
  133.     {
  134.         if (!$this->version) {
  135.             $version = (string) $this->VERSION;
  136.             switch ($version) {
  137.                 case '2.1':
  138.                     $this->version self::VCARD21;
  139.                     break;
  140.                 case '3.0':
  141.                     $this->version self::VCARD30;
  142.                     break;
  143.                 case '4.0':
  144.                     $this->version self::VCARD40;
  145.                     break;
  146.                 default:
  147.                     // We don't want to cache the version if it's unknown,
  148.                     // because we might get a version property in a bit.
  149.                     return self::UNKNOWN;
  150.             }
  151.         }
  152.         return $this->version;
  153.     }
  154.     /**
  155.      * Converts the document to a different vcard version.
  156.      *
  157.      * Use one of the VCARD constants for the target. This method will return
  158.      * a copy of the vcard in the new version.
  159.      *
  160.      * At the moment the only supported conversion is from 3.0 to 4.0.
  161.      *
  162.      * If input and output version are identical, a clone is returned.
  163.      *
  164.      * @param int $target
  165.      *
  166.      * @return VCard
  167.      */
  168.     public function convert($target)
  169.     {
  170.         $converter = new VObject\VCardConverter();
  171.         return $converter->convert($this$target);
  172.     }
  173.     /**
  174.      * VCards with version 2.1, 3.0 and 4.0 are found.
  175.      *
  176.      * If the VCARD doesn't know its version, 2.1 is assumed.
  177.      */
  178.     const DEFAULT_VERSION self::VCARD21;
  179.     /**
  180.      * Validates the node for correctness.
  181.      *
  182.      * The following options are supported:
  183.      *   Node::REPAIR - May attempt to automatically repair the problem.
  184.      *
  185.      * This method returns an array with detected problems.
  186.      * Every element has the following properties:
  187.      *
  188.      *  * level - problem level.
  189.      *  * message - A human-readable string describing the issue.
  190.      *  * node - A reference to the problematic node.
  191.      *
  192.      * The level means:
  193.      *   1 - The issue was repaired (only happens if REPAIR was turned on)
  194.      *   2 - An inconsequential issue
  195.      *   3 - A severe issue.
  196.      *
  197.      * @param int $options
  198.      *
  199.      * @return array
  200.      */
  201.     public function validate($options 0)
  202.     {
  203.         $warnings = [];
  204.         $versionMap = [
  205.             self::VCARD21 => '2.1',
  206.             self::VCARD30 => '3.0',
  207.             self::VCARD40 => '4.0',
  208.         ];
  209.         $version $this->select('VERSION');
  210.         if (=== count($version)) {
  211.             $version = (string) $this->VERSION;
  212.             if ('2.1' !== $version && '3.0' !== $version && '4.0' !== $version) {
  213.                 $warnings[] = [
  214.                     'level' => 3,
  215.                     'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
  216.                     'node' => $this,
  217.                 ];
  218.                 if ($options self::REPAIR) {
  219.                     $this->VERSION $versionMap[self::DEFAULT_VERSION];
  220.                 }
  221.             }
  222.             if ('2.1' === $version && ($options self::PROFILE_CARDDAV)) {
  223.                 $warnings[] = [
  224.                     'level' => 3,
  225.                     'message' => 'CardDAV servers are not allowed to accept vCard 2.1.',
  226.                     'node' => $this,
  227.                 ];
  228.             }
  229.         }
  230.         $uid $this->select('UID');
  231.         if (=== count($uid)) {
  232.             if ($options self::PROFILE_CARDDAV) {
  233.                 // Required for CardDAV
  234.                 $warningLevel 3;
  235.                 $message 'vCards on CardDAV servers MUST have a UID property.';
  236.             } else {
  237.                 // Not required for regular vcards
  238.                 $warningLevel 2;
  239.                 $message 'Adding a UID to a vCard property is recommended.';
  240.             }
  241.             if ($options self::REPAIR) {
  242.                 $this->UID VObject\UUIDUtil::getUUID();
  243.                 $warningLevel 1;
  244.             }
  245.             $warnings[] = [
  246.                 'level' => $warningLevel,
  247.                 'message' => $message,
  248.                 'node' => $this,
  249.             ];
  250.         }
  251.         $fn $this->select('FN');
  252.         if (!== count($fn)) {
  253.             $repaired false;
  254.             if (($options self::REPAIR) && === count($fn)) {
  255.                 // We're going to try to see if we can use the contents of the
  256.                 // N property.
  257.                 if (isset($this->N)) {
  258.                     $value explode(';', (string) $this->N);
  259.                     if (isset($value[1]) && $value[1]) {
  260.                         $this->FN $value[1].' '.$value[0];
  261.                     } else {
  262.                         $this->FN $value[0];
  263.                     }
  264.                     $repaired true;
  265.                 // Otherwise, the ORG property may work
  266.                 } elseif (isset($this->ORG)) {
  267.                     $this->FN = (string) $this->ORG;
  268.                     $repaired true;
  269.                 // Otherwise, the NICKNAME property may work
  270.                 } elseif (isset($this->NICKNAME)) {
  271.                     $this->FN = (string) $this->NICKNAME;
  272.                     $repaired true;
  273.                 // Otherwise, the EMAIL property may work
  274.                 } elseif (isset($this->EMAIL)) {
  275.                     $this->FN = (string) $this->EMAIL;
  276.                     $repaired true;
  277.                 }
  278.             }
  279.             $warnings[] = [
  280.                 'level' => $repaired 3,
  281.                 'message' => 'The FN property must appear in the VCARD component exactly 1 time',
  282.                 'node' => $this,
  283.             ];
  284.         }
  285.         return array_merge(
  286.             parent::validate($options),
  287.             $warnings
  288.         );
  289.     }
  290.     /**
  291.      * A simple list of validation rules.
  292.      *
  293.      * This is simply a list of properties, and how many times they either
  294.      * must or must not appear.
  295.      *
  296.      * Possible values per property:
  297.      *   * 0 - Must not appear.
  298.      *   * 1 - Must appear exactly once.
  299.      *   * + - Must appear at least once.
  300.      *   * * - Can appear any number of times.
  301.      *   * ? - May appear, but not more than once.
  302.      *
  303.      * @var array
  304.      */
  305.     public function getValidationRules()
  306.     {
  307.         return [
  308.             'ADR' => '*',
  309.             'ANNIVERSARY' => '?',
  310.             'BDAY' => '?',
  311.             'CALADRURI' => '*',
  312.             'CALURI' => '*',
  313.             'CATEGORIES' => '*',
  314.             'CLIENTPIDMAP' => '*',
  315.             'EMAIL' => '*',
  316.             'FBURL' => '*',
  317.             'IMPP' => '*',
  318.             'GENDER' => '?',
  319.             'GEO' => '*',
  320.             'KEY' => '*',
  321.             'KIND' => '?',
  322.             'LANG' => '*',
  323.             'LOGO' => '*',
  324.             'MEMBER' => '*',
  325.             'N' => '?',
  326.             'NICKNAME' => '*',
  327.             'NOTE' => '*',
  328.             'ORG' => '*',
  329.             'PHOTO' => '*',
  330.             'PRODID' => '?',
  331.             'RELATED' => '*',
  332.             'REV' => '?',
  333.             'ROLE' => '*',
  334.             'SOUND' => '*',
  335.             'SOURCE' => '*',
  336.             'TEL' => '*',
  337.             'TITLE' => '*',
  338.             'TZ' => '*',
  339.             'URL' => '*',
  340.             'VERSION' => '1',
  341.             'XML' => '*',
  342.             // FN is commented out, because it's already handled by the
  343.             // validate function, which may also try to repair it.
  344.             // 'FN'           => '+',
  345.             'UID' => '?',
  346.         ];
  347.     }
  348.     /**
  349.      * Returns a preferred field.
  350.      *
  351.      * VCards can indicate whether a field such as ADR, TEL or EMAIL is
  352.      * preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x
  353.      * being a number between 1 and 100).
  354.      *
  355.      * If neither of those parameters are specified, the first is returned, if
  356.      * a field with that name does not exist, null is returned.
  357.      *
  358.      * @param string $fieldName
  359.      *
  360.      * @return VObject\Property|null
  361.      */
  362.     public function preferred($propertyName)
  363.     {
  364.         $preferred null;
  365.         $lastPref 101;
  366.         foreach ($this->select($propertyName) as $field) {
  367.             $pref 101;
  368.             if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) {
  369.                 $pref 1;
  370.             } elseif (isset($field['PREF'])) {
  371.                 $pref $field['PREF']->getValue();
  372.             }
  373.             if ($pref $lastPref || is_null($preferred)) {
  374.                 $preferred $field;
  375.                 $lastPref $pref;
  376.             }
  377.         }
  378.         return $preferred;
  379.     }
  380.     /**
  381.      * Returns a property with a specific TYPE value (ADR, TEL, or EMAIL).
  382.      *
  383.      * This function will return null if the property does not exist. If there are
  384.      * multiple properties with the same TYPE value, only one will be returned.
  385.      *
  386.      * @param string $propertyName
  387.      * @param string $type
  388.      *
  389.      * @return VObject\Property|null
  390.      */
  391.     public function getByType($propertyName$type)
  392.     {
  393.         foreach ($this->select($propertyName) as $field) {
  394.             if (isset($field['TYPE']) && $field['TYPE']->has($type)) {
  395.                 return $field;
  396.             }
  397.         }
  398.     }
  399.     /**
  400.      * This method should return a list of default property values.
  401.      *
  402.      * @return array
  403.      */
  404.     protected function getDefaults()
  405.     {
  406.         return [
  407.             'VERSION' => '4.0',
  408.             'PRODID' => '-//Sabre//Sabre VObject '.VObject\Version::VERSION.'//EN',
  409.             'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(),
  410.         ];
  411.     }
  412.     /**
  413.      * This method returns an array, with the representation as it should be
  414.      * encoded in json. This is used to create jCard or jCal documents.
  415.      *
  416.      * @return array
  417.      */
  418.     #[\ReturnTypeWillChange]
  419.     public function jsonSerialize()
  420.     {
  421.         // A vcard does not have sub-components, so we're overriding this
  422.         // method to remove that array element.
  423.         $properties = [];
  424.         foreach ($this->children() as $child) {
  425.             $properties[] = $child->jsonSerialize();
  426.         }
  427.         return [
  428.             strtolower($this->name),
  429.             $properties,
  430.         ];
  431.     }
  432.     /**
  433.      * This method serializes the data into XML. This is used to create xCard or
  434.      * xCal documents.
  435.      *
  436.      * @param Xml\Writer $writer XML writer
  437.      */
  438.     public function xmlSerialize(Xml\Writer $writer): void
  439.     {
  440.         $propertiesByGroup = [];
  441.         foreach ($this->children() as $property) {
  442.             $group $property->group;
  443.             if (!isset($propertiesByGroup[$group])) {
  444.                 $propertiesByGroup[$group] = [];
  445.             }
  446.             $propertiesByGroup[$group][] = $property;
  447.         }
  448.         $writer->startElement(strtolower($this->name));
  449.         foreach ($propertiesByGroup as $group => $properties) {
  450.             if (!empty($group)) {
  451.                 $writer->startElement('group');
  452.                 $writer->writeAttribute('name'strtolower($group));
  453.             }
  454.             foreach ($properties as $property) {
  455.                 switch ($property->name) {
  456.                     case 'VERSION':
  457.                         break;
  458.                     case 'XML':
  459.                         $value $property->getParts();
  460.                         $fragment = new Xml\Element\XmlFragment($value[0]);
  461.                         $writer->write($fragment);
  462.                         break;
  463.                     default:
  464.                         $property->xmlSerialize($writer);
  465.                         break;
  466.                 }
  467.             }
  468.             if (!empty($group)) {
  469.                 $writer->endElement();
  470.             }
  471.         }
  472.         $writer->endElement();
  473.     }
  474.     /**
  475.      * Returns the default class for a property name.
  476.      *
  477.      * @param string $propertyName
  478.      *
  479.      * @return string
  480.      */
  481.     public function getClassNameForPropertyName($propertyName)
  482.     {
  483.         $className parent::getClassNameForPropertyName($propertyName);
  484.         // In vCard 4, BINARY no longer exists, and we need URI instead.
  485.         if (VObject\Property\Binary::class == $className && self::VCARD40 === $this->getDocumentType()) {
  486.             return VObject\Property\Uri::class;
  487.         }
  488.         return $className;
  489.     }
  490. }