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