Mercurial > hg > rc1
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 } |
