Skip to content

Commit

Permalink
Added: Button allowing customers to force syncing of mailboxes quota…
Browse files Browse the repository at this point in the history
… info (Client interface)

 Added: Percent value for mailboxes quota (Client interface)
 Enhancement: Display a static warning to customer when at least one of his mailboxes is over quota (Client interface)
  • Loading branch information
nuxwin committed Apr 8, 2017
1 parent f8cf60b commit a7e0879
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 51 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG
Expand Up @@ -13,7 +13,10 @@ DISTRIBUTION
Fixed: Make sure that sendmail* packages are pre-uninstalled

FRONTEND
Readded: Mailboxes quota information (info are retrieved by parsing maildirsize file)
Added: Button allowing customers to force syncing of mailboxes quota info (Client interface)
Added: Percent value for mailboxes quota (Client interface)
Enhancement: Display a static warning to customer when at least one of his mailboxes is over quota (Client interface)
Readded: Mailboxes quota information (Client interface)

INSTALLER
Fixed: composer.phar archive is not updated due to wrong working directory
Expand Down
28 changes: 20 additions & 8 deletions gui/library/Functions/Client.php
Expand Up @@ -524,18 +524,28 @@ function send_alias_order_email($aliasName)
/**
* Parse data from the given maildirsize file
*
* Because processing several maildirsize files can be time consuming, the data are stored in session for next 5 minutes.
* It is possible to force update by changing the $refreshData flag valye to TRUE
*
* @see http://www.courier-mta.org/imap/README.maildirquota.html
* @param string $maildirsizeFilePath
* @param bool $refreshData Flag indicating if data must be refreshed
* @return array|bool Array containing maildirsize data, FALSE on failure
*/
function parseMaildirsize($maildirsizeFilePath)
function parseMaildirsize($maildirsizeFilePath, $refreshData = FALSE)
{
if (!$refreshData && !empty($_SESSION['maildirsize'][$maildirsizeFilePath])
&& $_SESSION['maildirsize']['TIMESTAMP'] < (time() + 300)
) {
return $_SESSION['maildirsize'][$maildirsizeFilePath];
}

$fh = @fopen($maildirsizeFilePath, 'r');
if (!$fh) {
return false;
}

$maildirsize = array(
$_SESSION['maildirsize'][$maildirsizeFilePath] = array(
'QUOTA_BYTES' => 0,
'QUOTA_MESSAGES' => 0,
'BYTE_COUNT' => 0,
Expand All @@ -549,13 +559,13 @@ function parseMaildirsize($maildirsizeFilePath)
list($quotaBytes, $quotaMessages) = explode(',', $line);

if (($quotaBytes) && preg_match('/(\d+)S/i', $quotaBytes, $m)) {
$maildirsize['QUOTA_BYTES'] = $m[1];
$_SESSION['maildirsize'][$maildirsizeFilePath]['QUOTA_BYTES'] = $m[1];
} else {
break; # No Quota byte info, skipping full parsing
break; # No quota data, skipping...
}

if ($quotaMessages && preg_match('/(\d+)C/i', $quotaMessages, $m)) {
$maildirsize['QUOTA_MESSAGES'] = $m[1];
$_SESSION['maildirsize'][$maildirsizeFilePath]['QUOTA_MESSAGES'] = $m[1];
}

$firstLine = 0;
Expand All @@ -564,11 +574,13 @@ function parseMaildirsize($maildirsizeFilePath)

# Parse byte count and file count
if (preg_match('/^\s*(-?\d+)\s+(-?\d+)\s*$/', $line, $m)) {
$maildirsize['BYTE_COUNT'] += $m[1];
$maildirsize['FILE_COUNT'] += $m[2];
$_SESSION['maildirsize'][$maildirsizeFilePath]['BYTE_COUNT'] += $m[1];
$_SESSION['maildirsize'][$maildirsizeFilePath]['FILE_COUNT'] += $m[2];
}
}

fclose($fh);
return $maildirsize;

$_SESSION['maildirsize'][$maildirsizeFilePath]['TIMESTAMP'] = time();
return $_SESSION['maildirsize'];
}
96 changes: 65 additions & 31 deletions gui/public/client/mail_accounts.php
Expand Up @@ -105,24 +105,20 @@ function _client_generateUserMailAutoRespond($tpl, $mailId, $mailStatus, $mailAu
/**
* Generate Mail accounts list
*
* @param iMSCP_pTemplate $tpl reference to the template object
* @param iMSCP_pTemplate $tpl Template engine
* @param int $mainDmnId Customer main domain unique identifier
* @return int number of subdomain mails addresses
* @return int number of mail accounts
*/
function _client_generateMailAccountsList($tpl, $mainDmnId)
{
$stmt = exec_query(
"
SELECT mail_id, CONCAT(LEFT(mail_forward, 30), IF(LENGTH(mail_forward) > 30, '...', '')) AS mail_forward,
mail_type, status, mail_auto_respond, quota, mail_addr
FROM
mail_users
WHERE
domain_id = ?
AND
mail_type NOT LIKE '%catchall%'
ORDER BY
mail_addr ASC, mail_type DESC
SELECT mail_id, CONCAT(LEFT(mail_forward, 30), IF(LENGTH(mail_forward) > 30, '...', '')) AS mail_forward,
mail_type, status, mail_auto_respond, quota, mail_addr
FROM mail_users
WHERE domain_id = ?
AND mail_type NOT LIKE '%catchall%'
ORDER BY mail_addr ASC, mail_type DESC
",
$mainDmnId
);
Expand All @@ -133,13 +129,14 @@ function _client_generateMailAccountsList($tpl, $mainDmnId)
return 0;
}


$dmnProps = get_domain_default_props($_SESSION['user_id']);

$postfixConfig = new iMSCP_Config_Handler_File(
utils_normalizePath(iMSCP_Registry::get('config')->CONF_DIR . '/postfix/postfix.data')
);

$syncQuotaInfo = isset($_GET['sync_quota_info']);
$hasMailboxes = false;
$overQuota = false;

while ($row = $stmt->fetchRow(PDO::FETCH_ASSOC)) {
list($mailDelete, $mailDeleteScript, $mailEdit, $mailEditScript) = _client_generateUserMailAction(
$row['mail_id'], $row['status']
Expand All @@ -159,30 +156,51 @@ function _client_generateMailAccountsList($tpl, $mainDmnId)
$mailType .= '<br>';
}

if((strpos($row['mail_type'], '_mail') !== false)) {
if ($row['status'] == 'ok' && strpos($row['mail_type'], '_mail') !== false) {
$hasMailboxes = true;
list($user, $domain) = explode('@', $row['mail_addr']);
$info = parseMaildirsize(utils_normalizePath(
$postfixConfig['MTA_VIRTUAL_MAIL_DIR'] . "/$domain/$user/maildirsize"
));

if($info !== FALSE) {
$mailQuotaInfo = tr(
'%s / %s of %s total available',
bytesHuman($info['BYTE_COUNT'], NULL, 0),
bytesHuman($info['QUOTA_BYTES'], NULL, 0),
($dmnProps['mail_quota'] > 0) ? bytesHuman($dmnProps['mail_quota'], NULL, 0) : tr('Unlimited')
);
$maildirsize = parseMaildirsize(
utils_normalizePath($postfixConfig['MTA_VIRTUAL_MAIL_DIR'] . "/$domain/$user/maildirsize"),
$syncQuotaInfo
);

if ($maildirsize !== FALSE) {
$quotaPercent = ($maildirsize['QUOTA_BYTES'] > 0)
? ceil(100 * $maildirsize['BYTE_COUNT'] / $maildirsize['QUOTA_BYTES']) : 0;

if ($quotaPercent) {
if ($quotaPercent >= 100) {
$overQuota = true;
}

$mailQuotaInfo = sprintf(
($quotaPercent >= 95)
? '<span style="color:red">%s / %s (%s%%)</span>'
: '%s / %s (%s%%)',
bytesHuman($maildirsize['BYTE_COUNT'], NULL, 1),
bytesHuman($maildirsize['QUOTA_BYTES'], NULL, 1),
$quotaPercent
);
} else {
$mailQuotaInfo = sprintf(
'%s / %s (%s%%)',
bytesHuman($maildirsize['BYTE_COUNT'], NULL, 1),
($maildirsize['QUOTA_BYTES'] > 1)
? bytesHuman($maildirsize['QUOTA_BYTES'], NULL, 1) : tr('Unlimited'),
0
);
}
} else {
$mailQuotaInfo = tr('Unavailable');
}
} else {
$mailQuotaInfo = tr('N/A');
$mailQuotaInfo = tr('Unavailable');
}

$tpl->assign(array(
'MAIL_ADDR' => tohtml(decode_idna($mailAddr)),
'MAIL_TYPE' => $mailType,
'MAIL_QUOTA_INFO' => tohtml($mailQuotaInfo),
'MAIL_QUOTA_INFO' => $mailQuotaInfo,
'MAIL_STATUS' => translate_dmn_status($row['status']),
'MAIL_DELETE' => $mailDelete,
'MAIL_DELETE_SCRIPT' => $mailDeleteScript,
Expand All @@ -196,6 +214,19 @@ function _client_generateMailAccountsList($tpl, $mainDmnId)
$tpl->parse('MAIL_ITEM', '.mail_item');
}

if ($syncQuotaInfo) {
set_page_message(tr('Mailboxes quota info were synced.'), 'success');
redirectTo('mail_accounts.php');
}

if (!$hasMailboxes) {
$tpl->assign('SYNC_QUOTA_INFO_LINK', '');
}

if ($overQuota) {
set_page_message(tr('At least one of your mailbox is over quota.'), 'static_warning');
}

return $rowCount;
}

Expand Down Expand Up @@ -263,7 +294,8 @@ function client_generatePage($tpl)
'mail_items' => 'mail_feature',
'mail_item' => 'mail_items',
'auto_respond_item' => 'mail_item',
'auto_respond_edit_link' => 'auto_respond_item'
'auto_respond_edit_link' => 'auto_respond_item',
'sync_quota_info_link' => 'mail_items'
));
$tpl->assign(array(
'TR_PAGE_TITLE' => tr('Client / Email / Overview'),
Expand All @@ -277,7 +309,9 @@ function client_generatePage($tpl)
'TR_MESSAGE_DELETE' => tojs(tr('Are you sure you want to delete %s?', '%s')),
'TR_MESSAGE_DELETE_SELECTED_ITEMS' => tojs(tr('Are you sure you want to delete all selected mail accounts?')),
'TR_DELETE_SELECTED_ITEMS' => tr('Delete selected mail accounts'),
'TR_MESSAGE_DELETE_SELECTED_ITEMS_ERR' => tojs(tr('You must select a least one mail account to delete'))
'TR_MESSAGE_DELETE_SELECTED_ITEMS_ERR' => tojs(tr('You must select a least one mail account to delete')),
'TR_SYNC_QUOTA_INFO' => tr('Sync quota info'),
'TR_SYNC_QUOTA_TOOLTIP' => tohtml(tr('Force synching of mailboxes quota info. Quota info are automatically synced every 5 minutes.'), 'htmlAttr')
));

iMSCP_Events_Aggregator::getInstance()->registerListener('onGetJsTranslations', function ($e) {
Expand Down
29 changes: 21 additions & 8 deletions gui/public/client/mail_delete.php
Expand Up @@ -28,7 +28,9 @@
*/
function client_deleteMailAccount($mailId, $domainId)
{
$stmt = exec_query('SELECT mail_addr FROM mail_users WHERE mail_id = ? AND domain_id = ?', array(
static $postfixConfig = NULL;

$stmt = exec_query('SELECT mail_addr, mail_type FROM mail_users WHERE mail_id = ? AND domain_id = ?', array(
$mailId, $domainId
));

Expand All @@ -37,11 +39,22 @@ function client_deleteMailAccount($mailId, $domainId)
}

$row = $stmt->fetchRow();
$mailAddr = $row['mail_addr'];

iMSCP_Events_Aggregator::getInstance()->dispatch(iMSCP_Events::onBeforeDeleteMail, array('mailId' => $mailId));
exec_query('UPDATE mail_users SET status = ? WHERE mail_id = ?', array('todelete', $mailId));


if (strpos($row['mail_type'], '_mail') !== false) {
# Remove cached quota info if any
if (NULL === $postfixConfig) {
$postfixConfig = new iMSCP_Config_Handler_File(
utils_normalizePath(iMSCP_Registry::get('config')->CONF_DIR . '/postfix/postfix.data')
);
}

list($user, $domain) = explode('@', $row['mail_addr']);
unset($_SESSION['maildirsize'][utils_normalizePath($postfixConfig['MTA_VIRTUAL_MAIL_DIR'] . "/$domain/$user/maildirsize")]);
}

# Update or delete forward accounts and/or catch-alls that list mail_addr of the account that is being deleted
# Forward accounts:
# A forward account which only forward on the mail_addr of the account that is being deleted will be also deleted,
Expand All @@ -55,21 +68,21 @@ function client_deleteMailAccount($mailId, $domainId)
WHERE mail_addr <> :mail_addr AND (mail_acc RLIKE :rlike OR mail_forward RLIKE :rlike)
',
array(
'mail_addr' => $mailAddr,
'rlike' => '(,|^)' . $mailAddr . '(,|$)'
'mail_addr' => $row['mail_addr'],
'rlike' => '(,|^)' . $row['mail_addr'] . '(,|$)'
)
);
if ($stmt->rowCount()) {
while ($row = $stmt->fetchRow()) {
if ($row['mail_forward'] == '_no_') {
# Catchall
$row['mail_acc'] = implode(',', preg_grep(
'/^' . quotemeta($mailAddr) . '$/', explode(',', $row['mail_acc']), PREG_GREP_INVERT
'/^' . quotemeta($row['mail_addr']) . '$/', explode(',', $row['mail_acc']), PREG_GREP_INVERT
));
} else {
# Forward account
$row['mail_forward'] = implode(',', preg_grep(
'/^' . quotemeta($mailAddr) . '$/', explode(',', $row['mail_forward']), PREG_GREP_INVERT
'/^' . quotemeta($row['mail_addr']) . '$/', explode(',', $row['mail_forward']), PREG_GREP_INVERT
));
}

Expand All @@ -85,7 +98,7 @@ function client_deleteMailAccount($mailId, $domainId)

delete_autoreplies_log_entries();
iMSCP_Events_Aggregator::getInstance()->dispatch(iMSCP_Events::onAfterDeleteMail, array('mailId' => $mailId));
set_page_message(tr('Mail account %s successfully scheduled for deletion.', decode_idna($mailAddr)), 'success');
set_page_message(tr('Mail account %s successfully scheduled for deletion.', decode_idna($row['mail_addr'])), 'success');
}

/***********************************************************************************************************************
Expand Down
7 changes: 7 additions & 0 deletions gui/public/client/mail_edit.php
Expand Up @@ -212,6 +212,13 @@ function client_editMailAccount()
array($password, $forwardList, $mailType, 'tochange', $mailQuotaLimitBytes, $mailData['mail_id'])
);

# Force synching of quota info on next load (or remove cached data in case of normal account changed to forward account)
$postfixConfig = new iMSCP_Config_Handler_File(
utils_normalizePath(iMSCP_Registry::get('config')->CONF_DIR . '/postfix/postfix.data')
);
list($user, $domain) = explode('@', $mailAddr);
unset($_SESSION['maildirsize'][utils_normalizePath($postfixConfig['MTA_VIRTUAL_MAIL_DIR'] . "/$domain/$user/maildirsize")]);

iMSCP_Events_Aggregator::getInstance()->dispatch(iMSCP_Events::onAfterEditMail, array(
'mailId' => $mailData['mail_id']
));
Expand Down
15 changes: 12 additions & 3 deletions gui/themes/default/client/mail_accounts.tpl
Expand Up @@ -102,10 +102,19 @@
</tr>
<!-- EDP: mail_item -->
</tbody>
<tbody>
<tr>
<td colspan="6">
<div class="buttons">
<!-- BDP: sync_quota_info_link -->
<a href="/client/mail_accounts.php?sync_quota_info=1" title="{TR_SYNC_QUOTA_TOOLTIP}" class="link_as_button">{TR_SYNC_QUOTA_INFO}</a>
<!-- EDP: sync_quota_info_link -->
<input type="submit" name="Submit" value="{TR_DELETE_SELECTED_ITEMS}">
</div>
</td>
</tr>
</tbody>
</table>
<div class="buttons">
<input type="submit" name="Submit" value="{TR_DELETE_SELECTED_ITEMS}">
</div>
</form>
<!-- EDP: mail_items -->
<!-- EDP: mail_feature -->

0 comments on commit a7e0879

Please sign in to comment.