diff --git a/.gitignore b/.gitignore index 43d8b94f2..aaef1eb55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /.idea +/local /nbproject /npm-debug.log /node_modules diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Common/PdoAbstract.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Common/PdoAbstract.php new file mode 100644 index 000000000..89c88b4f3 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Common/PdoAbstract.php @@ -0,0 +1,182 @@ +oLogger = $oLogger instanceof \MailSo\Log\Logger ? $oLogger : null; + } + + /** + * @param \RainLoop\Account $oAccount + * + * @return array + */ + protected function getPdoAccessData(\RainLoop\Account $oAccount) + { + $aResult = array('mysql', '', '', ''); + return $aResult; + } + + /** + * @param \RainLoop\Account $oAccount + * @staticvar array $aPdoCache + * + * @return \PDO + * + * @throws \Exception + */ + protected function getPDO(\RainLoop\Account $oAccount) + { + static $aPdoCache = array(); + + $sEmail = $oAccount->ParentEmailHelper(); + if (isset($aPdoCache[$sEmail])) + { + return $aPdoCache[$sEmail]; + } + + if (!\class_exists('PDO')) + { + throw new \Exception('Class PDO does not exist'); + } + + // TODO + $sType = $sDsn = $sDbLogin = $sDbPassword = ''; + list($sType, $sDsn, $sDbLogin, $sDbPassword) = $this->getPdoAccessData($oAccount); + + $oPdo = false; + try + { + $oPdo = new \PDO($sDsn, $sDbLogin, $sDbPassword); + if ($oPdo) + { + $oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + if ('mysql' === $sType) + { + + } + } + } + catch (\Exception $oException) + { + throw $oException; + } + + if ($oPdo) + { + $aPdoCache[$sEmail] = $oPdo; + } + else + { + throw new \Exception('PDO = false'); + } + + return $oPdo; + } + + /** + * @param \RainLoop\Account $oAccount + * @param string $sSql + * @param array $aParams + * + * @return \PDOStatement|null + */ + protected function prepareAndExecute(\RainLoop\Account $oAccount, $sSql, $aParams = array()) + { + if ($this->oLogger) + { + $this->oLogger->Write($sSql, \MailSo\Log\Enumerations\Type::INFO, 'SQL'); + } + + $mResult = null; + $oStmt = $this->getPDO($oAccount)->prepare($sSql); + if ($oStmt) + { + foreach ($aParams as $sName => $aValue) + { + $oStmt->bindValue($sName, $aValue[0], $aValue[1]); + } + + $mResult = $oStmt->execute() ? $oStmt : null; + } + + return $mResult; + } + + /** + * @param \RainLoop\Account $oAccount + * @param bool $bSkipInsert = false + * + * @return int + */ + protected function getUserId(\RainLoop\Account $oAccount, $bSkipInsert = false) + { + $sEmail = \strtolower($oAccount->ParentEmailHelper()); + + $oStmt = $this->prepareAndExecute($oAccount, + 'SELECT `id_user` FROM `rainloop_users` WHERE `email` = :email LIMIT 1', + array( + ':email' => array($sEmail, \PDO::PARAM_STR) + )); + + $mRow = $oStmt->fetch(\PDO::FETCH_ASSOC); + if ($mRow && isset($mRow['id_user']) && \is_numeric($mRow['id_user'])) + { + return (int) $mRow['id_user']; + } + + if (!$bSkipInsert) + { + $oStmt->closeCursor(); + + $oStmt = $this->prepareAndExecute($oAccount, + 'INSERT INTO rainloop_users (`email`) VALUES (:email)', + array( + ':email' => array($sEmail, \PDO::PARAM_STR) + )); + + return $this->getUserId($oAccount, true); + } + + throw new \Exception('id_user = 0'); + } + + /** + * @param string $sSearch + * + * @return string + */ + protected function convertSearchValue($sSearch) + { + return '%'.$sSearch.'%'; + } + + /** + * @param string $sValue + * + * @return string + */ + protected function quoteValue($sValue) + { + return '\''.$sValue.'\''; + } +} \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Contact.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Contact.php index 7681906b0..ada452b2c 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Contact.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Contact.php @@ -14,20 +14,25 @@ class Contact */ public $IdUser; + /** + * @var string + */ + public $DisplayName; + + /** + * @var string + */ + public $DisplayEmail; + /** * @var string */ public $DisplayInList; /** - * @var bool + * @var int */ - public $IsAuto; - - /** - * @var bool - */ - public $IsShare; + public $Type; /** * @var bool @@ -47,7 +52,7 @@ class Contact /** * @var array */ - public $Tags; + public $TagsIds; /** * @var array @@ -63,12 +68,14 @@ class Contact { $this->IdContact = 0; $this->IdUser = 0; + $this->DisplayName = ''; + $this->DisplayEmail = ''; $this->DisplayInList = ''; - $this->IsAuto = false; + $this->Type = \RainLoop\Providers\PersonalAddressBook\Enumerations\ContactType::DEFAULT_; $this->IsShare = false; $this->CanBeChanged = false; $this->Changed = \time(); - $this->Tags = array(); + $this->TagsIds = array(); $this->Properties = array(); } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Enumerations/ContactType.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Enumerations/ContactType.php new file mode 100644 index 000000000..0b279137b --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Enumerations/ContactType.php @@ -0,0 +1,10 @@ +oLogger = $oLogger instanceof \MailSo\Log\Logger ? $oLogger : null; - } - /** * @return bool */ @@ -28,48 +22,263 @@ class MySqlPersonalAddressBook implements \RainLoop\Providers\PersonalAddressBoo /** * @param \RainLoop\Account $oAccount - * @staticvar array $aPdoCache - * @return \PDO + * @param int $iIdContact + * + * @return \RainLoop\Providers\PersonalAddressBook\Classes\Contact|null */ - private function getPDO($oAccount) + public function GetContactById($oAccount, $iIdContact) { - static $aPdoCache = array(); + return null; + } - $sEmail = $oAccount->ParentEmailHelper(); - if (isset($aPdoCache[$sEmail])) + /** + * @param \RainLoop\Account $oAccount + * @param \RainLoop\Providers\PersonalAddressBook\Classes\Contact $oContact + * + * @return bool + */ + public function CreateContact($oAccount, &$oContact) + { + return false; + } + + /** + * @param \RainLoop\Account $oAccount + * @param \RainLoop\Providers\PersonalAddressBook\Classes\Contact $oContact + * + * @return bool + */ + public function UpdateContact($oAccount, &$oContact) + { + return false; + } + + /** + * @param \RainLoop\Account $oAccount + * @param array $aContactIds + * + * @return bool + */ + public function DeleteContacts($oAccount, $aContactIds) + { + $iUserID = $this->getUserId($oAccount); + + $aContactIds = \array_filter($aContactIds, function (&$mItem) { + $mItem = (int) $mItem; + return 0 < $mItem; + }); + + if (0 === \count($aContactIds)) { - return $aPdoCache[$sEmail]; + return false; } - if (!\class_exists('PDO')) + return !!$this->prepareAndExecute($oAccount, + 'DELETE FROM `rainloop_pab_contacts` WHERE id_user = :id_user AND `id_contact` IN ('.\implode(',', $aContactIds).')', + array(':id_user' => array($iUserID, \PDO::PARAM_INT))); + } + + /** + * @param \RainLoop\Account $oAccount + * @param int $iType = \RainLoop\Providers\PersonalAddressBook\Enumerations\ContactType::DEFAULT_ + * @param int $iOffset = 0 + * @param type $iLimit = 20 + * @param string $sSearch = '' + * + * @return array + */ + public function GetContacts($oAccount, + $iType = \RainLoop\Providers\PersonalAddressBook\Enumerations\ContactType::DEFAULT_, + $iOffset = 0, $iLimit = 20, $sSearch = '') + { + $iOffset = 0 <= $iOffset ? $iOffset : 0; + $iLimit = 0 < $iLimit ? (int) $iLimit : 20; + $sSearch = \trim($sSearch); + + $iUserID = $this->getUserId($oAccount); + + if (!\in_array($iType, array(ContactType::SHARE, ContactType::AUTO))) { - throw new \Exception('class_exists=PDO'); + $iType = ContactType::DEFAULT_; } - $sDsn = ''; - $sDbLogin = ''; - $sDbPassword = ''; - - $oPdo = false; - try + $sSql = 'SELECT * FROM `rainloop_pab_contacts` WHERE id_user = :id_user AND `type` = :type'; + $aParams = array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + ':type' => array($iType, \PDO::PARAM_INT) + ); + + if (0 < \strlen($sSearch)) { - $oPdo = new \PDO($sDsn, $sDbLogin, $sDbPassword); - if ($oPdo) + $sSql .= ' AND `id_contact` IN ('. + 'SELECT DISTINCT `id_contact` FROM `rainloop_pab_prop` WHERE id_user = :id_user AND `value` LIKE :search'. + ')'; + + $aParams[':search'] = array($this->convertSearchValue($sSearch), \PDO::PARAM_STR); + } + + $sSql .= ' ORDER BY `display_in_list` ASC LIMIT :limit OFFSET :offset'; + $aParams[':limit'] = array($iLimit, \PDO::PARAM_INT); + $aParams[':offset'] = array($iOffset, \PDO::PARAM_INT); + + return array(); + } + + /** + * @param \RainLoop\Account $oAccount + * @param string $sSearch + * + * @return array + * + * @throws \InvalidArgumentException + */ + public function GetSuggestions($oAccount, $sSearch) + { + $iLimit = 20; + + $sSearch = \trim($sSearch); + if (0 === \strlen($sSearch)) + { + throw new \InvalidArgumentException('$sSearch'); + } + + $iUserID = $this->getUserId($oAccount); + + $sTypes = implode(',', array( + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER, PropertyType::FULLNAME + )); + + $sSql = 'SELECT DISTINCT `id_contact` FROM `rainloop_pab_prop` '. + 'WHERE id_user = :id_user AND `type` IN ('.$sTypes.') AND `value` LIKE :search'; + + $aParams = array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + ':limit' => array($iLimit, \PDO::PARAM_INT), + ':search' => array($this->convertSearchValue($sSearch), \PDO::PARAM_STR) + ); + + $sSql .= ' ORDER BY `frec` ASC LIMIT :limit'; + + $oStmt = $this->prepareAndExecute($oAccount, $sSql, $aParams); + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + $aIdContacts = array(); + if (\is_array($aFetch) && 0 < \count($aFetch)) { - $oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + foreach ($aFetch as $aItem) + { + $iIdContact = $aItem && isset($aItem['id_contact']) ? (int) $aItem['id_contact'] : 0; + if (0 < $iIdContact) + { + $aIdContacts[] = $iIdContact; + } + } + } + + unset($aFetch); + + if (0 < count($aIdContacts)) + { + $oStmt->closeCursor(); + + $sTypes = implode(',', array( + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER, PropertyType::FULLNAME + )); + + $sSql = 'SELECT `id_contact`, `type`, `value` FROM `rainloop_pab_prop` '. + 'WHERE id_user = :id_user AND `type` IN ('.$sTypes.') AND `id_contact` IN ('.\implode(',', $aIdContacts).')'; + + $oStmt = $this->prepareAndExecute($oAccount, $sSql, array( + ':id_user' => array($iUserID, \PDO::PARAM_INT) + )); + + if ($oStmt) + { + $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + $aResult = array(); + if (\is_array($aFetch) && 0 < \count($aFetch)) + { + foreach ($aFetch as $aItem) + { + if ($aItem && isset($aItem['id_contact'], $aItem['type'], $aItem['value'])) + { + $iId = $aItem['id_contact']; + if (!isset($aResult[$iId])) + { + $aResult[$iId] = array('', ''); + } + + if ('' === $aResult[$iId][0] && \in_array((int) $aItem['type'], + array(PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER))) + { + $aResult[$iId][0] = $aItem['value']; + } + else if ('' === $aResult[$iId][1] &&\in_array((int) $aItem['type'], array(PropertyType::FULLNAME))) + { + $aResult[$iId][1] = $aItem['value']; + } + } + } + + $aResult = array_filter($aResult, function ($aItem) { + return '' !== $aItem[0]; + }); + } + + unset($aFetch); + + return \array_values($aResult); + } } } - catch (\Exception $oException) + + return array(); + } + + /** + * @param \RainLoop\Account $oAccount + * @param array $aEmail + * + * @return bool + */ + public function IncFrec($oAccount, $aEmail) + { + $iUserID = $this->getUserId($oAccount); + + $self = $this; + $aEmail = \array_filter($aEmail, function (&$mItem) use ($self) { + $mItem = \strtolower(\trim($mItem)); + if (0 < \strlen($mItem)) + { + $mItem = $self->quoteValue($mItem); + return true; + } + return false; + }); + + if (0 === \count($aEmail)) { -// throw $oException; - $oPdo = false; + throw new \InvalidArgumentException('$aEmail'); + } + + $sTypes = implode(',', array( + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER + )); + + $sSql = 'UPDATE `rainloop_pab_prop` SET `frec` = `frec` + 1 WHERE id_user = :id_user AND `type` IN ('.$sTypes; + + if (1 === \count($aEmail)) + { + $sSql .= ') AND `value` = '.$aEmail[0]; + } + else + { + $sSql .= ') AND `value` IN ('.\implode(',', $aEmail).')'; } - if ($oPdo) - { - $aPdoCache[$oAccount->ParentEmailHelper()] = $oPdo; - } - - return $oPdo; + return !!$this->prepareAndExecute($oAccount, $sSql, array( + ':id_user' => array($iUserID, \PDO::PARAM_INT), + )); } } \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/mysql-schema.sql b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/mysql-schema.sql index 1c2f1b139..49e2efef6 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/mysql-schema.sql +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/mysql-schema.sql @@ -23,8 +23,7 @@ CREATE TABLE IF NOT EXISTS `rainloop_pab_contacts` ( `id_contact` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `id_user` int(11) UNSIGNED NOT NULL, `display_in_list` varchar(255) NOT NULL DEFAULT '', - `is_auto` tinyint(1) NOT NULL DEFAULT '0', - `is_share` tinyint(1) NOT NULL DEFAULT '0', + `type` int(11) UNSIGNED NOT NULL DEFAULT '0', `changed` int(11) UNSIGNED NOT NULL DEFAULT '0', CONSTRAINT `id_user_fk_rainloop_pab_contacts` FOREIGN KEY (`id_user`) @@ -34,6 +33,7 @@ CREATE TABLE IF NOT EXISTS `rainloop_pab_contacts` ( -- Table structure for table `rainloop_pab_prop` CREATE TABLE IF NOT EXISTS `rainloop_pab_prop` ( + `id_prop` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `id_contact` int(11) UNSIGNED NOT NULL, `id_user` int(11) UNSIGNED NOT NULL, `type` int(11) UNSIGNED NOT NULL,