Compare commits
9 Commits
5.0.0
...
8f45736b1b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f45736b1b | ||
|
|
8385197f11 | ||
|
|
ca16e3ca39 | ||
|
|
c194661fa3 | ||
|
|
e539c60e9b | ||
|
|
bea25d8396 | ||
|
|
b4506a0a3e | ||
|
|
54b96ef059 | ||
|
|
7de47160b6 |
2
.github/workflows/php-lint.yml
vendored
2
.github/workflows/php-lint.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.2', '8.3', '8.4']
|
php-version: ['8.2', '8.3', '8.4']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup PHP ${{ matrix.php-version }}
|
- name: Setup PHP ${{ matrix.php-version }}
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
|
|||||||
2
.github/workflows/phpcs.yml
vendored
2
.github/workflows/phpcs.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
phpcs:
|
phpcs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
|
|||||||
@@ -86,3 +86,5 @@ CREATE TABLE IF NOT EXISTS `ident_switch`
|
|||||||
INDEX `IX_ident_switch_iid`(`iid`),
|
INDEX `IX_ident_switch_iid`(`iid`),
|
||||||
INDEX `IX_ident_switch_parent_id`(`parent_id`)
|
INDEX `IX_ident_switch_parent_id`(`parent_id`)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
INSERT INTO `system` (`name`, `value`) VALUES ('ident_switch-version', '2026021000');
|
||||||
|
|||||||
@@ -85,3 +85,5 @@ CREATE TABLE ident_switch
|
|||||||
CREATE INDEX IX_ident_switch_user_id ON ident_switch(user_id);
|
CREATE INDEX IX_ident_switch_user_id ON ident_switch(user_id);
|
||||||
CREATE INDEX IX_ident_switch_iid ON ident_switch(iid);
|
CREATE INDEX IX_ident_switch_iid ON ident_switch(iid);
|
||||||
CREATE INDEX IX_ident_switch_parent_id ON ident_switch(parent_id);
|
CREATE INDEX IX_ident_switch_parent_id ON ident_switch(parent_id);
|
||||||
|
|
||||||
|
INSERT INTO system (name, value) VALUES ('ident_switch-version', '2026021000');
|
||||||
|
|||||||
@@ -84,3 +84,5 @@ CREATE TABLE ident_switch
|
|||||||
CREATE INDEX IX_ident_switch_user_id ON ident_switch(user_id);
|
CREATE INDEX IX_ident_switch_user_id ON ident_switch(user_id);
|
||||||
CREATE INDEX IX_ident_switch_iid ON ident_switch(iid);
|
CREATE INDEX IX_ident_switch_iid ON ident_switch(iid);
|
||||||
CREATE INDEX IX_ident_switch_parent_id ON ident_switch(parent_id);
|
CREATE INDEX IX_ident_switch_parent_id ON ident_switch(parent_id);
|
||||||
|
|
||||||
|
INSERT INTO system (name, value) VALUES ('ident_switch-version', '2026021000');
|
||||||
|
|||||||
4
composer.lock
generated
4
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "46b2d3558cb304fde0539d1b85971382",
|
"content-hash": "c1389b5e002c6014eeddc8105bf77db1",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "roundcube/plugin-installer",
|
"name": "roundcube/plugin-installer",
|
||||||
@@ -154,5 +154,5 @@
|
|||||||
"ext-ctype": "*"
|
"ext-ctype": "*"
|
||||||
},
|
},
|
||||||
"platform-dev": {},
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.9.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,62 +19,30 @@ var ident_switch_portDefaults = {
|
|||||||
sieve: { '': 4190, tls: 4190, ssl: 4190 }
|
sieve: { '': 4190, tls: 4190, ssl: 4190 }
|
||||||
};
|
};
|
||||||
|
|
||||||
$(function() {
|
/**
|
||||||
|
* Flag to ensure delegated event handlers are registered only once.
|
||||||
|
*/
|
||||||
|
var ident_switch_delegated = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the identity form: apply mode visibility, preconfig, placeholders.
|
||||||
|
* Called on each form render (initial page load + AJAX identity loads).
|
||||||
|
*/
|
||||||
|
function plugin_switchIdent_init() {
|
||||||
|
// Register delegated event handlers once (survive DOM replacements)
|
||||||
|
if (!ident_switch_delegated) {
|
||||||
|
ident_switch_delegated = true;
|
||||||
|
plugin_switchIdent_bindDelegatedEvents();
|
||||||
|
}
|
||||||
|
|
||||||
// Apply initial mode visibility
|
// Apply initial mode visibility
|
||||||
var initialMode = $("SELECT[name='_ident_switch.form.common.mode']").val() || 'primary';
|
var initialMode = $("SELECT[name='_ident_switch.form.common.mode']").val() || 'primary';
|
||||||
plugin_switchIdent_mode_onChange(initialMode);
|
plugin_switchIdent_mode_onChange(initialMode);
|
||||||
plugin_switchIdent_processPreconfig();
|
|
||||||
|
|
||||||
// Bind security change handlers
|
// Apply initial auth visibility for SMTP and Sieve
|
||||||
$.each(['imap', 'smtp', 'sieve'], function(i, proto) {
|
|
||||||
var secSel = "SELECT[name='_ident_switch.form." + proto + ".security']";
|
|
||||||
$(secSel).on('change', function() {
|
|
||||||
plugin_switchIdent_onSecurityChange(proto, $(this).val());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind blur handlers for smart placeholder clearing
|
|
||||||
$.each(['imap', 'smtp', 'sieve'], function(i, proto) {
|
|
||||||
var portFld = $("INPUT[name='_ident_switch.form." + proto + ".port']");
|
|
||||||
portFld.on('blur', function() {
|
|
||||||
plugin_switchIdent_clearIfDefault($(this));
|
|
||||||
});
|
|
||||||
var hostFld = $("INPUT[name='_ident_switch.form." + proto + ".host']");
|
|
||||||
hostFld.on('blur', function() {
|
|
||||||
plugin_switchIdent_clearIfDefault($(this));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// IMAP host → update SMTP/Sieve host placeholders
|
|
||||||
$("INPUT[name='_ident_switch.form.imap.host']").on('change blur', function() {
|
|
||||||
var imapHost = $(this).val() || 'localhost';
|
|
||||||
$("INPUT[name='_ident_switch.form.smtp.host']").attr('placeholder', imapHost);
|
|
||||||
$("INPUT[name='_ident_switch.form.sieve.host']").attr('placeholder', imapHost);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind auth change handlers for SMTP and Sieve custom credentials
|
|
||||||
$.each(['smtp', 'sieve'], function(i, proto) {
|
$.each(['smtp', 'sieve'], function(i, proto) {
|
||||||
var authSel = "SELECT[name='_ident_switch.form." + proto + ".auth']";
|
var authVal = $("SELECT[name='_ident_switch.form." + proto + ".auth']").val();
|
||||||
$(authSel).on('change', function() {
|
plugin_switchIdent_onAuthChange(proto, authVal);
|
||||||
plugin_switchIdent_onAuthChange(proto, $(this).val());
|
|
||||||
});
|
|
||||||
// Apply initial visibility
|
|
||||||
plugin_switchIdent_onAuthChange(proto, $(authSel).val());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delimiter mode handler
|
|
||||||
$("SELECT[name='_ident_switch.form.imap.delimiter_mode']").on('change', function() {
|
|
||||||
if ($(this).val() === 'manual') {
|
|
||||||
$('#ident-switch-delimiter-input').show();
|
|
||||||
} else {
|
|
||||||
$('#ident-switch-delimiter-input').hide();
|
|
||||||
$("INPUT[name='_ident_switch.form.imap.delimiter']").val('');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Watch email field for dynamic preconfig application
|
|
||||||
$("INPUT[name='_email']").on('change blur', function() {
|
|
||||||
plugin_switchIdent_onEmailChange($(this).val());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set initial placeholders from current field values
|
// Set initial placeholders from current field values
|
||||||
@@ -87,6 +55,72 @@ $(function() {
|
|||||||
$("INPUT[name='_ident_switch.form.smtp.host']").attr('placeholder', initialImapHost);
|
$("INPUT[name='_ident_switch.form.smtp.host']").attr('placeholder', initialImapHost);
|
||||||
$("INPUT[name='_ident_switch.form.sieve.host']").attr('placeholder', initialImapHost);
|
$("INPUT[name='_ident_switch.form.sieve.host']").attr('placeholder', initialImapHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable mode select until a valid email is entered
|
||||||
|
var $modeSelect = $("SELECT[name='_ident_switch.form.common.mode']");
|
||||||
|
if (initialEmail && initialEmail.indexOf('@') > 0) {
|
||||||
|
$modeSelect.prop('disabled', false);
|
||||||
|
plugin_switchIdent_onEmailChange(initialEmail);
|
||||||
|
} else {
|
||||||
|
$modeSelect.prop('disabled', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register delegated event handlers on the document.
|
||||||
|
* These survive DOM replacements (AJAX form reloads).
|
||||||
|
*/
|
||||||
|
function plugin_switchIdent_bindDelegatedEvents() {
|
||||||
|
// Security change handlers
|
||||||
|
$.each(['imap', 'smtp', 'sieve'], function(i, proto) {
|
||||||
|
$(document).on('change', "SELECT[name='_ident_switch.form." + proto + ".security']", function() {
|
||||||
|
plugin_switchIdent_onSecurityChange(proto, $(this).val());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Blur handlers for smart placeholder clearing
|
||||||
|
$.each(['imap', 'smtp', 'sieve'], function(i, proto) {
|
||||||
|
$(document).on('blur', "INPUT[name='_ident_switch.form." + proto + ".port']", function() {
|
||||||
|
plugin_switchIdent_clearIfDefault($(this));
|
||||||
|
});
|
||||||
|
$(document).on('blur', "INPUT[name='_ident_switch.form." + proto + ".host']", function() {
|
||||||
|
plugin_switchIdent_clearIfDefault($(this));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// IMAP host → update SMTP/Sieve host placeholders
|
||||||
|
$(document).on('change blur', "INPUT[name='_ident_switch.form.imap.host']", function() {
|
||||||
|
var imapHost = $(this).val() || 'localhost';
|
||||||
|
$("INPUT[name='_ident_switch.form.smtp.host']").attr('placeholder', imapHost);
|
||||||
|
$("INPUT[name='_ident_switch.form.sieve.host']").attr('placeholder', imapHost);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auth change handlers for SMTP and Sieve custom credentials
|
||||||
|
$.each(['smtp', 'sieve'], function(i, proto) {
|
||||||
|
$(document).on('change', "SELECT[name='_ident_switch.form." + proto + ".auth']", function() {
|
||||||
|
plugin_switchIdent_onAuthChange(proto, $(this).val());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delimiter mode handler
|
||||||
|
$(document).on('change', "SELECT[name='_ident_switch.form.imap.delimiter_mode']", function() {
|
||||||
|
if ($(this).val() === 'manual') {
|
||||||
|
$('#ident-switch-delimiter-input').show();
|
||||||
|
} else {
|
||||||
|
$('#ident-switch-delimiter-input').hide();
|
||||||
|
$("INPUT[name='_ident_switch.form.imap.delimiter']").val('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch email field for dynamic preconfig application
|
||||||
|
$(document).on('change blur', "INPUT[name='_email']", function() {
|
||||||
|
plugin_switchIdent_onEmailChange($(this).val());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run init on initial page load
|
||||||
|
$(function() {
|
||||||
|
plugin_switchIdent_init();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,32 +227,43 @@ function plugin_switchIdent_onAuthChange(proto, authVal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply or remove preconfig readonly state on form fields.
|
||||||
|
* Reads the hidden readonly field value and enables/disables fields accordingly.
|
||||||
|
*/
|
||||||
function plugin_switchIdent_processPreconfig() {
|
function plugin_switchIdent_processPreconfig() {
|
||||||
var disFld = $("INPUT[name='_ident_switch.form.common.readonly']");
|
var disFld = $("INPUT[name='_ident_switch.form.common.readonly']");
|
||||||
disFld.parentsUntil("TABLE", "TR").hide();
|
disFld.parentsUntil("TABLE", "TR").hide();
|
||||||
|
|
||||||
var disVal = parseInt(disFld.val(), 10) || 0;
|
var disVal = parseInt(disFld.val(), 10) || 0;
|
||||||
|
|
||||||
|
// All fields that can be locked by preconfig
|
||||||
|
var lockedFields = [
|
||||||
|
"INPUT[name='_ident_switch.form.imap.host']",
|
||||||
|
"SELECT[name='_ident_switch.form.imap.security']",
|
||||||
|
"INPUT[name='_ident_switch.form.imap.port']",
|
||||||
|
"INPUT[name='_ident_switch.form.smtp.host']",
|
||||||
|
"SELECT[name='_ident_switch.form.smtp.security']",
|
||||||
|
"INPUT[name='_ident_switch.form.smtp.port']",
|
||||||
|
"SELECT[name='_ident_switch.form.smtp.auth']",
|
||||||
|
"INPUT[name='_ident_switch.form.smtp.username']",
|
||||||
|
"INPUT[name='_ident_switch.form.smtp.password']",
|
||||||
|
"SELECT[name='_ident_switch.form.imap.delimiter_mode']",
|
||||||
|
"INPUT[name='_ident_switch.form.imap.delimiter']",
|
||||||
|
"INPUT[name='_ident_switch.form.sieve.host']",
|
||||||
|
"SELECT[name='_ident_switch.form.sieve.security']",
|
||||||
|
"INPUT[name='_ident_switch.form.sieve.port']",
|
||||||
|
"SELECT[name='_ident_switch.form.sieve.auth']",
|
||||||
|
"INPUT[name='_ident_switch.form.sieve.username']",
|
||||||
|
"INPUT[name='_ident_switch.form.sieve.password']"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Reset all to enabled first (handles navigation from readonly to non-readonly)
|
||||||
|
$.each(lockedFields, function(_, sel) { $(sel).prop("disabled", false); });
|
||||||
|
$("INPUT[name='_ident_switch.form.imap.username']").prop("disabled", false);
|
||||||
|
|
||||||
if (disVal > 0) {
|
if (disVal > 0) {
|
||||||
$("INPUT[name='_ident_switch.form.imap.host']").prop("disabled", true);
|
$.each(lockedFields, function(_, sel) { $(sel).prop("disabled", true); });
|
||||||
$("SELECT[name='_ident_switch.form.imap.security']").prop("disabled", true);
|
|
||||||
$("INPUT[name='_ident_switch.form.imap.port']").prop("disabled", true);
|
|
||||||
|
|
||||||
$("INPUT[name='_ident_switch.form.smtp.host']").prop("disabled", true);
|
|
||||||
$("SELECT[name='_ident_switch.form.smtp.security']").prop("disabled", true);
|
|
||||||
$("INPUT[name='_ident_switch.form.smtp.port']").prop("disabled", true);
|
|
||||||
$("SELECT[name='_ident_switch.form.smtp.auth']").prop("disabled", true);
|
|
||||||
$("INPUT[name='_ident_switch.form.smtp.username']").prop("disabled", true);
|
|
||||||
$("INPUT[name='_ident_switch.form.smtp.password']").prop("disabled", true);
|
|
||||||
|
|
||||||
$("SELECT[name='_ident_switch.form.imap.delimiter_mode']").prop("disabled", true);
|
|
||||||
$("INPUT[name='_ident_switch.form.imap.delimiter']").prop("disabled", true);
|
|
||||||
|
|
||||||
$("INPUT[name='_ident_switch.form.sieve.host']").prop("disabled", true);
|
|
||||||
$("SELECT[name='_ident_switch.form.sieve.security']").prop("disabled", true);
|
|
||||||
$("INPUT[name='_ident_switch.form.sieve.port']").prop("disabled", true);
|
|
||||||
$("SELECT[name='_ident_switch.form.sieve.auth']").prop("disabled", true);
|
|
||||||
$("INPUT[name='_ident_switch.form.sieve.username']").prop("disabled", true);
|
|
||||||
$("INPUT[name='_ident_switch.form.sieve.password']").prop("disabled", true);
|
|
||||||
}
|
}
|
||||||
if (disVal === 2) {
|
if (disVal === 2) {
|
||||||
$("INPUT[name='_ident_switch.form.imap.username']").prop("disabled", true);
|
$("INPUT[name='_ident_switch.form.imap.username']").prop("disabled", true);
|
||||||
@@ -236,6 +281,9 @@ function plugin_switchIdent_onEmailChange(email) {
|
|||||||
var domain = email.substring(atPos + 1).toLowerCase();
|
var domain = email.substring(atPos + 1).toLowerCase();
|
||||||
if (!domain) return;
|
if (!domain) return;
|
||||||
|
|
||||||
|
// Enable mode select now that we have a valid email
|
||||||
|
$("SELECT[name='_ident_switch.form.common.mode']").prop('disabled', false);
|
||||||
|
|
||||||
// Update label and username placeholders to match current email
|
// Update label and username placeholders to match current email
|
||||||
$("INPUT[name='_ident_switch.form.common.label']").attr('placeholder', email);
|
$("INPUT[name='_ident_switch.form.common.label']").attr('placeholder', email);
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ $(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Place switcher in Larry skin: replace username in top-right corner.
|
* Place switcher in Larry skin: replace username in top-right corner of #topline.
|
||||||
*/
|
*/
|
||||||
function plugin_switchIdent_addCbLarry($wrapper, $sw) {
|
function plugin_switchIdent_addCbLarry($wrapper, $sw) {
|
||||||
var $topRight = $('#topline .topright');
|
var $topRight = $('#topline .topright');
|
||||||
@@ -65,12 +65,6 @@ function plugin_switchIdent_addCbLarry($wrapper, $sw) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sw.css({
|
|
||||||
'font-weight': 'bold',
|
|
||||||
'max-width': '200px',
|
|
||||||
'text-overflow': 'ellipsis'
|
|
||||||
});
|
|
||||||
|
|
||||||
$topRight.find('.username').hide();
|
$topRight.find('.username').hide();
|
||||||
$topRight.prepend($wrapper);
|
$topRight.prepend($wrapper);
|
||||||
return true;
|
return true;
|
||||||
@@ -99,14 +93,13 @@ function plugin_switchIdent_addCbElastic($wrapper, $sw) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sw.css({
|
$sw.css({
|
||||||
'background': 'transparent',
|
'background-color': 'transparent',
|
||||||
'border': 'none',
|
'border': 'none',
|
||||||
'font-weight': 'bold',
|
'font-weight': 'bold',
|
||||||
'color': 'inherit',
|
'color': 'inherit',
|
||||||
'box-shadow': 'none',
|
'box-shadow': 'none',
|
||||||
'max-width': '200px',
|
|
||||||
'text-overflow': 'ellipsis',
|
'text-overflow': 'ellipsis',
|
||||||
'padding': '0 0.25em'
|
'padding': '0 1.2em 0 0.25em'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hide original username text and elements
|
// Hide original username text and elements
|
||||||
|
|||||||
@@ -15,6 +15,78 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Account switcher <select>: hide native arrow, show SVG chevron.
|
||||||
|
Scoped to Elastic (.header-title.username) and Larry (#topline .topright) where
|
||||||
|
the switcher replaces a styled header label; Classic uses Roundcube's native
|
||||||
|
select rendering instead (see #taskbar rule below). */
|
||||||
|
.header-title.username .ident-switch-wrapper select,
|
||||||
|
#topline .topright .ident-switch-wrapper select {
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23ccc'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 0.3em center;
|
||||||
|
background-size: 10px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prevent flash of original username before JS prepends the switcher.
|
||||||
|
* The JS hides these elements at DOMReady, but the CSS rules below hide them
|
||||||
|
* earlier (during initial render) — only loaded when the switcher will actually
|
||||||
|
* be shown, so users without secondary accounts keep the default username display.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Elastic skin: switcher is prepended inside .header-title.username.
|
||||||
|
Hide the original username via visibility on the parent (affects the text
|
||||||
|
node), then re-show the wrapper. Unlike color/font-size, this preserves the
|
||||||
|
parent's flex/line-height context, keeping the wrapper vertically centered. */
|
||||||
|
.header-title.username {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.header-title.username > *:not(.ident-switch-wrapper) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.header-title.username > .ident-switch-wrapper {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Larry skin: switcher is prepended next to .username inside #topline .topright */
|
||||||
|
#topline .topright > .username {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Larry skin: blend the <select> into the dark #topline header (Larry styles a
|
||||||
|
<span> here, not a <select>, so without these the native white control sticks
|
||||||
|
out on the dark background). */
|
||||||
|
#topline .topright .ident-switch-wrapper {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
#topline .topright .ident-switch-wrapper select {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
color: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0 1.2em 0 0.25em;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Classic skin: switcher is prepended inside #taskbar (right-aligned, nowrap, 24px tall).
|
||||||
|
Drop width:100% so it sits inline before the menu links instead of pushing them
|
||||||
|
off-screen. The select keeps Roundcube's default native rendering. */
|
||||||
|
#taskbar > .ident-switch-wrapper {
|
||||||
|
width: auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
#taskbar > .ident-switch-wrapper select {
|
||||||
|
max-width: 180px;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Unread badge */
|
/* Unread badge */
|
||||||
.ident-switch-badge {
|
.ident-switch-badge {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@@ -158,10 +158,6 @@ class ident_switch extends rcube_plugin
|
|||||||
default => null,
|
default => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if ($rc->task === 'mail') {
|
|
||||||
$this->include_stylesheet('ident_switch.css');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +184,9 @@ class ident_switch extends rcube_plugin
|
|||||||
$iid = intval($iid_s);
|
$iid = intval($iid_s);
|
||||||
}
|
}
|
||||||
|
|
||||||
$accNames = [$_SESSION['global_alias'] ?? $rc->user->data['username']];
|
$primaryIdentity = $rc->user->get_identity();
|
||||||
|
$primaryName = !empty($primaryIdentity['name']) ? $primaryIdentity['name'] : null;
|
||||||
|
$accNames = [$_SESSION['global_alias'] ?? $primaryName ?? $rc->user->data['username']];
|
||||||
$accValues = [-1];
|
$accValues = [-1];
|
||||||
$accSelected = -1;
|
$accSelected = -1;
|
||||||
$iidMap = [0 => -1]; // primary account: iid 0 → select value -1
|
$iidMap = [0 => -1]; // primary account: iid 0 → select value -1
|
||||||
@@ -216,6 +214,7 @@ class ident_switch extends rcube_plugin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->include_stylesheet('ident_switch.css');
|
||||||
$this->include_script('ident_switch-switch.js');
|
$this->include_script('ident_switch-switch.js');
|
||||||
|
|
||||||
// Pass config to JS environment
|
// Pass config to JS environment
|
||||||
@@ -223,7 +222,7 @@ class ident_switch extends rcube_plugin
|
|||||||
|
|
||||||
$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;',
|
||||||
'onchange' => 'plugin_switchIdent_switch(this.value);',
|
'onchange' => 'plugin_switchIdent_switch(this.value);',
|
||||||
]);
|
]);
|
||||||
$select->add($accNames, $accValues);
|
$select->add($accNames, $accValues);
|
||||||
|
|||||||
@@ -177,6 +177,9 @@ class IdentSwitchChecker
|
|||||||
$host = 'tls://' . $host;
|
$host = 'tls://' . $host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$primaryIdentity = $rc->user->get_identity();
|
||||||
|
$primaryName = !empty($primaryIdentity['name']) ? $primaryIdentity['name'] : null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'iid' => 0,
|
'iid' => 0,
|
||||||
'imap_host' => $host,
|
'imap_host' => $host,
|
||||||
@@ -185,7 +188,7 @@ class IdentSwitchChecker
|
|||||||
'username' => $rc->user->data['username'],
|
'username' => $rc->user->data['username'],
|
||||||
'password' => $_SESSION['password' . $postfix],
|
'password' => $_SESSION['password' . $postfix],
|
||||||
'email' => $rc->user->data['username'],
|
'email' => $rc->user->data['username'],
|
||||||
'label' => $_SESSION['global_alias'] ?? $rc->user->data['username'],
|
'label' => $_SESSION['global_alias'] ?? $primaryName ?? $rc->user->data['username'],
|
||||||
'notify_basic' => null,
|
'notify_basic' => null,
|
||||||
'notify_sound' => null,
|
'notify_sound' => null,
|
||||||
'notify_desktop' => null,
|
'notify_desktop' => null,
|
||||||
|
|||||||
@@ -736,6 +736,9 @@ class IdentSwitchForm
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-run JS init on each form render (handles AJAX identity loads)
|
||||||
|
$rc->output->add_script('plugin_switchIdent_init();', 'docready');
|
||||||
|
|
||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,11 +929,11 @@ class IdentSwitchForm
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill in missing validated data from preconfig when identity is readonly.
|
* Enforce readonly preconfig values on validated data.
|
||||||
*
|
*
|
||||||
* When a preconfig has readonly=true, form fields are disabled in the
|
* When a preconfig has readonly=true, this overwrites the validated
|
||||||
* browser and not submitted via POST. This fills the missing values
|
* data with preconfig values regardless of what the user submitted.
|
||||||
* from the preconfig on the server side.
|
* This prevents tampering via browser devtools (re-enabling disabled fields).
|
||||||
*
|
*
|
||||||
* @param array $data Validated data from validate(), modified in place.
|
* @param array $data Validated data from validate(), modified in place.
|
||||||
* @param string $email Email address of the identity.
|
* @param string $email Email address of the identity.
|
||||||
@@ -947,26 +950,30 @@ class IdentSwitchForm
|
|||||||
$record = ['email' => $email];
|
$record = ['email' => $email];
|
||||||
$preconfig->apply($record);
|
$preconfig->apply($record);
|
||||||
|
|
||||||
// Map simple preconfig record keys to validated data keys
|
// Force preconfig values (overwrite any tampered POST data)
|
||||||
$map = [
|
$map = [
|
||||||
'ident_switch.form.imap.port' => 'imap.port',
|
'ident_switch.form.imap.port' => 'imap.port',
|
||||||
'ident_switch.form.imap.delimiter' => 'imap.delimiter',
|
'ident_switch.form.imap.delimiter' => 'imap.delimiter',
|
||||||
'ident_switch.form.imap.username' => 'imap.user',
|
|
||||||
'ident_switch.form.smtp.port' => 'smtp.port',
|
'ident_switch.form.smtp.port' => 'smtp.port',
|
||||||
'ident_switch.form.sieve.port' => 'sieve.port',
|
'ident_switch.form.sieve.port' => 'sieve.port',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($map as $recordKey => $dataKey) {
|
foreach ($map as $recordKey => $dataKey) {
|
||||||
if (empty($data[$dataKey]) && isset($record[$recordKey])) {
|
if (isset($record[$recordKey])) {
|
||||||
$data[$dataKey] = $record[$recordKey];
|
$data[$dataKey] = $record[$recordKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compose hosts with security scheme (preconfig stores them separately)
|
// Force username if preconfig defines it
|
||||||
|
if (isset($record['ident_switch.form.imap.username'])) {
|
||||||
|
$data['imap.user'] = $record['ident_switch.form.imap.username'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force hosts with security scheme
|
||||||
foreach (['imap', 'smtp', 'sieve'] as $proto) {
|
foreach (['imap', 'smtp', 'sieve'] as $proto) {
|
||||||
$hostKey = "ident_switch.form.{$proto}.host";
|
$hostKey = "ident_switch.form.{$proto}.host";
|
||||||
$secKey = "ident_switch.form.{$proto}.security";
|
$secKey = "ident_switch.form.{$proto}.security";
|
||||||
if (empty($data["{$proto}.host"]) && !empty($record[$hostKey])) {
|
if (!empty($record[$hostKey])) {
|
||||||
$security = $record[$secKey] ?? '';
|
$security = $record[$secKey] ?? '';
|
||||||
$data["{$proto}.host"] = self::compose_host_scheme($record[$hostKey], $security);
|
$data["{$proto}.host"] = self::compose_host_scheme($record[$hostKey], $security);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user