From 91e3bd72d6e672c918521d75ffa8333478cd28bd Mon Sep 17 00:00:00 2001 From: RainLoop Team Date: Fri, 18 Apr 2014 19:31:30 +0400 Subject: [PATCH] New adress book provider --- .../0.0.0/app/libraries/RainLoop/Actions.php | 45 + .../RainLoop/Providers/AddressBook.php | 285 +++++ .../AddressBook/AddressBookInterface.php | 65 + .../Providers/AddressBook/Classes/Contact.php | 545 +++++++++ .../AddressBook/Classes/Property.php | 107 ++ .../AddressBook/Enumerations/PropertyType.php | 46 + .../AddressBook/Enumerations/ScopeType.php | 9 + .../Providers/AddressBook/PdoAddressBook.php | 1065 +++++++++++++++++ 8 files changed, 2167 insertions(+) create mode 100644 rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php create mode 100644 rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/AddressBookInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Contact.php create mode 100644 rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Property.php create mode 100644 rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Enumerations/PropertyType.php create mode 100644 rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Enumerations/ScopeType.php create mode 100644 rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index 3392e8f82..4377e1df8 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -72,6 +72,11 @@ class Actions */ private $oSettingsProvider; + /** + * @var \RainLoop\Providers\AddressBook + */ + private $oAddressBookProvider; + /** * @var \RainLoop\Providers\PersonalAddressBook */ @@ -121,6 +126,7 @@ class Actions $this->oFilesProvider = null; $this->oSettingsProvider = null; $this->oDomainProvider = null; + $this->oAddressBookProvider = null; $this->oPersonalAddressBookProvider = null; $this->oSuggestionsProvider = null; $this->oChangePasswordProvider = null; @@ -254,6 +260,26 @@ class Actions $oResult = new \RainLoop\Providers\PersonalAddressBook\PdoPersonalAddressBook($sDsn, $sUser, $sPassword, $sDsnType); } + $oResult->SetLogger($this->Logger()); + break; + case 'address-book': + // \RainLoop\Providers\AddressBook\AddressBookInterface + + $sDsn = \trim($this->Config()->Get('contacts', 'pdo_dsn', '')); + $sUser = \trim($this->Config()->Get('contacts', 'pdo_user', '')); + $sPassword = (string) $this->Config()->Get('contacts', 'pdo_password', ''); + + $sDsnType = $this->ValidateContactPdoType(\trim($this->Config()->Get('contacts', 'type', 'sqlite'))); + if ('sqlite' === $sDsnType) + { + $oResult = new \RainLoop\Providers\AddressBook\PdoAddressBook( + 'sqlite:'.APP_PRIVATE_DATA.'AddressBook.sqlite', '', '', 'sqlite'); + } + else + { + $oResult = new \RainLoop\Providers\AddressBook\PdoAddressBook($sDsn, $sUser, $sPassword, $sDsnType); + } + $oResult->SetLogger($this->Logger()); break; case 'suggestions': @@ -579,6 +605,25 @@ class Actions return $this->oSuggestionsProvider; } + + /** + * @param \RainLoop\Account $oAccount = null + * @param bool $bForceEnable = false + * + * @return \RainLoop\Providers\AddressBook + */ + public function AddressBookProvider($oAccount = null, $bForceEnable = false) + { + if (null === $this->oAddressBookProvider) + { + $this->oAddressBookProvider = new \RainLoop\Providers\AddressBook( + $this->Config()->Get('contacts', 'enable', false) || $bForceEnable ? $this->fabrica('address-book', $oAccount) : null); + + $this->oAddressBookProvider->SetLogger($this->Logger()); + } + + return $this->oAddressBookProvider; + } /** * @param \RainLoop\Account $oAccount = null diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php new file mode 100644 index 000000000..dadb736f7 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php @@ -0,0 +1,285 @@ +oDriver = null; + if ($oDriver instanceof \RainLoop\Providers\AddressBook\AddressBookInterface) + { + $this->oDriver = $oDriver; + } + } + + /** + * @return string + */ + public function Test() + { + \sleep(1); + return $this->oDriver instanceof \RainLoop\Providers\AddressBook\AddressBookInterface ? + $this->oDriver->Test() : 'Personal address book driver is not allowed'; + } + + /** + * @return bool + */ + public function IsActive() + { + return $this->oDriver instanceof \RainLoop\Providers\AddressBook\AddressBookInterface && + $this->oDriver->IsSupported(); + } + + /** + * @return bool + */ + public function IsSupported() + { + return $this->oDriver instanceof \RainLoop\Providers\AddressBook\AddressBookInterface && + $this->oDriver->IsSupported(); + } + + /** + * @param string $sEmail + * @param \RainLoop\Providers\AddressBook\Classes\Contact $oContact + * + * @return bool + */ + public function ContactSave($sEmail, &$oContact) + { + return $this->IsActive() ? $this->oDriver->ContactSave($sEmail, $oContact) : false; + } + + /** + * @param string $sEmail + * @param array $aContactIds + * + * @return bool + */ + public function DeleteContacts($sEmail, $aContactIds) + { + return $this->IsActive() ? $this->oDriver->DeleteContacts($sEmail, $aContactIds) : false; + } + + /** + * @param string $sEmail + * @param int $iOffset = 0 + * @param type $iLimit = 20 + * @param string $sSearch = '' + * @param int $iResultCount = 0 + * + * @return array + */ + public function GetContacts($sEmail, $iOffset = 0, $iLimit = 20, $sSearch = '', &$iResultCount = 0) + { + return $this->IsActive() ? $this->oDriver->GetContacts($sEmail, + $iOffset, $iLimit, $sSearch, $iResultCount) : array(); + } + + /** + * @param string $sEmail + * @param string $mID + * @param bool $bIsStrID = false + * + * @return \RainLoop\Providers\AddressBook\Classes\Contact|null + */ + public function GetContactByID($sEmail, $mID, $bIsStrID = false) + { + return $this->IsActive() ? $this->oDriver->GetContactByID($sEmail, $mID, $bIsStrID) : null; + } + + /** + * @param string $sEmail + * @param string $sSearch + * @param int $iLimit = 20 + * + * @return array + * + * @throws \InvalidArgumentException + */ + public function GetSuggestions($sEmail, $sSearch, $iLimit = 20) + { + return $this->IsActive() ? $this->oDriver->GetSuggestions($sEmail, $sSearch, $iLimit) : array(); + } + + /** + * @param string $sEmail + * @param array $aEmails + * @param bool $bCreateAuto = true + * + * @return bool + */ + public function IncFrec($sEmail, $aEmails, $bCreateAuto = true) + { + return $this->IsActive() ? $this->oDriver->IncFrec($sEmail, $aEmails, $bCreateAuto) : false; + } + + /** + * @param string $sCsvName + * + * @return int + */ + private function csvNameToTypeConvertor($sCsvName) + { + static $aMap = null; + if (null === $aMap) + { + $aMap = array( + 'Title' => PropertyType::FULLNAME, + 'First Name' => PropertyType::FIRST_NAME, + 'Middle Name' => PropertyType::MIDDLE_NAME, + 'Last Name' => PropertyType::LAST_NAME, + 'Suffix' => PropertyType::NAME_SUFFIX, + 'Business Fax' => PropertyType::FAX_BUSSINES, + 'Business Phone' => PropertyType::PHONE_BUSSINES, + 'Business Phone 2' => PropertyType::PHONE_BUSSINES, + 'Company Main Phone' => PropertyType::PHONE_BUSSINES, + 'Home Fax' => PropertyType::FAX_PERSONAL, + 'Home Phone' => PropertyType::PHONE_PERSONAL, + 'Home Phone 2' => PropertyType::PHONE_PERSONAL, + 'Mobile Phone' => PropertyType::MOBILE_PERSONAL, + 'Other Fax' => PropertyType::FAX_OTHER, + 'Other Phone' => PropertyType::PHONE_OTHER, +// 'Primary Phone' => PropertyType::PHONE_PERSONAL, + 'E-mail Address' => PropertyType::EMAIl_PERSONAL, + 'E-mail 2 Address' => PropertyType::EMAIl_OTHER, + 'E-mail 3 Address' => PropertyType::EMAIl_OTHER, + 'E-mail Display Name' => PropertyType::FULLNAME, + 'E-mail 2 Display Name' => PropertyType::FULLNAME, + 'E-mail 3 Display Name' => PropertyType::FULLNAME, + 'Notes' => PropertyType::NOTE, + 'Web Page' => PropertyType::WEB_PAGE_PERSONAL, + 'WebPage' => PropertyType::WEB_PAGE_PERSONAL, + ); + + $aMap = \array_change_key_case($aMap, CASE_LOWER); + } + + $sCsvNameLower = \MailSo\Base\Utils::IsAscii($sCsvName) ? \strtolower($sCsvName) : ''; + return isset($aMap[$sCsvNameLower]) ? $aMap[$sCsvNameLower] : PropertyType::UNKNOWN; + } + + /** + * @param string $sEmail + * @param array $aCsvData + * + * @return int + */ + public function ImportCsvArray($sEmail, $aCsvData) + { + $iCount = 0; + if ($this->IsActive() && \is_array($aCsvData) && 0 < \count($aCsvData)) + { + $oContact = new \RainLoop\Providers\AddressBook\Classes\Contact(); + foreach ($aCsvData as $aItem) + { + foreach ($aItem as $sItemName => $sItemValue) + { + $sItemName = \trim($sItemName); + $sItemValue = \trim($sItemValue); + + if (!empty($sItemName) && !empty($sItemValue)) + { + $iType = $this->csvNameToTypeConvertor($sItemName); + if (PropertyType::UNKNOWN !== $iType) + { + $oProp = new \RainLoop\Providers\AddressBook\Classes\Property(); + $oProp->Type = $iType; + $oProp->Value = $sItemValue; + + $oContact->Properties[] = $oProp; + } + } + } + + if ($oContact && 0 < \count($oContact->Properties)) + { + if ($this->ContactSave($sEmail, $oContact)) + { + $iCount++; + } + } + + $oContact->Clear(); + } + + unset($oContact); + } + + return $iCount; + } + + /** + * @param string $sEmail + * @param string $sVcfData + * + * @return int + */ + public function ImportVcfFile($sEmail, $sVcfData) + { + $iCount = 0; + if ($this->IsActive() && \is_string($sVcfData)) + { + $sVcfData = \trim($sVcfData); + if ("\xef\xbb\xbf" === \substr($sVcfData, 0, 3)) + { + $sVcfData = \substr($sVcfData, 3); + } + + $oVCardSplitter = null; + try + { + $oVCardSplitter = new \Sabre\VObject\Splitter\VCard($sVcfData); + } + catch (\Exception $oExc) + { + $this->Logger()->WriteException($oExc); + } + + if ($oVCardSplitter) + { + $oContact = new \RainLoop\Providers\AddressBook\Classes\Contact(); + + $oVCard = null; + + while ($oVCard = $oVCardSplitter->getNext()) + { + if ($oVCard instanceof \Sabre\VObject\Component\VCard) + { + if (empty($oVCard->UID)) + { + $oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID(); + } + + $oContact->ParseVCard($oVCard, $oVCard->serialize()); + if (0 < \count($oContact->Properties)) + { + if ($this->ContactSave($sEmail, $oContact)) + { + $iCount++; + } + } + + $oContact->Clear(); + } + } + } + } + + return $iCount; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/AddressBookInterface.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/AddressBookInterface.php new file mode 100644 index 000000000..02c6a585e --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/AddressBookInterface.php @@ -0,0 +1,65 @@ +Clear(); + } + + public function Clear() + { + $this->IdContact = ''; + $this->IdContactStr = ''; + $this->IdUser = 0; + $this->Display = ''; + $this->Changed = \time(); + $this->Properties = array(); + $this->ReadOnly = false; + $this->IdPropertyFromSearch = 0; + $this->Hash = ''; + } + + public function UpdateDependentValues() + { + $sLastName = ''; + $sFirstName = ''; + $sEmail = ''; + $sOther = ''; + + $oFullNameProperty = null; + + foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty) + { + if ($oProperty) + { + $oProperty->UpdateDependentValues(); + + if (!$oFullNameProperty && PropertyType::FULLNAME === $oProperty->Type) + { + $oFullNameProperty =& $oProperty; + } + + if (0 < \strlen($oProperty->Value)) + { + if ('' === $sEmail && $oProperty->IsEmail()) + { + $sEmail = $oProperty->Value; + } + else if ('' === $sLastName && PropertyType::LAST_NAME === $oProperty->Type) + { + $sLastName = $oProperty->Value; + } + else if ('' === $sFirstName && PropertyType::FIRST_NAME === $oProperty->Type) + { + $sFirstName = $oProperty->Value; + } + else if (\in_array($oProperty->Type, array(PropertyType::FULLNAME, + PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, PropertyType::PHONE_OTHER, + PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, PropertyType::MOBILE_OTHER + ))) + { + $sOther = $oProperty->Value; + } + } + } + } + + if (empty($this->IdContactStr)) + { + $this->RegenerateContactStr(); + } + + $sDisplay = ''; + if (0 < \strlen($sLastName) || 0 < \strlen($sFirstName)) + { + $sDisplay = \trim($sFirstName.' '.$sLastName); + } + + if ('' === $sDisplay && 0 < \strlen($sEmail)) + { + $sDisplay = \trim($sEmail); + } + + if ('' === $sDisplay) + { + $sDisplay = $sOther; + } + + $this->Display = \trim($sDisplay); + + $bNewFull = false; + if (!$oFullNameProperty) + { + $oFullNameProperty = new \RainLoop\Providers\AddressBook\Classes\Property(PropertyType::FULLNAME, $this->Display); + $bNewFull = true; + } + + $oFullNameProperty->Value = $this->Display; + $oFullNameProperty->UpdateDependentValues(); + + if ($bNewFull) + { + $this->Properties[] = $oFullNameProperty; + } + } + + /** + * @return array + */ + public function RegenerateContactStr() + { + $this->IdContactStr = \Sabre\DAV\UUIDUtil::getUUID(); + } + + /** + * @return array + */ + public function GetEmails() + { + $aResult = array(); + foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty) + { + if ($oProperty && $oProperty->IsEmail()) + { + $aResult[] = $oProperty->Value; + } + } + + return \array_unique($aResult); + } + + /** + * @param \Sabre\VObject\Document $oVCard + */ + public function ParseVCard($oVCard) + { + $bNew = empty($this->IdContact); + + if (!$bNew) + { + $this->Properties = array(); + } + + $aProperties = array(); + if ($oVCard) + { + $bOldVersion = empty($oVCard->VERSION) ? false : + \in_array((string) $oVCard->VERSION, array('2.1', '2.0', '1.0')); + + $this->IdContactStr = !empty($oVCard->UID) ? (string) $oVCard->UID : \Sabre\DAV\UUIDUtil::getUUID(); + + if (isset($oVCard->FN) && '' !== \trim($oVCard->FN)) + { + $sValue = \trim($oVCard->FN); + if ($bOldVersion && !isset($oVCard->FN->parameters['CHARSET'])) + { + if (0 < \strlen($sValue)) + { + $sEncValue = @\utf8_encode($sValue); + if (0 === \strlen($sEncValue)) + { + $sEncValue = $sValue; + } + + $sValue = $sEncValue; + } + } + + $sValue = \MailSo\Base\Utils::Utf8Clear($sValue); + $aProperties[] = new Property(PropertyType::FULLNAME, $sValue); + } + + if (isset($oVCard->NICKNAME) && '' !== \trim($oVCard->NICKNAME)) + { + $sValue = \trim($oVCard->NICKNAME); + if ($bOldVersion && !isset($oVCard->NICKNAME->parameters['CHARSET'])) + { + if (0 < \strlen($sValue)) + { + $sEncValue = @\utf8_encode($sValue); + if (0 === \strlen($sEncValue)) + { + $sEncValue = $sValue; + } + + $sValue = $sEncValue; + } + } + + $sValue = \MailSo\Base\Utils::Utf8Clear($sValue); + $aProperties[] = new Property(PropertyType::NICK_NAME, $sValue); + } + +// if (isset($oVCard->NOTE) && '' !== \trim($oVCard->NOTE)) +// { +// $sValue = \trim($oVCard->NOTE); +// if ($bOldVersion) +// { +// if (0 < \strlen($sValue)) +// { +// $sEncValue = @\utf8_encode($sValue); +// if (0 === \strlen($sEncValue)) +// { +// $sEncValue = $sValue; +// } +// +// $sValue = $sEncValue; +// } +// } +// +// $sValue = \MailSo\Base\Utils::Utf8Clear($sValue); +// $aProperties[] = new Property(PropertyType::NOTE, $sValue); +// } + + if (isset($oVCard->N)) + { + $aNames = $oVCard->N->getParts(); + foreach ($aNames as $iIndex => $sValue) + { + $sValue = \trim($sValue); + if ($bOldVersion && !isset($oVCard->N->parameters['CHARSET'])) + { + if (0 < \strlen($sValue)) + { + $sEncValue = @\utf8_encode($sValue); + if (0 === \strlen($sEncValue)) + { + $sEncValue = $sValue; + } + + $sValue = $sEncValue; + } + } + + $sValue = \MailSo\Base\Utils::Utf8Clear($sValue); + switch ($iIndex) { + case 0: + $aProperties[] = new Property(PropertyType::LAST_NAME, $sValue); + break; + case 1: + $aProperties[] = new Property(PropertyType::FIRST_NAME, $sValue); + break; + case 2: + $aProperties[] = new Property(PropertyType::MIDDLE_NAME, $sValue); + break; + case 3: + $aProperties[] = new Property(PropertyType::NAME_PREFIX, $sValue); + break; + case 4: + $aProperties[] = new Property(PropertyType::NAME_SUFFIX, $sValue); + break; + } + } + } + + if (isset($oVCard->EMAIL)) + { + $bPref = false; + foreach($oVCard->EMAIL as $oEmail) + { + $oTypes = $oEmail ? $oEmail['TYPE'] : null; + $sEmail = $oEmail ? \trim($oEmail->getValue()) : ''; + + if (0 < \strlen($sEmail)) + { + if ($oTypes) + { + $oProp = new Property($oTypes->has('WORK') ? PropertyType::EMAIl_BUSSINES : PropertyType::EMAIl_PERSONAL, $sEmail); + if (!$bPref && $oTypes->has('pref')) + { + $bPref = true; + \array_unshift($aProperties, $oProp); + } + else + { + \array_push($aProperties, $oProp); + } + } + else + { + \array_unshift($aProperties, + new Property(PropertyType::EMAIl_PERSONAL, $sEmail)); + } + } + } + } + +// if (isset($oVCard->URL)) +// { +// foreach($oVCard->URL as $oUrl) +// { +// $oTypes = $oUrl ? $oUrl['TYPE'] : null; +// $sUrl = $oUrl ? \trim((string) $oUrl) : ''; +// +// if (0 < \strlen($sUrl)) +// { +// \array_push($aProperties, +// new Property($oTypes && $oTypes->has('WORK') ? +// PropertyType::WEB_PAGE_BUSSINES : PropertyType::WEB_PAGE_PERSONAL, $sUrl)); +// } +// } +// } + + if (isset($oVCard->TEL)) + { + $bPref = false; + foreach($oVCard->TEL as $oTel) + { + $oTypes = $oTel ? $oTel['TYPE'] : null; + $sTel = $oTypes ? \trim((string) $oTel) : ''; + + if (0 < \strlen($sTel)) + { + if ($oTypes) + { + $oProp = null; + $bWork = $oTypes->has('WORK'); + + switch (true) + { + case $oTypes->has('VOICE'): + $oProp = new Property($bWork ? PropertyType::PHONE_BUSSINES : PropertyType::PHONE_PERSONAL, $sTel); + break; + case $oTypes->has('CELL'): + $oProp = new Property($bWork ? PropertyType::MOBILE_BUSSINES : PropertyType::MOBILE_PERSONAL, $sTel); + break; + case $oTypes->has('FAX'): + $oProp = new Property($bWork ? PropertyType::FAX_BUSSINES : PropertyType::FAX_PERSONAL, $sTel); + break; + case $oTypes->has('WORK'): + $oProp = new Property(PropertyType::MOBILE_BUSSINES, $sTel); + break; + default: + $oProp = new Property(PropertyType::MOBILE_PERSONAL, $sTel); + break; + } + + if ($oProp) + { + if (!$bPref && $oTypes->has('pref')) + { + $bPref = true; + \array_unshift($aProperties, $oProp); + } + else + { + \array_push($aProperties, $oProp); + } + } + } + else + { + \array_unshift($aProperties, + new Property(PropertyType::MOBILE_PERSONAL, $sTel)); + } + } + } + } + + $this->Properties = $aProperties; + } + + $this->UpdateDependentValues(false); + } + + /** + * @return string + */ + public function ToVCardObject($sPreVCard = '') + { + $oVCard = null; + if (0 < \strlen($sPreVCard)) + { + try + { + $oVCard = \Sabre\VObject\Reader::read($sPreVCard); + } + catch (\Exception $oExc) {}; + } + + if (!$oVCard) + { + $oVCard = new \Sabre\VObject\Component\VCard(); + } + + $oVCard->VERSION = '3.0'; + $oVCard->PRODID = '-//RainLoop//'.APP_VERSION.'//EN'; + + unset($oVCard->FN, $oVCard->EMAIL, $oVCard->TEL); + + $bPrefEmail = $bPrefPhone = false; + $sFirstName = $sLastName = $sMiddleName = $sSuffix = $sPrefix = ''; + foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty) + { + if ($oProperty) + { + switch ($oProperty->Type) + { + case PropertyType::FULLNAME: + $oVCard->FN = $oProperty->Value; + break; + case PropertyType::NICK_NAME: + $oVCard->NICKNAME = $oProperty->Value; + break; + case PropertyType::FIRST_NAME: + $sFirstName = $oProperty->Value; + break; + case PropertyType::LAST_NAME: + $sLastName = $oProperty->Value; + break; + case PropertyType::MIDDLE_NAME: + $sMiddleName = $oProperty->Value; + break; + case PropertyType::NAME_SUFFIX: + $sSuffix = $oProperty->Value; + break; + case PropertyType::NAME_PREFIX: + $sPrefix = $oProperty->Value; + break; + case PropertyType::EMAIl_PERSONAL: + case PropertyType::EMAIl_BUSSINES: + case PropertyType::EMAIl_OTHER: + $aParams = array('TYPE' => array('INTERNET')); + $aParams['TYPE'][] = PropertyType::EMAIl_BUSSINES === $oProperty->Type ? 'WORK' : 'HOME'; + + if (!$bPrefEmail) + { + $bPrefEmail = true; + $aParams['TYPE'][] = 'pref'; + } + $oVCard->add('EMAIL', $oProperty->Value, $aParams); + break; + case PropertyType::WEB_PAGE_PERSONAL: + case PropertyType::WEB_PAGE_BUSSINES: + case PropertyType::WEB_PAGE_OTHER: + $aParams = array('TYPE' => array()); + $aParams['TYPE'][] = PropertyType::WEB_PAGE_BUSSINES === $oProperty->Type ? 'WORK' : 'HOME'; + $oVCard->add('URL', $oProperty->Value, $aParams); + break; + case PropertyType::PHONE_PERSONAL: + case PropertyType::PHONE_BUSSINES: + case PropertyType::PHONE_OTHER: + case PropertyType::MOBILE_PERSONAL: + case PropertyType::MOBILE_BUSSINES: + case PropertyType::MOBILE_OTHER: + case PropertyType::FAX_PERSONAL: + case PropertyType::FAX_BUSSINES: + case PropertyType::FAX_OTHER: + $aParams = array('TYPE' => array()); + $sType = ''; + if (\in_array($oProperty->Type, array(PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, PropertyType::PHONE_OTHER))) + { + $sType = 'VOICE'; + } + else if (\in_array($oProperty->Type, array(PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, PropertyType::MOBILE_OTHER))) + { + $sType = 'CELL'; + } + else if (\in_array($oProperty->Type, array(PropertyType::FAX_PERSONAL, PropertyType::FAX_BUSSINES, PropertyType::FAX_OTHER))) + { + $sType = 'FAX'; + } + + if (!empty($sType)) + { + $aParams['TYPE'][] = $sType; + } + + $aParams['TYPE'][] = \in_array($oProperty->Type, array( + PropertyType::PHONE_BUSSINES, PropertyType::MOBILE_BUSSINES, PropertyType::FAX_BUSSINES)) ? 'WORK' : 'HOME'; + + if (!$bPrefPhone) + { + $bPrefPhone = true; + $aParams['TYPE'][] = 'pref'; + } + + $oVCard->add('TEL', $oProperty->Value, $aParams); + break; + } + } + } + + $oVCard->UID = $this->IdContactStr; + $oVCard->N = array($sLastName, $sFirstName, $sMiddleName, $sPrefix, $sSuffix); + $oVCard->REV = \gmdate('Ymd', $this->Changed).'T'.\gmdate('His', $this->Changed).'Z'; + + return $oVCard; + } + + /** + * @return string + */ + public function VCardUri() + { + return $this->IdContactStr.'.vcf'; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Property.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Property.php new file mode 100644 index 000000000..d9d2c2600 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Property.php @@ -0,0 +1,107 @@ +Clear(); + + $this->Type = $iType; + $this->Value = $sValue; + } + + public function Clear() + { + $this->IdProperty = 0; + + $this->Type = PropertyType::UNKNOWN; + $this->TypeCustom = ''; + + $this->Value = ''; + $this->ValueCustom = ''; + + $this->Frec = 0; + } + + /** + * @return bool + */ + public function IsEmail() + { + return \in_array($this->Type, array( + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER + )); + } + + /** + * @return bool + */ + public function IsPhone() + { + return \in_array($this->Type, array( + PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, PropertyType::PHONE_OTHER, + PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, PropertyType::MOBILE_OTHER, + PropertyType::FAX_PERSONAL, PropertyType::FAX_BUSSINES, PropertyType::FAX_OTHER + )); + } + + public function UpdateDependentValues() + { + $this->Value = \trim($this->Value); + $this->ValueCustom = \trim($this->ValueCustom); + $this->TypeCustom = \trim($this->TypeCustom); + + if (0 < \strlen($this->Value)) + { + // lower + if ($this->IsEmail()) + { + $this->Value = \strtolower($this->Value); + } + + // phones clear value for searching + if ($this->IsPhone()) + { + $sPhone = $this->Value; + $sPhone = \preg_replace('/^[+]+/', '', $sPhone); + $sPhone = \preg_replace('/[^\d]/', '', $sPhone); + $this->ValueCustom = $sPhone; + } + } + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Enumerations/PropertyType.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Enumerations/PropertyType.php new file mode 100644 index 000000000..9d25b857a --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Enumerations/PropertyType.php @@ -0,0 +1,46 @@ +sDsn = $sDsn; + $this->sUser = $sUser; + $this->sPassword = $sPassword; + $this->sDsnType = $sDsnType; + + $this->bExplain = false; // debug + } + + /** + * @return bool + */ + public function IsSupported() + { + $aDrivers = \class_exists('PDO') ? \PDO::getAvailableDrivers() : array(); + return \is_array($aDrivers) ? \in_array($this->sDsnType, $aDrivers) : false; + } + + /** + * @param string $sEmail + * @param \RainLoop\Providers\AddressBook\Classes\Contact $oContact + * + * @return bool + */ + public function ContactSave($sEmail, &$oContact) + { + $this->Sync(); + $iUserID = $this->getUserId($sEmail); + + $iIdContact = 0 < \strlen($oContact->IdContact) && \is_numeric($oContact->IdContact) ? (int) $oContact->IdContact : 0; + + $bUpdate = 0 < $iIdContact; + + $oContact->UpdateDependentValues(); + $oContact->Changed = \time(); + + try + { + if ($this->isTransactionSupported()) + { + $this->beginTransaction(); + } + + $aFreq = array(); + if ($bUpdate) + { + $aFreq = $this->getContactFreq($iUserID, $iIdContact); + + $sSql = 'UPDATE rainloop_ab_contacts SET id_contact_str = :id_contact_str, display = :display, changed = :changed, hash = :hash '. + 'WHERE id_user = :id_user AND id_contact = :id_contact'; + + $this->prepareAndExecute($sSql, + array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + ':id_contact' => array($iIdContact, \PDO::PARAM_INT), + ':id_contact_str' => array($oContact->IdContactStr, \PDO::PARAM_STR), + ':display' => array($oContact->Display, \PDO::PARAM_STR), + ':changed' => array($oContact->Changed, \PDO::PARAM_INT), + ':hash' => array($oContact->Hash, \PDO::PARAM_STR) + ) + ); + + // clear previos props + $this->prepareAndExecute( + 'DELETE FROM rainloop_ab_properties WHERE id_user = :id_user AND id_contact = :id_contact', + array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + ':id_contact' => array($iIdContact, \PDO::PARAM_INT) + ) + ); + } + else + { + $sSql = 'INSERT INTO rainloop_ab_contacts '. + '( id_user, id_contact_str, display, changed, hash) VALUES '. + '(:id_user, :id_contact_str, :display, :changed, :hash)'; + + $this->prepareAndExecute($sSql, + array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + ':id_contact_str' => array($oContact->IdContactStr, \PDO::PARAM_STR), + ':display' => array($oContact->Display, \PDO::PARAM_STR), + ':changed' => array($oContact->Changed, \PDO::PARAM_INT), + ':hash' => array($oContact->Hash, \PDO::PARAM_STR) + ) + ); + + $sLast = $this->lastInsertId('id_contact'); + if (\is_numeric($sLast) && 0 < (int) $sLast) + { + $iIdContact = (int) $sLast; + $oContact->IdContact = (string) $iIdContact; + } + } + + if (0 < $iIdContact) + { + $aParams = array(); + foreach ($oContact->Properties as /* @var $oProp \RainLoop\Providers\AddressBook\Classes\Property */ $oProp) + { + $iFreq = $oProp->Frec; + if ($oProp->IsEmail() && isset($aFreq[$oProp->Value])) + { + $iFreq = $aFreq[$oProp->Value]; + } + + $aParams[] = array( + ':id_contact' => array($iIdContact, \PDO::PARAM_INT), + ':id_user' => array($iUserID, \PDO::PARAM_INT), + ':prop_type' => array($oProp->Type, \PDO::PARAM_INT), + ':prop_type_custom' => array($oProp->TypeCustom, \PDO::PARAM_STR), + ':prop_value' => array($oProp->Value, \PDO::PARAM_STR), + ':prop_value_custom' => array($oProp->ValueCustom, \PDO::PARAM_STR), + ':prop_frec' => array($iFreq, \PDO::PARAM_INT), + ); + } + + $sSql = 'INSERT INTO rainloop_ab_properties '. + '( id_contact, id_user, prop_type, prop_type_custom, prop_value, prop_value_custom, prop_frec) VALUES '. + '(:id_contact, :id_user, :prop_type, :prop_type_custom, :prop_value, :prop_value_custom, :prop_frec)'; + + $this->prepareAndExecute($sSql, $aParams, true); + } + } + catch (\Exception $oException) + { + if ($this->isTransactionSupported()) + { + $this->rollBack(); + } + + throw $oException; + } + + if ($this->isTransactionSupported()) + { + $this->commit(); + } + + return 0 < $iIdContact; + } + + /** + * @param string $sEmail + * @param array $aContactIds + * + * @return bool + */ + public function DeleteContacts($sEmail, $aContactIds) + { + $this->Sync(); + $iUserID = $this->getUserId($sEmail); + + $aContactIds = \array_filter($aContactIds, function (&$mItem) { + $mItem = (int) \trim($mItem); + return 0 < $mItem; + }); + + if (0 === \count($aContactIds)) + { + return false; + } + + $sIDs = \implode(',', $aContactIds); + $aParams = array(':id_user' => array($iUserID, \PDO::PARAM_INT)); + + $this->prepareAndExecute('DELETE FROM rainloop_ab_properties WHERE id_user = :id_user AND id_contact IN ('.$sIDs.')', $aParams); + $this->prepareAndExecute('DELETE FROM rainloop_ab_contacts WHERE id_user = :id_user AND id_contact IN ('.$sIDs.')', $aParams); + + return true; + } + + /** + * @param string $sEmail + * @param int $iOffset = 0 + * @param int $iLimit = 20 + * @param string $sSearch = '' + * @param int $iResultCount = 0 + * + * @return array + */ + public function GetContacts($sEmail, $iOffset = 0, $iLimit = 20, $sSearch = '', &$iResultCount = 0) + { + $this->Sync(); + + $iOffset = 0 <= $iOffset ? $iOffset : 0; + $iLimit = 0 < $iLimit ? (int) $iLimit : 20; + $sSearch = \trim($sSearch); + + $iUserID = $this->getUserId($sEmail); + + $iCount = 0; + $aSearchIds = array(); + $aPropertyFromSearchIds = array(); + + if (0 < \strlen($sSearch)) + { + $sCustomSearch = $this->specialConvertSearchValueCustomPhone($sSearch); + if ('%%' === $sCustomSearch) + { + // TODO fix this + $sCustomSearch = ''; + } + + $sSql = 'SELECT id_user, id_prop, id_contact FROM rainloop_ab_properties '. + 'WHERE (id_user = :id_user) AND (prop_value LIKE :search ESCAPE \'=\''. + (0 < \strlen($sCustomSearch) ? ' OR (prop_type IN ('.\implode(',', array( + PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, PropertyType::PHONE_OTHER, + PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, PropertyType::MOBILE_OTHER, + PropertyType::FAX_PERSONAL, PropertyType::FAX_BUSSINES, PropertyType::FAX_OTHER + )).') AND prop_value_custom <> \'\' AND prop_value_custom LIKE :search_custom_phone)' : ''). + ') GROUP BY id_contact, id_prop'; + + $aParams = array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + ':search' => array($this->specialConvertSearchValue($sSearch, '='), \PDO::PARAM_STR) + ); + + if (0 < \strlen($sCustomSearch)) + { + $aParams[':search_custom_phone'] = array($sCustomSearch, \PDO::PARAM_STR); + } + + $oStmt = $this->prepareAndExecute($sSql, $aParams); + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if (\is_array($aFetch) && 0 < \count($aFetch)) + { + foreach ($aFetch as $aItem) + { + $iIdContact = $aItem && isset($aItem['id_contact']) ? (int) $aItem['id_contact'] : 0; + if (0 < $iIdContact) + { + $aSearchIds[] = $iIdContact; + $aPropertyFromSearchIds[$iIdContact] = isset($aItem['id_prop']) ? (int) $aItem['id_prop'] : 0; + } + } + } + + $aSearchIds = \array_unique($aSearchIds); + $iCount = \count($aSearchIds); + } + } + else + { + $sSql = 'SELECT COUNT(DISTINCT id_contact) as contact_count FROM rainloop_ab_properties '. + 'WHERE id_user = :id_user'; + + $aParams = array( + ':id_user' => array($iUserID, \PDO::PARAM_INT) + ); + + $oStmt = $this->prepareAndExecute($sSql, $aParams); + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if ($aFetch && isset($aFetch[0]['contact_count']) && is_numeric($aFetch[0]['contact_count']) && 0 < (int) $aFetch[0]['contact_count']) + { + $iCount = (int) $aFetch[0]['contact_count']; + } + } + } + + $iResultCount = $iCount; + + if (0 < $iCount) + { + $sSql = 'SELECT * FROM rainloop_ab_contacts WHERE id_user = :id_user'; + + $aParams = array( + ':id_user' => array($iUserID, \PDO::PARAM_INT) + ); + + if (0 < \count($aSearchIds)) + { + $sSql .= ' AND id_contact IN ('.implode(',', $aSearchIds).')'; + } + + $sSql .= ' ORDER BY display ASC LIMIT :limit OFFSET :offset'; + $aParams[':limit'] = array($iLimit, \PDO::PARAM_INT); + $aParams[':offset'] = array($iOffset, \PDO::PARAM_INT); + + $oStmt = $this->prepareAndExecute($sSql, $aParams); + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + + $aContacts = array(); + $aIdContacts = array(); + if (\is_array($aFetch) && 0 < \count($aFetch)) + { + foreach ($aFetch as $aItem) + { + $iIdContact = $aItem && isset($aItem['id_contact']) ? (int) $aItem['id_contact'] : 0; + if (0 < $iIdContact) + { + $aIdContacts[] = $iIdContact; + $oContact = new \RainLoop\Providers\AddressBook\Classes\Contact(); + + $oContact->IdContact = (string) $iIdContact; + $oContact->IdContactStr = isset($aItem['id_contact_str']) ? (string) $aItem['id_contact_str'] : ''; + $oContact->Display = isset($aItem['display']) ? (string) $aItem['display'] : ''; + $oContact->Changed = isset($aItem['changed']) ? (int) $aItem['changed'] : 0; + $oContact->ReadOnly = $iUserID !== (isset($aItem['id_user']) ? (int) $aItem['id_user'] : 0); + + $oContact->IdPropertyFromSearch = isset($aPropertyFromSearchIds[$iIdContact]) && + 0 < $aPropertyFromSearchIds[$iIdContact] ? $aPropertyFromSearchIds[$iIdContact] : 0; + + $aContacts[$iIdContact] = $oContact; + } + } + } + + unset($aFetch); + + if (0 < count($aIdContacts)) + { + $oStmt->closeCursor(); + + $sSql = 'SELECT * FROM rainloop_ab_properties WHERE id_contact IN ('.\implode(',', $aIdContacts).')'; + $oStmt = $this->prepareAndExecute($sSql); + + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if (\is_array($aFetch) && 0 < \count($aFetch)) + { + foreach ($aFetch as $aItem) + { + if ($aItem && isset($aItem['id_prop'], $aItem['id_contact'], $aItem['prop_type'], $aItem['prop_value'])) + { + $iId = (int) $aItem['id_contact']; + if (0 < $iId && isset($aContacts[$iId])) + { + $oProperty = new \RainLoop\Providers\AddressBook\Classes\Property(); + $oProperty->IdProperty = (int) $aItem['id_prop']; + $oProperty->Type = (int) $aItem['prop_type']; + $oProperty->TypeCustom = isset($aItem['prop_type_custom']) ? (string) $aItem['prop_type_custom'] : ''; + $oProperty->Value = (string) $aItem['prop_value']; + $oProperty->ValueCustom = isset($aItem['prop_value_custom']) ? (string) $aItem['prop_value_custom'] : ''; + $oProperty->Frec = isset($aItem['prop_frec']) ? (int) $aItem['prop_frec'] : 0; + + $aContacts[$iId]->Properties[] = $oProperty; + } + } + } + } + + unset($aFetch); + + foreach ($aContacts as &$oItem) + { + $oItem->UpdateDependentValues(); + } + + return \array_values($aContacts); + } + } + } + } + + return array(); + } + + /** + * @param string $sEmail + * @param string $mID + * @param bool $bIsStrID = false + * + * @return \RainLoop\Providers\AddressBook\Classes\Contact|null + */ + public function GetContactByID($sEmail, $mID, $bIsStrID = false) + { + $this->Sync(); + + $mID = \trim($mID); + + $iUserID = $this->getUserId($sEmail); + + $sSql = 'SELECT * FROM rainloop_ab_contacts WHERE id_user = :id_user'; + + $aParams = array( + ':id_user' => array($iUserID, \PDO::PARAM_INT) + ); + + if ($bIsStrID) + { + $sSql .= ' AND id_contact_str = :id_contact_str'; + $aParams[':id_contact_str'] = array($mID, \PDO::PARAM_STR); + } + else + { + $sSql .= ' AND id_contact = :id_contact'; + $aParams[':id_contact'] = array($mID, \PDO::PARAM_INT); + } + + $sSql .= ' LIMIT 1'; + + $oContact = null; + $iIdContact = 0; + + $oStmt = $this->prepareAndExecute($sSql, $aParams); + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + + if (\is_array($aFetch) && 0 < \count($aFetch)) + { + foreach ($aFetch as $aItem) + { + $iIdContact = $aItem && isset($aItem['id_contact']) ? (int) $aItem['id_contact'] : 0; + if (0 < $iIdContact) + { + $oContact = new \RainLoop\Providers\AddressBook\Classes\Contact(); + + $oContact->IdContact = (string) $iIdContact; + $oContact->IdContactStr = isset($aItem['id_contact_str']) ? (string) $aItem['id_contact_str'] : ''; + $oContact->Display = isset($aItem['display']) ? (string) $aItem['display'] : ''; + $oContact->Changed = isset($aItem['changed']) ? (int) $aItem['changed'] : 0; + $oContact->ReadOnly = $iUserID !== (isset($aItem['id_user']) ? (int) $aItem['id_user'] : 0); + $oContact->Hash = empty($aItem['hash']) ? '' : (string) $aItem['hash']; + } + } + } + + unset($aFetch); + + if (0 < $iIdContact && $oContact) + { + $oStmt->closeCursor(); + + $sSql = 'SELECT * FROM rainloop_ab_properties WHERE id_contact = '.$iIdContact; + $oStmt = $this->prepareAndExecute($sSql); + + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if (\is_array($aFetch) && 0 < \count($aFetch)) + { + foreach ($aFetch as $aItem) + { + if ($aItem && isset($aItem['id_prop'], $aItem['id_contact'], $aItem['prop_type'], $aItem['prop_value'])) + { + if ((string) $oContact->IdContact === (string) $aItem['id_contact']) + { + $oProperty = new \RainLoop\Providers\AddressBook\Classes\Property(); + $oProperty->IdProperty = (int) $aItem['id_prop']; + $oProperty->Type = (int) $aItem['prop_type']; + $oProperty->TypeCustom = isset($aItem['prop_type_custom']) ? (string) $aItem['prop_type_custom'] : ''; + $oProperty->Value = (string) $aItem['prop_value']; + $oProperty->ValueCustom = isset($aItem['prop_value_custom']) ? (string) $aItem['prop_value_custom'] : ''; + $oProperty->Frec = isset($aItem['prop_frec']) ? (int) $aItem['prop_frec'] : 0; + + $oContact->Properties[] = $oProperty; + } + } + } + } + + unset($aFetch); + + $oContact->UpdateDependentValues(); + } + } + } + + return $oContact; + } + + /** + * @param string $sEmail + * @param string $sSearch + * @param int $iLimit = 20 + * + * @return array + * + * @throws \InvalidArgumentException + */ + public function GetSuggestions($sEmail, $sSearch, $iLimit = 20) + { + $sSearch = \trim($sSearch); + if (0 === \strlen($sSearch)) + { + throw new \InvalidArgumentException('Empty Search argument'); + } + + $this->Sync(); + + $iUserID = $this->getUserId($sEmail); + + $sTypes = implode(',', array( + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER, PropertyType::FIRST_NAME, PropertyType::LAST_NAME + )); + + $sSql = 'SELECT id_contact, id_prop, prop_type, prop_value FROM rainloop_ab_properties '. + 'WHERE (id_user = :id_user) AND prop_type IN ('.$sTypes.') AND prop_value LIKE :search ESCAPE \'=\''; + + $aParams = array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + ':limit' => array($iLimit, \PDO::PARAM_INT), + ':search' => array($this->specialConvertSearchValue($sSearch, '='), \PDO::PARAM_STR) + ); + + $sSql .= ' ORDER BY prop_frec DESC'; + $sSql .= ' LIMIT :limit'; + + $aResult = array(); + + $oStmt = $this->prepareAndExecute($sSql, $aParams); + if ($oStmt) + { + $aIdContacts = array(); + $aIdProps = array(); + $aContactAllAccess = array(); + + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if (\is_array($aFetch) && 0 < \count($aFetch)) + { + foreach ($aFetch as $aItem) + { + $iIdContact = $aItem && isset($aItem['id_contact']) ? (int) $aItem['id_contact'] : 0; + $iIdProp = $aItem && isset($aItem['id_prop']) ? (int) $aItem['id_prop'] : 0; + $iType = $aItem && isset($aItem['prop_type']) ? (int) $aItem['prop_type'] : 0; + + if (0 < $iIdContact && 0 < $iIdProp) + { + $aIdContacts[$iIdContact] = $iIdContact; + $aIdProps[$iIdProp] = $iIdProp; + + if (\in_array($iType, array(PropertyType::LAST_NAME, PropertyType::FIRST_NAME))) + { + $aContactAllAccess[$iIdContact] = $iIdContact; + } + } + } + } + + unset($aFetch); + + $aIdContacts = \array_values($aIdContacts); + if (0 < count($aIdContacts)) + { + $oStmt->closeCursor(); + + $sTypes = \implode(',', array( + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER, PropertyType::FIRST_NAME, PropertyType::LAST_NAME + )); + + $sSql = 'SELECT id_prop, id_contact, prop_type, prop_value FROM rainloop_ab_properties '. + 'WHERE prop_type IN ('.$sTypes.') AND id_contact IN ('.\implode(',', $aIdContacts).')'; + + $oStmt = $this->prepareAndExecute($sSql); + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if (\is_array($aFetch) && 0 < \count($aFetch)) + { + $aNames = array(); + $aEmails = array(); + + foreach ($aFetch as $aItem) + { + if ($aItem && isset($aItem['id_prop'], $aItem['id_contact'], $aItem['prop_type'], $aItem['prop_value'])) + { + $iIdContact = (int) $aItem['id_contact']; + $iIdProp = (int) $aItem['id_prop']; + $iType = (int) $aItem['prop_type']; + + if (\in_array($iType, array(PropertyType::LAST_NAME, PropertyType::FIRST_NAME))) + { + if (!isset($aNames[$iIdContact])) + { + $aNames[$iIdContact] = array('', ''); + } + + $aNames[$iIdContact][PropertyType::LAST_NAME === $iType ? 0 : 1] = $aItem['prop_value']; + } + else if ((isset($aIdProps[$iIdProp]) || isset($aContactAllAccess[$iIdContact]))&& + \in_array($iType, array(PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES))) + { + if (!isset($aEmails[$iIdContact])) + { + $aEmails[$iIdContact] = array(); + } + + $aEmails[$iIdContact][] = $aItem['prop_value']; + } + } + } + + foreach ($aEmails as $iId => $aItems) + { + $aNameItem = isset($aNames[$iId]) && \is_array($aNames[$iId]) ? $aNames[$iId] : array('', ''); + $sNameItem = \trim($aNameItem[0].' '.$aNameItem[1]); + + foreach ($aItems as $sEmail) + { + $aResult[] = array($sEmail, $sNameItem); + } + } + } + + unset($aFetch); + + if ($iLimit < \count($aResult)) + { + $aResult = \array_slice($aResult, 0, $iLimit); + } + + return $aResult; + } + } + } + + return array(); + } + + /** + * @param string $sEmail + * @param array $aEmails + * @param bool $bCreateAuto = true + * + * @return bool + */ + public function IncFrec($sEmail, $aEmails, $bCreateAuto = true) + { + $self = $this; + $aEmailsObjects = \array_map(function ($mItem) { + $oResult = null; + try + { + $oResult = \MailSo\Mime\Email::Parse(\trim($mItem)); + } + catch (\Exception $oException) {} + return $oResult; + }, $aEmails); + + $aEmailsObjects = \array_filter($aEmailsObjects, function ($oItem) { + return !!$oItem; + }); + + if (0 === \count($aEmailsObjects)) + { + throw new \InvalidArgumentException('Empty Emails argument'); + } + + $this->Sync(); + $iUserID = $this->getUserId($sEmail); + + $sTypes = \implode(',', array( + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER + )); + + $aExists = array(); + $aEmailsToCreate = array(); + $aEmailsToUpdate = array(); + + if ($bCreateAuto) + { + $sSql = 'SELECT prop_value FROM rainloop_ab_properties WHERE id_user = :id_user AND prop_type IN ('.$sTypes.')'; + $oStmt = $this->prepareAndExecute($sSql, array( + ':id_user' => array($iUserID, \PDO::PARAM_INT) + )); + + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if (\is_array($aFetch) && 0 < \count($aFetch)) + { + foreach ($aFetch as $aItem) + { + if ($aItem && !empty($aItem['prop_value'])) + { + $aExists[] = \strtolower(\trim($aItem['prop_value'])); + } + } + } + } + + $aEmailsToCreate = \array_filter($aEmailsObjects, function ($oItem) use ($aExists, &$aEmailsToUpdate) { + if ($oItem) + { + $sEmail = \strtolower(\trim($oItem->GetEmail())); + if (0 < \strlen($sEmail)) + { + $aEmailsToUpdate[] = $sEmail; + return !\in_array($sEmail, $aExists); + } + } + + return false; + }); + } + else + { + foreach ($aEmailsObjects as $oItem) + { + if ($oItem) + { + $sEmailUpdate = \strtolower(\trim($oItem->GetEmail())); + if (0 < \strlen($sEmailUpdate)) + { + $aEmailsToUpdate[] = $sEmailUpdate; + } + } + } + } + + unset($aEmails, $aEmailsObjects); + + if (0 < \count($aEmailsToCreate)) + { + $oContact = new \RainLoop\Providers\AddressBook\Classes\Contact(); + foreach ($aEmailsToCreate as $oEmail) + { + if ('' !== \trim($oEmail->GetEmail())) + { + $oPropEmail = new \RainLoop\Providers\AddressBook\Classes\Property(); + $oPropEmail->Type = \RainLoop\Providers\AddressBook\Enumerations\PropertyType::EMAIl_PERSONAL; + $oPropEmail->Value = \strtolower(\trim($oEmail->GetEmail())); + + $oContact->Properties[] = $oPropEmail; + } + + if ('' !== \trim($oEmail->GetDisplayName())) + { + $sFirst = $sLast = ''; + $sFullName = $oEmail->GetDisplayName(); + if (false !== \strpos($sFullName, ' ')) + { + $aNames = explode(' ', $sFullName, 2); + $sFirst = isset($aNames[0]) ? $aNames[0] : ''; + $sLast = isset($aNames[1]) ? $aNames[1] : ''; + } + else + { + $sFirst = $sFullName; + } + + if (0 < \strlen($sFirst)) + { + $oPropName = new \RainLoop\Providers\AddressBook\Classes\Property(); + $oPropName->Type = \RainLoop\Providers\AddressBook\Enumerations\PropertyType::FIRST_NAME; + $oPropName->Value = \trim($sFirst); + + $oContact->Properties[] = $oPropName; + } + + if (0 < \strlen($sLast)) + { + $oPropName = new \RainLoop\Providers\AddressBook\Classes\Property(); + $oPropName->Type = \RainLoop\Providers\AddressBook\Enumerations\PropertyType::LAST_NAME; + $oPropName->Value = \trim($sLast); + + $oContact->Properties[] = $oPropName; + } + } + + if (0 < \count($oContact->Properties)) + { + $this->ContactSave($sEmail, $oContact); + } + + $oContact->Clear(); + } + } + + $sSql = 'UPDATE rainloop_ab_properties SET prop_frec = prop_frec + 1 WHERE id_user = :id_user AND prop_type IN ('.$sTypes; + + $aEmailsQuoted = \array_map(function ($mItem) use ($self) { + return $self->quoteValue($mItem); + }, $aEmailsToUpdate); + + if (1 === \count($aEmailsQuoted)) + { + $sSql .= ') AND prop_value = '.$aEmailsQuoted[0]; + } + else + { + $sSql .= ') AND prop_value IN ('.\implode(',', $aEmailsQuoted).')'; + } + + return !!$this->prepareAndExecute($sSql, array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + )); + } + + /** + * @return string + */ + public function Test() + { + $sResult = ''; + try + { + $this->Sync(); + if (0 >= $this->getVersion($this->sDsnType.'-ab-version')) + { + $sResult = 'Unknown database error'; + } + } + catch (\Exception $oException) + { + $sResult = $oException->getMessage(); + if (!empty($sResult) && !\MailSo\Base\Utils::IsAscii($sResult) && !\MailSo\Base\Utils::IsUtf8($sResult)) + { + $sResult = @\utf8_encode($sResult); + } + + if (!\is_string($sResult) || empty($sResult)) + { + $sResult = 'Unknown database error'; + } + } + + return $sResult; + } + + private function getInitialTablesArray($sDbType) + { + switch ($sDbType) + { + case 'mysql': + $sInitial = <<sDsnType) + { + case 'mysql': + return $this->dataBaseUpgrade($this->sDsnType.'-ab-version', array( + 1 => $this->getInitialTablesArray($this->sDsnType) + )); + case 'pgsql': + return $this->dataBaseUpgrade($this->sDsnType.'-ab-version', array( + 1 => $this->getInitialTablesArray($this->sDsnType) + )); + case 'sqlite': + return $this->dataBaseUpgrade($this->sDsnType.'-ab-version', array( + 1 => $this->getInitialTablesArray($this->sDsnType) + )); + } + + return false; + } + + /** + * @param int $iUserID + * @param int $iIdContact + * @return array + */ + private function getContactFreq($iUserID, $iIdContact) + { + $aResult = array(); + + $sTypes = \implode(',', array( + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES + )); + + $sSql = 'SELECT prop_value, prop_frec FROM rainloop_ab_properties WHERE id_user = :id_user AND id_contact = :id_contact AND prop_type IN ('.$sTypes.')'; + $aParams = array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + ':id_contact' => array($iIdContact, \PDO::PARAM_INT) + ); + + $oStmt = $this->prepareAndExecute($sSql, $aParams); + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if (\is_array($aFetch)) + { + foreach ($aFetch as $aItem) + { + if ($aItem && !empty($aItem['prop_value']) && !empty($aItem['prop_frec'])) + { + $aResult[$aItem['prop_value']] = (int) $aItem['prop_frec']; + } + } + } + } + + return $aResult; + } + + /** + * @param string $sSearch + * @param string $sEscapeSign = '=' + * + * @return string + */ + private function specialConvertSearchValue($sSearch, $sEscapeSign = '=') + { + return '%'.\str_replace(array($sEscapeSign, '_', '%'), + array($sEscapeSign.$sEscapeSign, $sEscapeSign.'_', $sEscapeSign.'%'), $sSearch).'%'; + } + + /** + * @param string $sSearch + * + * @return string + */ + private function specialConvertSearchValueCustomPhone($sSearch) + { + return '%'.\preg_replace('/[^\d]/', '', $sSearch).'%'; + } + + /** + * @return array + */ + protected function getPdoAccessData() + { + return array($this->sDsnType, $this->sDsn, $this->sUser, $this->sPassword); + } +} \ No newline at end of file