Add background mail checking and new mail notifications
This commit is contained in:
@@ -46,6 +46,22 @@ CREATE TABLE IF NOT EXISTS `ident_switch`
|
|||||||
smallint
|
smallint
|
||||||
NOT NULL
|
NOT NULL
|
||||||
DEFAULT 1,
|
DEFAULT 1,
|
||||||
|
`notify_check`
|
||||||
|
smallint
|
||||||
|
NOT NULL
|
||||||
|
DEFAULT 1,
|
||||||
|
`notify_basic`
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL,
|
||||||
|
`notify_sound`
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL,
|
||||||
|
`notify_desktop`
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL,
|
||||||
|
`notify_sound_url`
|
||||||
|
varchar(255)
|
||||||
|
DEFAULT NULL,
|
||||||
`drafts_mbox`
|
`drafts_mbox`
|
||||||
varchar(64),
|
varchar(64),
|
||||||
`sent_mbox`
|
`sent_mbox`
|
||||||
|
|||||||
30
SQL/mysql/2026021002.sql
Normal file
30
SQL/mysql/2026021002.sql
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
ALTER TABLE `ident_switch`
|
||||||
|
ADD `notify_check`
|
||||||
|
smallint
|
||||||
|
NOT NULL
|
||||||
|
DEFAULT 1
|
||||||
|
AFTER `sieve_auth`;
|
||||||
|
|
||||||
|
ALTER TABLE `ident_switch`
|
||||||
|
ADD `notify_basic`
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL
|
||||||
|
AFTER `notify_check`;
|
||||||
|
|
||||||
|
ALTER TABLE `ident_switch`
|
||||||
|
ADD `notify_sound`
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL
|
||||||
|
AFTER `notify_basic`;
|
||||||
|
|
||||||
|
ALTER TABLE `ident_switch`
|
||||||
|
ADD `notify_desktop`
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL
|
||||||
|
AFTER `notify_sound`;
|
||||||
|
|
||||||
|
ALTER TABLE `ident_switch`
|
||||||
|
ADD `notify_sound_url`
|
||||||
|
varchar(255)
|
||||||
|
DEFAULT NULL
|
||||||
|
AFTER `notify_desktop`;
|
||||||
@@ -47,6 +47,22 @@ CREATE TABLE ident_switch
|
|||||||
smallint
|
smallint
|
||||||
NOT NULL
|
NOT NULL
|
||||||
DEFAULT(1),
|
DEFAULT(1),
|
||||||
|
notify_check
|
||||||
|
smallint
|
||||||
|
NOT NULL
|
||||||
|
DEFAULT(1),
|
||||||
|
notify_basic
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL,
|
||||||
|
notify_sound
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL,
|
||||||
|
notify_desktop
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL,
|
||||||
|
notify_sound_url
|
||||||
|
varchar(255)
|
||||||
|
DEFAULT NULL,
|
||||||
drafts_mbox
|
drafts_mbox
|
||||||
varchar(64),
|
varchar(64),
|
||||||
sent_mbox
|
sent_mbox
|
||||||
|
|||||||
25
SQL/postgres/2026021002.sql
Normal file
25
SQL/postgres/2026021002.sql
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_check
|
||||||
|
smallint
|
||||||
|
NOT NULL
|
||||||
|
DEFAULT 1;
|
||||||
|
|
||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_basic
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_sound
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_desktop
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_sound_url
|
||||||
|
varchar(255)
|
||||||
|
DEFAULT NULL;
|
||||||
@@ -47,6 +47,22 @@ CREATE TABLE ident_switch
|
|||||||
smallint
|
smallint
|
||||||
NOT NULL
|
NOT NULL
|
||||||
DEFAULT 1,
|
DEFAULT 1,
|
||||||
|
notify_check
|
||||||
|
smallint
|
||||||
|
NOT NULL
|
||||||
|
DEFAULT 1,
|
||||||
|
notify_basic
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL,
|
||||||
|
notify_sound
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL,
|
||||||
|
notify_desktop
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL,
|
||||||
|
notify_sound_url
|
||||||
|
varchar(255)
|
||||||
|
DEFAULT NULL,
|
||||||
drafts_mbox
|
drafts_mbox
|
||||||
varchar(64),
|
varchar(64),
|
||||||
sent_mbox
|
sent_mbox
|
||||||
|
|||||||
25
SQL/sqlite/2026021002.sql
Normal file
25
SQL/sqlite/2026021002.sql
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_check
|
||||||
|
smallint
|
||||||
|
NOT NULL
|
||||||
|
DEFAULT 1;
|
||||||
|
|
||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_basic
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_sound
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_desktop
|
||||||
|
smallint
|
||||||
|
DEFAULT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE ident_switch
|
||||||
|
ADD notify_sound_url
|
||||||
|
varchar(255)
|
||||||
|
DEFAULT NULL;
|
||||||
@@ -50,3 +50,23 @@ $config['ident_switch.preconfig'] = [
|
|||||||
'user' => 'mbox',
|
'user' => 'mbox',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enable background mail checking across secondary accounts.
|
||||||
|
* Shows unread counts in the account switcher and a badge for new messages.
|
||||||
|
* Default: true.
|
||||||
|
*/
|
||||||
|
$config['ident_switch.check_mail'] = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Round-robin mode: check one identity per refresh cycle instead of all.
|
||||||
|
* Useful with many accounts to reduce IMAP connections per refresh.
|
||||||
|
* Default: false (all identities checked every cycle).
|
||||||
|
*/
|
||||||
|
$config['ident_switch.round_robin'] = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hide the warning shown to users when the newmail_notifier plugin is not active.
|
||||||
|
* Default: false (warning is displayed).
|
||||||
|
*/
|
||||||
|
$config['ident_switch.hide_notifier_warning'] = false;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* ident_switch - Account switcher UI component.
|
* ident_switch - Account switcher UI and new mail notifications.
|
||||||
|
*
|
||||||
|
* Places the hidden <select> from the footer into the appropriate
|
||||||
|
* skin location (Larry, Classic, Elastic), shows it, and registers
|
||||||
|
* notification listeners for background mail checking.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2016-2018 Boris Gulay
|
* Copyright (C) 2016-2018 Boris Gulay
|
||||||
|
* Copyright (C) 2026 Gecka
|
||||||
*
|
*
|
||||||
* Original code licensed under GPL-3.0+.
|
* Original code licensed under GPL-3.0+.
|
||||||
* New contributions licensed under AGPL-3.0+.
|
* New contributions licensed under AGPL-3.0+.
|
||||||
@@ -10,69 +15,258 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
$sw = $('#plugin-ident_switch-account');
|
var $wrapper = $('#ident-switch-wrapper');
|
||||||
isOk = false;
|
if (!$wrapper.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (rcmail.env['skin']) {
|
var $sw = $wrapper.find('#plugin-ident_switch-account');
|
||||||
|
var placed = false;
|
||||||
|
|
||||||
|
switch (rcmail.env.skin) {
|
||||||
case 'larry':
|
case 'larry':
|
||||||
isOk = plugin_switchIdent_addCbLarry($sw);
|
placed = plugin_switchIdent_addCbLarry($wrapper, $sw);
|
||||||
break;
|
break;
|
||||||
case 'classic':
|
case 'classic':
|
||||||
isOk = plugin_switchIdent_addCbClassic($sw);
|
placed = plugin_switchIdent_addCbClassic($wrapper, $sw);
|
||||||
break;
|
break;
|
||||||
case 'elastic':
|
case 'elastic':
|
||||||
isOk = plugin_switchIdent_addCbElastic($sw);
|
placed = plugin_switchIdent_addCbElastic($wrapper, $sw);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOk)
|
if (!placed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$sw.show();
|
$sw.show();
|
||||||
|
|
||||||
|
// Store original option texts for badge appending
|
||||||
|
$sw.find('option').each(function() {
|
||||||
|
$(this).data('orig-text', $(this).text());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register server-side notification listeners
|
||||||
|
rcmail.addEventListener('plugin.ident_switch.update_counts', ident_switch_updateCounts);
|
||||||
|
rcmail.addEventListener('plugin.ident_switch.notify', ident_switch_onNotify);
|
||||||
|
|
||||||
|
// Apply initial counts from page load
|
||||||
|
if (rcmail.env.ident_switch_initial_counts) {
|
||||||
|
ident_switch_updateCounts(rcmail.env.ident_switch_initial_counts);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function plugin_switchIdent_addCbLarry($sw) {
|
/**
|
||||||
var $truName = $('.topright .username');
|
* Place switcher in Larry skin: replace username in top-right corner.
|
||||||
if ($truName.length > 0) {
|
*/
|
||||||
if ($sw.length > 0) {
|
function plugin_switchIdent_addCbLarry($wrapper, $sw) {
|
||||||
$sw.prependTo('.topright');
|
var $topRight = $('#topline .topright');
|
||||||
$truName.hide();
|
if (!$topRight.length) {
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sw.css({
|
||||||
|
'font-weight': 'bold',
|
||||||
|
'max-width': '200px',
|
||||||
|
'text-overflow': 'ellipsis'
|
||||||
|
});
|
||||||
|
|
||||||
|
$topRight.find('.username').hide();
|
||||||
|
$topRight.prepend($wrapper);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function plugin_switchIdent_addCbClassic($sw) {
|
/**
|
||||||
|
* Place switcher in Classic skin: prepend to task bar.
|
||||||
|
*/
|
||||||
|
function plugin_switchIdent_addCbClassic($wrapper) {
|
||||||
var $taskBar = $('#taskbar');
|
var $taskBar = $('#taskbar');
|
||||||
if ($taskBar.length > 0) {
|
if (!$taskBar.length) {
|
||||||
$taskBar.prepend($sw);
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
$taskBar.prepend($wrapper);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function plugin_switchIdent_addCbElastic($sw) {
|
/**
|
||||||
var $taskBar = $('.header-title.username');
|
* Place switcher in Elastic skin: replace username in header.
|
||||||
$sw.css("background-color", "transparent").css("border","none");
|
*/
|
||||||
$sw.css("background-position-x","left 0.75rem").css("padding","0 0 0 2rem");
|
function plugin_switchIdent_addCbElastic($wrapper, $sw) {
|
||||||
$sw.css("font-weight","bold").css("box-shadow","none");
|
var $target = $('.header-title.username');
|
||||||
if ($taskBar.length > 0) {
|
if (!$target.length) {
|
||||||
$taskBar.prepend($sw);
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
$sw.css({
|
||||||
|
'background': 'transparent',
|
||||||
|
'border': 'none',
|
||||||
|
'font-weight': 'bold',
|
||||||
|
'color': 'inherit',
|
||||||
|
'box-shadow': 'none',
|
||||||
|
'max-width': '200px',
|
||||||
|
'text-overflow': 'ellipsis',
|
||||||
|
'padding': '0 0.25em'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide original username text and elements
|
||||||
|
$target.contents().filter(function() {
|
||||||
|
return this.nodeType === 3;
|
||||||
|
}).remove();
|
||||||
|
$target.children().not('#ident-switch-wrapper').hide();
|
||||||
|
|
||||||
|
$target.prepend($wrapper);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform account switch via AJAX (called from <select> onchange).
|
||||||
|
*/
|
||||||
function plugin_switchIdent_switch(val) {
|
function plugin_switchIdent_switch(val) {
|
||||||
rcmail.env.unread_counts = {};
|
rcmail.env.unread_counts = {};
|
||||||
console.log(rcmail.env.unread_counts);
|
rcmail.http_post('plugin.ident_switch.switch', {
|
||||||
rcmail.http_post('plugin.ident_switch.switch', { '_ident-id': val, '_mbox': rcmail.env.mailbox });
|
'_ident-id': val,
|
||||||
|
'_mbox': rcmail.env.mailbox
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function plugin_switchIdent_fixIdent(iid) {
|
/**
|
||||||
if (parseInt(iid) > 0)
|
* Update per-account counts in select options and total badge.
|
||||||
$("#_from").val(iid);
|
*
|
||||||
|
* Each entry in data is {unseen: N, baseline: B}.
|
||||||
|
* - Option text: "account (baseline+delta)" when delta > 0, else "account (unseen)"
|
||||||
|
* - Badge: sum of deltas across all accounts (new messages not yet visited).
|
||||||
|
*
|
||||||
|
* @param {Object} data - Map of iid to {unseen, baseline}.
|
||||||
|
*/
|
||||||
|
function ident_switch_updateCounts(data) {
|
||||||
|
var map = rcmail.env.ident_switch_iid_map || {};
|
||||||
|
var $select = $('#plugin-ident_switch-account');
|
||||||
|
var totalDelta = 0;
|
||||||
|
|
||||||
|
// Reset all options to original text
|
||||||
|
$select.find('option').each(function() {
|
||||||
|
var orig = $(this).data('orig-text');
|
||||||
|
if (orig) {
|
||||||
|
$(this).text(orig);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// On mail task, skip count for the active account (already shown in folder list)
|
||||||
|
var selectedVal = rcmail.env.task === 'mail' ? $select.val() : null;
|
||||||
|
|
||||||
|
// Update each option with its count
|
||||||
|
for (var iid in data) {
|
||||||
|
if (!data.hasOwnProperty(iid)) continue;
|
||||||
|
var info = data[iid];
|
||||||
|
var unseen = parseInt(info.unseen) || 0;
|
||||||
|
var baseline = parseInt(info.baseline) || 0;
|
||||||
|
var delta = Math.max(0, unseen - baseline);
|
||||||
|
|
||||||
|
totalDelta += delta;
|
||||||
|
|
||||||
|
if (map[iid] === undefined) continue;
|
||||||
|
var selectVal = '' + map[iid];
|
||||||
|
var $opt = $select.find('option[value="' + selectVal + '"]');
|
||||||
|
if (!$opt.length) continue;
|
||||||
|
|
||||||
|
// Skip active account on mail task
|
||||||
|
if (selectVal === selectedVal) continue;
|
||||||
|
|
||||||
|
var suffix;
|
||||||
|
if (delta > 0) {
|
||||||
|
suffix = ' (' + baseline + '+' + delta + ')';
|
||||||
|
} else if (unseen > 0) {
|
||||||
|
suffix = ' (' + unseen + ')';
|
||||||
|
} else {
|
||||||
|
suffix = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suffix) {
|
||||||
|
$opt.text($opt.data('orig-text') + suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update total badge (only new messages not yet visited)
|
||||||
|
var $badge = $('#ident-switch-badge');
|
||||||
|
if (totalDelta > 0) {
|
||||||
|
$badge.text(totalDelta).show();
|
||||||
|
} else {
|
||||||
|
$badge.hide().text('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle new mail notification from server.
|
||||||
|
* @param {Object} data - {iid, label, count, basic, sound, desktop}
|
||||||
|
*/
|
||||||
|
function ident_switch_onNotify(data) {
|
||||||
|
if (data.basic) {
|
||||||
|
ident_switch_notifyBasic();
|
||||||
|
}
|
||||||
|
if (data.sound) {
|
||||||
|
ident_switch_notifySound();
|
||||||
|
}
|
||||||
|
if (data.desktop) {
|
||||||
|
ident_switch_notifyDesktop(data.label, data.count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic notification: change page title.
|
||||||
|
*/
|
||||||
|
function ident_switch_notifyBasic() {
|
||||||
|
var marker = '(*) ';
|
||||||
|
if (document.title.indexOf(marker) !== 0) {
|
||||||
|
document.title = marker + document.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sound notification: play newmail_notifier sound.
|
||||||
|
*/
|
||||||
|
function ident_switch_notifySound() {
|
||||||
|
var src = rcmail.assets_path('plugins/newmail_notifier/sound');
|
||||||
|
try {
|
||||||
|
new Audio(src + '.mp3').play().catch(function() {
|
||||||
|
new Audio(src + '.wav').play().catch(function() {});
|
||||||
|
});
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Desktop notification via Notification API.
|
||||||
|
*/
|
||||||
|
function ident_switch_notifyDesktop(label, count) {
|
||||||
|
if (!('Notification' in window)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Notification.permission === 'default') {
|
||||||
|
Notification.requestPermission();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Notification.permission !== 'granted') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = count + ' unread message' + (count > 1 ? 's' : '');
|
||||||
|
var popup = new Notification('New mail — ' + label, {
|
||||||
|
body: body,
|
||||||
|
tag: 'ident_switch_' + label,
|
||||||
|
icon: rcmail.assets_path('plugins/ident_switch/mail.png')
|
||||||
|
});
|
||||||
|
|
||||||
|
var timeout = (rcmail.env.newmail_notifier_timeout || 10) * 1000;
|
||||||
|
setTimeout(function() { popup.close(); }, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix identity selection in compose view when impersonating.
|
||||||
|
*/
|
||||||
|
function plugin_switchIdent_fixIdent(iid) {
|
||||||
|
if (parseInt(iid) > 0) {
|
||||||
|
$('#_from').val(iid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
ident_switch.css
Normal file
32
ident_switch.css
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* ident_switch - Account switcher styles.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Gecka
|
||||||
|
*
|
||||||
|
* Licensed under AGPL-3.0+.
|
||||||
|
*
|
||||||
|
* @url https://github.com/Gecka-apps/ident_switch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Wrapper around <select> + badge */
|
||||||
|
.ident-switch-wrapper {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unread badge */
|
||||||
|
.ident-switch-badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 1.4em;
|
||||||
|
padding: 0.2em 0.5em;
|
||||||
|
font-size: 0.7em;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #e74c3c;
|
||||||
|
border-radius: 1em;
|
||||||
|
margin-left: 0.3em;
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
require_once __DIR__ . '/lib/IdentSwitchPreconfig.php';
|
require_once __DIR__ . '/lib/IdentSwitchPreconfig.php';
|
||||||
require_once __DIR__ . '/lib/IdentSwitchForm.php';
|
require_once __DIR__ . '/lib/IdentSwitchForm.php';
|
||||||
require_once __DIR__ . '/lib/IdentSwitchSwitcher.php';
|
require_once __DIR__ . '/lib/IdentSwitchSwitcher.php';
|
||||||
|
require_once __DIR__ . '/lib/IdentSwitchChecker.php';
|
||||||
|
|
||||||
class ident_switch extends rcube_plugin
|
class ident_switch extends rcube_plugin
|
||||||
{
|
{
|
||||||
@@ -47,9 +48,16 @@ class ident_switch extends rcube_plugin
|
|||||||
/** @var int Sieve authentication: no authentication required. */
|
/** @var int Sieve authentication: no authentication required. */
|
||||||
public const SIEVE_AUTH_NONE = 2;
|
public const SIEVE_AUTH_NONE = 2;
|
||||||
|
|
||||||
|
/** @var int Notification checking: enabled. */
|
||||||
|
public const NOTIFY_CHECK_ENABLED = 1;
|
||||||
|
|
||||||
|
/** @var int Notification checking: disabled. */
|
||||||
|
public const NOTIFY_CHECK_DISABLED = 0;
|
||||||
|
|
||||||
private IdentSwitchForm $form;
|
private IdentSwitchForm $form;
|
||||||
private IdentSwitchSwitcher $switcher;
|
private IdentSwitchSwitcher $switcher;
|
||||||
private IdentSwitchPreconfig $preconfig;
|
private IdentSwitchPreconfig $preconfig;
|
||||||
|
private IdentSwitchChecker $checker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize plugin: register hooks, actions, and save default folder config.
|
* Initialize plugin: register hooks, actions, and save default folder config.
|
||||||
@@ -59,9 +67,11 @@ class ident_switch extends rcube_plugin
|
|||||||
$this->form = new IdentSwitchForm($this);
|
$this->form = new IdentSwitchForm($this);
|
||||||
$this->switcher = new IdentSwitchSwitcher();
|
$this->switcher = new IdentSwitchSwitcher();
|
||||||
$this->preconfig = new IdentSwitchPreconfig($this);
|
$this->preconfig = new IdentSwitchPreconfig($this);
|
||||||
|
$this->checker = new IdentSwitchChecker();
|
||||||
|
|
||||||
$this->add_hook('startup', [$this, 'on_startup']);
|
$this->add_hook('startup', [$this, 'on_startup']);
|
||||||
$this->add_hook('render_page', [$this, 'on_render_page']);
|
$this->add_hook('render_page', [$this, 'on_render_page']);
|
||||||
|
$this->add_hook('refresh', [$this, 'on_refresh']);
|
||||||
$this->add_hook('smtp_connect', [$this, 'on_smtp_connect']);
|
$this->add_hook('smtp_connect', [$this, 'on_smtp_connect']);
|
||||||
$this->add_hook('managesieve_connect', [$this, 'on_managesieve_connect']);
|
$this->add_hook('managesieve_connect', [$this, 'on_managesieve_connect']);
|
||||||
$this->add_hook('identity_form', [$this, 'on_identity_form']);
|
$this->add_hook('identity_form', [$this, 'on_identity_form']);
|
||||||
@@ -75,6 +85,8 @@ class ident_switch extends rcube_plugin
|
|||||||
|
|
||||||
$this->register_action('plugin.ident_switch.switch', [$this, 'on_switch']);
|
$this->register_action('plugin.ident_switch.switch', [$this, 'on_switch']);
|
||||||
|
|
||||||
|
$this->load_config();
|
||||||
|
|
||||||
$rc = rcmail::get_instance();
|
$rc = rcmail::get_instance();
|
||||||
foreach (rcube_storage::$folder_types as $type) {
|
foreach (rcube_storage::$folder_types as $type) {
|
||||||
$key = $type . '_mbox_default' . self::MY_POSTFIX;
|
$key = $type . '_mbox_default' . self::MY_POSTFIX;
|
||||||
@@ -131,6 +143,10 @@ class ident_switch extends rcube_plugin
|
|||||||
default => null,
|
default => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ($rc->task === 'mail') {
|
||||||
|
$this->include_stylesheet('ident_switch.css');
|
||||||
|
}
|
||||||
|
|
||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +154,7 @@ class ident_switch extends rcube_plugin
|
|||||||
* Render the account switcher dropdown in the mail view.
|
* Render the account switcher dropdown in the mail view.
|
||||||
*
|
*
|
||||||
* Queries the database for all enabled alternative accounts and generates
|
* Queries the database for all enabled alternative accounts and generates
|
||||||
* an HTML select element that is injected into the page footer.
|
* an HTML select element with an unread badge, injected into the page footer.
|
||||||
*
|
*
|
||||||
* @param rcmail $rc Roundcube instance.
|
* @param rcmail $rc Roundcube instance.
|
||||||
* @param array $args Hook arguments for page rendering.
|
* @param array $args Hook arguments for page rendering.
|
||||||
@@ -160,6 +176,7 @@ class ident_switch extends rcube_plugin
|
|||||||
$accNames = [$_SESSION['global_alias'] ?? $rc->user->data['username']];
|
$accNames = [$_SESSION['global_alias'] ?? $rc->user->data['username']];
|
||||||
$accValues = [-1];
|
$accValues = [-1];
|
||||||
$accSelected = -1;
|
$accSelected = -1;
|
||||||
|
$iidMap = [0 => -1]; // primary account: iid 0 → select value -1
|
||||||
|
|
||||||
// Get list of alternative accounts
|
// Get list of alternative accounts
|
||||||
$sql = "SELECT "
|
$sql = "SELECT "
|
||||||
@@ -171,15 +188,14 @@ class ident_switch extends rcube_plugin
|
|||||||
$qRec = $rc->db->query($sql, $rc->user->data['user_id'], self::DB_ENABLED);
|
$qRec = $rc->db->query($sql, $rc->user->data['user_id'], self::DB_ENABLED);
|
||||||
while ($r = $rc->db->fetch_assoc($qRec)) {
|
while ($r = $rc->db->fetch_assoc($qRec)) {
|
||||||
$accValues[] = $r['id'];
|
$accValues[] = $r['id'];
|
||||||
|
$iidMap[$r['iid']] = $r['id'];
|
||||||
if ($iid == $r['iid']) {
|
if ($iid == $r['iid']) {
|
||||||
$accSelected = $r['id'];
|
$accSelected = $r['id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make label
|
|
||||||
$lbl = $r['label'];
|
$lbl = $r['label'];
|
||||||
if (!$lbl) {
|
if (!$lbl) {
|
||||||
$username = $r['username'] ?: $r['email'];
|
$username = $r['username'] ?: $r['email'];
|
||||||
|
|
||||||
$lbl = str_contains($username, '@')
|
$lbl = str_contains($username, '@')
|
||||||
? $username
|
? $username
|
||||||
: $username . '@' . ($r['host'] ?: 'localhost');
|
: $username . '@' . ($r['host'] ?: 'localhost');
|
||||||
@@ -187,18 +203,60 @@ class ident_switch extends rcube_plugin
|
|||||||
$accNames[] = rcube::Q($lbl);
|
$accNames[] = rcube::Q($lbl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render UI if user has extra accounts
|
if (count($accValues) <= 1) {
|
||||||
if (count($accValues) > 1) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->include_script('ident_switch-switch.js');
|
$this->include_script('ident_switch-switch.js');
|
||||||
|
|
||||||
|
// Pass config to JS environment
|
||||||
|
$rc->output->set_env('ident_switch_iid_map', $iidMap);
|
||||||
|
|
||||||
$select = new html_select([
|
$select = new html_select([
|
||||||
'id' => 'plugin-ident_switch-account',
|
'id' => 'plugin-ident_switch-account',
|
||||||
'style' => 'display: none; padding: 0;',
|
'style' => 'display: none; padding: 0;',
|
||||||
'onchange' => 'plugin_switchIdent_switch(this.value);',
|
'onchange' => 'plugin_switchIdent_switch(this.value);',
|
||||||
]);
|
]);
|
||||||
$select->add($accNames, $accValues);
|
$select->add($accNames, $accValues);
|
||||||
$rc->output->add_footer($select->show([$accSelected]));
|
|
||||||
|
$html = '<span id="ident-switch-wrapper" class="ident-switch-wrapper">'
|
||||||
|
. $select->show([$accSelected])
|
||||||
|
. '<span id="ident-switch-badge" class="ident-switch-badge" style="display:none"></span>'
|
||||||
|
. '</span>';
|
||||||
|
|
||||||
|
$rc->output->add_footer($html);
|
||||||
|
|
||||||
|
if (!$rc->config->get('ident_switch.check_mail', true)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run initial check and pass counts via env
|
||||||
|
$this->checker->check_new_mail([]);
|
||||||
|
$counts = $_SESSION['ident_switch_counts'] ?? [];
|
||||||
|
$initialCounts = [];
|
||||||
|
foreach ($counts as $cIid => $info) {
|
||||||
|
$initialCounts[$cIid] = [
|
||||||
|
'unseen' => $info['unseen'],
|
||||||
|
'baseline' => $info['baseline'] ?? $info['unseen'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$rc->output->set_env('ident_switch_initial_counts', $initialCounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle refresh hook: check new mail on secondary identities.
|
||||||
|
*
|
||||||
|
* @param array $args Hook arguments (empty for refresh).
|
||||||
|
* @return array Unmodified hook arguments.
|
||||||
|
*/
|
||||||
|
public function on_refresh(array $args): array
|
||||||
|
{
|
||||||
|
$rc = rcmail::get_instance();
|
||||||
|
if (!$rc->config->get('ident_switch.check_mail', true)) {
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->checker->check_new_mail($args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
269
lib/IdentSwitchChecker.php
Normal file
269
lib/IdentSwitchChecker.php
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ident_switch - Background mail checker.
|
||||||
|
*
|
||||||
|
* Checks new mail across secondary identities during the refresh
|
||||||
|
* cycle and sends unread counts + notifications to the client.
|
||||||
|
* When impersonating a secondary account, also checks the primary account.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Gecka
|
||||||
|
*
|
||||||
|
* Licensed under AGPL-3.0+.
|
||||||
|
*
|
||||||
|
* @url https://github.com/Gecka-apps/ident_switch
|
||||||
|
*/
|
||||||
|
class IdentSwitchChecker
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called on each refresh cycle.
|
||||||
|
*
|
||||||
|
* Builds the list of identities to check (excluding the currently active one,
|
||||||
|
* including the primary account when impersonating), then checks them
|
||||||
|
* in round-robin or all-at-once mode.
|
||||||
|
*
|
||||||
|
* @param array $args Hook arguments (empty for refresh hook).
|
||||||
|
* @return array Unmodified hook arguments.
|
||||||
|
*/
|
||||||
|
public function check_new_mail(array $args): array
|
||||||
|
{
|
||||||
|
$rc = rcmail::get_instance();
|
||||||
|
|
||||||
|
$identities = $this->get_checkable_identities($rc);
|
||||||
|
|
||||||
|
// Exclude the currently active secondary identity (RC already checks it)
|
||||||
|
$activeIid = $_SESSION['iid' . ident_switch::MY_POSTFIX] ?? -1;
|
||||||
|
$identities = array_values(array_filter($identities, function ($id) use ($activeIid) {
|
||||||
|
return $id['iid'] != $activeIid;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// When impersonating, also check the primary account
|
||||||
|
$isImpersonating = strcasecmp($rc->user->data['username'], $_SESSION['username']) !== 0;
|
||||||
|
if ($isImpersonating) {
|
||||||
|
$primary = $this->get_primary_identity($rc);
|
||||||
|
if ($primary) {
|
||||||
|
$identities[] = $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($identities)) {
|
||||||
|
$this->send_counts($rc);
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rc->config->get('ident_switch.round_robin', false)) {
|
||||||
|
// Round-robin: check one identity per refresh cycle
|
||||||
|
$index = ($_SESSION['ident_switch_check_index'] ?? -1) + 1;
|
||||||
|
if ($index >= count($identities)) {
|
||||||
|
$index = 0;
|
||||||
|
}
|
||||||
|
$_SESSION['ident_switch_check_index'] = $index;
|
||||||
|
$this->check_identity($rc, $identities[$index]);
|
||||||
|
} else {
|
||||||
|
foreach ($identities as $identity) {
|
||||||
|
$this->check_identity($rc, $identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->send_counts($rc);
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a single identity for unseen messages and notify if new mail.
|
||||||
|
*
|
||||||
|
* @param rcmail $rc Roundcube instance.
|
||||||
|
* @param array $identity Identity record from the database.
|
||||||
|
*/
|
||||||
|
private function check_identity(rcmail $rc, array $identity): void
|
||||||
|
{
|
||||||
|
$counts = $_SESSION['ident_switch_counts'] ?? [];
|
||||||
|
$previousCount = $counts[$identity['iid']]['unseen'] ?? 0;
|
||||||
|
|
||||||
|
$count = $this->check_unseen($rc, $identity, $previousCount);
|
||||||
|
|
||||||
|
ident_switch::write_log("Check identity {$identity['iid']} ({$identity['email']}): unseen={$count}, previous={$previousCount}");
|
||||||
|
|
||||||
|
// Set baseline on first check; preserve it across subsequent checks
|
||||||
|
$baseline = $counts[$identity['iid']]['baseline'] ?? $count;
|
||||||
|
|
||||||
|
$counts[$identity['iid']] = [
|
||||||
|
'unseen' => $count,
|
||||||
|
'baseline' => $baseline,
|
||||||
|
'checked_at' => time(),
|
||||||
|
];
|
||||||
|
$_SESSION['ident_switch_counts'] = $counts;
|
||||||
|
|
||||||
|
if ($count > $previousCount) {
|
||||||
|
$this->send_notification($rc, $identity, $count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to an identity's IMAP server and return INBOX unseen count.
|
||||||
|
*
|
||||||
|
* @param rcmail $rc Roundcube instance.
|
||||||
|
* @param array $identity Identity DB record.
|
||||||
|
* @param int $previousCount Previous unseen count (returned on error).
|
||||||
|
* @return int Unseen message count.
|
||||||
|
*/
|
||||||
|
private function check_unseen(rcmail $rc, array $identity, int $previousCount): int
|
||||||
|
{
|
||||||
|
$imap = new rcube_imap_generic();
|
||||||
|
|
||||||
|
$host = $identity['imap_host'] ?: 'localhost';
|
||||||
|
$port = $identity['imap_port'] ?: 143;
|
||||||
|
$ssl = false;
|
||||||
|
|
||||||
|
if (!empty($identity['flags']) && ($identity['flags'] & ident_switch::DB_SECURE_IMAP_TLS)) {
|
||||||
|
$ssl = 'tls';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip and parse protocol prefix from host
|
||||||
|
$hostLower = strtolower($host);
|
||||||
|
if (str_starts_with($hostLower, 'tls://')) {
|
||||||
|
$ssl = 'tls';
|
||||||
|
$host = substr($host, 6);
|
||||||
|
} elseif (str_starts_with($hostLower, 'ssl://')) {
|
||||||
|
$ssl = 'ssl';
|
||||||
|
$host = substr($host, 6);
|
||||||
|
if (!$identity['imap_port']) {
|
||||||
|
$port = 993;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = $identity['username'] ?: $identity['email'];
|
||||||
|
$password = $rc->decrypt($identity['password']);
|
||||||
|
|
||||||
|
$result = $imap->connect($host, $username, $password, [
|
||||||
|
'port' => $port,
|
||||||
|
'ssl_mode' => $ssl,
|
||||||
|
'timeout' => 5,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
ident_switch::write_log("Failed to check mail for identity {$identity['iid']}: " . $imap->error);
|
||||||
|
return $previousCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = $imap->status('INBOX', ['UNSEEN']);
|
||||||
|
$unseen = $status['UNSEEN'] ?? 0;
|
||||||
|
|
||||||
|
$imap->closeConnection();
|
||||||
|
|
||||||
|
return $unseen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a virtual identity record for the primary account.
|
||||||
|
*
|
||||||
|
* When the user has switched to a secondary account, the primary account's
|
||||||
|
* connection details are saved in session with the MY_POSTFIX suffix.
|
||||||
|
*
|
||||||
|
* @param rcmail $rc Roundcube instance.
|
||||||
|
* @return array|null Identity-like array, or null if session data is missing.
|
||||||
|
*/
|
||||||
|
private function get_primary_identity(rcmail $rc): ?array
|
||||||
|
{
|
||||||
|
$postfix = ident_switch::MY_POSTFIX;
|
||||||
|
|
||||||
|
if (!isset($_SESSION['password' . $postfix])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = $_SESSION['storage_host' . $postfix] ?? 'localhost';
|
||||||
|
$port = $_SESSION['storage_port' . $postfix] ?? 143;
|
||||||
|
$ssl = $_SESSION['storage_ssl' . $postfix] ?? null;
|
||||||
|
|
||||||
|
// Prepend protocol prefix so check_unseen() can parse it
|
||||||
|
if ($ssl === 'ssl' && !str_starts_with(strtolower($host), 'ssl://')) {
|
||||||
|
$host = 'ssl://' . $host;
|
||||||
|
} elseif ($ssl === 'tls' && !str_starts_with(strtolower($host), 'tls://')) {
|
||||||
|
$host = 'tls://' . $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'iid' => 0,
|
||||||
|
'imap_host' => $host,
|
||||||
|
'imap_port' => $port,
|
||||||
|
'flags' => 0,
|
||||||
|
'username' => $rc->user->data['username'],
|
||||||
|
'password' => $_SESSION['password' . $postfix],
|
||||||
|
'email' => $rc->user->data['username'],
|
||||||
|
'label' => $_SESSION['global_alias'] ?? $rc->user->data['username'],
|
||||||
|
'notify_basic' => null,
|
||||||
|
'notify_sound' => null,
|
||||||
|
'notify_desktop' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all enabled identities that have mail checking enabled.
|
||||||
|
*
|
||||||
|
* @param rcmail $rc Roundcube instance.
|
||||||
|
* @return array List of identity records.
|
||||||
|
*/
|
||||||
|
private function get_checkable_identities(rcmail $rc): array
|
||||||
|
{
|
||||||
|
$sql = 'SELECT isw.iid, isw.imap_host, isw.imap_port, isw.flags, '
|
||||||
|
. 'isw.username, isw.password, isw.label, '
|
||||||
|
. 'isw.notify_basic, isw.notify_sound, isw.notify_desktop, '
|
||||||
|
. 'ii.email '
|
||||||
|
. 'FROM ' . $rc->db->table_name(ident_switch::TABLE) . ' isw '
|
||||||
|
. 'INNER JOIN ' . $rc->db->table_name('identities') . ' ii ON isw.iid = ii.identity_id '
|
||||||
|
. 'WHERE isw.user_id = ? AND isw.flags & ? > 0 AND isw.notify_check = ?';
|
||||||
|
|
||||||
|
$q = $rc->db->query($sql, $rc->user->ID, ident_switch::DB_ENABLED, ident_switch::NOTIFY_CHECK_ENABLED);
|
||||||
|
|
||||||
|
$identities = [];
|
||||||
|
while ($r = $rc->db->fetch_assoc($q)) {
|
||||||
|
$identities[] = $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $identities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send all cached unseen counts to client JS.
|
||||||
|
*
|
||||||
|
* @param rcmail $rc Roundcube instance.
|
||||||
|
*/
|
||||||
|
private function send_counts(rcmail $rc): void
|
||||||
|
{
|
||||||
|
$counts = $_SESSION['ident_switch_counts'] ?? [];
|
||||||
|
$data = [];
|
||||||
|
foreach ($counts as $iid => $info) {
|
||||||
|
$data[$iid] = [
|
||||||
|
'unseen' => $info['unseen'],
|
||||||
|
'baseline' => $info['baseline'] ?? $info['unseen'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$rc->output->command('plugin.ident_switch.update_counts', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send notification command to client for a specific identity.
|
||||||
|
*
|
||||||
|
* @param rcmail $rc Roundcube instance.
|
||||||
|
* @param array $identity Identity record.
|
||||||
|
* @param int $count New unseen count.
|
||||||
|
*/
|
||||||
|
private function send_notification(rcmail $rc, array $identity, int $count): void
|
||||||
|
{
|
||||||
|
$basic = $identity['notify_basic'] ?? $rc->config->get('newmail_notifier_basic', false);
|
||||||
|
$sound = $identity['notify_sound'] ?? $rc->config->get('newmail_notifier_sound', false);
|
||||||
|
$desktop = $identity['notify_desktop'] ?? $rc->config->get('newmail_notifier_desktop', false);
|
||||||
|
|
||||||
|
$label = $identity['label'] ?: $identity['email'];
|
||||||
|
|
||||||
|
$rc->output->command('plugin.ident_switch.notify', [
|
||||||
|
'iid' => $identity['iid'],
|
||||||
|
'label' => $label,
|
||||||
|
'count' => $count,
|
||||||
|
'basic' => (bool)$basic,
|
||||||
|
'sound' => (bool)$sound,
|
||||||
|
'desktop' => (bool)$desktop,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,6 +100,48 @@ class IdentSwitchForm
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the notification form fields for identity settings.
|
||||||
|
*
|
||||||
|
* Shows the actual newmail_notifier default value in each tri-state select.
|
||||||
|
* Selecting the default option stores NULL so changes to global defaults propagate.
|
||||||
|
*
|
||||||
|
* @param array $record Identity record data used for default values.
|
||||||
|
* @return array Form field definitions for notification preferences.
|
||||||
|
*/
|
||||||
|
public function get_notification_fields(array &$record): array
|
||||||
|
{
|
||||||
|
$rc = rcmail::get_instance();
|
||||||
|
$prefix = 'ident_switch.form.notify.';
|
||||||
|
|
||||||
|
$defaultKeys = [
|
||||||
|
'basic' => 'newmail_notifier_basic',
|
||||||
|
'sound' => 'newmail_notifier_sound',
|
||||||
|
'desktop' => 'newmail_notifier_desktop',
|
||||||
|
];
|
||||||
|
|
||||||
|
$triState = function (string $name) use ($prefix, &$record, $rc, $defaultKeys): string {
|
||||||
|
$defaultVal = $rc->config->get($defaultKeys[$name] ?? '', false);
|
||||||
|
$defaultLabel = $defaultVal
|
||||||
|
? $this->plugin->gettext('form.notify.on')
|
||||||
|
: $this->plugin->gettext('form.notify.off');
|
||||||
|
$defaultLabel .= ' (' . strtolower($this->plugin->gettext('form.notify.default')) . ')';
|
||||||
|
|
||||||
|
$select = new html_select(['name' => "_{$prefix}{$name}"]);
|
||||||
|
$select->add($defaultLabel, '');
|
||||||
|
$select->add($this->plugin->gettext('form.notify.on'), '1');
|
||||||
|
$select->add($this->plugin->gettext('form.notify.off'), '0');
|
||||||
|
return $select->show($record[$prefix . $name] ?? '');
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
$prefix . 'check' => ['type' => 'checkbox'],
|
||||||
|
$prefix . 'basic' => ['value' => $triState('basic')],
|
||||||
|
$prefix . 'sound' => ['value' => $triState('sound')],
|
||||||
|
$prefix . 'desktop' => ['value' => $triState('desktop')],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle identity_form hook: add plugin-specific fields to the identity editor.
|
* Handle identity_form hook: add plugin-specific fields to the identity editor.
|
||||||
*
|
*
|
||||||
@@ -150,6 +192,10 @@ class IdentSwitchForm
|
|||||||
'sieve_host' => 'sieve.host',
|
'sieve_host' => 'sieve.host',
|
||||||
'sieve_port' => 'sieve.port',
|
'sieve_port' => 'sieve.port',
|
||||||
'sieve_auth' => 'sieve.auth',
|
'sieve_auth' => 'sieve.auth',
|
||||||
|
'notify_check' => 'notify.check',
|
||||||
|
'notify_basic' => 'notify.basic',
|
||||||
|
'notify_sound' => 'notify.sound',
|
||||||
|
'notify_desktop' => 'notify.desktop',
|
||||||
];
|
];
|
||||||
foreach ($row as $k => $v) {
|
foreach ($row as $k => $v) {
|
||||||
if (isset($dbToForm[$k])) {
|
if (isset($dbToForm[$k])) {
|
||||||
@@ -189,6 +235,22 @@ class IdentSwitchForm
|
|||||||
'name' => $this->plugin->gettext('form.sieve.caption'),
|
'name' => $this->plugin->gettext('form.sieve.caption'),
|
||||||
'content' => $this->get_sieve_fields($record),
|
'content' => $this->get_sieve_fields($record),
|
||||||
];
|
];
|
||||||
|
if (!$rc->config->get('ident_switch.check_mail', true)) {
|
||||||
|
// Admin disabled background mail checking
|
||||||
|
} elseif ($rc->plugins->get_plugin('newmail_notifier')) {
|
||||||
|
$args['form']['ident_switch.notify'] = [
|
||||||
|
'name' => $this->plugin->gettext('form.notify.caption'),
|
||||||
|
'content' => $this->get_notification_fields($record),
|
||||||
|
];
|
||||||
|
} elseif (!$rc->config->get('ident_switch.hide_notifier_warning', false)) {
|
||||||
|
$args['form']['ident_switch.notify'] = [
|
||||||
|
'name' => $this->plugin->gettext('form.notify.caption'),
|
||||||
|
'content' => html::div(
|
||||||
|
['class' => 'boxinformation'],
|
||||||
|
rcube::Q($this->plugin->gettext('form.notify.requires_newmail_notifier'))
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
@@ -449,6 +511,18 @@ class IdentSwitchForm
|
|||||||
return $retVal;
|
return $retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notification settings
|
||||||
|
$retVal['notify.check'] = self::get_field_value('notify', 'check', false) ? 1 : 0;
|
||||||
|
|
||||||
|
$notifyBasic = self::get_field_value('notify', 'basic');
|
||||||
|
$retVal['notify.basic'] = ($notifyBasic !== null && $notifyBasic !== '') ? (int)$notifyBasic : null;
|
||||||
|
|
||||||
|
$notifySound = self::get_field_value('notify', 'sound');
|
||||||
|
$retVal['notify.sound'] = ($notifySound !== null && $notifySound !== '') ? (int)$notifySound : null;
|
||||||
|
|
||||||
|
$notifyDesktop = self::get_field_value('notify', 'desktop');
|
||||||
|
$retVal['notify.desktop'] = ($notifyDesktop !== null && $notifyDesktop !== '') ? (int)$notifyDesktop : null;
|
||||||
|
|
||||||
// Get also password
|
// Get also password
|
||||||
$retVal['imap.pass'] = self::get_field_value('imap', 'password', false, true);
|
$retVal['imap.pass'] = self::get_field_value('imap', 'password', false, true);
|
||||||
|
|
||||||
@@ -505,13 +579,19 @@ class IdentSwitchForm
|
|||||||
// Record already exists, will update it
|
// Record already exists, will update it
|
||||||
$sql = 'UPDATE ' .
|
$sql = 'UPDATE ' .
|
||||||
$rc->db->table_name(ident_switch::TABLE) .
|
$rc->db->table_name(ident_switch::TABLE) .
|
||||||
' SET flags = ?, label = ?, imap_host = ?, imap_port = ?, imap_delimiter = ?, username = ?, password = ?, smtp_host = ?, smtp_port = ?, smtp_auth = ?, sieve_host = ?, sieve_port = ?, sieve_auth = ?, user_id = ?, iid = ?' .
|
' SET flags = ?, label = ?, imap_host = ?, imap_port = ?, imap_delimiter = ?, username = ?, password = ?,' .
|
||||||
|
' smtp_host = ?, smtp_port = ?, smtp_auth = ?, sieve_host = ?, sieve_port = ?, sieve_auth = ?,' .
|
||||||
|
' notify_check = ?, notify_basic = ?, notify_sound = ?, notify_desktop = ?,' .
|
||||||
|
' user_id = ?, iid = ?' .
|
||||||
' WHERE id = ?';
|
' WHERE id = ?';
|
||||||
} elseif ($data['flags'] & ident_switch::DB_ENABLED) {
|
} elseif ($data['flags'] & ident_switch::DB_ENABLED) {
|
||||||
// No record exists, create new one
|
// No record exists, create new one
|
||||||
$sql = 'INSERT INTO ' .
|
$sql = 'INSERT INTO ' .
|
||||||
$rc->db->table_name(ident_switch::TABLE) .
|
$rc->db->table_name(ident_switch::TABLE) .
|
||||||
'(flags, label, imap_host, imap_port, imap_delimiter, username, password, smtp_host, smtp_port, smtp_auth, sieve_host, sieve_port, sieve_auth, user_id, iid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
'(flags, label, imap_host, imap_port, imap_delimiter, username, password,' .
|
||||||
|
' smtp_host, smtp_port, smtp_auth, sieve_host, sieve_port, sieve_auth,' .
|
||||||
|
' notify_check, notify_basic, notify_sound, notify_desktop,' .
|
||||||
|
' user_id, iid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -536,6 +616,10 @@ class IdentSwitchForm
|
|||||||
$data['sieve.host'],
|
$data['sieve.host'],
|
||||||
$data['sieve.port'],
|
$data['sieve.port'],
|
||||||
$data['sieve.auth'],
|
$data['sieve.auth'],
|
||||||
|
$data['notify.check'] ?? 1,
|
||||||
|
$data['notify.basic'] ?? null,
|
||||||
|
$data['notify.sound'] ?? null,
|
||||||
|
$data['notify.desktop'] ?? null,
|
||||||
$rc->user->ID,
|
$rc->user->ID,
|
||||||
$data['id'],
|
$data['id'],
|
||||||
$r['id'] ?? null
|
$r['id'] ?? null
|
||||||
|
|||||||
@@ -139,6 +139,17 @@ class IdentSwitchPreconfig
|
|||||||
$record['ident_switch.form.common.readonly'] = $loginSet ? 2 : 1;
|
$record['ident_switch.form.common.readonly'] = $loginSet ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notification defaults from preconfig
|
||||||
|
if (isset($cfg['notify_check'])) {
|
||||||
|
$record['ident_switch.form.notify.check'] = $cfg['notify_check'] ? 1 : 0;
|
||||||
|
}
|
||||||
|
foreach (['notify_basic', 'notify_sound', 'notify_desktop'] as $key) {
|
||||||
|
if (isset($cfg[$key])) {
|
||||||
|
$formKey = 'ident_switch.form.notify.' . substr($key, 7);
|
||||||
|
$record[$formKey] = $cfg[$key] === null ? '' : ($cfg[$key] ? '1' : '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (bool)($cfg['readonly'] ?? false);
|
return (bool)($cfg['readonly'] ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ class IdentSwitchSwitcher
|
|||||||
$rc->session->remove('folders');
|
$rc->session->remove('folders');
|
||||||
$rc->session->remove('unseen_count');
|
$rc->session->remove('unseen_count');
|
||||||
|
|
||||||
|
// Reset baseline for the target account so delta goes back to 0
|
||||||
|
$this->reset_baseline($identId == -1 ? 0 : null, $rc, $identId);
|
||||||
|
|
||||||
if ($identId == -1) {
|
if ($identId == -1) {
|
||||||
// Switch to main account
|
// Switch to main account
|
||||||
ident_switch::write_log('Switching mailbox back to default.');
|
ident_switch::write_log('Switching mailbox back to default.');
|
||||||
@@ -346,4 +349,34 @@ class IdentSwitchSwitcher
|
|||||||
}
|
}
|
||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the baseline for a target account so delta display resets to 0.
|
||||||
|
*
|
||||||
|
* For primary account (identId=-1), iid is 0.
|
||||||
|
* For secondary accounts, look up iid from the ident_switch table.
|
||||||
|
*
|
||||||
|
* @param int|null $iid Known iid (0 for primary), or null to look up.
|
||||||
|
* @param rcmail $rc Roundcube instance.
|
||||||
|
* @param mixed $identId The ident_switch.id value for secondary accounts.
|
||||||
|
*/
|
||||||
|
private function reset_baseline(?int $iid, rcmail $rc, mixed $identId): void
|
||||||
|
{
|
||||||
|
if ($iid === null) {
|
||||||
|
// Look up iid from ident_switch table for secondary account
|
||||||
|
$sql = 'SELECT iid FROM ' . $rc->db->table_name(ident_switch::TABLE) . ' WHERE id = ? AND user_id = ?';
|
||||||
|
$q = $rc->db->query($sql, $identId, $rc->user->ID);
|
||||||
|
$r = $rc->db->fetch_assoc($q);
|
||||||
|
if (!$r) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$iid = (int)$r['iid'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$counts = $_SESSION['ident_switch_counts'] ?? [];
|
||||||
|
if (isset($counts[$iid])) {
|
||||||
|
unset($counts[$iid]['baseline']);
|
||||||
|
$_SESSION['ident_switch_counts'] = $counts;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,34 @@ $labels['form.sieve.auth.imap'] = 'Wie IMAP';
|
|||||||
$labels['form.sieve.auth.none'] = 'Keine';
|
$labels['form.sieve.auth.none'] = 'Keine';
|
||||||
|
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
$labels['form.notify.caption'] = 'Benachrichtigungen';
|
||||||
|
|
||||||
|
// Check for new mail
|
||||||
|
$labels['form.notify.check'] = 'Auf neue Nachrichten prüfen';
|
||||||
|
|
||||||
|
// Focus / favicon
|
||||||
|
$labels['form.notify.basic'] = 'Fokus / Favicon';
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
$labels['form.notify.sound'] = 'Ton';
|
||||||
|
|
||||||
|
// Desktop notification
|
||||||
|
$labels['form.notify.desktop'] = 'Desktop-Benachrichtigung';
|
||||||
|
|
||||||
|
// Use default
|
||||||
|
$labels['form.notify.default'] = 'Standard';
|
||||||
|
|
||||||
|
// On
|
||||||
|
$labels['form.notify.on'] = 'Ein';
|
||||||
|
|
||||||
|
// Off
|
||||||
|
$labels['form.notify.off'] = 'Aus';
|
||||||
|
|
||||||
|
// Requires newmail_notifier plugin
|
||||||
|
$labels['form.notify.requires_newmail_notifier'] = 'Benachrichtigungen erfordern das Plugin newmail_notifier. Bitte aktivieren Sie es oder kontaktieren Sie Ihren Administrator.';
|
||||||
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|
||||||
// Value in \'Server host name\' field is too long (64 chars max).
|
// Value in \'Server host name\' field is too long (64 chars max).
|
||||||
|
|||||||
@@ -83,6 +83,35 @@ $labels['form.sieve.auth.imap'] = 'As IMAP';
|
|||||||
$labels['form.sieve.auth.none'] = 'None';
|
$labels['form.sieve.auth.none'] = 'None';
|
||||||
|
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
$labels['form.notify.caption'] = 'Notifications';
|
||||||
|
|
||||||
|
// Check for new mail
|
||||||
|
$labels['form.notify.check'] = 'Check for new mail';
|
||||||
|
|
||||||
|
// Focus / favicon
|
||||||
|
$labels['form.notify.basic'] = 'Focus / favicon';
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
$labels['form.notify.sound'] = 'Sound';
|
||||||
|
|
||||||
|
// Desktop notification
|
||||||
|
$labels['form.notify.desktop'] = 'Desktop notification';
|
||||||
|
|
||||||
|
|
||||||
|
// Use default
|
||||||
|
$labels['form.notify.default'] = 'default';
|
||||||
|
|
||||||
|
// On
|
||||||
|
$labels['form.notify.on'] = 'On';
|
||||||
|
|
||||||
|
// Off
|
||||||
|
$labels['form.notify.off'] = 'Off';
|
||||||
|
|
||||||
|
// Requires newmail_notifier plugin
|
||||||
|
$labels['form.notify.requires_newmail_notifier'] = 'Notifications require the newmail_notifier plugin. Please enable it or contact your administrator.';
|
||||||
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|
||||||
// Value in \'Server host name\' field is too long (64 chars max).
|
// Value in \'Server host name\' field is too long (64 chars max).
|
||||||
|
|||||||
@@ -83,6 +83,34 @@ $labels['form.sieve.auth.imap'] = 'Identique à IMAP';
|
|||||||
$labels['form.sieve.auth.none'] = 'Aucune';
|
$labels['form.sieve.auth.none'] = 'Aucune';
|
||||||
|
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
$labels['form.notify.caption'] = 'Notifications';
|
||||||
|
|
||||||
|
// Check for new mail
|
||||||
|
$labels['form.notify.check'] = 'Vérifier les nouveaux messages';
|
||||||
|
|
||||||
|
// Focus / favicon
|
||||||
|
$labels['form.notify.basic'] = 'Focus / favicon';
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
$labels['form.notify.sound'] = 'Son';
|
||||||
|
|
||||||
|
// Desktop notification
|
||||||
|
$labels['form.notify.desktop'] = 'Notification bureau';
|
||||||
|
|
||||||
|
// Use default
|
||||||
|
$labels['form.notify.default'] = 'par défaut';
|
||||||
|
|
||||||
|
// On
|
||||||
|
$labels['form.notify.on'] = 'Activé';
|
||||||
|
|
||||||
|
// Off
|
||||||
|
$labels['form.notify.off'] = 'Désactivé';
|
||||||
|
|
||||||
|
// Requires newmail_notifier plugin
|
||||||
|
$labels['form.notify.requires_newmail_notifier'] = 'Les notifications nécessitent le plugin newmail_notifier. Veuillez l\'activer ou contacter votre administrateur.';
|
||||||
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|
||||||
// Value in \'Server host name\' field is too long (64 chars max).
|
// Value in \'Server host name\' field is too long (64 chars max).
|
||||||
|
|||||||
@@ -83,6 +83,34 @@ $labels['form.sieve.auth.imap'] = 'Come IMAP';
|
|||||||
$labels['form.sieve.auth.none'] = 'Nessuna';
|
$labels['form.sieve.auth.none'] = 'Nessuna';
|
||||||
|
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
$labels['form.notify.caption'] = 'Notifiche';
|
||||||
|
|
||||||
|
// Check for new mail
|
||||||
|
$labels['form.notify.check'] = 'Controlla nuova posta';
|
||||||
|
|
||||||
|
// Focus / favicon
|
||||||
|
$labels['form.notify.basic'] = 'Focus / favicon';
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
$labels['form.notify.sound'] = 'Suono';
|
||||||
|
|
||||||
|
// Desktop notification
|
||||||
|
$labels['form.notify.desktop'] = 'Notifica desktop';
|
||||||
|
|
||||||
|
// Use default
|
||||||
|
$labels['form.notify.default'] = 'predefinito';
|
||||||
|
|
||||||
|
// On
|
||||||
|
$labels['form.notify.on'] = 'Attivo';
|
||||||
|
|
||||||
|
// Off
|
||||||
|
$labels['form.notify.off'] = 'Disattivo';
|
||||||
|
|
||||||
|
// Requires newmail_notifier plugin
|
||||||
|
$labels['form.notify.requires_newmail_notifier'] = 'Le notifiche richiedono il plugin newmail_notifier. Abilitarlo o contattare l\'amministratore.';
|
||||||
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|
||||||
// Value in \'Server host name\' field is too long (64 chars max).
|
// Value in \'Server host name\' field is too long (64 chars max).
|
||||||
|
|||||||
@@ -83,6 +83,34 @@ $labels['form.sieve.auth.imap'] = 'Zoals IMAP';
|
|||||||
$labels['form.sieve.auth.none'] = 'Geen';
|
$labels['form.sieve.auth.none'] = 'Geen';
|
||||||
|
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
$labels['form.notify.caption'] = 'Meldingen';
|
||||||
|
|
||||||
|
// Check for new mail
|
||||||
|
$labels['form.notify.check'] = 'Controleer op nieuwe berichten';
|
||||||
|
|
||||||
|
// Focus / favicon
|
||||||
|
$labels['form.notify.basic'] = 'Focus / favicon';
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
$labels['form.notify.sound'] = 'Geluid';
|
||||||
|
|
||||||
|
// Desktop notification
|
||||||
|
$labels['form.notify.desktop'] = 'Bureaubladmelding';
|
||||||
|
|
||||||
|
// Use default
|
||||||
|
$labels['form.notify.default'] = 'standaard';
|
||||||
|
|
||||||
|
// On
|
||||||
|
$labels['form.notify.on'] = 'Aan';
|
||||||
|
|
||||||
|
// Off
|
||||||
|
$labels['form.notify.off'] = 'Uit';
|
||||||
|
|
||||||
|
// Requires newmail_notifier plugin
|
||||||
|
$labels['form.notify.requires_newmail_notifier'] = 'Meldingen vereisen de newmail_notifier-plug-in. Schakel deze in of neem contact op met uw beheerder.';
|
||||||
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|
||||||
// Value in \'Server host name\' field is too long (64 chars max).
|
// Value in \'Server host name\' field is too long (64 chars max).
|
||||||
|
|||||||
@@ -83,6 +83,34 @@ $labels['form.sieve.auth.imap'] = 'Как IMAP';
|
|||||||
$labels['form.sieve.auth.none'] = 'Нет';
|
$labels['form.sieve.auth.none'] = 'Нет';
|
||||||
|
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
$labels['form.notify.caption'] = 'Уведомления';
|
||||||
|
|
||||||
|
// Check for new mail
|
||||||
|
$labels['form.notify.check'] = 'Проверять новую почту';
|
||||||
|
|
||||||
|
// Focus / favicon
|
||||||
|
$labels['form.notify.basic'] = 'Фокус / фавикон';
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
$labels['form.notify.sound'] = 'Звук';
|
||||||
|
|
||||||
|
// Desktop notification
|
||||||
|
$labels['form.notify.desktop'] = 'Уведомление на рабочем столе';
|
||||||
|
|
||||||
|
// Use default
|
||||||
|
$labels['form.notify.default'] = 'по умолчанию';
|
||||||
|
|
||||||
|
// On
|
||||||
|
$labels['form.notify.on'] = 'Включено';
|
||||||
|
|
||||||
|
// Off
|
||||||
|
$labels['form.notify.off'] = 'Выключено';
|
||||||
|
|
||||||
|
// Requires newmail_notifier plugin
|
||||||
|
$labels['form.notify.requires_newmail_notifier'] = 'Уведомления требуют плагин newmail_notifier. Пожалуйста, включите его или обратитесь к администратору.';
|
||||||
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|
||||||
// Value in \'Server host name\' field is too long (64 chars max).
|
// Value in \'Server host name\' field is too long (64 chars max).
|
||||||
|
|||||||
@@ -83,6 +83,34 @@ $labels['form.sieve.auth.imap'] = 'Tako kot IMAP';
|
|||||||
$labels['form.sieve.auth.none'] = 'Brez';
|
$labels['form.sieve.auth.none'] = 'Brez';
|
||||||
|
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
$labels['form.notify.caption'] = 'Obvestila';
|
||||||
|
|
||||||
|
// Check for new mail
|
||||||
|
$labels['form.notify.check'] = 'Preveri nova sporočila';
|
||||||
|
|
||||||
|
// Focus / favicon
|
||||||
|
$labels['form.notify.basic'] = 'Fokus / favicon';
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
$labels['form.notify.sound'] = 'Zvok';
|
||||||
|
|
||||||
|
// Desktop notification
|
||||||
|
$labels['form.notify.desktop'] = 'Namizno obvestilo';
|
||||||
|
|
||||||
|
// Use default
|
||||||
|
$labels['form.notify.default'] = 'privzeto';
|
||||||
|
|
||||||
|
// On
|
||||||
|
$labels['form.notify.on'] = 'Vključeno';
|
||||||
|
|
||||||
|
// Off
|
||||||
|
$labels['form.notify.off'] = 'Izključeno';
|
||||||
|
|
||||||
|
// Requires newmail_notifier plugin
|
||||||
|
$labels['form.notify.requires_newmail_notifier'] = 'Obvestila zahtevajo vtičnik newmail_notifier. Prosimo, omogočite ga ali se obrnite na skrbnika.';
|
||||||
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|
||||||
// Value in \'Server host name\' field is too long (64 chars max).
|
// Value in \'Server host name\' field is too long (64 chars max).
|
||||||
|
|||||||
Reference in New Issue
Block a user