Synchronizing with a remote CardDAV server (#42, #138, #139)

+ Updated contacts sql shema (breaking changes)
+ Fixes
- Removed SabreDAV Server
- Removed Contacts Sharing (awhile, code refactoring)
This commit is contained in:
RainLoop Team 2014-04-26 01:50:17 +04:00
parent 54a4c2657a
commit d29f20789f
78 changed files with 2024 additions and 2317 deletions

View file

@ -77,11 +77,6 @@ class Actions
*/
private $oAddressBookProvider;
/**
* @var \RainLoop\Providers\PersonalAddressBook
*/
private $oPersonalAddressBookProvider;
/**
* @var \RainLoop\Providers\Suggestions
*/
@ -127,7 +122,6 @@ class Actions
$this->oSettingsProvider = null;
$this->oDomainProvider = null;
$this->oAddressBookProvider = null;
$this->oPersonalAddressBookProvider = null;
$this->oSuggestionsProvider = null;
$this->oChangePasswordProvider = null;
$this->oTwoFactorAuthProvider = null;
@ -242,26 +236,6 @@ class Actions
$oResult = new \RainLoop\Providers\Domain\DefaultDomain(APP_PRIVATE_DATA.'domains',
$this->Cacher());
break;
case 'personal-address-book':
// \RainLoop\Providers\PersonalAddressBook\PersonalAddressBookInterface
$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\PersonalAddressBook\PdoPersonalAddressBook(
'sqlite:'.APP_PRIVATE_DATA.'PersonalAddressBook.sqlite', '', '', 'sqlite');
}
else
{
$oResult = new \RainLoop\Providers\PersonalAddressBook\PdoPersonalAddressBook($sDsn, $sUser, $sPassword, $sDsnType);
}
$oResult->SetLogger($this->Logger());
break;
case 'address-book':
// \RainLoop\Providers\AddressBook\AddressBookInterface
@ -625,32 +599,6 @@ class Actions
return $this->oAddressBookProvider;
}
/**
* @param \RainLoop\Account $oAccount = null
* @param bool $bForceEnable = false
*
* @return \RainLoop\Providers\PersonalAddressBook
*/
public function PersonalAddressBookProvider($oAccount = null, $bForceEnable = false)
{
if (null === $this->oPersonalAddressBookProvider)
{
$this->oPersonalAddressBookProvider = new \RainLoop\Providers\PersonalAddressBook(
$this->Config()->Get('contacts', 'enable', false) || $bForceEnable ? $this->fabrica('personal-address-book', $oAccount) : null);
$this->oPersonalAddressBookProvider->SetLogger($this->Logger());
$this->oPersonalAddressBookProvider->ConsiderShare(
!!$this->Config()->Get('contacts', 'allow_sharing', false));
}
else if ($oAccount && $this->oPersonalAddressBookProvider->IsSupported())
{
$this->oPersonalAddressBookProvider->SetAccount($oAccount);
}
return $this->oPersonalAddressBookProvider;
}
/**
* @return \OAuth2\Client|null
*/
@ -1033,10 +981,10 @@ class Actions
'CustomLogoutLink' => $oConfig->Get('labs', 'custom_logout_link', ''),
'AllowAdditionalAccounts' => (bool) $oConfig->Get('webmail', 'allow_additional_accounts', true),
'AllowIdentities' => (bool) $oConfig->Get('webmail', 'allow_identities', true),
'DetermineUserLanguage' => (bool) $oConfig->Get('labs', 'determine_user_language', false),
'AllowPrefetch' => (bool) $oConfig->Get('labs', 'allow_prefetch', true),
'AllowCustomLogin' => (bool) $oConfig->Get('login', 'allow_custom_login', false),
'LoginDefaultDomain' => $oConfig->Get('login', 'default_domain', ''),
'DetermineUserLanguage' => (bool) $oConfig->Get('labs', 'determine_user_language', false),
'AllowThemes' => (bool) $oConfig->Get('webmail', 'allow_themes', true),
'AllowCustomTheme' => (bool) $oConfig->Get('webmail', 'allow_custom_theme', true),
'ChangePasswordIsAllowed' => false,
@ -1060,7 +1008,7 @@ class Actions
$oAccount = $this->getAccountFromToken(false);
if ($oAccount instanceof \RainLoop\Account)
{
$oPab = $this->PersonalAddressBookProvider($oAccount);
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
$aResult['Auth'] = true;
$aResult['Email'] = $oAccount->Email();
@ -1069,59 +1017,27 @@ class Actions
$aResult['AccountHash'] = $oAccount->Hash();
$aResult['AccountSignMe'] = $oAccount->SignMe();
$aResult['ChangePasswordIsAllowed'] = $this->ChangePasswordProvider()->PasswordChangePossibility($oAccount);
$aResult['ContactsIsAllowed'] = $oPab->IsActive();
$aResult['ContactsSharingIsAllowed'] = $oPab->IsSharingAllowed();
$aResult['ContactsIsAllowed'] = $oAddressBookProvider->IsActive();
$aResult['ContactsSharingIsAllowed'] = $oAddressBookProvider->IsSharingAllowed();
$aResult['ContactsSyncIsAllowed'] = (bool) $oConfig->Get('contacts', 'allow_sync', false);
$aResult['ContactsSyncServer'] = '';
$aResult['EnableContactsSync'] = false;
$aResult['ContactsSyncUrl'] = '';
$aResult['ContactsSyncUser'] = '';
$aResult['ContactsSyncPassword'] = '';
$aResult['ContactsSyncPabUrl'] = '';
if ($aResult['ContactsSyncIsAllowed'])
if ($aResult['ContactsIsAllowed'] && $aResult['ContactsSyncIsAllowed'])
{
$sDavDomain = (string) $oConfig->Get('labs', 'sync_dav_domain', '');
if (empty($sDavDomain))
$mData = $this->getContactsSyncData($oAccount);
if (\is_array($mData))
{
$aResult['ContactsSyncServer'] = $this->Http()->GetHost(false, true, true);
}
else
{
$sDavDomain = \rtrim($sDavDomain, '/\\ ');
$sDavDomainWithoutScheme = \preg_replace('/https?:\/\//i', '', \trim($sDavDomain));
$aResult['ContactsSyncServer'] = $sDavDomainWithoutScheme;
}
$aResult['ContactsSyncUser'] = $oAccount->ParentEmailHelper();
try
{
$aResult['ContactsSyncPassword'] = $oPab->GetUserHashByEmail($aResult['ContactsSyncUser'], true);
}
catch (\Exception $oException)
{
$this->Logger()->WriteException($oException);
}
if (empty($sDavDomain))
{
$sUrl = \rtrim(\trim($this->Http()->GetScheme().'://'.$this->Http()->GetHost(true, false).$this->Http()->GetPath()), '/\\');
$sUrl = \preg_replace('/index\.php(.*)$/i', '', $sUrl);
$aResult['ContactsSyncPabUrl'] = $sUrl.'/index.php/dav/';
}
else
{
$aResult['ContactsSyncPabUrl'] = \preg_match('/^https?:\/\//i', $sDavDomain) ? $sDavDomain : 'http://'.$sDavDomain;
}
if (!empty($aResult['ContactsSyncPabUrl']))
{
$aResult['ContactsSyncPabUrl'] .= '/addressbooks/'.$oAccount->ParentEmailHelper().'/default/';
$aResult['EnableContactsSync'] = isset($mData['Enable']) ? !!$mData['Enable'] : false;
$aResult['ContactsSyncUrl'] = isset($mData['Url']) ? \trim($mData['Url']) : '';
$aResult['ContactsSyncUser'] = isset($mData['User']) ? \trim($mData['User']) : '';
$aResult['ContactsSyncPassword'] = APP_DUMMY;
}
}
if ($aResult['AccountSignMe'])
{
$sToken = \RainLoop\Utils::GetCookie(self::AUTH_MAILTO_TOKEN_KEY, null);
@ -2273,7 +2189,7 @@ class Actions
return $self->ValidateContactPdoType($sType);
});
$sTestMessage = $this->PersonalAddressBookProvider(null, true)->Test();
$sTestMessage = $this->AddressBookProvider(null, true)->Test();
return $this->DefaultResponse(__FUNCTION__, array(
'Result' => '' === $sTestMessage,
'Message' => \MailSo\Base\Utils::Utf8Clear($sTestMessage, '?')
@ -4571,7 +4487,7 @@ class Actions
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSendMessage);
}
if ($oMessage && $this->PersonalAddressBookProvider($oAccount)->IsActive())
if ($oMessage && $this->AddressBookProvider($oAccount)->IsActive())
{
$aArrayToFrec = array();
$oToCollection = $oMessage->GetTo();
@ -4588,7 +4504,7 @@ class Actions
{
$oSettings = $this->SettingsProvider()->Load($oAccount);
$this->PersonalAddressBookProvider($oAccount)->IncFrec(
$this->AddressBookProvider($oAccount)->IncFrec(
$oAccount->ParentEmailHelper(), \array_values($aArrayToFrec), !!$oSettings->GetConf('ContactsAutosave', true));
}
}
@ -4684,6 +4600,95 @@ class Actions
return $this->DefaultResponse(__FUNCTION__, $aQuota);
}
private function getContactsSyncData($oAccount)
{
$mResult = null;
$sData = $this->StorageProvider()->Get($oAccount->ParentEmailHelper(),
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
'contacts_sync'
);
if (!empty($sData))
{
$mData = \RainLoop\Utils::DecodeKeyValues($sData);
if (\is_array($mData))
{
$mResult = array(
'Enable' => isset($mData['Enable']) ? !!$mData['Enable'] : false,
'Url' => isset($mData['Url']) ? \trim($mData['Url']) : '',
'User' => isset($mData['User']) ? \trim($mData['User']) : '',
'Password' => isset($mData['Password']) ? $mData['Password'] : ''
);
}
}
return $mResult;
}
/**
* @return array
*/
public function DoSaveContactsSyncData()
{
$oAccount = $this->getAccountFromToken();
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
if (!$oAddressBookProvider || !$oAddressBookProvider->IsActive())
{
return $this->FalseResponse(__FUNCTION__);
}
$bEnabled = '1' === (string) $this->GetActionParam('Enable', '0');
$sUrl = $this->GetActionParam('Url', '');
$sUser = $this->GetActionParam('User', '');
$sPassword = $this->GetActionParam('Password', '');
$mData = $this->getContactsSyncData($oAccount);
$bResult = $this->StorageProvider()->Put($oAccount->ParentEmailHelper(),
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
'contacts_sync',
\RainLoop\Utils::EncodeKeyValues(array(
'Enable' => $bEnabled,
'User' => $sUser,
'Password' => APP_DUMMY === $sPassword && isset($mData['Password']) ?
$mData['Password'] : (APP_DUMMY === $sPassword ? '' : $sPassword),
'Url' => $sUrl
))
);
return $this->DefaultResponse(__FUNCTION__, $bResult);
}
/**
* @return array
*/
public function DoContactsSync()
{
$bResult = false;
$oAccount = $this->getAccountFromToken();
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
if ($oAddressBookProvider && $oAddressBookProvider->IsActive())
{
$mData = $this->getContactsSyncData($oAccount);
if (\is_array($mData) && isset($mData['Enable'], $mData['User'], $mData['Password'], $mData['Url']) && $mData['Enable'])
{
$bResult = $oAddressBookProvider->Sync(
$oAccount->ParentEmailHelper(),
$mData['Url'], $mData['User'], $mData['Password']);
}
}
if (!$bResult)
{
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError);
}
return $this->TrueResponse(__FUNCTION__);
}
private function getTwoFactorInfo($sEmail, $bRemoveSecret = false)
{
$mData = null;
@ -4950,10 +4955,10 @@ class Actions
$iLimit = 0 > $iLimit ? 20 : $iLimit;
$iResultCount = 0;
if ($this->PersonalAddressBookProvider($oAccount)->IsActive())
if ($this->AddressBookProvider($oAccount)->IsActive())
{
$iResultCount = 0;
$mResult = $this->PersonalAddressBookProvider($oAccount)->GetContacts($oAccount->ParentEmailHelper(),
$mResult = $this->AddressBookProvider($oAccount)->GetContacts($oAccount->ParentEmailHelper(),
$iOffset, $iLimit, $sSearch, $iResultCount);
}
@ -4980,9 +4985,9 @@ class Actions
});
$bResult = false;
if (0 < \count($aFilteredUids) && $this->PersonalAddressBookProvider($oAccount)->IsActive())
if (0 < \count($aFilteredUids) && $this->AddressBookProvider($oAccount)->IsActive())
{
$bResult = $this->PersonalAddressBookProvider($oAccount)->DeleteContacts($oAccount->ParentEmailHelper(), $aFilteredUids);
$bResult = $this->AddressBookProvider($oAccount)->DeleteContacts($oAccount->ParentEmailHelper(), $aFilteredUids);
}
return $this->DefaultResponse(__FUNCTION__, $bResult);
@ -4997,68 +5002,55 @@ class Actions
$bResult = false;
$oPab = $this->PersonalAddressBookProvider($oAccount);
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
$sRequestUid = \trim($this->GetActionParam('RequestUid', ''));
if ($oPab && $oPab->IsActive() && 0 < \strlen($sRequestUid))
if ($oAddressBookProvider && $oAddressBookProvider->IsActive() && 0 < \strlen($sRequestUid))
{
$sUid = \trim($this->GetActionParam('Uid', ''));
$sUidStr = \trim($this->GetActionParam('UidStr', ''));
$iScopeType = (int) $this->GetActionParam('ScopeType', null);
if (!in_array($iScopeType, array(
\RainLoop\Providers\PersonalAddressBook\Enumerations\ScopeType::DEFAULT_,
\RainLoop\Providers\PersonalAddressBook\Enumerations\ScopeType::SHARE_ALL)))
{
$iScopeType = \RainLoop\Providers\PersonalAddressBook\Enumerations\ScopeType::DEFAULT_;
}
$oContact = null;
if (0 < \strlen($sUid))
{
$oContact = $oPab->GetContactByID($oAccount->ParentEmailHelper(), $sUid);
$oContact = $oAddressBookProvider->GetContactByID($oAccount->ParentEmailHelper(), $sUid);
}
if (!$oContact)
{
$oContact = new \RainLoop\Providers\PersonalAddressBook\Classes\Contact();
$oContact = new \RainLoop\Providers\AddressBook\Classes\Contact();
if (0 < \strlen($sUid))
{
$oContact->IdContact = $sUid;
}
}
if (0 < \strlen($sUidStr))
{
$oContact->IdContactStr = $sUidStr;
}
$oContact->ScopeType = $iScopeType;
$oContact->Properties = array();
$aProperties = $this->GetActionParam('Properties', array());
if (\is_array($aProperties))
{
foreach ($aProperties as $aItem)
{
if ($aItem && isset($aItem[0], $aItem[1]) &&
\is_numeric($aItem[0]))
if ($aItem && isset($aItem[0], $aItem[1]) && \is_numeric($aItem[0]))
{
$oProp = new \RainLoop\Providers\PersonalAddressBook\Classes\Property();
$oProp = new \RainLoop\Providers\AddressBook\Classes\Property();
$oProp->Type = (int) $aItem[0];
$oProp->Value = $aItem[1];
$oProp->ScopeType = $iScopeType;
$oProp->TypeStr = empty($aItem[2]) ? '': $aItem[2];
$oContact->Properties[] = $oProp;
}
}
}
$bResult = $oPab->ContactSave($oAccount->ParentEmailHelper(), $oContact);
if (!empty($oContact->Etag))
{
$oContact->Etag = \md5($oContact->ToVCard());
}
$bResult = $oAddressBookProvider->ContactSave($oAccount->ParentEmailHelper(), $oContact);
}
return $this->DefaultResponse(__FUNCTION__, array(
'RequestUid' => $sRequestUid,
'ResultID' => $bResult ? $oContact->IdContact : '',
'ResultIDStr' => $bResult ? $oContact->IdContactStr : '',
'Result' => $bResult
));
}
@ -5087,11 +5079,11 @@ class Actions
if ($iLimit > \count($aResult) && 0 < \strlen($sQuery))
{
// Personal Address Book
$oPab = $this->PersonalAddressBookProvider($oAccount);
if ($oPab && $oPab->IsActive())
// Address Book
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
if ($oAddressBookProvider && $oAddressBookProvider->IsActive())
{
$aSuggestions = $oPab->GetSuggestions($oAccount->ParentEmailHelper(), $sQuery, $iLimit);
$aSuggestions = $oAddressBookProvider->GetSuggestions($oAccount->ParentEmailHelper(), $sQuery, $iLimit);
if (0 === \count($aResult))
{
$aResult = $aSuggestions;
@ -5669,8 +5661,8 @@ class Actions
if ($oAccount && \is_resource($rFile))
{
$oPab = $this->PersonalAddressBookProvider($oAccount);
if ($oPab && $oPab->IsActive())
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
if ($oAddressBookProvider && $oAddressBookProvider->IsActive())
{
$sDelimiter = ((int) \strpos($sFileStart, ',') > (int) \strpos($sFileStart, ';')) ? ',' : ';';
@ -5706,7 +5698,7 @@ class Actions
if (\is_array($aData) && 0 < \count($aData))
{
$this->Logger()->Write('Import contacts from csv');
$iCount = $oPab->ImportCsvArray($oAccount->ParentEmailHelper(), $aData);
$iCount = $oAddressBookProvider->ImportCsvArray($oAccount->ParentEmailHelper(), $aData);
}
}
}
@ -5726,8 +5718,8 @@ class Actions
$iCount = 0;
if ($oAccount && \is_resource($rFile))
{
$oPab = $this->PersonalAddressBookProvider($oAccount);
if ($oPab && $oPab->IsActive())
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
if ($oAddressBookProvider && $oAddressBookProvider->IsActive())
{
$sFile = \stream_get_contents($rFile);
if (\is_resource($rFile))
@ -5738,7 +5730,7 @@ class Actions
if (is_string($sFile) && 5 < \strlen($sFile))
{
$this->Logger()->Write('Import contacts from vcf');
$iCount = $oPab->ImportVcfFile($oAccount->ParentEmailHelper(), $sFile);
$iCount = $oAddressBookProvider->ImportVcfFile($oAccount->ParentEmailHelper(), $sFile);
}
}
}
@ -5762,8 +5754,8 @@ class Actions
if ($oAccount)
{
$oPab = $this->PersonalAddressBookProvider($oAccount);
if ($oPab)
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
if ($oAddressBookProvider && $oAddressBookProvider->IsActive())
{
$iError = UPLOAD_ERR_OK;
$_FILES = isset($_FILES) ? $_FILES : null;
@ -6949,28 +6941,25 @@ class Actions
'Email' => \MailSo\Base\Utils::Utf8Clear($mResponse->GetEmail())
));
}
else if ('RainLoop\Providers\PersonalAddressBook\Classes\Contact' === $sClassName)
else if ('RainLoop\Providers\AddressBook\Classes\Contact' === $sClassName)
{
$mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array(
/* @var $mResponse \RainLoop\Providers\PersonalAddressBook\Classes\Contact */
/* @var $mResponse \RainLoop\Providers\AddressBook\Classes\Contact */
'IdContact' => $mResponse->IdContact,
'IdContactStr' => $mResponse->IdContactStr,
'Display' => \MailSo\Base\Utils::Utf8Clear($mResponse->Display),
'ReadOnly' => $mResponse->ReadOnly,
'ScopeType' => $mResponse->ScopeType,
'IdPropertyFromSearch' => $mResponse->IdPropertyFromSearch,
'Properties' => $this->responseObject($mResponse->Properties, $sParent, $aParameters)
));
}
else if ('RainLoop\Providers\PersonalAddressBook\Classes\Property' === $sClassName)
else if ('RainLoop\Providers\AddressBook\Classes\Property' === $sClassName)
{
$mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array(
/* @var $mResponse \RainLoop\Providers\PersonalAddressBook\Classes\Property */
/* @var $mResponse \RainLoop\Providers\AddressBook\Classes\Property */
'IdProperty' => $mResponse->IdProperty,
'Type' => $mResponse->Type,
'TypeCustom' => $mResponse->TypeCustom,
'Value' => \MailSo\Base\Utils::Utf8Clear($mResponse->Value),
'ValueCustom' => \MailSo\Base\Utils::Utf8Clear($mResponse->ValueCustom)
'TypeStr' => $mResponse->TypeStr,
'Value' => \MailSo\Base\Utils::Utf8Clear($mResponse->Value)
));
}
else if ('MailSo\Mail\Attachment' === $sClassName)

View file

@ -14,6 +14,11 @@ abstract class PdoAbstract
*/
protected $bExplain = false;
/**
* @var int
*/
protected $iResetTimer = 0;
/**
* @var \MailSo\Log\Logger
*/
@ -114,13 +119,21 @@ abstract class PdoAbstract
}
/**
* @param string $sName = null
* @param string $sTabelName = null
* @param string $sColumnName = null
*
* @return string
*/
protected function lastInsertId($sName = null)
protected function lastInsertId($sTabelName = null, $sColumnName = null)
{
return $this->getPDO()->lastInsertId($sName);
$mName = null;
if ('pgsql' === $this->sDbType &&
null !== $sTabelName && $sColumnName !== null)
{
$mName = \strtolower($sTabelName.'_'.$sColumnName.'_seq');
}
return null === $mName ? $this->getPDO()->lastInsertId() : $this->getPDO()->lastInsertId($mName);
}
/**

View file

@ -24,6 +24,8 @@ class Notifications
const NewPasswordWeak = 133;
const NewPasswordForbidden = 134;
const ContactsSyncError = 140;
const CantGetMessageList = 201;
const CantGetMessage = 202;
const CantDeleteMessage = 203;
@ -93,6 +95,8 @@ class Notifications
self::NewPasswordShort => 'NewPasswordShort',
self::NewPasswordWeak => 'NewPasswordWeak',
self::NewPasswordForbidden => 'NewPasswordForbidden',
self::ContactsSyncError => 'ContactsSyncError',
self::CantGetMessageList => 'CantGetMessageList',
self::CantGetMessage => 'CantGetMessage',

View file

@ -53,6 +53,28 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
$this->oDriver->IsSupported();
}
/**
* @return bool
*/
public function IsSharingAllowed()
{
return $this->oDriver instanceof \RainLoop\Providers\PersonalAddressBook\AddressBookInterface &&
$this->oDriver->IsSharingAllowed();
}
/**
* @param string $sEmail
* @param string $sUrl
* @param string $sUser
* @param string $sPassword
*
* @return bool
*/
public function Sync($sEmail, $sUrl, $sUser, $sPassword)
{
return $this->IsActive() ? $this->oDriver->Sync($sEmail, $sUrl, $sUser, $sPassword) : false;
}
/**
* @param string $sEmail
* @param \RainLoop\Providers\AddressBook\Classes\Contact $oContact
@ -140,37 +162,64 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
{
$aMap = array(
'Title' => PropertyType::FULLNAME,
'First Name' => PropertyType::FIRST_NAME,
'Middle Name' => PropertyType::MIDDLE_NAME,
'Last Name' => PropertyType::LAST_NAME,
'Name' => PropertyType::FULLNAME,
'FullName' => PropertyType::FULLNAME,
'DisplayName' => PropertyType::FULLNAME,
'First' => PropertyType::FIRST_NAME,
'FirstName' => PropertyType::FIRST_NAME,
'Middle' => PropertyType::MIDDLE_NAME,
'MiddleName' => PropertyType::MIDDLE_NAME,
'Last' => PropertyType::LAST_NAME,
'LastName' => 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,
'NickName' => PropertyType::NICK_NAME,
'BusinessFax' => array(PropertyType::PHONE, 'Work,Fax'),
'BusinessFax2' => array(PropertyType::PHONE, 'Work,Fax'),
'BusinessPhone' => array(PropertyType::PHONE, 'Work'),
'BusinessPhone2' => array(PropertyType::PHONE, 'Work'),
'BusinessPhone3' => array(PropertyType::PHONE, 'Work'),
'CompanyPhone' => array(PropertyType::PHONE, 'Work'),
'CompanyMainPhone' => array(PropertyType::PHONE, 'Work'),
'HomeFax' => array(PropertyType::PHONE, 'Home,Fax'),
'HomeFax2' => array(PropertyType::PHONE, 'Home,Fax'),
'HomePhone' => array(PropertyType::PHONE, 'Home'),
'HomePhone2' => array(PropertyType::PHONE, 'Home'),
'HomePhone3' => array(PropertyType::PHONE, 'Home'),
'Mobile' => array(PropertyType::PHONE, 'Mobile'),
'MobilePhone' => array(PropertyType::PHONE, 'Mobile'),
'BusinessMobile' => array(PropertyType::PHONE, 'Work,Mobile'),
'BusinessMobilePhone' => array(PropertyType::PHONE, 'Work,Mobile'),
'OtherFax' => array(PropertyType::PHONE, 'Other,Fax'),
'OtherPhone' => array(PropertyType::PHONE, 'Other'),
'PrimaryPhone' => array(PropertyType::PHONE, 'Pref,Home'),
'Email' => array(PropertyType::EMAIl, 'Home'),
'Email2' => array(PropertyType::EMAIl, 'Home'),
'Email3' => array(PropertyType::EMAIl, 'Home'),
'EmailAddress' => array(PropertyType::EMAIl, 'Home'),
'Email2Address' => array(PropertyType::EMAIl, 'Home'),
'Email3Address' => array(PropertyType::EMAIl, 'Home'),
'OtherEmail' => array(PropertyType::EMAIl, 'Other'),
'BusinessEmail' => array(PropertyType::EMAIl, 'Work'),
'BusinessEmail2' => array(PropertyType::EMAIl, 'Work'),
'BusinessEmail3' => array(PropertyType::EMAIl, 'Work'),
'PersonalEmail' => array(PropertyType::EMAIl, 'Home'),
'PersonalEmail2' => array(PropertyType::EMAIl, 'Home'),
'PersonalEmail3' => array(PropertyType::EMAIl, 'Home'),
'Notes' => PropertyType::NOTE,
'Web Page' => PropertyType::WEB_PAGE_PERSONAL,
'WebPage' => PropertyType::WEB_PAGE_PERSONAL,
'Web' => PropertyType::WEB_PAGE,
'BusinessWeb' => array(PropertyType::WEB_PAGE, 'Work'),
'WebPage' => PropertyType::WEB_PAGE,
'BusinessWebPage' => array(PropertyType::WEB_PAGE, 'Work'),
'WebSite' => PropertyType::WEB_PAGE,
'BusinessWebSite' => array(PropertyType::WEB_PAGE, 'Work'),
'PersonalWebSite' => PropertyType::WEB_PAGE
);
$aMap = \array_change_key_case($aMap, CASE_LOWER);
}
$sCsvNameLower = \MailSo\Base\Utils::IsAscii($sCsvName) ? \strtolower($sCsvName) : '';
return isset($aMap[$sCsvNameLower]) ? $aMap[$sCsvNameLower] : PropertyType::UNKNOWN;
$sCsvNameLower = \MailSo\Base\Utils::IsAscii($sCsvName) ? \preg_replace('/[\s\-]+/', '', \strtolower($sCsvName)) : '';
return !empty($sCsvNameLower) && isset($aMap[$sCsvNameLower]) ? $aMap[$sCsvNameLower] : PropertyType::UNKNOWN;
}
/**
@ -182,11 +231,15 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
public function ImportCsvArray($sEmail, $aCsvData)
{
$iCount = 0;
$iResetTimer = 0;
if ($this->IsActive() && \is_array($aCsvData) && 0 < \count($aCsvData))
{
$oContact = new \RainLoop\Providers\AddressBook\Classes\Contact();
foreach ($aCsvData as $aItem)
{
\MailSo\Base\Utils::ResetTimeLimit($iResetTimer);
foreach ($aItem as $sItemName => $sItemValue)
{
$sItemName = \trim($sItemName);
@ -194,12 +247,15 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
if (!empty($sItemName) && !empty($sItemValue))
{
$iType = $this->csvNameToTypeConvertor($sItemName);
$mData = $this->csvNameToTypeConvertor($sItemName);
$iType = \is_array($mData) ? $mData[0] : $mData;
if (PropertyType::UNKNOWN !== $iType)
{
$oProp = new \RainLoop\Providers\AddressBook\Classes\Property();
$oProp->Type = $iType;
$oProp->Value = $sItemValue;
$oProp->TypeStr = \is_array($mData) && !empty($mData[1]) ? $mData[1] : '';
$oContact->Properties[] = $oProp;
}
@ -232,6 +288,8 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
public function ImportVcfFile($sEmail, $sVcfData)
{
$iCount = 0;
$iResetTimer = 0;
if ($this->IsActive() && \is_string($sVcfData))
{
$sVcfData = \trim($sVcfData);
@ -260,12 +318,15 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
{
if ($oVCard instanceof \Sabre\VObject\Component\VCard)
{
\MailSo\Base\Utils::ResetTimeLimit($iResetTimer);
if (empty($oVCard->UID))
{
$oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID();
}
$oContact->ParseVCard($oVCard, $oVCard->serialize());
$oContact->PopulateByVCard($oVCard->serialize());
if (0 < \count($oContact->Properties))
{
if ($this->ContactSave($sEmail, $oContact))

View file

@ -47,7 +47,7 @@ class Contact
/**
* @var string
*/
public $Hash;
public $Etag;
public function __construct()
{
@ -64,7 +64,7 @@ class Contact
$this->Properties = array();
$this->ReadOnly = false;
$this->IdPropertyFromSearch = 0;
$this->Hash = '';
$this->Etag = '';
}
public function UpdateDependentValues()
@ -101,9 +101,8 @@ class Contact
{
$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
else if (\in_array($oProperty->Type, array(
PropertyType::FULLNAME, PropertyType::PHONE
)))
{
$sOther = $oProperty->Value;
@ -134,20 +133,16 @@ class Contact
}
$this->Display = \trim($sDisplay);
$bNewFull = false;
if (!$oFullNameProperty)
if ($oFullNameProperty)
{
$oFullNameProperty = new \RainLoop\Providers\AddressBook\Classes\Property(PropertyType::FULLNAME, $this->Display);
$bNewFull = true;
$oFullNameProperty->Value = $this->Display;
$oFullNameProperty->UpdateDependentValues();
}
$oFullNameProperty->Value = $this->Display;
$oFullNameProperty->UpdateDependentValues();
if ($bNewFull)
if (!$oFullNameProperty)
{
$this->Properties[] = $oFullNameProperty;
$this->Properties[] = new \RainLoop\Providers\AddressBook\Classes\Property(PropertyType::FULLNAME, $this->Display);
}
}
@ -177,85 +172,189 @@ class Contact
}
/**
* @param \Sabre\VObject\Document $oVCard
* @return string
*/
public function ParseVCard($oVCard)
public function CardDavNameUri()
{
$bNew = empty($this->IdContact);
return $this->IdContactStr.'.vcf';
}
if (!$bNew)
/**
* @return string
*/
public function ToVCard($sPreVCard = '')
{
$this->UpdateDependentValues();
$oVCard = null;
if (0 < \strlen($sPreVCard))
{
$this->Properties = array();
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, $oVCard->URL);
$sFirstName = $sLastName = $sMiddleName = $sSuffix = $sPrefix = '';
foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty)
{
if ($oProperty)
{
$sAddKey = '';
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:
if (empty($sAddKey))
{
$sAddKey = 'EMAIL';
}
case PropertyType::WEB_PAGE:
if (empty($sAddKey))
{
$sAddKey = 'URL';
}
case PropertyType::PHONE:
if (empty($sAddKey))
{
$sAddKey = 'TEL';
}
$aTypes = $oProperty->TypesAsArray();
$oVCard->add($sAddKey, $oProperty->Value, \is_array($aTypes) && 0 < \count($aTypes) ? array('TYPE' => $aTypes) : null);
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 (string) $oVCard->serialize();
}
/**
* @param mixed $oProp
* @param bool $bOldVersion
* @return string
*/
private function getPropertyValueHelper($oProp, $bOldVersion)
{
$sValue = \trim($oProp);
if ($bOldVersion && !isset($oProp->parameters['CHARSET']))
{
if (0 < \strlen($sValue))
{
$sEncValue = @\utf8_encode($sValue);
if (0 === \strlen($sEncValue))
{
$sEncValue = $sValue;
}
$sValue = $sEncValue;
}
}
return \MailSo\Base\Utils::Utf8Clear($sValue);
}
/**
* @param mixed $oProp
* @param bool $bOldVersion
* @return string
*/
private function addArrayPropertyHelper(&$aProperties, $oArrayProp, $iType)
{
foreach ($oArrayProp as $oProp)
{
$oTypes = $oProp ? $oProp['TYPE'] : null;
$aTypes = $oTypes ? $oTypes->getParts() : array();
$sValue = $oProp ? \trim($oProp->getValue()) : '';
if (0 < \strlen($sValue))
{
if (!$oTypes || $oTypes->has('PREF'))
{
\array_unshift($aProperties, new Property($iType, $sValue, \implode(',', $aTypes)));
}
else
{
\array_push($aProperties, new Property($iType, $sValue, \implode(',', $aTypes)));
}
}
}
}
public function PopulateByVCard($sVCard, $sEtag = '')
{
$this->Properties = array();
if (!empty($sEtag))
{
$this->Etag = $sEtag;
}
try
{
$oVCard = \Sabre\VObject\Reader::read($sVCard);
}
catch (\Exception $oExc) {};
$aProperties = array();
if ($oVCard)
{
$bOldVersion = empty($oVCard->VERSION) ? false :
$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();
$this->IdContactStr = $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);
$sValue = $this->getPropertyValueHelper($oVCard->FN, $bOldVersion);
$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);
$sValue = $sValue = $this->getPropertyValueHelper($oVCard->NICKNAME, $bOldVersion);
$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);
// $sValue = $this->getPropertyValueHelper($oVCard->NOTE, $bOldVersion);
// $aProperties[] = new Property(PropertyType::NOTE, $sValue);
// }
@ -302,244 +401,22 @@ class Contact
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));
}
}
}
$this->addArrayPropertyHelper(&$aProperties, $oVCard->EMAIL, PropertyType::EMAIl);
}
if (isset($oVCard->URL))
{
$this->addArrayPropertyHelper(&$aProperties, $oVCard->URL, PropertyType::WEB_PAGE);
}
// 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->addArrayPropertyHelper(&$aProperties, $oVCard->TEL, PropertyType::PHONE);
}
$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';
$this->UpdateDependentValues();
}
}

View file

@ -19,7 +19,7 @@ class Property
/**
* @var string
*/
public $TypeCustom;
public $TypeStr;
/**
* @var string
@ -37,12 +37,13 @@ class Property
public $Frec;
public function __construct(
$iType = \RainLoop\Providers\AddressBook\Enumerations\PropertyType::UNKNOWN, $sValue = '')
$iType = \RainLoop\Providers\AddressBook\Enumerations\PropertyType::UNKNOWN, $sValue = '', $sTypeStr = '')
{
$this->Clear();
$this->Type = $iType;
$this->Value = $sValue;
$this->TypeStr = $sTypeStr;
}
public function Clear()
@ -50,7 +51,7 @@ class Property
$this->IdProperty = 0;
$this->Type = PropertyType::UNKNOWN;
$this->TypeCustom = '';
$this->TypeStr = '';
$this->Value = '';
$this->ValueCustom = '';
@ -63,9 +64,7 @@ class Property
*/
public function IsEmail()
{
return \in_array($this->Type, array(
PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER
));
return PropertyType::EMAIl === $this->Type;
}
/**
@ -73,18 +72,29 @@ class Property
*/
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
));
return PropertyType::PHONE === $this->Type;
}
/**
* @return bool
*/
public function TypesAsArray()
{
$aResult = array();
if (!empty($this->TypeStr))
{
$sTypeStr = \preg_replace('/[\s]+/', '', $this->TypeStr);
$aResult = \explode(',', $sTypeStr);
}
return $aResult;
}
public function UpdateDependentValues()
{
$this->Value = \trim($this->Value);
$this->ValueCustom = \trim($this->ValueCustom);
$this->TypeCustom = \trim($this->TypeCustom);
$this->TypeStr = \trim($this->TypeStr);
if (0 < \strlen($this->Value))
{

View file

@ -16,21 +16,11 @@ class PropertyType
const NAME_PREFIX = 20;
const NAME_SUFFIX = 21;
const EMAIl_PERSONAL = 30;
const EMAIl_BUSSINES = 31;
const EMAIl_OTHER = 32;
const PHONE_PERSONAL = 50;
const PHONE_BUSSINES = 51;
const PHONE_OTHER = 52;
const MOBILE_PERSONAL = 60;
const MOBILE_BUSSINES = 61;
const MOBILE_OTHER = 62;
const FAX_PERSONAL = 70;
const FAX_BUSSINES = 71;
const FAX_OTHER = 72;
const EMAIl = 30;
const PHONE = 31;
// const MOBILE = 32;
// const FAX = 33;
const WEB_PAGE = 32;
const FACEBOOK = 90;
const SKYPE = 91;
@ -38,9 +28,5 @@ class PropertyType
const NOTE = 110;
const WEB_PAGE_PERSONAL = 220;
const WEB_PAGE_BUSSINES = 221;
const WEB_PAGE_OTHER = 222;
const CUSTOM = 250;
}

View file

@ -1,9 +0,0 @@
<?php
namespace RainLoop\Providers\AddressBook\Enumerations;
class ScopeType
{
const DEFAULT_ = 0;
const SHARE_ALL = 2;
}

View file

@ -47,15 +47,335 @@ class PdoAddressBook
return \is_array($aDrivers) ? \in_array($this->sDsnType, $aDrivers) : false;
}
/**
* @return bool
*/
public function IsSharingAllowed()
{
return $this->IsSupported() && false; // TODO
}
private function flushDeletedContacts($iUserID)
{
return !!$this->prepareAndExecute('DELETE FROM rainloop_ab_contacts WHERE id_user = :id_user AND deleted = 1', array(
':id_user' => array($iUserID, \PDO::PARAM_INT)
));
}
private function updateContactEtagAndTime($iUserID, $mID, $sEtag, $iChanged)
{
return !!$this->prepareAndExecute('UPDATE rainloop_ab_contacts SET changed = :changed, etag = :etag '.
'WHERE id_user = :id_user AND id_contact = :id_contact', array(
':id_user' => array($iUserID, \PDO::PARAM_INT),
':id_contact' => array($mID, \PDO::PARAM_INT),
':changed' => array($iChanged, \PDO::PARAM_INT),
':etag' => array($sEtag, \PDO::PARAM_STR)
)
);
}
private function prepearDatabaseSyncData($iUserID)
{
$aResult = array();
$oStmt = $this->prepareAndExecute('SELECT id_contact, id_contact_str, changed, deleted, etag FROM rainloop_ab_contacts WHERE id_user = :id_user', 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 && isset($aItem['id_contact'], $aItem['id_contact_str'], $aItem['changed'], $aItem['deleted'], $aItem['etag']) &&
!empty($aItem['id_contact_str']))
{
$aResult[$aItem['id_contact_str']] = array(
'id_contact' => $aItem['id_contact'],
'etag' => $aItem['etag'],
'changed' => (int) $aItem['changed'],
'deleted' => '1' === (string) $aItem['deleted']
);
}
}
}
}
return $aResult;
}
private function prepearRemoteSyncData($oClient, $sPath)
{
$mResult = false;
$aResponse = null;
try
{
$this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV');
$aResponse = $oClient->propFind($sPath, array(
'{DAV:}getlastmodified',
'{DAV:}getetag'
), 1);
}
catch (\Exception $oException)
{
$this->oLogger->WriteException($oException);
}
if (\is_array($aResponse))
{
$mResult = array();
foreach ($aResponse as $sKey => $aItem)
{
$sKey = \rtrim(\trim($sKey), '\\/');
if (!empty($sKey) && is_array($aItem))
{
$aItem = \array_change_key_case($aItem, \CASE_LOWER);
if (isset($aItem['{dav:}getetag'], $aItem['{dav:}getlastmodified']))
{
$aMatch = array();
if (\preg_match('/\/([^\/?]+)$/', $sKey, $aMatch) && !empty($aMatch[1]))
{
$sVcfFileName = $aMatch[1];
$sKeyID = \preg_replace('/\.vcf$/i', '', $sVcfFileName);
$mResult[$sKeyID] = array(
'vcf' => $sVcfFileName,
'etag' => \trim(\trim($aItem['{dav:}getetag']), '"\''),
'lastmodified' => $aItem['{dav:}getlastmodified'],
'changed' => \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($aItem['{dav:}getlastmodified']),
'deleted' => false
);
}
}
}
}
}
return $mResult;
}
private function davClientRequest($oClient, $sCmd, $sUrl, $mData = null)
{
\MailSo\Base\Utils::ResetTimeLimit($this->iResetTimer);
$this->oLogger->Write($sCmd.' '.$sUrl.('PUT' === $sCmd && null !== $mData ? ' ('.\strlen($mData).')' : ''),
\MailSo\Log\Enumerations\Type::INFO, 'DAV');
if ('PUT' === $sCmd)
{
$this->oLogger->Write($mData, \MailSo\Log\Enumerations\Type::INFO, 'DAV');
}
$oResponse = false;
try
{
$oResponse = 'PUT' === $sCmd && null !== $mData ?
$oClient->request($sCmd, $sUrl, $mData) : $oClient->request($sCmd, $sUrl);
if ('GET' === $sCmd || false)
{
$this->oLogger->WriteDump($oResponse, \MailSo\Log\Enumerations\Type::INFO, 'DAV');
}
}
catch (\Exception $oException)
{
$this->oLogger->WriteException($oException);
}
return $oResponse;
}
/**
* @param string $sEmail
* @param \RainLoop\Providers\AddressBook\Classes\Contact $oContact
* @param string $sUrl
* @param string $sUser
* @param string $sPassword
* @param string $sProxy = ''
*
* @return bool
*/
public function ContactSave($sEmail, &$oContact)
public function Sync($sEmail, $sUrl, $sUser, $sPassword, $sProxy = '')
{
$this->Sync();
$this->SyncDatabase();
$iUserID = $this->getUserId($sEmail);
if (0 >= $iUserID)
{
return false;
}
$aUrl = \parse_url($sUrl);
if (!\is_array($aUrl))
{
$aUrl = array();
}
$aUrl['scheme'] = isset($aUrl['scheme']) ? $aUrl['scheme'] : 'http';
$aUrl['host'] = isset($aUrl['host']) ? $aUrl['host'] : 'localhost';
$aUrl['port'] = isset($aUrl['port']) ? $aUrl['port'] : 80;
$aUrl['path'] = isset($aUrl['path']) ? \rtrim($aUrl['path'], '\\/').'/' : '/';
$aSettings = array(
'baseUri' => $aUrl['scheme'].'://'.$aUrl['host'].('80' === (string) $aUrl['port'] ? '' : ':'.$aUrl['port']),
'userName' => $sUser,
'password' => $sPassword
);
if (!empty($sProxy))
{
$aSettings['proxy'] = $sProxy;
}
$sPath = $aUrl['path'];
$oClient = new \Sabre\DAV\Client($aSettings);
$oClient->setVerifyPeer(false);
$aRemoteSyncData = $this->prepearRemoteSyncData($oClient, $sPath);
if (false === $aRemoteSyncData)
{
return false;
}
$aDatabaseSyncData = $this->prepearDatabaseSyncData($iUserID);
// $this->oLogger->WriteDump($aDatabaseSyncData);
// $this->oLogger->WriteDump($aRemoteSyncData);
//+++del (from carddav)
foreach ($aDatabaseSyncData as $sKey => $aData)
{
if ($aData['deleted'] &&
isset($aRemoteSyncData[$sKey], $aRemoteSyncData[$sKey]['vcf']))
{
$this->davClientRequest($oClient, 'DELETE', $sPath.$aRemoteSyncData[$sKey]['vcf']);
}
}
//---del
//+++del (from db)
$aIdsForDeletedion = array();
foreach ($aDatabaseSyncData as $sKey => $aData)
{
if (!$aData['deleted'] && !empty($aData['etag']) && !isset($aRemoteSyncData[$sKey]))
{
$aIdsForDeletedion[] = $aData['id_contact'];
}
}
if (0 < \count($aIdsForDeletedion))
{
$this->DeleteContacts($sEmail, $aIdsForDeletedion, false);
}
//---del
$this->flushDeletedContacts($iUserID);
//+++new or newer (from db)
foreach ($aDatabaseSyncData as $sKey => $aData)
{
if (!$aData['deleted'] &&
(empty($aData['etag']) && !isset($aRemoteSyncData[$sKey])) // new
||
(!empty($aData['etag']) && isset($aRemoteSyncData[$sKey]) && // newer
$aRemoteSyncData[$sKey]['etag'] !== $aData['etag'] &&
$aRemoteSyncData[$sKey]['changed'] < $aData['changed']
)
)
{
$mID = $aData['id_contact'];
$oContact = $this->GetContactByID($sEmail, $mID, false);
if ($oContact)
{
$sExsistensBody = '';
$mExsistenRemoteID = isset($aRemoteSyncData[$sKey]['vcf']) && !empty($aData['etag']) ? $aRemoteSyncData[$sKey]['vcf'] : '';
if (0 < \strlen($mExsistenRemoteID))
{
$oResponse = $this->davClientRequest($oClient, 'GET', $sPath.$mExsistenRemoteID);
if ($oResponse && isset($oResponse['headers'], $oResponse['body']))
{
$sExsistensBody = \trim($oResponse['body']);
}
}
$oResponse = $this->davClientRequest($oClient, 'PUT',
$sPath.$oContact->CardDavNameUri(), $oContact->ToVCard($sExsistensBody));
if ($oResponse && isset($oResponse['headers'], $oResponse['headers']['etag']))
{
$sEtag = \trim(\trim($oResponse['headers']['etag']), '"\'');
$sDate = !empty($oResponse['headers']['date']) ? \trim($oResponse['headers']['date']) : '';
if (!empty($sEtag))
{
$iChanged = empty($sDate) ? \time() : \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($sDate);
$this->updateContactEtagAndTime($iUserID, $mID, $sEtag, $iChanged);
}
}
}
unset($oContact);
}
}
//---new
//+++new or newer (from carddav)
foreach ($aRemoteSyncData as $sKey => $aData)
{
if (!isset($aDatabaseSyncData[$sKey]) // new
||
($aDatabaseSyncData[$sKey]['etag'] !== $aData['etag'] && // newer
$aDatabaseSyncData[$sKey]['changed'] < $aData['changed'])
)
{
$mExsistenContactID = isset($aDatabaseSyncData[$sKey]['id_contact']) ?
$aDatabaseSyncData[$sKey]['id_contact'] : '';
$oResponse = $this->davClientRequest($oClient, 'GET', $sPath.$aData['vcf']);
if ($oResponse && isset($oResponse['headers'], $oResponse['body']))
{
$sBody = \trim($oResponse['body']);
if (!empty($sBody))
{
$oContact = null;
if ($mExsistenContactID)
{
$oContact = $this->GetContactByID($sEmail, $mExsistenContactID);
}
if (!$oContact)
{
$oContact = new \RainLoop\Providers\AddressBook\Classes\Contact();
}
$oContact->PopulateByVCard($sBody,
!empty($oResponse['headers']['etag']) ? \trim(\trim($oResponse['headers']['etag']), '"\'') : '');
$this->ContactSave($sEmail, $oContact);
unset($oContact);
}
}
}
}
return true;
}
/**
* @param string $sEmail
* @param \RainLoop\Providers\AddressBook\Classes\Contact $oContact
* @param bool $bSyncDb = true
*
* @return bool
*/
public function ContactSave($sEmail, &$oContact, $bSyncDb = true)
{
if ($bSyncDb)
{
$this->SyncDatabase();
}
$iUserID = $this->getUserId($sEmail);
$iIdContact = 0 < \strlen($oContact->IdContact) && \is_numeric($oContact->IdContact) ? (int) $oContact->IdContact : 0;
@ -77,7 +397,7 @@ class PdoAddressBook
{
$aFreq = $this->getContactFreq($iUserID, $iIdContact);
$sSql = 'UPDATE rainloop_ab_contacts SET id_contact_str = :id_contact_str, display = :display, changed = :changed, hash = :hash '.
$sSql = 'UPDATE rainloop_ab_contacts SET id_contact_str = :id_contact_str, display = :display, changed = :changed, etag = :etag '.
'WHERE id_user = :id_user AND id_contact = :id_contact';
$this->prepareAndExecute($sSql,
@ -87,7 +407,7 @@ class PdoAddressBook
':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)
':etag' => array($oContact->Etag, \PDO::PARAM_STR)
)
);
@ -103,8 +423,8 @@ class PdoAddressBook
else
{
$sSql = 'INSERT INTO rainloop_ab_contacts '.
'( id_user, id_contact_str, display, changed, hash) VALUES '.
'(:id_user, :id_contact_str, :display, :changed, :hash)';
'( id_user, id_contact_str, display, changed, etag) VALUES '.
'(:id_user, :id_contact_str, :display, :changed, :etag)';
$this->prepareAndExecute($sSql,
array(
@ -112,11 +432,11 @@ class PdoAddressBook
':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)
':etag' => array($oContact->Etag, \PDO::PARAM_STR)
)
);
$sLast = $this->lastInsertId('id_contact');
$sLast = $this->lastInsertId('rainloop_ab_contacts', 'id_contact');
if (\is_numeric($sLast) && 0 < (int) $sLast)
{
$iIdContact = (int) $sLast;
@ -139,7 +459,7 @@ class PdoAddressBook
':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_type_str' => array($oProp->TypeStr, \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),
@ -147,8 +467,8 @@ class PdoAddressBook
}
$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)';
'( id_contact, id_user, prop_type, prop_type_str, prop_value, prop_value_custom, prop_frec) VALUES '.
'(:id_contact, :id_user, :prop_type, :prop_type_str, :prop_value, :prop_value_custom, :prop_frec)';
$this->prepareAndExecute($sSql, $aParams, true);
}
@ -174,12 +494,17 @@ class PdoAddressBook
/**
* @param string $sEmail
* @param array $aContactIds
* @param bool $bSyncDb = true
*
* @return bool
*/
public function DeleteContacts($sEmail, $aContactIds)
public function DeleteContacts($sEmail, $aContactIds, $bSyncDb = true)
{
$this->Sync();
if ($bSyncDb)
{
$this->SyncDatabase();
}
$iUserID = $this->getUserId($sEmail);
$aContactIds = \array_filter($aContactIds, function (&$mItem) {
@ -196,7 +521,44 @@ class PdoAddressBook
$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);
$aParams = array(
':id_user' => array($iUserID, \PDO::PARAM_INT),
':changed' => array(\time(), \PDO::PARAM_INT)
);
$this->prepareAndExecute('UPDATE rainloop_ab_contacts SET deleted = 1, changed = :changed '.
'WHERE id_user = :id_user AND id_contact IN ('.$sIDs.')', $aParams);
return true;
}
/**
* @param string $sEmail
* @param array $aTagsIds
*
* @return bool
*/
public function DeleteTags($sEmail, $aTagsIds)
{
$this->SyncDatabase();
$iUserID = $this->getUserId($sEmail);
$aTagsIds = \array_filter($aTagsIds, function (&$mItem) {
$mItem = (int) \trim($mItem);
return 0 < $mItem;
});
if (0 === \count($aTagsIds))
{
return false;
}
$sIDs = \implode(',', $aTagsIds);
$aParams = array(':id_user' => array($iUserID, \PDO::PARAM_INT));
$this->prepareAndExecute('DELETE FROM rainloop_pab_tags_contacts WHERE id_tag IN ('.$sIDs.')');
$this->prepareAndExecute('DELETE FROM rainloop_pab_tags WHERE id_user = :id_user AND id_tag IN ('.$sIDs.')', $aParams);
return true;
}
@ -212,7 +574,7 @@ class PdoAddressBook
*/
public function GetContacts($sEmail, $iOffset = 0, $iLimit = 20, $sSearch = '', &$iResultCount = 0)
{
$this->Sync();
$this->SyncDatabase();
$iOffset = 0 <= $iOffset ? $iOffset : 0;
$iLimit = 0 < $iLimit ? (int) $iLimit : 20;
@ -235,11 +597,7 @@ class PdoAddressBook
$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)' : '').
(0 < \strlen($sCustomSearch) ? ' OR prop_type = '.PropertyType::PHONE.' AND prop_value_custom <> \'\' AND prop_value_custom LIKE :search_custom_phone)' : '').
') GROUP BY id_contact, id_prop';
$aParams = array(
@ -297,7 +655,7 @@ class PdoAddressBook
if (0 < $iCount)
{
$sSql = 'SELECT * FROM rainloop_ab_contacts WHERE id_user = :id_user';
$sSql = 'SELECT * FROM rainloop_ab_contacts WHERE deleted = 0 AND id_user = :id_user';
$aParams = array(
':id_user' => array($iUserID, \PDO::PARAM_INT)
@ -367,7 +725,7 @@ class PdoAddressBook
$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->TypeStr = isset($aItem['prop_type_str']) ? (string) $aItem['prop_type_str'] : '';
$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;
@ -403,13 +761,11 @@ class PdoAddressBook
*/
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';
$sSql = 'SELECT * FROM rainloop_ab_contacts WHERE deleted = 0 AND id_user = :id_user';
$aParams = array(
':id_user' => array($iUserID, \PDO::PARAM_INT)
@ -450,7 +806,7 @@ class PdoAddressBook
$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'];
$oContact->Etag = empty($aItem['etag']) ? '' : (string) $aItem['etag'];
}
}
}
@ -478,7 +834,7 @@ class PdoAddressBook
$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->TypeStr = isset($aItem['prop_type_str']) ? (string) $aItem['prop_type_str'] : '';
$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;
@ -516,12 +872,12 @@ class PdoAddressBook
throw new \InvalidArgumentException('Empty Search argument');
}
$this->Sync();
$this->SyncDatabase();
$iUserID = $this->getUserId($sEmail);
$sTypes = implode(',', array(
PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER, PropertyType::FIRST_NAME, PropertyType::LAST_NAME
PropertyType::EMAIl, PropertyType::FIRST_NAME, PropertyType::LAST_NAME
));
$sSql = 'SELECT id_contact, id_prop, prop_type, prop_value FROM rainloop_ab_properties '.
@ -575,7 +931,7 @@ class PdoAddressBook
$oStmt->closeCursor();
$sTypes = \implode(',', array(
PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER, PropertyType::FIRST_NAME, PropertyType::LAST_NAME
PropertyType::EMAIl, PropertyType::FIRST_NAME, PropertyType::LAST_NAME
));
$sSql = 'SELECT id_prop, id_contact, prop_type, prop_value FROM rainloop_ab_properties '.
@ -607,8 +963,8 @@ class PdoAddressBook
$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)))
else if ((isset($aIdProps[$iIdProp]) || isset($aContactAllAccess[$iIdContact])) &&
PropertyType::EMAIl === $iType)
{
if (!isset($aEmails[$iIdContact]))
{
@ -676,22 +1032,19 @@ class PdoAddressBook
throw new \InvalidArgumentException('Empty Emails argument');
}
$this->Sync();
$this->SyncDatabase();
$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.')';
$sSql = 'SELECT prop_value FROM rainloop_ab_properties WHERE id_user = :id_user AND prop_type = :prop_type';
$oStmt = $this->prepareAndExecute($sSql, array(
':id_user' => array($iUserID, \PDO::PARAM_INT)
':id_user' => array($iUserID, \PDO::PARAM_INT),
':prop_type' => array(PropertyType::EMAIl, \PDO::PARAM_INT)
));
if ($oStmt)
@ -748,7 +1101,7 @@ class PdoAddressBook
if ('' !== \trim($oEmail->GetEmail()))
{
$oPropEmail = new \RainLoop\Providers\AddressBook\Classes\Property();
$oPropEmail->Type = \RainLoop\Providers\AddressBook\Enumerations\PropertyType::EMAIl_PERSONAL;
$oPropEmail->Type = \RainLoop\Providers\AddressBook\Enumerations\PropertyType::EMAIl;
$oPropEmail->Value = \strtolower(\trim($oEmail->GetEmail()));
$oContact->Properties[] = $oPropEmail;
@ -797,7 +1150,7 @@ class PdoAddressBook
}
}
$sSql = 'UPDATE rainloop_ab_properties SET prop_frec = prop_frec + 1 WHERE id_user = :id_user AND prop_type IN ('.$sTypes;
$sSql = 'UPDATE rainloop_ab_properties SET prop_frec = prop_frec + 1 WHERE id_user = :id_user AND prop_type = :prop_type';
$aEmailsQuoted = \array_map(function ($mItem) use ($self) {
return $self->quoteValue($mItem);
@ -805,15 +1158,16 @@ class PdoAddressBook
if (1 === \count($aEmailsQuoted))
{
$sSql .= ') AND prop_value = '.$aEmailsQuoted[0];
$sSql .= ' AND prop_value = '.$aEmailsQuoted[0];
}
else
{
$sSql .= ') AND prop_value IN ('.\implode(',', $aEmailsQuoted).')';
$sSql .= ' AND prop_value IN ('.\implode(',', $aEmailsQuoted).')';
}
return !!$this->prepareAndExecute($sSql, array(
':id_user' => array($iUserID, \PDO::PARAM_INT),
':prop_type' => array(PropertyType::EMAIl, \PDO::PARAM_INT)
));
}
@ -825,7 +1179,7 @@ class PdoAddressBook
$sResult = '';
try
{
$this->Sync();
$this->SyncDatabase();
if (0 >= $this->getVersion($this->sDsnType.'-ab-version'))
{
$sResult = 'Unknown database error';
@ -857,14 +1211,13 @@ class PdoAddressBook
CREATE TABLE IF NOT EXISTS rainloop_ab_contacts (
id_contact bigint UNSIGNED NOT NULL AUTO_INCREMENT,
id_contact_str varchar(128) NOT NULL DEFAULT \'\',
id_user int UNSIGNED NOT NULL,
display_name varchar(255) NOT NULL DEFAULT '',
display_email varchar(255) NOT NULL DEFAULT '',
display varchar(255) NOT NULL DEFAULT '',
changed int UNSIGNED NOT NULL DEFAULT 0,
hash varchar(128) NOT NULL DEFAULT \'\',
id_contact bigint UNSIGNED NOT NULL AUTO_INCREMENT,
id_contact_str varchar(128) NOT NULL DEFAULT '',
id_user int UNSIGNED NOT NULL,
display varchar(255) NOT NULL DEFAULT '',
changed int UNSIGNED NOT NULL DEFAULT 0,
deleted tinyint UNSIGNED NOT NULL DEFAULT 0,
etag varchar(128) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL DEFAULT '',
PRIMARY KEY(id_contact),
INDEX id_user_rainloop_ab_contacts_index (id_user)
@ -873,20 +1226,42 @@ CREATE TABLE IF NOT EXISTS rainloop_ab_contacts (
CREATE TABLE IF NOT EXISTS rainloop_ab_properties (
id_prop bigint UNSIGNED NOT NULL AUTO_INCREMENT,
id_contact bigint UNSIGNED NOT NULL,
id_user int UNSIGNED NOT NULL,
prop_type tinyint UNSIGNED NOT NULL,
prop_type_custom varchar(50) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL DEFAULT '',
prop_value varchar(255) NOT NULL DEFAULT '',
prop_value_custom varchar(255) NOT NULL DEFAULT '',
prop_frec int UNSIGNED NOT NULL DEFAULT 0,
id_prop bigint UNSIGNED NOT NULL AUTO_INCREMENT,
id_contact bigint UNSIGNED NOT NULL,
id_user int UNSIGNED NOT NULL,
prop_type tinyint UNSIGNED NOT NULL,
prop_type_str varchar(255) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL DEFAULT '',
prop_value varchar(255) NOT NULL DEFAULT '',
prop_value_custom varchar(255) NOT NULL DEFAULT '',
prop_frec int UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY(id_prop),
INDEX id_user_rainloop_ab_properties_index (id_user),
INDEX id_user_id_contact_rainloop_ab_properties_index (id_user, id_contact)
)/*!40000 ENGINE=INNODB *//*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE IF NOT EXISTS rainloop_ab_tags (
id_tag int UNSIGNED NOT NULL AUTO_INCREMENT,
id_user int UNSIGNED NOT NULL,
tag_name varchar(255) NOT NULL,
PRIMARY KEY(id_tag),
INDEX id_user_rainloop_ab_tags_index (id_user),
INDEX id_user_name_rainloop_ab_tags_index (id_user, tag_name)
)/*!40000 ENGINE=INNODB *//*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE IF NOT EXISTS rainloop_ab_tags_contacts (
id_tag int UNSIGNED NOT NULL,
id_contact bigint UNSIGNED NOT NULL,
INDEX id_tag_rainloop_ab_tags_contacts_index (id_tag),
INDEX id_contact_rainloop_ab_tags_contacts_index (id_contact)
)/*!40000 ENGINE=INNODB */;
MYSQLINITIAL;
break;
@ -894,31 +1269,48 @@ MYSQLINITIAL;
$sInitial = <<<POSTGRESINITIAL
CREATE TABLE rainloop_ab_contacts (
id_contact bigserial PRIMARY KEY,
id_user integer NOT NULL,
display_name varchar(128) NOT NULL DEFAULT '',
display_email varchar(128) NOT NULL DEFAULT '',
display varchar(128) NOT NULL DEFAULT '',
changed integer NOT NULL default 0.
hash varchar(128) NOT NULL DEFAULT ''
id_contact bigserial PRIMARY KEY,
id_contact_str varchar(128) NOT NULL DEFAULT '',
id_user integer NOT NULL,
display varchar(255) NOT NULL DEFAULT '',
changed integer NOT NULL default 0,
deleted integer NOT NULL default 0,
etag varchar(128) NOT NULL DEFAULT ''
);
CREATE INDEX id_user_rainloop_ab_contacts_index ON rainloop_ab_contacts (id_user);
CREATE TABLE rainloop_ab_properties (
id_prop bigserial PRIMARY KEY,
id_contact integer NOT NULL,
id_user integer NOT NULL,
prop_type integer NOT NULL,
prop_type_custom varchar(50) NOT NULL DEFAULT '',
prop_value varchar(128) NOT NULL DEFAULT '',
prop_value_custom varchar(128) NOT NULL DEFAULT '',
prop_frec integer NOT NULL default 0
id_prop bigserial PRIMARY KEY,
id_contact integer NOT NULL,
id_user integer NOT NULL,
prop_type integer NOT NULL,
prop_type_str varchar(255) NOT NULL DEFAULT '',
prop_value text NOT NULL DEFAULT '',
prop_value_custom text NOT NULL DEFAULT '',
prop_frec integer NOT NULL default 0
);
CREATE INDEX id_user_rainloop_ab_properties_index ON rainloop_ab_properties (id_user);
CREATE INDEX id_user_id_contact_rainloop_ab_properties_index ON rainloop_ab_properties (id_user, id_contact);
CREATE TABLE rainloop_ab_tags (
id_tag serial PRIMARY KEY,
id_user integer NOT NULL,
tag_name varchar(255) NOT NULL
);
CREATE INDEX id_user_rainloop_ab_tags_index ON rainloop_ab_tags (id_user);
CREATE INDEX id_user_name_rainloop_ab_tags_index ON rainloop_ab_tags (id_user, tag_name);
CREATE TABLE rainloop_ab_tags_contacts (
id_tag integer NOT NULL,
id_contact integer NOT NULL
);
CREATE INDEX id_tag_rainloop_ab_tags_index ON rainloop_ab_tags_contacts (id_tag);
CREATE INDEX id_contact_rainloop_ab_tags_index ON rainloop_ab_tags_contacts (id_contact);
POSTGRESINITIAL;
break;
@ -926,31 +1318,48 @@ POSTGRESINITIAL;
$sInitial = <<<SQLITEINITIAL
CREATE TABLE rainloop_ab_contacts (
id_contact integer NOT NULL PRIMARY KEY,
id_user integer NOT NULL,
display_name text NOT NULL DEFAULT '',
display_email text NOT NULL DEFAULT '',
display text NOT NULL DEFAULT '',
changed integer NOT NULL DEFAULT 0,
hash text NOT NULL DEFAULT ''
id_contact integer NOT NULL PRIMARY KEY,
id_contact_str text NOT NULL DEFAULT '',
id_user integer NOT NULL,
display text NOT NULL DEFAULT '',
changed integer NOT NULL DEFAULT 0,
deleted integer NOT NULL DEFAULT 0,
etag text NOT NULL DEFAULT ''
);
CREATE INDEX id_user_rainloop_ab_contacts_index ON rainloop_ab_contacts (id_user);
CREATE TABLE rainloop_ab_properties (
id_prop integer NOT NULL PRIMARY KEY,
id_contact integer NOT NULL,
id_user integer NOT NULL,
prop_type integer NOT NULL,
prop_type_custom text NOT NULL DEFAULT '',
prop_value text NOT NULL DEFAULT '',
prop_value_custom text NOT NULL DEFAULT '',
prop_frec integer NOT NULL DEFAULT 0
id_prop integer NOT NULL PRIMARY KEY,
id_contact integer NOT NULL,
id_user integer NOT NULL,
prop_type integer NOT NULL,
prop_type_str text NOT NULL DEFAULT '',
prop_value text NOT NULL DEFAULT '',
prop_value_custom text NOT NULL DEFAULT '',
prop_frec integer NOT NULL DEFAULT 0
);
CREATE INDEX id_user_rainloop_ab_properties_index ON rainloop_ab_properties (id_user);
CREATE INDEX id_user_id_contact_rainloop_ab_properties_index ON rainloop_ab_properties (id_user, id_contact);
CREATE TABLE rainloop_ab_tags (
id_tag integer NOT NULL PRIMARY KEY,
id_user integer NOT NULL,
tag_name text NOT NULL
);
CREATE INDEX id_user_rainloop_ab_tags_index ON rainloop_ab_tags (id_user);
CREATE INDEX id_user_name_rainloop_ab_tags_index ON rainloop_ab_tags (id_user, tag_name);
CREATE TABLE rainloop_ab_tags_contacts (
id_tag integer NOT NULL,
id_contact integer NOT NULL
);
CREATE INDEX id_tag_rainloop_ab_tags_index ON rainloop_ab_tags_contacts (id_tag);
CREATE INDEX id_contact_rainloop_ab_tags_index ON rainloop_ab_tags_contacts (id_contact);
SQLITEINITIAL;
break;
}
@ -974,22 +1383,32 @@ SQLITEINITIAL;
/**
* @return bool
*/
public function Sync()
public function SyncDatabase()
{
static $mCache = null;
if (null !== $mCache)
{
return $mCache;
}
$mCache = false;
switch ($this->sDsnType)
{
case 'mysql':
return $this->dataBaseUpgrade($this->sDsnType.'-ab-version', array(
$mCache = $this->dataBaseUpgrade($this->sDsnType.'-ab-version', array(
1 => $this->getInitialTablesArray($this->sDsnType)
));
break;
case 'pgsql':
return $this->dataBaseUpgrade($this->sDsnType.'-ab-version', array(
$mCache = $this->dataBaseUpgrade($this->sDsnType.'-ab-version', array(
1 => $this->getInitialTablesArray($this->sDsnType)
));
break;
case 'sqlite':
return $this->dataBaseUpgrade($this->sDsnType.'-ab-version', array(
$mCache = $this->dataBaseUpgrade($this->sDsnType.'-ab-version', array(
1 => $this->getInitialTablesArray($this->sDsnType)
));
break;
}
return false;
@ -1004,14 +1423,11 @@ SQLITEINITIAL;
{
$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.')';
$sSql = 'SELECT prop_value, prop_frec FROM rainloop_ab_properties WHERE id_user = :id_user AND id_contact = :id_contact AND prop_type = :type';
$aParams = array(
':id_user' => array($iUserID, \PDO::PARAM_INT),
':id_contact' => array($iIdContact, \PDO::PARAM_INT)
':id_contact' => array($iIdContact, \PDO::PARAM_INT),
':type' => array(PropertyType::EMAIl, \PDO::PARAM_INT)
);
$oStmt = $this->prepareAndExecute($sSql, $aParams);

View file

@ -18,16 +18,33 @@ class Storage extends \RainLoop\Providers\AbstractProvider
}
/**
* @param \RainLoop\Account|null $oAccount
* @param \RainLoop\Account|string|null $oAccount
* @param int $iStorageType
* @param string $iStorageType
*
* @return bool
*/
public function verifyAccount($oAccount, $iStorageType)
{
if (\RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY !== $iStorageType &&
!($oAccount instanceof \RainLoop\Account || \is_string($oAccount)))
{
return false;
}
return true;
}
/**
* @param \RainLoop\Account|string|null $oAccount
* @param int $iStorageType
* @param string $sKey
* @param string $sValue
*
* @return bool
*/
public function Put($oAccount, $iStorageType, $sKey, $sValue)
{
if (\RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY !== $iStorageType && !($oAccount instanceof \RainLoop\Account))
if (!$this->verifyAccount($oAccount, $iStorageType))
{
return false;
}
@ -36,7 +53,7 @@ class Storage extends \RainLoop\Providers\AbstractProvider
}
/**
* @param \RainLoop\Account|null $oAccount
* @param \RainLoop\Account|string|null $oAccount
* @param int $iStorageType
* @param string $sKey
* @param mixed $mDefault = false
@ -45,7 +62,7 @@ class Storage extends \RainLoop\Providers\AbstractProvider
*/
public function Get($oAccount, $iStorageType, $sKey, $mDefault = false)
{
if (\RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY !== $iStorageType && !($oAccount instanceof \RainLoop\Account))
if (!$this->verifyAccount($oAccount, $iStorageType))
{
return $mDefault;
}
@ -54,7 +71,7 @@ class Storage extends \RainLoop\Providers\AbstractProvider
}
/**
* @param \RainLoop\Account|null $oAccount
* @param \RainLoop\Account|string|null $oAccount
* @param int $iStorageType
* @param string $sKey
*
@ -62,7 +79,7 @@ class Storage extends \RainLoop\Providers\AbstractProvider
*/
public function Clear($oAccount, $iStorageType, $sKey)
{
if (\RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY !== $iStorageType && !($oAccount instanceof \RainLoop\Account))
if (!$this->verifyAccount($oAccount, $iStorageType))
{
return false;
}

View file

@ -20,7 +20,7 @@ class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface
}
/**
* @param \RainLoop\Account|null $oAccount
* @param \RainLoop\Account|string|null $oAccount
* @param int $iStorageType
* @param string $sKey
* @param string $sValue
@ -34,7 +34,7 @@ class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface
}
/**
* @param \RainLoop\Account|null $oAccount
* @param \RainLoop\Account|string|null $oAccount
* @param int $iStorageType
* @param string $sKey
* @param mixed $mDefault = false
@ -54,7 +54,7 @@ class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface
}
/**
* @param \RainLoop\Account|null $oAccount
* @param \RainLoop\Account|string|null $oAccount
* @param int $iStorageType
* @param string $sKey
*
@ -73,22 +73,27 @@ class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface
}
/**
* @param \RainLoop\Account|null $oAccount
* @param \RainLoop\Account|string|null $mAccount
* @param int $iStorageType
* @param string $sKey
* @param bool $bMkDir = false
*
* @return string
*/
private function generateFileName($oAccount, $iStorageType, $sKey, $bMkDir = false)
private function generateFileName($mAccount, $iStorageType, $sKey, $bMkDir = false)
{
if (!$oAccount)
if (null === $mAccount)
{
$iStorageType = \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY;
}
$sEmail = $oAccount ? \preg_replace('/[^a-z0-9\-\.@]+/', '_',
('' === $oAccount->ParentEmail() ? '' : $oAccount->ParentEmail().'/').$oAccount->Email()) : '';
$sEmail = $mAccount instanceof \RainLoop\Account ? \preg_replace('/[^a-z0-9\-\.@]+/', '_',
('' === $mAccount->ParentEmail() ? '' : $mAccount->ParentEmail().'/').$mAccount->Email()) : '';
if (\is_string($mAccount) && empty($sEmail))
{
$sEmail = \preg_replace('/[^a-z0-9\-\.@]+/', '_', $mAccount);
}
$sTypePath = $sKeyPath = '';
switch ($iStorageType)

View file

@ -1,46 +0,0 @@
<?php
namespace RainLoop\SabreDAV;
class AuthBasic extends \Sabre\DAV\Auth\Backend\AbstractBasic
{
/**
* @var \RainLoop\Providers\PersonalAddressBook
*/
private $oPersonalAddressBook;
/**
* @param \RainLoop\Providers\PersonalAddressBook $oPersonalAddressBook
*/
public function __construct($oPersonalAddressBook)
{
$this->oPersonalAddressBook = $oPersonalAddressBook;
}
/**
* @param string $sUserName
* @param string $sPassword
*
* @return bool
*/
protected function validateUserPass($sUserName, $sPassword)
{
$sHash = '';
try
{
$sHash = $this->oPersonalAddressBook->GetUserHashByEmail($sUserName, true);
}
catch (Exception $oException) {}
// var_dump($sHash);
// exit();
if (!empty($sHash) && $sPassword === $sHash)
{
$this->currentUser = $sUserName;
return true;
}
return false;
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace RainLoop\SabreDAV;
class AuthDigest extends \Sabre\DAV\Auth\Backend\AbstractDigest
{
/**
* @var \RainLoop\Providers\PersonalAddressBook
*/
private $oPersonalAddressBook;
/**
* @param \RainLoop\Providers\PersonalAddressBook $oPersonalAddressBook
*/
public function __construct($oPersonalAddressBook)
{
$this->oPersonalAddressBook = $oPersonalAddressBook;
}
/**
* @param string $sRealm
* @param string $sUserName
*
* @return string|null
*/
public function getDigestHash($sRealm, $sUserName)
{
$sHash = '';
try
{
$sHash = $this->oPersonalAddressBook->GetUserHashByEmail($sUserName, true);
}
catch (Exception $oException) {}
// var_dump($sHash);
// exit();
if (!empty($sHash))
{
$this->currentUser = $sUserName;
return \md5($sUserName.':'.$sRealm.':'.$sHash);
}
return null;
}
}

View file

@ -1,421 +0,0 @@
<?php
namespace RainLoop\SabreDAV;
class CardDAV implements \Sabre\CardDAV\Backend\BackendInterface
{
/**
* @var \RainLoop\Providers\PersonalAddressBook
*/
private $oPersonalAddressBook;
/**
* @var \RainLoop\SabreDAV\AuthBasic
*/
private $oAuthBackend;
/**
* @param \RainLoop\Providers\PersonalAddressBook $oPersonalAddressBook
* @param \RainLoop\SabreDAV\AuthBasic $oAuthBackend
*/
public function __construct($oPersonalAddressBook, &$oAuthBackend)
{
$this->oPersonalAddressBook = $oPersonalAddressBook;
$this->oPersonalAddressBook->ConsiderShare(false);
$this->oAuthBackend = $oAuthBackend;
}
/**
* @param mixed $mData
*/
private function writeLog($mData)
{
$this->oPersonalAddressBook->Logger()->WriteMixed($mData);
}
/**
* @param string $sPrincipalUri
*
* @return string
*/
private function getEmailFromPrincipalUri($sPrincipalUri)
{
$sEmail = '';
$aMatch = array();
if (\preg_match('/\/?principals\/([^@\/]+@[^@\/]+)/i', $sPrincipalUri, $aMatch) && !empty($aMatch[1]))
{
$sEmail = \trim($aMatch[1]);
}
return $sEmail;
}
/**
* @param string $sPrincipalUri = ''
* @param string $mAddressBookID = ''
*
* @return string
*/
private function getAuthEmail($sPrincipalUri = '', $mAddressBookID = '')
{
$sGetCurrentUser = \trim($this->oAuthBackend->getCurrentUser());
if (0 < \strlen($sPrincipalUri) && 0 < \strlen($sGetCurrentUser) &&
$sGetCurrentUser !== $this->getEmailFromPrincipalUri($sPrincipalUri))
{
$sGetCurrentUser = '';
}
if (0 < \strlen((string) $mAddressBookID) && 0 < \strlen($sGetCurrentUser) &&
(string) $mAddressBookID !== (string) $this->oPersonalAddressBook->GetUserUidByEmail($sGetCurrentUser))
{
$sGetCurrentUser = '';
}
return $sGetCurrentUser;
}
/**
* Returns the list of addressbooks for a specific user.
*
* @param string $sPrincipalUri
* @return array
*/
public function getAddressBooksForUser($sPrincipalUri)
{
$this->writeLog('::getAddressBooksForUser('.$sPrincipalUri.')');
$aAddressBooks = array();
$sEmail = $this->getAuthEmail($sPrincipalUri);
if (0 < strlen($sEmail))
{
$mAddressBookID = $this->oPersonalAddressBook->GetUserUidByEmail($sEmail);
if (!empty($mAddressBookID))
{
$aAddressBooks[] = array(
'id' => $mAddressBookID,
'uri' => 'default',
'principaluri' => $sPrincipalUri,
'{DAV:}displayname' => 'Personal Address Book',
'{'.\Sabre\CardDAV\Plugin::NS_CARDDAV.'}addressbook-description' => 'Personal Address Book',
'{http://calendarserver.org/ns/}getctag' => $this->oPersonalAddressBook->GetCtagByEmail($sEmail),
'{'.\Sabre\CardDAV\Plugin::NS_CARDDAV.'}supported-address-data' => new \Sabre\CardDAV\Property\SupportedAddressData()
);
}
}
return $aAddressBooks;
}
/**
* Updates an addressbook's properties
*
* See Sabre\DAV\IProperties for a description of the mutations array, as
* well as the return value.
*
* @param mixed $mAddressBookID
* @param array $aMutations
* @see Sabre\DAV\IProperties::updateProperties
* @return bool|array
*/
public function updateAddressBook($mAddressBookID, array $aMutations)
{
$this->writeLog('::updateAddressBook('.$mAddressBookID.', array $aMutations['.\count($aMutations).'])');
return false;
}
/**
* Creates a new address book
*
* @param string $sPrincipalUri
* @param string $sUrl Just the 'basename' of the url.
* @param array $aProperties
*
* @return void
*/
public function createAddressBook($sPrincipalUri, $sUrl, array $aProperties)
{
$this->writeLog('::createAddressBook('.$sPrincipalUri.', '.$sUrl.', array $aProperties['.\count($aProperties).'])');
}
/**
* Deletes an entire addressbook and all its contents
*
* @param mixed $mAddressBookID
*
* @return void
*/
public function deleteAddressBook($mAddressBookID)
{
$this->writeLog('::deleteAddressBook('.$mAddressBookID.')');
}
/**
* Returns all cards for a specific addressbook id.
*
* This method should return the following properties for each card:
* * carddata - raw vcard data
* * uri - Some unique url
* * lastmodified - A unix timestamp
*
* It's recommended to also return the following properties:
* * etag - A unique etag. This must change every time the card changes.
* * size - The size of the card in bytes.
*
* If these last two properties are provided, less time will be spent
* calculating them. If they are specified, you can also ommit carddata.
* This may speed up certain requests, especially with large cards.
*
* @param mixed $mAddressBookID
*
* @return array
*/
public function getCards($mAddressBookID)
{
$this->writeLog('::getCards('.$mAddressBookID.')');
$aResult = array();
if (!empty($mAddressBookID))
{
$sEmail = $this->getAuthEmail('', $mAddressBookID);
if (!empty($sEmail))
{
$aList = $this->oPersonalAddressBook->GetContacts($sEmail, 0, 5000);
foreach ($aList as /* @var $oItem \RainLoop\Providers\PersonalAddressBook\Classes\Contact */ $oItem)
{
if (!$oItem->ReadOnly)
{
$aResult[] = array(
'uri' => $oItem->VCardUri(),
'lastmodified' => $oItem->Changed,
'etag' => $oItem->CardDavHash,
'size' => $oItem->CardDavSize
);
}
}
}
}
return $aResult;
}
/**
* Returns a specfic card.
*
* The same set of properties must be returned as with getCards. The only
* exception is that 'carddata' is absolutely required.
*
* @param mixed $mAddressBookID
* @param string $sCardUri
* @return array
*/
public function getCard($mAddressBookID, $sCardUri)
{
$this->writeLog('::getCard('.$mAddressBookID.', '.$sCardUri.')');
$mResult = false;
$oContact = null;
if (!empty($mAddressBookID) && !empty($sCardUri) && '.vcf' === \substr($sCardUri, -4))
{
$sEmail = $this->getAuthEmail('', $mAddressBookID);
if (!empty($sEmail))
{
$oContact = $this->oPersonalAddressBook->GetContactByID($sEmail, \substr($sCardUri, 0, -4), true);
}
}
if ($oContact)
{
$mResult = array(
'uri' => $oContact->VCardUri(),
'lastmodified' => $oContact->Changed,
'etag' => $oContact->CardDavHash,
'size' => $oContact->CardDavSize,
'carddata' => $oContact->CardDavData
);
}
return $mResult;
}
/**
* Creates a new card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag is for the
* newly created resource, and must be enclosed with double quotes (that
* is, the string itself must contain the double quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $mAddressBookID
* @param string $sCardUri
* @param string $sCardData
*
* @return string|null
*/
public function createCard($mAddressBookID, $sCardUri, $sCardData)
{
$this->writeLog('::createCard('.$mAddressBookID.', '.$sCardUri.', $sCardData)');
$this->writeLog($sCardData);
$oVCard = null;
if (!empty($mAddressBookID) && !empty($sCardUri) && '.vcf' === \substr($sCardUri, -4) && 0 < \strlen($sCardData))
{
try
{
$oVCard = \Sabre\VObject\Reader::read($sCardData);
}
catch (\Exception $oException)
{
$this->writeLog($oException);
}
if ($oVCard)
{
$sEmail = $this->getAuthEmail('', $mAddressBookID);
if (!empty($sEmail))
{
if (empty($oVCard->UID))
{
$oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID();
$sCardData = $oVCard->serialize();
}
$oContact = new \RainLoop\Providers\PersonalAddressBook\Classes\Contact();
$oContact->ParseVCard($oVCard, $sCardData);
$this->oPersonalAddressBook->ContactSave($sEmail, $oContact);
}
}
}
return null;
}
/**
* Updates a card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag should
* match that of the updated resource, and must be enclosed with double
* quotes (that is: the string itself must contain the actual quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $mAddressBookID
* @param string $sCardUri
* @param string $sCardData
*
* @return string|null
*/
public function updateCard($mAddressBookID, $sCardUri, $sCardData)
{
$this->writeLog('::updateCard('.$mAddressBookID.', '.$sCardUri.', $sCardData)');
$this->writeLog($sCardData);
if (!empty($mAddressBookID) && !empty($sCardUri) && '.vcf' === \substr($sCardUri, -4) && 0 < \strlen($sCardData))
{
try
{
$oVCard = \Sabre\VObject\Reader::read($sCardData);
}
catch (\Exception $oException)
{
$this->writeLog($oException);
}
if ($oVCard)
{
$sEmail = $this->getAuthEmail('', $mAddressBookID);
if (!empty($sEmail))
{
$iRev = 0;
$aMatch = array();
if (!empty($oVCard->REV) && \preg_match('/(20[0-9][0-9])([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])Z/i', $oVCard->REV, $aMatch))
{// 1Y 2m 3d 4H 5i 6s
$iRev = \gmmktime($aMatch[4], $aMatch[5], $aMatch[6], $aMatch[2], $aMatch[3], $aMatch[1]);
}
$oContact = $this->oPersonalAddressBook->GetContactByID($sEmail, \substr($sCardUri, 0, -4), true);
if ($oContact && (0 === $iRev || $oContact->Changed < $iRev))
{
if (empty($oVCard->UID))
{
$oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID();
$sCardData = $oVCard->serialize();
}
$oContact->ParseVCard($oVCard, $sCardData);
if ($this->oPersonalAddressBook->ContactSave($sEmail, $oContact) && !empty($oContact->CardDavHash))
{
return '"'.$oContact->CardDavHash.'"';
}
}
else
{
if ($oContact && $oContact->Changed < $iRev)
{
$this->writeLog('Obsolete revision: ['.(empty($oVCard->REV) ? '' : $oVCard->REV).', '.$oContact->Changed.', '.$iRev.']');
}
}
}
}
}
return null;
}
/**
* Deletes a card
*
* @param mixed $mAddressBookID
* @param string $sCardUri
*
* @return bool
*/
public function deleteCard($mAddressBookID, $sCardUri)
{
$this->writeLog('::deleteCard('.$mAddressBookID.', '.$sCardUri.')');
$bResult = false;
$oContact = null;
if (!empty($mAddressBookID) && !empty($sCardUri) && '.vcf' === \substr($sCardUri, -4))
{
$sEmail = $this->getAuthEmail('', $mAddressBookID);
if (!empty($sEmail))
{
$oContact = $this->oPersonalAddressBook->GetContactByID($sEmail, \substr($sCardUri, 0, -4), true);
}
}
if ($oContact)
{
$bResult = $this->oPersonalAddressBook->DeleteContacts($sEmail, array($oContact->IdContact));
}
return $bResult;
}
}

View file

@ -1,65 +0,0 @@
<?php
namespace RainLoop\SabreDAV;
class Logger extends \Sabre\DAV\ServerPlugin
{
/**
* @var \MailSo\Log\Logger
*/
private $oLogger;
/**
* @param \MailSo\Log\Logger $oLogger
*/
public function __construct($oLogger)
{
$this->oLogger = null;
if ($oLogger instanceof \MailSo\Log\Logger)
{
$this->oLogger = $oLogger;
}
}
public function initialize(\Sabre\DAV\Server $server)
{
$this->server = $server;
$this->server->subscribeEvent('beforeMethod', array($this, 'beforeMethod'), 30);
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'logger';
}
/**
* This method is called before any HTTP method, but after authentication.
*
* @param string $sMethod
* @param string $sPath
* @throws \Sabre\DAV\Exception\NotAuthenticated
* @return bool
*/
public function beforeMethod($sMethod, $sPath)
{
if ($this->oLogger)
{
if (true)
{
$body = $this->server->httpRequest->getBody(true);
$this->server->httpRequest->setBody($body);
$this->oLogger->Write($body, \MailSo\Log\Enumerations\Type::INFO, 'DAV');
}
}
return true;
}
}

View file

@ -1,205 +0,0 @@
<?php
namespace RainLoop\SabreDAV;
class Principal extends \Sabre\DAVACL\PrincipalBackend\AbstractBackend
{
/**
* @var \RainLoop\Providers\PersonalAddressBook
*/
private $oPersonalAddressBook;
/**
* @var \RainLoop\SabreDAV\AuthBasic
*/
private $oAuthBackend;
/**
* @param \RainLoop\Providers\PersonalAddressBook $oPersonalAddressBook
* @param \RainLoop\SabreDAV\AuthBasic $oAuthBackend
*/
public function __construct($oPersonalAddressBook, &$oAuthBackend)
{
$this->oPersonalAddressBook = $oPersonalAddressBook;
$this->oAuthBackend = $oAuthBackend;
}
/**
* Returns a list of principals based on a prefix.
*
* This prefix will often contain something like 'principals'. You are only
* expected to return principals that are in this base path.
*
* You are expected to return at least a 'uri' for every user, you can
* return any additional properties if you wish so. Common properties are:
* {DAV:}displayname
* {http://sabredav.org/ns}email-address - This is a custom SabreDAV
* field that's actually injected in a number of other properties. If
* you have an email address, use this property.
*
* @param string $prefixPath
* @return array
*/
function getPrincipalsByPrefix($prefixPath)
{
$aResult = array();
if (\preg_match('/^principals/', $prefixPath))
{
$sGetCurrentUser = $this->oAuthBackend->getCurrentUser();
if (!empty($sGetCurrentUser))
{
$aResult[] = array(
'uri' => 'principals/'.$sGetCurrentUser,
'{DAV:}displayname' => $sGetCurrentUser,
'{http://sabredav.org/ns}email-address' => $sGetCurrentUser
);
}
}
return $aResult;
}
/**
* Returns a specific principal, specified by it's path.
* The returned structure should be the exact same as from
* getPrincipalsByPrefix.
*
* @param string $sPath
* @return array
*/
function getPrincipalByPath($sPath)
{
$sGetCurrentUser = $this->oAuthBackend->getCurrentUser();
if ('principals/'.$sGetCurrentUser === $sPath)
{
return array(
'uri' => 'principals/'.$sGetCurrentUser,
'{DAV:}displayname' => $sGetCurrentUser,
'{http://sabredav.org/ns}email-address' => $sGetCurrentUser
);
}
return array();
}
/**
* Updates one ore more webdav properties on a principal.
*
* The list of mutations is supplied as an array. Each key in the array is
* a propertyname, such as {DAV:}displayname.
*
* Each value is the actual value to be updated. If a value is null, it
* must be deleted.
*
* This method should be atomic. It must either completely succeed, or
* completely fail. Success and failure can simply be returned as 'true' or
* 'false'.
*
* It is also possible to return detailed failure information. In that case
* an array such as this should be returned:
*
* array(
* 200 => array(
* '{DAV:}prop1' => null,
* ),
* 201 => array(
* '{DAV:}prop2' => null,
* ),
* 403 => array(
* '{DAV:}prop3' => null,
* ),
* 424 => array(
* '{DAV:}prop4' => null,
* ),
* );
*
* In this previous example prop1 was successfully updated or deleted, and
* prop2 was succesfully created.
*
* prop3 failed to update due to '403 Forbidden' and because of this prop4
* also could not be updated with '424 Failed dependency'.
*
* This last example was actually incorrect. While 200 and 201 could appear
* in 1 response, if there's any error (403) the other properties should
* always fail with 423 (failed dependency).
*
* But anyway, if you don't want to scratch your head over this, just
* return true or false.
*
* @param string $path
* @param array $mutations
* @return array|bool
*/
function updatePrincipal($path, $mutations)
{
return true;
}
/**
* This method is used to search for principals matching a set of
* properties.
*
* This search is specifically used by RFC3744's principal-property-search
* REPORT. You should at least allow searching on
* http://sabredav.org/ns}email-address.
*
* The actual search should be a unicode-non-case-sensitive search. The
* keys in searchProperties are the WebDAV property names, while the values
* are the property values to search on.
*
* If multiple properties are being searched on, the search should be
* AND'ed.
*
* This method should simply return an array with full principal uri's.
*
* If somebody attempted to search on a property the backend does not
* support, you should simply return 0 results.
*
* You can also just return 0 results if you choose to not support
* searching at all, but keep in mind that this may stop certain features
* from working.
*
* @param string $prefixPath
* @param array $searchProperties
* @return array
*/
function searchPrincipals($prefixPath, array $searchProperties)
{
return array();
}
/**
* Returns the list of members for a group-principal
*
* @param string $principal
* @return array
*/
function getGroupMemberSet($principal)
{
return array();
}
/**
* Returns the list of groups a principal is a member of
*
* @param string $principal
* @return array
*/
function getGroupMembership($principal)
{
return array();
}
/**
* Updates the list of group members for a group principal.
*
* The principals should be passed as a list of uri's.
*
* @param string $principal
* @param array $members
* @return void
*/
function setGroupMemberSet($principal, array $members)
{
}
}

View file

@ -108,29 +108,14 @@ class Service
$bCached = false;
$sResult = '';
$sPathInfo = \trim(\trim($this->oHttp->GetServer('PATH_INFO', '')), ' /');
if (!empty($sPathInfo))
{
if ('dav' !== \substr($sPathInfo, 0, 3))
{
$sPathInfo = '';
}
}
if (empty($sPathInfo))
$sQuery = \trim(\trim($this->oHttp->GetServer('QUERY_STRING', '')), ' /');
$iPos = \strpos($sQuery, '&');
if (0 < $iPos)
{
$sQuery = \trim(\trim($this->oHttp->GetServer('QUERY_STRING', '')), ' /');
$iPos = \strpos($sQuery, '&');
if (0 < $iPos)
{
$sQuery = \substr($sQuery, 0, $iPos);
}
$sQuery = \substr($sQuery, 0, $iPos);
}
else
{
$sQuery = $sPathInfo;
}
$this->oActions->Plugins()->RunHook('filter.http-query', array(&$sQuery));
$aPaths = \explode('/', $sQuery);
$this->oActions->Plugins()->RunHook('filter.http-paths', array(&$aPaths));

View file

@ -719,85 +719,6 @@ class ServiceActions
));
}
public function HostDav()
{
try
{
\set_error_handler(function ($errno, $errstr, $errfile, $errline ) {
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
});
$oPersonalAddressBookProvider = $this->oActions->PersonalAddressBookProvider();
$oAuthBackend = null;
if ($this->Config()->Get('labs', 'sync_dav_digest_auth', false))
{
$oAuthBackend = new \RainLoop\SabreDAV\AuthDigest($oPersonalAddressBookProvider);
}
else
{
$oAuthBackend = new \RainLoop\SabreDAV\AuthBasic($oPersonalAddressBookProvider);
}
$oCarddavBackend = new \RainLoop\SabreDAV\CardDAV($oPersonalAddressBookProvider, $oAuthBackend);
$oPrincipalBackend = new \RainLoop\SabreDAV\Principal($oPersonalAddressBookProvider, $oAuthBackend);
$oPrincipalCollection = new \Sabre\DAVACL\PrincipalCollection($oPrincipalBackend);
$oPrincipalCollection->disableListing = true;
$oAddressBookRoot = new \Sabre\CardDAV\AddressBookRoot($oPrincipalBackend, $oCarddavBackend);
$aTree = array($oPrincipalCollection, $oAddressBookRoot);
$this->Plugins()->RunHook('filter.sabre-dav-tree', array(&$aTree));
$oServer = new \Sabre\DAV\Server($aTree);
$sBaseUri = '/';
if (false !== \strpos($this->oHttp->GetUrl(), '/index.php/dav/'))
{
$aPath = \trim($this->oHttp->GetPath(), '/\\ ');
$sBaseUri = (0 < \strlen($aPath) ? '/'.$aPath : '').'/index.php/dav/';
}
$this->Plugins()->RunHook('filter.sabre-dav-base-url', array(&$sBaseUri));
$oServer->setBaseUri($sBaseUri);
// Plugins
$oServer->addPlugin(new \Sabre\DAV\Auth\Plugin($oAuthBackend, 'RainLoop'));
$oServer->addPlugin(new \Sabre\CardDAV\Plugin());
$oServer->addPlugin(new \Sabre\DAVACL\Plugin());
$oServer->addPlugin(new \Sabre\CardDAV\VCFExportPlugin());
$oServer->addPlugin(new \RainLoop\SabreDAV\Logger($this->Logger()));
if ($this->Config()->Get('labs', 'sync_use_dav_browser', false))
{
$oServer->addPlugin(new \Sabre\DAV\Browser\Plugin());
}
$this->Plugins()->RunHook('filter.sabre-dav-before-exec', array(&$oServer));
$oServer->exec();
}
catch (\Exception $oException)
{
$this->Logger()->WriteException($oException);
}
}
/**
* @return string
*/
public function ServiceDav()
{
if ($this->oActions->Config()->Get('contacts', 'allow_sync', false))
{
$this->HostDav();
}
return '';
}
/**
* @return string
*/

View file

@ -315,6 +315,7 @@ class Client {
// Return headers as part of the response
CURLOPT_HEADER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_USERAGENT => 'RainLoop DAV Client',
// Automatically follow redirects
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,