Added counters of unread messages for additional accounts (#377)

This commit is contained in:
RainLoop Team 2015-02-02 00:46:23 +04:00
parent abddb3d828
commit 79233ad83c
19 changed files with 333 additions and 93 deletions

View file

@ -337,11 +337,11 @@ class Http
}
/**
* @param bool $bCheckProxy = true
* @param bool $bCheckProxy = false
*
* @return string
*/
public function GetClientIp($bCheckProxy = true)
public function GetClientIp($bCheckProxy = false)
{
$sIp = '';
if ($bCheckProxy && null !== $this->GetServer('HTTP_CLIENT_IP', null))

View file

@ -44,8 +44,10 @@ class Logger extends \MailSo\Base\Collection
/**
* @access protected
*
* @param bool $bRegPhpErrorHandler = false
*/
protected function __construct()
protected function __construct($bRegPhpErrorHandler = true)
{
parent::__construct();
@ -55,16 +57,22 @@ class Logger extends \MailSo\Base\Collection
$this->bShowSecter = false;
$this->bHideErrorNotices = false;
\set_error_handler(array(&$this, '__phpErrorHandler'));
if ($bRegPhpErrorHandler)
{
\set_error_handler(array(&$this, '__phpErrorHandler'));
}
\register_shutdown_function(array(&$this, '__loggerShutDown'));
}
/**
* @param bool $bRegPhpErrorHandler = false
*
* @return \MailSo\Log\Logger
*/
public static function NewInstance()
public static function NewInstance($bRegPhpErrorHandler = false)
{
return new self();
return new self($bRegPhpErrorHandler);
}
/**

View file

@ -869,6 +869,24 @@ class MailClient
return self::GenerateHash($sFolderName, $iCount, $iUnseenCount, $sUidNext);
}
/**
* @return int
*
* @throws \MailSo\Net\Exceptions\Exception
* @throws \MailSo\Imap\Exceptions\Exception
*/
public function InboxUnreadCount()
{
$aFolderStatus = $this->oImapClient->FolderStatus('INBOX', array(
\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN
));
$iResult = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN]) ?
(int) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN] : 0;
return 0 < $iResult ? $iResult : 0;
}
/**
* @param string $sSearch
* @param bool $bDetectGmail = true

View file

@ -40,6 +40,11 @@ class Actions
*/
private $oLogger;
/**
* @var \MailSo\Log\Logger
*/
private $oLoggerAuth;
/**
* @var \RainLoop\Social
*/
@ -359,60 +364,119 @@ class Actions
}
/**
* @param string $sLine
* @param \RainLoop\Model\Account $oAccount = null
*
* @return string
*/
private function compileLogFileName()
private function compileLogParams($sLine, $oAccount = null)
{
$sFileName = (string) $this->Config()->Get('logs', 'filename', '');
if (false !== \strpos($sFileName, '{date:'))
if (false !== \strpos($sLine, '{date:'))
{
$sFileName = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) {
$sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) {
return \gmdate($aMatch[1]);
}, $sFileName);
}, $sLine);
$sFileName = \preg_replace('/\{date:([^}]*)\}/', 'date', $sFileName);
$sLine = \preg_replace('/\{date:([^}]*)\}/', 'date', $sLine);
}
if (false !== \strpos($sFileName, '{user:'))
if (false !== \strpos($sLine, '{imap:') || false !== \strpos($sLine, '{smtp:'))
{
if (false !== \strpos($sFileName, '{user:uid}'))
if (!$oAccount)
{
$sFileName = \str_replace('{user:uid}',
$this->ParseQueryAuthString();
$oAccount = $this->getAccountFromToken(false);
}
if ($oAccount)
{
$sLine = \str_replace('{imap:login}', $oAccount->IncLogin(), $sLine);
$sLine = \str_replace('{imap:host}', $oAccount->DomainIncHost(), $sLine);
$sLine = \str_replace('{imap:port}', $oAccount->DomainIncPort(), $sLine);
$sLine = \str_replace('{smtp:login}', $oAccount->OutLogin(), $sLine);
$sLine = \str_replace('{smtp:host}', $oAccount->DomainOutHost(), $sLine);
$sLine = \str_replace('{smtp:port}', $oAccount->DomainOutPort(), $sLine);
}
$sLine = \preg_replace('/\{imap:([^}]*)\}/i', 'imap', $sLine);
$sLine = \preg_replace('/\{smtp:([^}]*)\}/i', 'imap', $sLine);
}
if (false !== \strpos($sLine, '{request:'))
{
if (false !== \strpos($sLine, '{request:ip}'))
{
$sLine = \str_replace('{request:ip}', $this->Http()->GetClientIp(
$this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $sLine);
}
$sLine = \preg_replace('/\{request:([^}]*)\}/i', 'request', $sLine);
}
if (false !== \strpos($sLine, '{user:'))
{
if (false !== \strpos($sLine, '{user:uid}'))
{
$sLine = \str_replace('{user:uid}',
\base_convert(\sprintf('%u', \crc32(\md5(\RainLoop\Utils::GetConnectionToken()))), 10, 32),
$sFileName
$sLine
);
}
if (false !== \strpos($sFileName, '{user:ip}'))
if (false !== \strpos($sLine, '{user:ip}'))
{
$sFileName = \str_replace('{user:ip}', $this->Http()->GetClientIp(), $sFileName);
$sLine = \str_replace('{user:ip}', $this->Http()->GetClientIp(
$this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $sLine);
}
if (\preg_match('/\{user:(email|login|domain)\}/i', $sFileName))
if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine))
{
$this->ParseQueryAuthString();
if (!$oAccount)
{
$this->ParseQueryAuthString();
$oAccount = $this->getAccountFromToken(false);
}
$oAccount = $this->getAccountFromToken(false);
if ($oAccount)
{
$sEmail = $oAccount->Email();
$sFileName = \str_replace('{user:email}', $sEmail, $sFileName);
$sFileName = \str_replace('{user:login}', \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sFileName);
$sFileName = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sFileName);
$sLine = \str_replace('{user:email}', $sEmail, $sLine);
$sLine = \str_replace('{user:login}', \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sLine);
$sLine = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sLine);
}
}
$sFileName = \preg_replace('/\{user:([^}]*)\}/i', 'unknown', $sFileName);
$sLine = \preg_replace('/\{user:([^}]*)\}/i', 'unknown', $sLine);
}
if (false !== \strpos($sFileName, '{labs:'))
if (false !== \strpos($sLine, '{labs:'))
{
$sFileName = \preg_replace_callback('/\{labs:rand:([1-9])\}/', function ($aMatch) {
$sLine = \preg_replace_callback('/\{labs:rand:([1-9])\}/', function ($aMatch) {
return \rand(\pow(10, $aMatch[1] - 1), \pow(10, $aMatch[1]) - 1);
}, $sFileName);
}, $sLine);
$sFileName = \preg_replace('/\{labs:([^}]*)\}/', 'labs', $sFileName);
$sLine = \preg_replace('/\{labs:([^}]*)\}/', 'labs', $sLine);
}
return $sLine;
}
/**
* @param string $sFileName
*
* @return string
*/
private function compileLogFileName($sFileName)
{
$sFileName = \trim($sFileName);
if (0 !== \strlen($sFileName))
{
$sFileName = $this->compileLogParams($sFileName);
$sFileName = \preg_replace('/[\/]+/', '/', \preg_replace('/[.]+/', '.', $sFileName));
$sFileName = \preg_replace('/[^a-zA-Z0-9@_+=\-\.\/!()\[\]]/', '', $sFileName);
}
if (0 === \strlen($sFileName))
@ -420,9 +484,6 @@ class Actions
$sFileName = 'rainloop-log.txt';
}
$sFileName = \preg_replace('/[\/]+/', '/', \preg_replace('/[.]+/', '.', $sFileName));
$sFileName = \preg_replace('/[^a-zA-Z0-9@_+=\-\.\/!()\[\]]/', '', $sFileName);
return $sFileName;
}
@ -775,11 +836,13 @@ class Actions
{
$this->oLogger = \MailSo\Log\Logger::SingletonInstance();
if (!!$this->Config()->Get('logs', 'enable', true))
if (!!$this->Config()->Get('logs', 'enable', false))
{
$this->oLogger->SetShowSecter(!$this->Config()->Get('logs', 'hide_passwords', true));
$sLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName();
$sLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName(
$this->Config()->Get('logs', 'filename', ''));
$sLogFileDir = \dirname($sLogFileFullPath);
if (!@is_dir($sLogFileDir))
@ -804,7 +867,8 @@ class Actions
$oHttp = $this->Http();
$this->oLogger->Write('[DATE:'.\gmdate('d.m.y').'][RL:'.APP_VERSION.'][PHP:'.PHP_VERSION.'][IP:'.
$oHttp->GetClientIp().'][PID:'.(\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? \getmypid() : 'unknown').']['.
$oHttp->GetClientIp($this->Config()->Get('labs', 'http_client_ip_check_proxy', false)).'][PID:'.
(\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? \getmypid() : 'unknown').']['.
$oHttp->GetServer('SERVER_SOFTWARE', '~').']['.
(\MailSo\Base\Utils::FunctionExistsAndEnabled('php_sapi_name') ? \php_sapi_name() : '~' ).']'
);
@ -829,6 +893,40 @@ class Actions
return $this->oLogger;
}
/**
* @return \MailSo\Log\Logger
*/
public function LoggerAuth()
{
if (null === $this->oLoggerAuth)
{
$this->oLoggerAuth = \MailSo\Log\Logger::NewInstance(false);
if (!!$this->Config()->Get('logs', 'auth_logging', false))
{
$sAuthLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName(
$this->Config()->Get('logs', 'auth_logging_filename', ''));
$sLogFileDir = \dirname($sAuthLogFileFullPath);
if (!@is_dir($sLogFileDir))
{
@mkdir($sLogFileDir, 0755, true);
}
$this->oLoggerAuth->AddForbiddenType(\MailSo\Log\Enumerations\Type::MEMORY);
$this->oLoggerAuth->AddForbiddenType(\MailSo\Log\Enumerations\Type::TIME);
$this->oLoggerAuth->AddForbiddenType(\MailSo\Log\Enumerations\Type::TIME_DELTA);
$this->oLoggerAuth->Add(
\MailSo\Log\Drivers\File::NewInstance($sAuthLogFileFullPath)
);
}
}
return $this->oLoggerAuth;
}
/**
* @return array
*/
@ -1524,10 +1622,11 @@ class Actions
/**
* @param \RainLoop\Model\Account $oAccount
* @param bool $bAuthLog = false
*
* @throws \RainLoop\Exceptions\ClientException
*/
public function CheckMailConnection($oAccount)
public function CheckMailConnection($oAccount, $bAuthLog = false)
{
try
{
@ -1543,6 +1642,16 @@ class Actions
}
catch (\MailSo\Imap\Exceptions\LoginBadCredentialsException $oException)
{
if ($bAuthLog)
{
$sLine = $this->Config()->Get('logs', 'auth_logging_format', '');
if (!empty($sLine))
{
$this->LoggerAuth()->Write($this->compileLogParams($sLine, $oAccount),
\MailSo\Log\Enumerations\Type::WARNING, 'IMAP');
}
}
if ($this->Config()->Get('labs', 'imap_show_login_alert', true))
{
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError,
@ -1724,7 +1833,7 @@ class Actions
try
{
$this->CheckMailConnection($oAccount);
$this->CheckMailConnection($oAccount, true);
}
catch (\Exception $oException)
{
@ -2373,6 +2482,40 @@ class Actions
));
}
/**
* @param string $sHash
*
* @return int
*
* @throws \MailSo\Base\Exceptions\Exception
*/
public function getAccountUnredCountFromHash($sHash)
{
$iResult = 0;
$oAccount = $this->GetAccountFromCustomToken($sHash, false);
if ($oAccount)
{
try
{
$oMailClient = \MailSo\Mail\MailClient::NewInstance();
$oMailClient->SetLogger($this->Logger());
$oAccount->IncConnectAndLoginHelper($this->Plugins(),$oMailClient, $this->Config());
$iResult = $oMailClient->InboxUnreadCount();
$oMailClient->LogoutAndDisconnect();
}
catch (\Exception $oException)
{
$this->Logger()->WriteException($oException);
}
}
return $iResult;
}
/**
* @return array
*
@ -2382,13 +2525,32 @@ class Actions
{
$oAccount = $this->getAccountFromToken();
$bComplete = true;
$aCounts = array();
if ($this->Config()->Get('webmail', 'allow_additional_accounts', true))
{
$iLimit = 7;
$mAccounts = $this->GetAccounts($oAccount);
foreach ($mAccounts as $sEmail => $sHash)
if (\is_array($mAccounts) && 0 < \count($mAccounts))
{
$aCounts[] = array(\MailSo\Base\Utils::IdnToUtf8($sEmail), 0);
if ($iLimit > \count($mAccounts))
{
$mAccounts = \array_slice($mAccounts, 0, $iLimit);
}
else
{
$bComplete = false;
}
if (0 < \count($mAccounts))
{
foreach ($mAccounts as $sEmail => $sHash)
{
$aCounts[] = array(\MailSo\Base\Utils::IdnToUtf8($sEmail),
$oAccount->Email() === $sEmail ? 0 : $this->getAccountUnredCountFromHash($sHash));
}
}
}
}
else
@ -2397,7 +2559,7 @@ class Actions
}
return $this->DefaultResponse(__FUNCTION__, array(
'Complete' => true,
'Complete' => $bComplete,
'Counts' => $aCounts
));
}

View file

@ -188,12 +188,22 @@ Patterns:
{user:domain} - Replaced by user\'s domain name (the domain part of an email)
If user is not logged in, value is set to "unknown"
{user:uid} - Replaced by user\'s UID regardless of account currently used
{user:ip} - Replaced by user\'s IP address
{user:ip}
{request:ip} - Replaced by user\'s IP address
Others:
{imap:login} {imap:host} {imap:port}
{smtp:login} {smtp:host} {smtp:port}
Examples:
filename = "log-{date:Y-m-d}.txt"
filename = "{date:Y-m-d}/{user:domain}/{user:email}_{user:uid}.log"
filename = "{user:email}-{date:Y-m-d}.txt"')
filename = "{user:email}-{date:Y-m-d}.txt"'),
'auth_logging' => array(false, 'Enable auth logging in a separate file (for fail2ban)'),
'auth_logging_filename' => array('fail2ban/auth-{date:Y-m-d}.txt'),
'auth_logging_format' => array('Auth failed: ip={request:ip} user={imap:login} host={imap:host} port={imap:port}')
),
'debug' => array(
@ -287,6 +297,7 @@ Enables caching in the system'),
'allow_external_login' => array(false),
'allow_external_sso' => array(false),
'external_sso_key' => array(''),
'http_client_ip_check_proxy' => array(false),
'fast_cache_memcache_host' => array('127.0.0.1'),
'fast_cache_memcache_port' => array(11211),
'fast_cache_memcache_expire' => array(43200),

View file

@ -1,11 +1,15 @@
<div class="b-system-drop-down g-ui-user-select-none">
<div class="b-toolbar">
<div class="btn-toolbar">
<div class="accountPlace" data-bind="text: emailTitle()"></div>
<div class="btn-group pull-right dropdown colored-toggle" data-bind="registrateBootstrapDropdown: true, openDropdownTrigger: accountMenuDropdownTrigger">
<a id="top-system-dropdown-id" href="#" tabindex="-1" class="btn btn-ellipsis btn-block dropdown-toggle system-dropdown" data-toggle="dropdown">
<i data-bind="css: {'icon-user': !loading(), 'icon-spinner animated': loading()}"
></i>&nbsp;&nbsp;<span class="caret"></span>
<i data-bind="css: {'icon-user': !accounts.loading(), 'icon-spinner animated': accounts.loading()}"
></i>
&nbsp;
<b data-bind="text: accountsUnreadCount, visible: 100 > accountsUnreadCount() && 0 < accountsUnreadCount()"></b>
<b data-bind="visible: 99 < accountsUnreadCount()">99+</b>
&nbsp;
<span class="caret"></span>
</a>
<ul class="dropdown-menu g-ui-menu" tabindex="-1" role="menu" aria-labelledby="top-system-dropdown-id">
@ -21,7 +25,7 @@
<i class="icon-ok"></i>
<i class="icon-user"></i>
&nbsp;&nbsp;
<span class="email-title" data-bind="text: email"></span>
<span class="email-title" data-bind="text: email, attr: {title: email}"></span>
</a>
</li>
<!-- /ko -->
@ -59,9 +63,9 @@
<span class="i18n" data-i18n-text="TOP_TOOLBAR/BUTTON_LOGOUT"></span>
</a>
</li>
</ul>
</div>
<div class="accountPlace pull-right" data-bind="text: emailTitle()"></div>
</div>
</div>
</div>