Contacts tags support (unstable - step 1).

+ Small fixes
This commit is contained in:
RainLoop Team 2014-05-13 12:29:36 +04:00
parent 74fa12c000
commit 0ca00dec40
34 changed files with 1790 additions and 801 deletions

File diff suppressed because it is too large Load diff

View file

@ -23,7 +23,7 @@ abstract class PdoAbstract
* @var string
*/
protected $sDbType;
/**
* @return bool
*/
@ -67,7 +67,7 @@ abstract class PdoAbstract
{
return $this->oPDO;
}
if (!\class_exists('PDO'))
{
throw new \Exception('Class PDO does not exist');
@ -100,7 +100,7 @@ abstract class PdoAbstract
{
throw $oException;
}
if ($oPdo)
{
$this->oPDO = $oPdo;
@ -127,7 +127,7 @@ abstract class PdoAbstract
{
$mName = \strtolower($sTabelName.'_'.$sColumnName.'_seq');
}
return null === $mName ? $this->getPDO()->lastInsertId() : $this->getPDO()->lastInsertId($mName);
}
@ -222,15 +222,15 @@ abstract class PdoAbstract
$mResult->closeCursor();
}
}
/**
* @param string $sSql
* @param mixed $mData
*/
protected function writeLog($sSql)
protected function writeLog($mData)
{
if ($this->oLogger)
{
$this->oLogger->WriteMixed($sSql, \MailSo\Log\Enumerations\Type::INFO, 'SQL');
$this->oLogger->WriteMixed($mData, \MailSo\Log\Enumerations\Type::INFO, 'SQL');
}
}
@ -324,7 +324,7 @@ abstract class PdoAbstract
}
$this->writeLog($sQuery);
$oStmt = $oPdo->prepare($sQuery);
if ($oStmt->execute(array($sName)))
{
@ -355,7 +355,7 @@ abstract class PdoAbstract
/**
* @param string $sName
* @param int $iVersion
*
*
* @return bool
*/
protected function setVersion($sName, $iVersion)
@ -371,7 +371,7 @@ abstract class PdoAbstract
$sQuery = 'DELETE FROM rainloop_system WHERE sys_name = ? AND value_int <= ?;';
$this->writeLog($sQuery);
$oStmt = $oPdo->prepare($sQuery);
$bResult = !!$oStmt->execute(array($sName.'_version', $iVersion));
if ($bResult)
@ -507,7 +507,7 @@ rl_email text NOT NULL DEFAULT \'\'
{
$oPdo->rollBack();
}
throw $oException;
}
}
@ -550,7 +550,7 @@ rl_email text NOT NULL DEFAULT \'\'
{
$oPdo = false;
$bResult = false;
foreach ($aData as $iVersion => $aQuery)
{
if (0 === \count($aQuery))
@ -580,7 +580,7 @@ rl_email text NOT NULL DEFAULT \'\'
if (false === $bExec)
{
$this->writeLog('Result: false');
$bResult = false;
break;
}
@ -605,7 +605,7 @@ rl_email text NOT NULL DEFAULT \'\'
{
$oPdo->rollBack();
}
throw $oException;
}

View file

@ -85,7 +85,7 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
{
return $this->IsActive() ? $this->oDriver->Export($sEmail, $sType) : false;
}
/**
* @param string $sEmail
* @param \RainLoop\Providers\AddressBook\Classes\Contact $oContact
@ -123,6 +123,16 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
$iOffset, $iLimit, $sSearch, $iResultCount) : array();
}
/**
* @param string $sEmail
*
* @return array
*/
public function GetContactTags($sEmail)
{
return $this->IsActive() ? $this->oDriver->GetContactTags($sEmail) : array();
}
/**
* @param string $sEmail
* @param string $mID
@ -238,7 +248,7 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
$aMap = \array_change_key_case($aMap, CASE_LOWER);
}
$sCsvNameLower = \MailSo\Base\Utils::IsAscii($sCsvName) ? \preg_replace('/[\s\-]+/', '', \strtolower($sCsvName)) : '';
return !empty($sCsvNameLower) && isset($aMap[$sCsvNameLower]) ? $aMap[$sCsvNameLower] : PropertyType::UNKNOWN;
}
@ -258,7 +268,7 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
foreach ($aCsvData as $aItem)
{
\MailSo\Base\Utils::ResetTimeLimit();
foreach ($aItem as $sItemName => $sItemValue)
{
$sItemName = \trim($sItemName);
@ -294,10 +304,10 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
unset($oContact);
}
return $iCount;
}
/**
* @param string $sEmail
* @param string $sVcfData
@ -336,14 +346,14 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
if ($oVCard instanceof \Sabre\VObject\Component\VCard)
{
\MailSo\Base\Utils::ResetTimeLimit();
if (empty($oVCard->UID))
{
$oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID();
}
$oContact->PopulateByVCard($oVCard->serialize());
if (0 < \count($oContact->Properties))
{
if ($this->ContactSave($sEmail, $oContact))

View file

@ -37,6 +37,11 @@ class Contact
/**
* @var array
*/
public $Tags;
/**
* @var bool
*/
public $ReadOnly;
/**
@ -58,15 +63,15 @@ class Contact
{
$this->IdContact = '';
$this->IdContactStr = '';
$this->IdUser = 0;
$this->Display = '';
$this->Changed = \time();
$this->Properties = array();
$this->Tags = array();
$this->ReadOnly = false;
$this->IdPropertyFromSearch = 0;
$this->Etag = '';
}
public function UpdateDependentValues()
{
$sLastName = '';
@ -75,7 +80,7 @@ class Contact
$sOther = '';
$oFullNameProperty = null;
foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty)
{
if ($oProperty)
@ -133,7 +138,7 @@ class Contact
}
$this->Display = \trim($sDisplay);
if ($oFullNameProperty)
{
$oFullNameProperty->Value = $this->Display;
@ -200,7 +205,7 @@ class Contact
{
$oVCard = new \Sabre\VObject\Component\VCard();
}
$oVCard->VERSION = '3.0';
$oVCard->PRODID = '-//RainLoop//'.APP_VERSION.'//EN';
@ -482,7 +487,7 @@ class Contact
public function PopulateByVCard($sVCard, $sEtag = '')
{
$this->Properties = array();
if (!empty($sEtag))
{
$this->Etag = $sEtag;

View file

@ -0,0 +1,33 @@
<?php
namespace RainLoop\Providers\AddressBook\Classes;
class Tag
{
/**
* @var string
*/
public $IdContactTag;
/**
* @var string
*/
public $Name;
/**
* @var bool
*/
public $ReadOnly;
public function __construct()
{
$this->Clear();
}
public function Clear()
{
$this->IdContactTag = '';
$this->Name = '';
$this->ReadOnly = false;
}
}

View file

@ -17,7 +17,7 @@ class PdoAddressBook
* @var string
*/
private $sDsnType;
/**
* @var string
*/
@ -73,7 +73,7 @@ class PdoAddressBook
)
);
}
private function prepearDatabaseSyncData($iUserID)
{
$aResult = array();
@ -122,7 +122,7 @@ class PdoAddressBook
{
$this->oLogger->WriteException($oException);
}
if (\is_array($aResponse))
{
$mResult = array();
@ -159,7 +159,7 @@ class PdoAddressBook
private function davClientRequest($oClient, $sCmd, $sUrl, $mData = null)
{
\MailSo\Base\Utils::ResetTimeLimit();
$this->oLogger->Write($sCmd.' '.$sUrl.('PUT' === $sCmd && null !== $mData ? ' ('.\strlen($mData).')' : ''),
\MailSo\Log\Enumerations\Type::INFO, 'DAV');
@ -183,7 +183,7 @@ class PdoAddressBook
{
$this->oLogger->WriteException($oException);
}
return $oResponse;
}
@ -199,13 +199,13 @@ class PdoAddressBook
public function Sync($sEmail, $sUrl, $sUser, $sPassword, $sProxy = '')
{
$this->SyncDatabase();
$iUserID = $this->getUserId($sEmail);
if (0 >= $iUserID)
{
return false;
}
$aUrl = \parse_url($sUrl);
if (!\is_array($aUrl))
{
@ -233,6 +233,9 @@ class PdoAddressBook
$oClient = new \Sabre\DAV\Client($aSettings);
$oClient->setVerifyPeer(false);
$this->oLogger->AddSecret($sPassword);
$this->oLogger->Write('User: '.$aSettings['userName'].', Url: '.$sUrl, \MailSo\Log\Enumerations\Type::INFO, 'DAV');
$aRemoteSyncData = $this->prepearRemoteSyncData($oClient, $sPath);
if (false === $aRemoteSyncData)
{
@ -276,7 +279,7 @@ class PdoAddressBook
//+++new or newer (from db)
foreach ($aDatabaseSyncData as $sKey => $aData)
{
if (!$aData['deleted'] &&
if (!$aData['deleted'] &&
(empty($aData['etag']) && !isset($aRemoteSyncData[$sKey])) // new
||
(!empty($aData['etag']) && isset($aRemoteSyncData[$sKey]) && // newer
@ -302,7 +305,7 @@ class PdoAddressBook
$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']), '"\'');
@ -314,7 +317,7 @@ class PdoAddressBook
}
}
}
unset($oContact);
}
}
@ -348,7 +351,7 @@ class PdoAddressBook
{
$oContact = new \RainLoop\Providers\AddressBook\Classes\Contact();
}
$oContact->PopulateByVCard($sBody,
!empty($oResponse['headers']['etag']) ? \trim(\trim($oResponse['headers']['etag']), '"\'') : '');
@ -421,16 +424,16 @@ class PdoAddressBook
{
$this->SyncDatabase();
}
$iUserID = $this->getUserId($sEmail);
$iIdContact = 0 < \strlen($oContact->IdContact) && \is_numeric($oContact->IdContact) ? (int) $oContact->IdContact : 0;
$bUpdate = 0 < $iIdContact;
$oContact->UpdateDependentValues();
$oContact->Changed = \time();
try
{
if ($this->isTransactionSupported())
@ -465,6 +468,14 @@ class PdoAddressBook
':id_contact' => array($iIdContact, \PDO::PARAM_INT)
)
);
// clear previos tags
$this->prepareAndExecute(
'DELETE FROM rainloop_ab_tags_contacts WHERE id_contact = :id_contact',
array(
':id_contact' => array($iIdContact, \PDO::PARAM_INT)
)
);
}
else
{
@ -517,6 +528,44 @@ class PdoAddressBook
'(:id_contact, :id_user, :prop_type, :prop_type_str, :prop_value, :prop_value_custom, :prop_frec)';
$this->prepareAndExecute($sSql, $aParams, true);
$aTags = array();
$aContactTags = $this->GetContactTags($sEmail);
$fFindFunc = function ($sName) use ($aContactTags) {
$iResult = 0;
foreach ($aContactTags as $oItem)
{
if ($oItem && $oItem->Name === $sName)
{
$iResult = (int) $oItem->IdContactTag;
break;
}
}
return $iResult;
};
foreach ($oContact->Tags as $sTagName)
{
$iTagID = $fFindFunc($sTagName);
if (0 >= $iTagID)
{
$iTagID = $this->CreateTag($sEmail, $sTagName, false);
}
if (\is_int($iTagID) && 0 < $iTagID)
{
$aTags[] = array(
':id_tag' => array($iTagID, \PDO::PARAM_INT),
':id_contact' => array($iIdContact, \PDO::PARAM_INT)
);
}
}
$sSql = 'INSERT INTO rainloop_ab_tags_contacts (id_tag, id_contact) VALUES (:id_tag, :id_contact)';
$this->prepareAndExecute($sSql, $aTags, true);
}
}
catch (\Exception $oException)
@ -537,6 +586,64 @@ class PdoAddressBook
return 0 < $iIdContact;
}
/**
* @param string $sEmail
* @param string $sName
* @param bool $bSyncDb = true
*
* @return bool|int
*/
public function CreateTag($sEmail, $sName, $bSyncDb = true)
{
if ($bSyncDb)
{
$this->SyncDatabase();
}
$iUserID = $this->getUserId($sEmail);
$mResult = false;
try
{
if ($this->isTransactionSupported())
{
$this->beginTransaction();
}
$sSql = 'INSERT INTO rainloop_ab_tags '.
'(id_user, tag_name) VALUES (:id_user, :tag_name)';
$this->prepareAndExecute($sSql,
array(
':id_user' => array($iUserID, \PDO::PARAM_INT),
':tag_name' => array($sName, \PDO::PARAM_STR)
)
);
$sLast = $this->lastInsertId('rainloop_ab_contacts', 'id_contact');
if (\is_numeric($sLast) && 0 < (int) $sLast)
{
$mResult = (int) $sLast;
}
}
catch (\Exception $oException)
{
if ($this->isTransactionSupported())
{
$this->rollBack();
}
throw $oException;
}
if ($this->isTransactionSupported())
{
$this->commit();
}
return $mResult;
}
/**
* @param string $sEmail
* @param array $aContactIds
@ -615,7 +722,7 @@ class PdoAddressBook
* @param int $iLimit = 20
* @param string $sSearch = ''
* @param int $iResultCount = 0
*
*
* @return array
*/
public function GetContacts($sEmail, $iOffset = 0, $iLimit = 20, $sSearch = '', &$iResultCount = 0)
@ -631,7 +738,7 @@ class PdoAddressBook
$iCount = 0;
$aSearchIds = array();
$aPropertyFromSearchIds = array();
if (0 < \strlen($sSearch))
{
$sCustomSearch = $this->specialConvertSearchValueCustomPhone($sSearch);
@ -645,7 +752,7 @@ class PdoAddressBook
PropertyType::EMAIl, PropertyType::FIRST_NAME, PropertyType::LAST_NAME, PropertyType::NICK_NAME,
PropertyType::PHONE, PropertyType::WEB_PAGE
));
$sSql = 'SELECT id_user, id_prop, id_contact FROM rainloop_ab_properties '.
'WHERE (id_user = :id_user) AND prop_type IN ('.$sSearchTypes.') AND (prop_value LIKE :search ESCAPE \'=\''.
(0 < \strlen($sCustomSearch) ? ' OR (prop_type = '.PropertyType::PHONE.' AND prop_value_custom <> \'\' AND prop_value_custom LIKE :search_custom_phone)' : '').
@ -686,7 +793,7 @@ class PdoAddressBook
{
$sSql = 'SELECT COUNT(DISTINCT id_contact) as contact_count FROM rainloop_ab_properties '.
'WHERE id_user = :id_user';
$aParams = array(
':id_user' => array($iUserID, \PDO::PARAM_INT)
);
@ -704,10 +811,11 @@ class PdoAddressBook
$iResultCount = $iCount;
$aResult = array();
if (0 < $iCount)
{
$sSql = 'SELECT * FROM rainloop_ab_contacts WHERE deleted = 0 AND id_user = :id_user';
$aParams = array(
':id_user' => array($iUserID, \PDO::PARAM_INT)
);
@ -794,13 +902,136 @@ class PdoAddressBook
$oItem->UpdateDependentValues();
}
return \array_values($aContacts);
$aResult = \array_values($aContacts);
}
}
}
}
return array();
if (\is_array($aResult) && 0 < \count($aResult))
{
$aIdContacts = array();
$aIdTagsContacts = array();
$aTags = $this->GetContactTags($sEmail);
if (\is_array($aTags) && 0 < \count($aTags))
{
foreach ($aResult as $oItem)
{
$aIdContacts[$oItem->IdContact] = true;
}
if (0 < \count($aIdContacts))
{
$sSql = 'SELECT id_tag, id_contact FROM rainloop_ab_tags_contacts WHERE id_contact IN ('.\implode(',', \array_keys($aIdContacts)).')';
$oStmt = $this->prepareAndExecute($sSql);
if ($oStmt)
{
$aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
if (\is_array($aFetch) && 0 < \count($aFetch))
{
foreach ($aFetch as $aItem)
{
if ($aItem && isset($aItem['id_tag'], $aItem['id_contact']))
{
$sID = (string) $aItem['id_contact'];
if (!isset($aIdTagsContacts[$sID]))
{
$aIdTagsContacts[$sID] = array();
}
$aIdTagsContacts[$sID][] = (string) $aItem['id_tag'];
}
}
}
unset($aFetch);
}
if (0 < \count($aIdTagsContacts))
{
$fFindFunc = function ($sID) use ($aTags) {
foreach ($aTags as $oItem)
{
if ($oItem && (string) $oItem->IdContactTag === $sID)
{
return\trim($oItem->Name);
}
}
return '';
};
foreach ($aResult as &$oItem)
{
$sID = $oItem ? (string) $oItem->IdContact : '';
if (0 < \strlen($sID) && isset($aIdTagsContacts[$sID]) && \is_array($aIdTagsContacts[$sID]))
{
$aNames = array();
foreach ($aIdTagsContacts[$sID] as $sSubID)
{
$sName = $fFindFunc($sSubID);
if (0 < \strlen($sName))
{
$aNames[] = $sName;
}
}
$oItem->Tags = $aNames;
}
}
}
}
}
}
return $aResult;
}
/**
* @param string $sEmail
*
* @return array
*/
public function GetContactTags($sEmail)
{
$this->SyncDatabase();
$iUserID = $this->getUserId($sEmail);
$sSql = 'SELECT id_tag, id_user, tag_name FROM rainloop_ab_tags WHERE id_user = :id_user ORDER BY tag_name ASC';
$aParams = array(
':id_user' => array($iUserID, \PDO::PARAM_INT)
);
$aResult = array();
$oStmt = $this->prepareAndExecute($sSql, $aParams);
if ($oStmt)
{
$aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
if (\is_array($aFetch) && 0 < \count($aFetch))
{
foreach ($aFetch as $aItem)
{
$iIdContactTag = $aItem && isset($aItem['id_tag']) ? (int) $aItem['id_tag'] : 0;
if (0 < $iIdContactTag)
{
$oContactTag = new \RainLoop\Providers\AddressBook\Classes\Tag();
$oContactTag->IdContactTag = (string) $iIdContactTag;
$oContactTag->Name = isset($aItem['tag_name']) ? (string) $aItem['tag_name'] : '';
$oContactTag->ReadOnly = $iUserID !== (isset($aItem['id_user']) ? (int) $aItem['id_user'] : 0);
$aResult[] = $oContactTag;
}
}
}
}
return $aResult;
}
/**
@ -924,7 +1155,7 @@ class PdoAddressBook
}
$this->SyncDatabase();
$iUserID = $this->getUserId($sEmail);
$sTypes = implode(',', array(
@ -933,7 +1164,7 @@ class PdoAddressBook
$sSql = 'SELECT id_contact, id_prop, prop_type, prop_value FROM rainloop_ab_properties '.
'WHERE (id_user = :id_user) AND prop_type IN ('.$sTypes.') AND prop_value LIKE :search ESCAPE \'=\'';
$aParams = array(
':id_user' => array($iUserID, \PDO::PARAM_INT),
':limit' => array($iLimit, \PDO::PARAM_INT),
@ -944,7 +1175,7 @@ class PdoAddressBook
$sSql .= ' LIMIT :limit';
$aResult = array();
$oStmt = $this->prepareAndExecute($sSql, $aParams);
if ($oStmt)
{
@ -960,7 +1191,7 @@ class PdoAddressBook
$iIdContact = $aItem && isset($aItem['id_contact']) ? (int) $aItem['id_contact'] : 0;
$iIdProp = $aItem && isset($aItem['id_prop']) ? (int) $aItem['id_prop'] : 0;
$iType = $aItem && isset($aItem['prop_type']) ? (int) $aItem['prop_type'] : 0;
if (0 < $iIdContact && 0 < $iIdProp)
{
$aIdContacts[$iIdContact] = $iIdContact;
@ -985,7 +1216,7 @@ class PdoAddressBook
if (0 < count($aIdContacts))
{
$oStmt->closeCursor();
$sTypes = \implode(',', array(
PropertyType::EMAIl, PropertyType::FIRST_NAME, PropertyType::LAST_NAME, PropertyType::NICK_NAME
));
@ -1042,10 +1273,10 @@ class PdoAddressBook
{
$bName = \in_array(PropertyType::FIRST_NAME, $aContactAllAccess[$iId]) || \in_array(PropertyType::LAST_NAME, $aContactAllAccess[$iId]);
$bNick = \in_array(PropertyType::NICK_NAME, $aContactAllAccess[$iId]);
$aNameItem = isset($aNames[$iId]) && \is_array($aNames[$iId]) ? $aNames[$iId] : array('', '');
$sNameItem = \trim($aNameItem[0].' '.$aNameItem[1]);
$sNickItem = isset($aNicks[$iId]) ? $aNicks[$iId] : '';
foreach ($aItems as $sEmail)
@ -1054,7 +1285,7 @@ class PdoAddressBook
{
$aResult[] = array($sEmail, $sNameItem);
}
if ($bNick)
{
$aResult[] = array($sEmail, $sNickItem);
@ -1089,7 +1320,7 @@ class PdoAddressBook
{
$aResult = \array_slice($aResult, 0, $iLimit);
}
return $aResult;
}
}
@ -1121,7 +1352,7 @@ class PdoAddressBook
$aEmailsObjects = \array_filter($aEmailsObjects, function ($oItem) {
return !!$oItem;
});
if (0 === \count($aEmailsObjects))
{
throw new \InvalidArgumentException('Empty Emails argument');
@ -1156,7 +1387,7 @@ class PdoAddressBook
}
}
}
$aEmailsToCreate = \array_filter($aEmailsObjects, function ($oItem) use ($aExists, &$aEmailsToUpdate) {
if ($oItem)
{
@ -1185,7 +1416,7 @@ class PdoAddressBook
}
}
}
unset($aEmails, $aEmailsObjects);
if (0 < \count($aEmailsToCreate))
@ -1225,7 +1456,7 @@ class PdoAddressBook
$oContact->Properties[] = $oPropName;
}
if (0 < \strlen($sLast))
{
$oPropName = new \RainLoop\Providers\AddressBook\Classes\Property();
@ -1240,17 +1471,17 @@ class PdoAddressBook
{
$this->ContactSave($sEmail, $oContact);
}
$oContact->Clear();
}
}
$sSql = 'UPDATE rainloop_ab_properties SET prop_frec = prop_frec + 1 WHERE id_user = :id_user AND prop_type = :prop_type';
$aEmailsQuoted = \array_map(function ($mItem) use ($self) {
return $self->quoteValue($mItem);
}, $aEmailsToUpdate);
if (1 === \count($aEmailsQuoted))
{
$sSql .= ' AND prop_value = '.$aEmailsQuoted[0];
@ -1259,7 +1490,7 @@ class PdoAddressBook
{
$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)
@ -1506,7 +1737,7 @@ SQLITEINITIAL;
break;
}
return false;
return $mCache;
}
/**