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