1<?php 2 3namespace RainLoop\Providers\AddressBook\Classes; 4 5use 6 RainLoop\Providers\AddressBook\Enumerations\PropertyType, 7 RainLoop\Providers\AddressBook\Classes\Property 8; 9 10class Contact 11{ 12 /** 13 * @var string 14 */ 15 public $IdContact; 16 17 /** 18 * @var string 19 */ 20 public $IdContactStr; 21 22 /** 23 * @var string 24 */ 25 public $Display; 26 27 /** 28 * @var int 29 */ 30 public $Changed; 31 32 /** 33 * @var array 34 */ 35 public $Properties; 36 37 /** 38 * @var bool 39 */ 40 public $ReadOnly; 41 42 /** 43 * @var int 44 */ 45 public $IdPropertyFromSearch; 46 47 /** 48 * @var string 49 */ 50 public $Etag; 51 52 public function __construct() 53 { 54 $this->Clear(); 55 } 56 57 public function Clear() 58 { 59 $this->IdContact = ''; 60 $this->IdContactStr = ''; 61 $this->Display = ''; 62 $this->Changed = \time(); 63 $this->Properties = array(); 64 $this->ReadOnly = false; 65 $this->IdPropertyFromSearch = 0; 66 $this->Etag = ''; 67 } 68 69 public function PopulateDisplayAndFullNameValue($bForceFullNameReplace = false) 70 { 71 $sFullName = ''; 72 $sLastName = ''; 73 $sFirstName = ''; 74 $sEmail = ''; 75 $sOther = ''; 76 77 $oFullNameProperty = null; 78 79 foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty) 80 { 81 if ($oProperty) 82 { 83 $oProperty->UpdateDependentValues(); 84 85 if (!$oFullNameProperty && PropertyType::FULLNAME === $oProperty->Type) 86 { 87 $oFullNameProperty =& $oProperty; 88 } 89 90 if (0 < \strlen($oProperty->Value)) 91 { 92 if ('' === $sEmail && $oProperty->IsEmail()) 93 { 94 $sEmail = $oProperty->Value; 95 } 96 else if ('' === $sLastName && PropertyType::LAST_NAME === $oProperty->Type) 97 { 98 $sLastName = $oProperty->Value; 99 } 100 else if ('' === $sFirstName && PropertyType::FIRST_NAME === $oProperty->Type) 101 { 102 $sFirstName = $oProperty->Value; 103 } 104 else if ('' === $sFullName && PropertyType::FULLNAME === $oProperty->Type) 105 { 106 $sFullName = $oProperty->Value; 107 } 108 else if ('' === $sOther && \in_array($oProperty->Type, array( 109 PropertyType::PHONE 110 ))) 111 { 112 $sOther = $oProperty->Value; 113 } 114 } 115 } 116 } 117 118 $sDisplay = $bForceFullNameReplace ? '' : \trim($sFullName); 119 120 if ('' === $sDisplay && (0 < \strlen($sLastName) || 0 < \strlen($sFirstName))) 121 { 122 $sDisplay = \trim($sFirstName.' '.$sLastName); 123 } 124 125 if ('' === $sDisplay) 126 { 127 $sDisplay = \trim($sFullName); 128 } 129 130 if ('' === $sDisplay) 131 { 132 $sDisplay = \trim($sEmail); 133 } 134 135 if ('' === $sDisplay) 136 { 137 $sDisplay = \trim($sOther); 138 } 139 140 $this->Display = \trim($sDisplay); 141 142 if ($oFullNameProperty) 143 { 144 $oFullNameProperty->Value = $this->Display; 145 $oFullNameProperty->UpdateDependentValues(); 146 } 147 148 if (!$oFullNameProperty) 149 { 150 $this->Properties[] = new \RainLoop\Providers\AddressBook\Classes\Property(PropertyType::FULLNAME, $this->Display); 151 } 152 } 153 154 public function UpdateDependentValues() 155 { 156 if (empty($this->IdContactStr)) 157 { 158 $this->RegenerateContactStr(); 159 } 160 161 $this->PopulateDisplayAndFullNameValue(); 162 } 163 164 /** 165 * @return array 166 */ 167 public function RegenerateContactStr() 168 { 169 $this->IdContactStr = \class_exists('SabreForRainLoop\DAV\Client') ? 170 \SabreForRainLoop\DAV\UUIDUtil::getUUID() : \MailSo\Base\Utils::Md5Rand(); 171 } 172 173 /** 174 * @return array 175 */ 176 public function GetEmails() 177 { 178 $aResult = array(); 179 foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty) 180 { 181 if ($oProperty && $oProperty->IsEmail()) 182 { 183 $aResult[] = $oProperty->Value; 184 } 185 } 186 187 return \array_unique($aResult); 188 } 189 190 /** 191 * @return string 192 */ 193 public function CardDavNameUri() 194 { 195 return $this->IdContactStr.'.vcf'; 196 } 197 198 /** 199 * @return string 200 */ 201 public function ToVCard($sPreVCard = '', $oLogger = null) 202 { 203 $this->UpdateDependentValues(); 204 205 if (!\class_exists('SabreForRainLoop\DAV\Client')) 206 { 207 return ''; 208 } 209 210 if ("\xef\xbb\xbf" === \substr($sPreVCard, 0, 3)) 211 { 212 $sPreVCard = \substr($sPreVCard, 3); 213 } 214 215 $oVCard = null; 216 if (0 < \strlen($sPreVCard)) 217 { 218 try 219 { 220 $oVCard = \SabreForRainLoop\VObject\Reader::read($sPreVCard); 221 } 222 catch (\Exception $oExc) 223 { 224 if ($oLogger) 225 { 226 $oLogger->WriteException($oExc); 227 $oLogger->WriteDump($sPreVCard); 228 } 229 } 230 } 231 232// if ($oLogger) 233// { 234// $oLogger->WriteDump($sPreVCard); 235// } 236 237 if (!$oVCard) 238 { 239 $oVCard = new \SabreForRainLoop\VObject\Component\VCard(); 240 } 241 242 $oVCard->VERSION = '3.0'; 243 $oVCard->PRODID = '-//RainLoop//'.APP_VERSION.'//EN'; 244 245 unset($oVCard->FN, $oVCard->EMAIL, $oVCard->TEL, $oVCard->URL, $oVCard->NICKNAME); 246 247 $sUid = $sFirstName = $sLastName = $sMiddleName = $sSuffix = $sPrefix = ''; 248 foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty) 249 { 250 if ($oProperty) 251 { 252 $sAddKey = ''; 253 switch ($oProperty->Type) 254 { 255 case PropertyType::FULLNAME: 256 $oVCard->FN = $oProperty->Value; 257 break; 258 case PropertyType::NICK_NAME: 259 $oVCard->NICKNAME = $oProperty->Value; 260 break; 261 case PropertyType::NOTE: 262 $oVCard->NOTE = $oProperty->Value; 263 break; 264 case PropertyType::UID: 265 $sUid = $oProperty->Value; 266 break; 267 case PropertyType::FIRST_NAME: 268 $sFirstName = $oProperty->Value; 269 break; 270 case PropertyType::LAST_NAME: 271 $sLastName = $oProperty->Value; 272 break; 273 case PropertyType::MIDDLE_NAME: 274 $sMiddleName = $oProperty->Value; 275 break; 276 case PropertyType::NAME_SUFFIX: 277 $sSuffix = $oProperty->Value; 278 break; 279 case PropertyType::NAME_PREFIX: 280 $sPrefix = $oProperty->Value; 281 break; 282 case PropertyType::EMAIl: 283 if (empty($sAddKey)) 284 { 285 $sAddKey = 'EMAIL'; 286 } 287 case PropertyType::WEB_PAGE: 288 if (empty($sAddKey)) 289 { 290 $sAddKey = 'URL'; 291 } 292 case PropertyType::PHONE: 293 if (empty($sAddKey)) 294 { 295 $sAddKey = 'TEL'; 296 } 297 298 $aTypes = $oProperty->TypesAsArray(); 299 $oVCard->add($sAddKey, $oProperty->Value, \is_array($aTypes) && 0 < \count($aTypes) ? array('TYPE' => $aTypes) : null); 300 break; 301 } 302 } 303 } 304 305 $oVCard->UID = empty($sUid) ? $this->IdContactStr : $sUid; 306 $oVCard->N = array($sLastName, $sFirstName, $sMiddleName, $sPrefix, $sSuffix); 307 $oVCard->REV = \gmdate('Ymd', $this->Changed).'T'.\gmdate('His', $this->Changed).'Z'; 308 309 return (string) $oVCard->serialize(); 310 } 311 312 /** 313 * @return string 314 */ 315 public function ToCsv($bWithHeader = false) 316 { 317 $aData = array(); 318 if ($bWithHeader) 319 { 320 $aData[] = array( 321 'Title', 'First Name', 'Middle Name', 'Last Name', 'Nick Name', 'Display Name', 322 'Company', 'Department', 'Job Title', 'Office Location', 323 'E-mail Address', 'Notes', 'Web Page', 'Birthday', 324 'Other Email', 'Other Phone', 'Other Mobile', 'Mobile Phone', 325 'Home Email', 'Home Phone', 'Home Fax', 326 'Home Street', 'Home City', 'Home State', 'Home Postal Code', 'Home Country', 327 'Business Email', 'Business Phone', 'Business Fax', 328 'Business Street', 'Business City', 'Business State', 'Business Postal Code', 'Business Country' 329 ); 330 } 331 332 $aValues = array( 333 '', // 0 'Title', 334 '', // 1 'First Name', 335 '', // 2 'Middle Name', 336 '', // 3 'Last Name', 337 '', // 4 'Nick Name', 338 '', // 5 'Display Name', 339 '', // 6 'Company', 340 '', // 7 'Department', 341 '', // 8 'Job Title', 342 '', // 9 'Office Location', 343 '', // 10 'E-mail Address', 344 '', // 11 'Notes', 345 '', // 12 'Web Page', 346 '', // 13 'Birthday', 347 '', // 14 'Other Email', 348 '', // 15 'Other Phone', 349 '', // 16 'Other Mobile', 350 '', // 17 'Mobile Phone', 351 '', // 18 'Home Email', 352 '', // 19 'Home Phone', 353 '', // 20 'Home Fax', 354 '', // 21 'Home Street', 355 '', // 22 'Home City', 356 '', // 23 'Home State', 357 '', // 24 'Home Postal Code', 358 '', // 25 'Home Country', 359 '', // 26 'Business Email', 360 '', // 27 'Business Phone', 361 '', // 28 'Business Fax', 362 '', // 29 'Business Street', 363 '', // 30 'Business City', 364 '', // 31 'Business State', 365 '', // 32 'Business Postal Code', 366 '' // 33 'Business Country' 367 ); 368 369 $this->UpdateDependentValues(); 370 371 foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty) 372 { 373 $iIndex = -1; 374 if ($oProperty) 375 { 376 $aUpperTypes = $oProperty->TypesUpperAsArray(); 377 switch ($oProperty->Type) 378 { 379 case PropertyType::FULLNAME: 380 $iIndex = 5; 381 break; 382 case PropertyType::NICK_NAME: 383 $iIndex = 4; 384 break; 385 case PropertyType::FIRST_NAME: 386 $iIndex = 1; 387 break; 388 case PropertyType::LAST_NAME: 389 $iIndex = 3; 390 break; 391 case PropertyType::MIDDLE_NAME: 392 $iIndex = 2; 393 break; 394 case PropertyType::EMAIl: 395 switch (true) 396 { 397 case \in_array('OTHER', $aUpperTypes): 398 $iIndex = 14; 399 break; 400 case \in_array('WORK', $aUpperTypes): 401 $iIndex = 26; 402 break; 403 default: 404 $iIndex = 18; 405 break; 406 } 407 break; 408 case PropertyType::PHONE: 409 switch (true) 410 { 411 case \in_array('OTHER', $aUpperTypes): 412 $iIndex = 15; 413 break; 414 case \in_array('WORK', $aUpperTypes): 415 $iIndex = 27; 416 break; 417 case \in_array('MOBILE', $aUpperTypes): 418 $iIndex = 17; 419 break; 420 default: 421 $iIndex = 19; 422 break; 423 } 424 break; 425 case PropertyType::WEB_PAGE: 426 $iIndex = 12; 427 break; 428 case PropertyType::NOTE: 429 $iIndex = 11; 430 break; 431 } 432 433 if (-1 < $iIndex) 434 { 435 $aValues[$iIndex] = $oProperty->Value; 436 } 437 } 438 } 439 440 // subfix 441 if (empty($aValues[10])) // 'E-mail Address' 442 { 443 if (!empty($aValues[18])) 444 { 445 $aValues[10] = $aValues[18]; 446 } 447 else if (!empty($aValues[26])) 448 { 449 $aValues[10] = $aValues[26]; 450 } 451 else if (!empty($aValues[14])) 452 { 453 $aValues[10] = $aValues[14]; 454 } 455 } 456 457 $aData[] = \array_map(function ($sValue) { 458 $sValue = \trim($sValue); 459 return \preg_match('/[\r\n,"]/', $sValue) ? '"'.\str_replace('"', '""', $sValue).'"' : $sValue; 460 }, $aValues); 461 462 $sResult = ''; 463 foreach ($aData as $aSubData) 464 { 465 $sResult .= \implode(',', $aSubData)."\r\n"; 466 } 467 468 return $sResult; 469 } 470 471 /** 472 * @param mixed $oProp 473 * @param bool $bOldVersion 474 * @return string 475 */ 476 private function getPropertyValueHelper($oProp, $bOldVersion) 477 { 478 $sValue = \trim($oProp); 479 if ($bOldVersion && !isset($oProp->parameters['CHARSET'])) 480 { 481 if (0 < \strlen($sValue)) 482 { 483 $sEncValue = @\utf8_encode($sValue); 484 if (0 === \strlen($sEncValue)) 485 { 486 $sEncValue = $sValue; 487 } 488 489 $sValue = $sEncValue; 490 } 491 } 492 493 return \MailSo\Base\Utils::Utf8Clear($sValue); 494 } 495 496 /** 497 * @param mixed $oProp 498 * @param bool $bOldVersion 499 * @return string 500 */ 501 private function addArrayPropertyHelper(&$aProperties, $oArrayProp, $iType) 502 { 503 foreach ($oArrayProp as $oProp) 504 { 505 $oTypes = $oProp ? $oProp['TYPE'] : null; 506 $aTypes = $oTypes ? $oTypes->getParts() : array(); 507 $sValue = $oProp ? \trim($oProp->getValue()) : ''; 508 509 if (0 < \strlen($sValue)) 510 { 511 if (!$oTypes || $oTypes->has('PREF')) 512 { 513 \array_unshift($aProperties, new Property($iType, $sValue, \implode(',', $aTypes))); 514 } 515 else 516 { 517 \array_push($aProperties, new Property($iType, $sValue, \implode(',', $aTypes))); 518 } 519 } 520 } 521 } 522 523 public function PopulateByVCard($sUid, $sVCard, $sEtag = '', $oLogger = null) 524 { 525 if ("\xef\xbb\xbf" === \substr($sVCard, 0, 3)) 526 { 527 $sVCard = \substr($sVCard, 3); 528 } 529 530 $this->Properties = array(); 531 532 if (!\class_exists('SabreForRainLoop\DAV\Client')) 533 { 534 return false; 535 } 536 537 if (!empty($sEtag)) 538 { 539 $this->Etag = $sEtag; 540 } 541 542 $this->IdContactStr = $sUid; 543 544 try 545 { 546 $oVCard = \SabreForRainLoop\VObject\Reader::read($sVCard); 547 } 548 catch (\Exception $oExc) 549 { 550 if ($oLogger) 551 { 552 $oLogger->WriteException($oExc); 553 $oLogger->WriteDump($sVCard); 554 } 555 } 556 557// if ($oLogger) 558// { 559// $oLogger->WriteDump($sVCard); 560// } 561 562 $bOwnCloud = false; 563 $aProperties = array(); 564 565 if ($oVCard) 566 { 567 $bOwnCloud = empty($oVCard->PRODID) ? false : 568 false !== \strpos(\strtolower($oVCard->PRODID), 'owncloud'); 569 570 $bOldVersion = empty($oVCard->VERSION) ? false : 571 \in_array((string) $oVCard->VERSION, array('2.1', '2.0', '1.0')); 572 573 if (isset($oVCard->FN) && '' !== \trim($oVCard->FN)) 574 { 575 $sValue = $this->getPropertyValueHelper($oVCard->FN, $bOldVersion); 576 $aProperties[] = new Property(PropertyType::FULLNAME, $sValue); 577 } 578 579 if (isset($oVCard->NICKNAME) && '' !== \trim($oVCard->NICKNAME)) 580 { 581 $sValue = $sValue = $this->getPropertyValueHelper($oVCard->NICKNAME, $bOldVersion); 582 $aProperties[] = new Property(PropertyType::NICK_NAME, $sValue); 583 } 584 585 if (isset($oVCard->NOTE) && '' !== \trim($oVCard->NOTE)) 586 { 587 $sValue = $this->getPropertyValueHelper($oVCard->NOTE, $bOldVersion); 588 $aProperties[] = new Property(PropertyType::NOTE, $sValue); 589 } 590 591 if (isset($oVCard->N)) 592 { 593 $aNames = $oVCard->N->getParts(); 594 foreach ($aNames as $iIndex => $sValue) 595 { 596 $sValue = \trim($sValue); 597 if ($bOldVersion && !isset($oVCard->N->parameters['CHARSET'])) 598 { 599 if (0 < \strlen($sValue)) 600 { 601 $sEncValue = @\utf8_encode($sValue); 602 if (0 === \strlen($sEncValue)) 603 { 604 $sEncValue = $sValue; 605 } 606 607 $sValue = $sEncValue; 608 } 609 } 610 611 $sValue = \MailSo\Base\Utils::Utf8Clear($sValue); 612 switch ($iIndex) { 613 case 0: 614 $aProperties[] = new Property(PropertyType::LAST_NAME, $sValue); 615 break; 616 case 1: 617 $aProperties[] = new Property(PropertyType::FIRST_NAME, $sValue); 618 break; 619 case 2: 620 $aProperties[] = new Property(PropertyType::MIDDLE_NAME, $sValue); 621 break; 622 case 3: 623 $aProperties[] = new Property(PropertyType::NAME_PREFIX, $sValue); 624 break; 625 case 4: 626 $aProperties[] = new Property(PropertyType::NAME_SUFFIX, $sValue); 627 break; 628 } 629 } 630 } 631 632 if (isset($oVCard->EMAIL)) 633 { 634 $this->addArrayPropertyHelper($aProperties, $oVCard->EMAIL, PropertyType::EMAIl); 635 } 636 637 if (isset($oVCard->URL)) 638 { 639 $this->addArrayPropertyHelper($aProperties, $oVCard->URL, PropertyType::WEB_PAGE); 640 } 641 642 if (isset($oVCard->TEL)) 643 { 644 $this->addArrayPropertyHelper($aProperties, $oVCard->TEL, PropertyType::PHONE); 645 } 646 647 $sUidValue = $oVCard->UID ? (string) $oVCard->UID : \SabreForRainLoop\DAV\UUIDUtil::getUUID(); 648 $aProperties[] = new Property(PropertyType::UID, $sUidValue); 649 650 if (empty($this->IdContactStr)) 651 { 652 $this->IdContactStr = $sUidValue; 653 } 654 655 $this->Properties = $aProperties; 656 } 657 658 $this->UpdateDependentValues(); 659 660 return true; 661 } 662} 663