'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\\Float', 'INTEGER' => 'Sabre\\VObject\\Property\\Integer', 'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', '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 */ static $propertyMap = array( // vCard 2.1 properties and up 'N' => 'Sabre\\VObject\\Property\\Text', 'FN' => 'Sabre\\VObject\\Property\\FlatText', 'PHOTO' => 'Sabre\\VObject\\Property\\Binary', // Todo: we should add a class for Binary values. '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', // rfc4770 properties 'IMPP' => 'Sabre\\VObject\\Property\\Uri', // vCard 4.0 properties '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', // 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 void */ 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 : $this->version = self::UNKNOWN; break; } } 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 */ 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 */ function validate($options = 0) { $warnings = array(); $versionMap = array( self::VCARD21 => '2.1', self::VCARD30 => '3.0', self::VCARD40 => '4.0', ); $version = $this->select('VERSION'); if (count($version)===1) { $version = (string)$this->VERSION; if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') { $warnings[] = array( '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 ($version === '2.1' && ($options & self::PROFILE_CARDDAV)) { $warnings[] = array( 'level' => 3, 'message' => 'CardDAV servers are not allowed to accept vCard 2.1.', 'node' => $this, ); } } $uid = $this->select('UID'); if (count($uid) === 0) { 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[] = array( 'level' => $warningLevel, 'message' => $message, 'node' => $this, ); } $fn = $this->select('FN'); if (count($fn)!==1) { $repaired = false; if (($options & self::REPAIR) && count($fn) === 0) { // 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; } } $warnings[] = array( '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 */ function getValidationRules() { return array( '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 */ 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; } /** * This method should return a list of default property values. * * @return array */ protected function getDefaults() { return array( 'VERSION' => '3.0', 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN', ); } /** * 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 */ function jsonSerialize() { // A vcard does not have sub-components, so we're overriding this // method to remove that array element. $properties = array(); foreach($this->children as $child) { $properties[] = $child->jsonSerialize(); } return array( strtolower($this->name), $properties, ); } /** * Returns the default class for a property name. * * @param string $propertyName * @return string */ function getClassNameForPropertyName($propertyName) { $className = parent::getClassNameForPropertyName($propertyName); // In vCard 4, BINARY no longer exists, and we need URI instead. if ($className == 'Sabre\\VObject\\Property\\Binary' && $this->getDocumentType()===self::VCARD40) { return 'Sabre\\VObject\\Property\\Uri'; } return $className; } }