VCard.php 15 KB

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