comparison vendor/sabre/vobject/lib/VCardConverter.php @ 7:430dbd5346f7

vendor sabre as distributed
author Charlie Root
date Sat, 13 Jan 2018 09:06:10 -0500
parents
children
comparison
equal deleted inserted replaced
6:cec75ba50afc 7:430dbd5346f7
1 <?php
2
3 namespace Sabre\VObject;
4
5 /**
6 * This utility converts vcards from one version to another.
7 *
8 * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/).
9 * @author Evert Pot (http://evertpot.com/)
10 * @license http://sabre.io/license/ Modified BSD License
11 */
12 class VCardConverter {
13
14 /**
15 * Converts a vCard object to a new version.
16 *
17 * targetVersion must be one of:
18 * Document::VCARD21
19 * Document::VCARD30
20 * Document::VCARD40
21 *
22 * Currently only 3.0 and 4.0 as input and output versions.
23 *
24 * 2.1 has some minor support for the input version, it's incomplete at the
25 * moment though.
26 *
27 * If input and output version are identical, a clone is returned.
28 *
29 * @param Component\VCard $input
30 * @param int $targetVersion
31 */
32 public function convert(Component\VCard $input, $targetVersion) {
33
34 $inputVersion = $input->getDocumentType();
35 if ($inputVersion===$targetVersion) {
36 return clone $input;
37 }
38
39 if (!in_array($inputVersion, array(Document::VCARD21, Document::VCARD30, Document::VCARD40))) {
40 throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
41 }
42 if (!in_array($targetVersion, array(Document::VCARD30, Document::VCARD40))) {
43 throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
44 }
45
46 $newVersion = $targetVersion===Document::VCARD40?'4.0':'3.0';
47
48 $output = new Component\VCard(array(
49 'VERSION' => $newVersion,
50 ));
51
52 foreach($input->children as $property) {
53
54 $this->convertProperty($input, $output, $property, $targetVersion);
55
56 }
57
58 return $output;
59
60 }
61
62 /**
63 * Handles conversion of a single property.
64 *
65 * @param Component\VCard $input
66 * @param Component\VCard $output
67 * @param Property $property
68 * @param int $targetVersion
69 * @return void
70 */
71 protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) {
72
73 // Skipping these, those are automatically added.
74 if (in_array($property->name, array('VERSION', 'PRODID'))) {
75 return;
76 }
77
78 $parameters = $property->parameters();
79 $valueType = null;
80 if (isset($parameters['VALUE'])) {
81 $valueType = $parameters['VALUE']->getValue();
82 unset($parameters['VALUE']);
83 }
84 if (!$valueType) {
85 $valueType = $property->getValueType();
86 }
87 $newProperty = $output->createProperty(
88 $property->name,
89 $property->getParts(),
90 array(), // parameters will get added a bit later.
91 $valueType
92 );
93
94
95 if ($targetVersion===Document::VCARD30) {
96
97 if ($property instanceof Property\Uri && in_array($property->name, array('PHOTO','LOGO','SOUND'))) {
98
99 $newProperty = $this->convertUriToBinary($output, $newProperty);
100
101 } elseif ($property instanceof Property\VCard\DateAndOrTime) {
102
103 // In vCard 4, the birth year may be optional. This is not the
104 // case for vCard 3. Apple has a workaround for this that
105 // allows applications that support Apple's extension still
106 // omit birthyears in vCard 3, but applications that do not
107 // support this, will just use a random birthyear. We're
108 // choosing 1604 for the birthyear, because that's what apple
109 // uses.
110 $parts = DateTimeParser::parseVCardDateTime($property->getValue());
111 if (is_null($parts['year'])) {
112 $newValue = '1604-' . $parts['month'] . '-' . $parts['date'];
113 $newProperty->setValue($newValue);
114 $newProperty['X-APPLE-OMIT-YEAR'] = '1604';
115 }
116
117 if ($newProperty->name == 'ANNIVERSARY') {
118 // Microsoft non-standard anniversary
119 $newProperty->name = 'X-ANNIVERSARY';
120
121 // We also need to add a new apple property for the same
122 // purpose. This apple property needs a 'label' in the same
123 // group, so we first need to find a groupname that doesn't
124 // exist yet.
125 $x = 1;
126 while($output->select('ITEM' . $x . '.')) {
127 $x++;
128 }
129 $output->add('ITEM' . $x . '.X-ABDATE', $newProperty->getValue(), array('VALUE' => 'DATE-AND-OR-TIME'));
130 $output->add('ITEM' . $x . '.X-ABLABEL', '_$!<Anniversary>!$_');
131 }
132
133 } elseif ($property->name === 'KIND') {
134
135 switch(strtolower($property->getValue())) {
136 case 'org' :
137 // vCard 3.0 does not have an equivalent to KIND:ORG,
138 // but apple has an extension that means the same
139 // thing.
140 $newProperty = $output->createProperty('X-ABSHOWAS','COMPANY');
141 break;
142
143 case 'individual' :
144 // Individual is implicit, so we skip it.
145 return;
146
147 case 'group' :
148 // OS X addressbook property
149 $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND','GROUP');
150 break;
151 }
152
153
154 }
155
156 } elseif ($targetVersion===Document::VCARD40) {
157
158 // These properties were removed in vCard 4.0
159 if (in_array($property->name, array('NAME', 'MAILER', 'LABEL', 'CLASS'))) {
160 return;
161 }
162
163 if ($property instanceof Property\Binary) {
164
165 $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters);
166
167 } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) {
168
169 // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR',
170 // then we're stripping the year from the vcard 4 value.
171 $parts = DateTimeParser::parseVCardDateTime($property->getValue());
172 if ($parts['year']===$property['X-APPLE-OMIT-YEAR']->getValue()) {
173 $newValue = '--' . $parts['month'] . '-' . $parts['date'];
174 $newProperty->setValue($newValue);
175 }
176
177 // Regardless if the year matched or not, we do need to strip
178 // X-APPLE-OMIT-YEAR.
179 unset($parameters['X-APPLE-OMIT-YEAR']);
180
181 }
182 switch($property->name) {
183 case 'X-ABSHOWAS' :
184 if (strtoupper($property->getValue()) === 'COMPANY') {
185 $newProperty = $output->createProperty('KIND','ORG');
186 }
187 break;
188 case 'X-ADDRESSBOOKSERVER-KIND' :
189 if (strtoupper($property->getValue()) === 'GROUP') {
190 $newProperty = $output->createProperty('KIND','GROUP');
191 }
192 break;
193 case 'X-ANNIVERSARY' :
194 $newProperty->name = 'ANNIVERSARY';
195 // If we already have an anniversary property with the same
196 // value, ignore.
197 foreach ($output->select('ANNIVERSARY') as $anniversary) {
198 if ($anniversary->getValue() === $newProperty->getValue()) {
199 return;
200 }
201 }
202 break;
203 case 'X-ABDATE' :
204 // Find out what the label was, if it exists.
205 if (!$property->group) {
206 break;
207 }
208 $label = $input->{$property->group . '.X-ABLABEL'};
209
210 // We only support converting anniversaries.
211 if (!$label || $label->getValue()!=='_$!<Anniversary>!$_') {
212 break;
213 }
214
215 // If we already have an anniversary property with the same
216 // value, ignore.
217 foreach ($output->select('ANNIVERSARY') as $anniversary) {
218 if ($anniversary->getValue() === $newProperty->getValue()) {
219 return;
220 }
221 }
222 $newProperty->name = 'ANNIVERSARY';
223 break;
224 // Apple's per-property label system.
225 case 'X-ABLABEL' :
226 if($newProperty->getValue() === '_$!<Anniversary>!$_') {
227 // We can safely remove these, as they are converted to
228 // ANNIVERSARY properties.
229 return;
230 }
231 break;
232
233 }
234
235 }
236
237 // set property group
238 $newProperty->group = $property->group;
239
240 if ($targetVersion===Document::VCARD40) {
241 $this->convertParameters40($newProperty, $parameters);
242 } else {
243 $this->convertParameters30($newProperty, $parameters);
244 }
245
246 // Lastly, we need to see if there's a need for a VALUE parameter.
247 //
248 // We can do that by instantating a empty property with that name, and
249 // seeing if the default valueType is identical to the current one.
250 $tempProperty = $output->createProperty($newProperty->name);
251 if ($tempProperty->getValueType() !== $newProperty->getValueType()) {
252 $newProperty['VALUE'] = $newProperty->getValueType();
253 }
254
255 $output->add($newProperty);
256
257
258 }
259
260 /**
261 * Converts a BINARY property to a URI property.
262 *
263 * vCard 4.0 no longer supports BINARY properties.
264 *
265 * @param Component\VCard $output
266 * @param Property\Uri $property The input property.
267 * @param $parameters List of parameters that will eventually be added to
268 * the new property.
269 * @return Property\Uri
270 */
271 protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) {
272
273 $value = $newProperty->getValue();
274 $newProperty = $output->createProperty(
275 $newProperty->name,
276 null, // no value
277 array(), // no parameters yet
278 'URI' // Forcing the BINARY type
279 );
280
281 $mimeType = 'application/octet-stream';
282
283 // See if we can find a better mimetype.
284 if (isset($parameters['TYPE'])) {
285
286 $newTypes = array();
287 foreach($parameters['TYPE']->getParts() as $typePart) {
288 if (in_array(
289 strtoupper($typePart),
290 array('JPEG','PNG','GIF')
291 )) {
292 $mimeType = 'image/' . strtolower($typePart);
293 } else {
294 $newTypes[] = $typePart;
295 }
296 }
297
298 // If there were any parameters we're not converting to a
299 // mime-type, we need to keep them.
300 if ($newTypes) {
301 $parameters['TYPE']->setParts($newTypes);
302 } else {
303 unset($parameters['TYPE']);
304 }
305
306 }
307
308 $newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($value));
309 return $newProperty;
310
311 }
312
313 /**
314 * Converts a URI property to a BINARY property.
315 *
316 * In vCard 4.0 attachments are encoded as data: uri. Even though these may
317 * be valid in vCard 3.0 as well, we should convert those to BINARY if
318 * possible, to improve compatibility.
319 *
320 * @param Component\VCard $output
321 * @param Property\Uri $property The input property.
322 * @return Property\Binary|null
323 */
324 protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) {
325
326 $value = $newProperty->getValue();
327
328 // Only converting data: uris
329 if (substr($value, 0, 5)!=='data:') {
330 return $newProperty;
331 }
332
333 $newProperty = $output->createProperty(
334 $newProperty->name,
335 null, // no value
336 array(), // no parameters yet
337 'BINARY'
338 );
339
340 $mimeType = substr($value, 5, strpos($value, ',')-5);
341 if (strpos($mimeType, ';')) {
342 $mimeType = substr($mimeType,0,strpos($mimeType, ';'));
343 $newProperty->setValue(base64_decode(substr($value, strpos($value,',')+1)));
344 } else {
345 $newProperty->setValue(substr($value, strpos($value,',')+1));
346 }
347 unset($value);
348
349 $newProperty['ENCODING'] = 'b';
350 switch($mimeType) {
351
352 case 'image/jpeg' :
353 $newProperty['TYPE'] = 'JPEG';
354 break;
355 case 'image/png' :
356 $newProperty['TYPE'] = 'PNG';
357 break;
358 case 'image/gif' :
359 $newProperty['TYPE'] = 'GIF';
360 break;
361
362 }
363
364
365 return $newProperty;
366
367 }
368
369 /**
370 * Adds parameters to a new property for vCard 4.0
371 *
372 * @param Property $newProperty
373 * @param array $parameters
374 * @return void
375 */
376 protected function convertParameters40(Property $newProperty, array $parameters) {
377
378 // Adding all parameters.
379 foreach($parameters as $param) {
380
381 // vCard 2.1 allowed parameters with no name
382 if ($param->noName) $param->noName = false;
383
384 switch($param->name) {
385
386 // We need to see if there's any TYPE=PREF, because in vCard 4
387 // that's now PREF=1.
388 case 'TYPE' :
389 foreach($param->getParts() as $paramPart) {
390
391 if (strtoupper($paramPart)==='PREF') {
392 $newProperty->add('PREF','1');
393 } else {
394 $newProperty->add($param->name, $paramPart);
395 }
396
397 }
398 break;
399 // These no longer exist in vCard 4
400 case 'ENCODING' :
401 case 'CHARSET' :
402 break;
403
404 default :
405 $newProperty->add($param->name, $param->getParts());
406 break;
407
408 }
409
410 }
411
412 }
413
414 /**
415 * Adds parameters to a new property for vCard 3.0
416 *
417 * @param Property $newProperty
418 * @param array $parameters
419 * @return void
420 */
421 protected function convertParameters30(Property $newProperty, array $parameters) {
422
423 // Adding all parameters.
424 foreach($parameters as $param) {
425
426 // vCard 2.1 allowed parameters with no name
427 if ($param->noName) $param->noName = false;
428
429 switch($param->name) {
430
431 case 'ENCODING' :
432 // This value only existed in vCard 2.1, and should be
433 // removed for anything else.
434 if (strtoupper($param->getValue())!=='QUOTED-PRINTABLE') {
435 $newProperty->add($param->name, $param->getParts());
436 }
437 break;
438
439 /*
440 * Converting PREF=1 to TYPE=PREF.
441 *
442 * Any other PREF numbers we'll drop.
443 */
444 case 'PREF' :
445 if ($param->getValue()=='1') {
446 $newProperty->add('TYPE','PREF');
447 }
448 break;
449
450 default :
451 $newProperty->add($param->name, $param->getParts());
452 break;
453
454 }
455
456 }
457
458 }
459 }