367 lines
13 KiB
JavaScript
367 lines
13 KiB
JavaScript
/**
|
|
* ident_switch - Identity settings form handler.
|
|
*
|
|
* Copyright (C) 2018 Boris Gulay
|
|
* Copyright (C) 2026 Gecka
|
|
*
|
|
* Original code licensed under GPL-3.0+.
|
|
* New contributions licensed under AGPL-3.0+.
|
|
*
|
|
* @url https://github.com/Gecka-apps/ident_switch
|
|
*/
|
|
|
|
/**
|
|
* Default ports per protocol and security type.
|
|
*/
|
|
var ident_switch_portDefaults = {
|
|
imap: { '': 143, tls: 143, ssl: 993 },
|
|
smtp: { '': 25, tls: 587, ssl: 465 },
|
|
sieve: { '': 4190, tls: 4190, ssl: 4190 }
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
var initialMode = $("SELECT[name='_ident_switch.form.common.mode']").val() || 'primary';
|
|
plugin_switchIdent_mode_onChange(initialMode);
|
|
|
|
// Apply initial auth visibility for SMTP and Sieve
|
|
$.each(['smtp', 'sieve'], function(i, proto) {
|
|
var authVal = $("SELECT[name='_ident_switch.form." + proto + ".auth']").val();
|
|
plugin_switchIdent_onAuthChange(proto, authVal);
|
|
});
|
|
|
|
// Set initial placeholders from current field values
|
|
var initialEmail = $("INPUT[name='_email']").val();
|
|
if (initialEmail) {
|
|
$("INPUT[name='_ident_switch.form.common.label']").attr('placeholder', initialEmail);
|
|
}
|
|
var initialImapHost = $("INPUT[name='_ident_switch.form.imap.host']").val();
|
|
if (initialImapHost) {
|
|
$("INPUT[name='_ident_switch.form.smtp.host']").attr('placeholder', initialImapHost);
|
|
$("INPUT[name='_ident_switch.form.sieve.host']").attr('placeholder', initialImapHost);
|
|
}
|
|
|
|
// Re-apply preconfig from current email (handles form re-render after create abort
|
|
// where PHP record may lack email, so readonly was not set server-side)
|
|
if (initialEmail && initialEmail.indexOf('@') > 0) {
|
|
plugin_switchIdent_onEmailChange(initialEmail);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
});
|
|
|
|
/**
|
|
* Handle mode select change: show/hide form sections based on selected mode.
|
|
* @param {string} mode - 'primary', 'alias:N', or 'separate'.
|
|
*/
|
|
function plugin_switchIdent_mode_onChange(mode) {
|
|
// Find the fieldsets/legends for each section
|
|
var $modeFld = $("SELECT[name='_ident_switch.form.common.mode']");
|
|
var $allFieldsets = $modeFld.closest('form').find('fieldset');
|
|
var $readonlyRow = $("INPUT[name='_ident_switch.form.common.readonly']").parentsUntil('FIELDSET', 'TR, .row');
|
|
|
|
// Separate account fieldsets (label, IMAP, SMTP, Sieve, Notify)
|
|
var separateFieldsets = [];
|
|
$allFieldsets.each(function() {
|
|
var $fs = $(this);
|
|
if ($fs.find("INPUT[name='_ident_switch.form.common.label']").length ||
|
|
$fs.find("INPUT[name='_ident_switch.form.imap.host']").length ||
|
|
$fs.find("INPUT[name='_ident_switch.form.smtp.host']").length ||
|
|
$fs.find("INPUT[name='_ident_switch.form.sieve.host']").length ||
|
|
$fs.find("INPUT[name='_ident_switch.form.notify.check']").length) {
|
|
separateFieldsets.push($fs);
|
|
}
|
|
});
|
|
|
|
if (mode === 'separate') {
|
|
// Show all separate account fieldsets
|
|
$.each(separateFieldsets, function(_, $fs) { $fs.show(); });
|
|
plugin_switchIdent_processPreconfig();
|
|
} else {
|
|
// Hide all separate account fieldsets for primary and alias modes
|
|
$.each(separateFieldsets, function(_, $fs) { $fs.hide(); });
|
|
}
|
|
|
|
// Always hide readonly row
|
|
$readonlyRow.hide();
|
|
}
|
|
|
|
/**
|
|
* Handle security dropdown change: update port placeholder and show/hide warning.
|
|
* @param {string} proto - Protocol name (imap, smtp, sieve).
|
|
* @param {string} security - Selected security value ('', 'tls', 'ssl').
|
|
*/
|
|
function plugin_switchIdent_onSecurityChange(proto, security) {
|
|
var portFld = $("INPUT[name='_ident_switch.form." + proto + ".port']");
|
|
var defaults = ident_switch_portDefaults[proto];
|
|
// Check if port value matches any known default for this protocol
|
|
var portVal = portFld.val();
|
|
var isDefault = !portVal;
|
|
if (portVal) {
|
|
$.each(defaults, function(_, v) {
|
|
if (parseInt(portVal) === v) {
|
|
isDefault = true;
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update placeholder to new default
|
|
var newDefault = defaults[security] || defaults[''];
|
|
portFld.attr('placeholder', newDefault);
|
|
|
|
// If port was empty or matched a known default, clear it
|
|
if (isDefault) {
|
|
portFld.val('');
|
|
}
|
|
|
|
// Show/hide security warning
|
|
var warningId = '#ident-switch-security-warning-' + proto;
|
|
if (security === '') {
|
|
$(warningId).show();
|
|
} else {
|
|
$(warningId).hide();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* On blur: clear field if value matches its placeholder.
|
|
* @param {jQuery} $field - The input field.
|
|
*/
|
|
function plugin_switchIdent_clearIfDefault($field) {
|
|
var val = $.trim($field.val());
|
|
var placeholder = $field.attr('placeholder') || '';
|
|
if (val !== '' && val === String(placeholder)) {
|
|
$field.val('');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle auth dropdown change: show/hide custom credential fields.
|
|
* @param {string} proto - Protocol name (smtp, sieve).
|
|
* @param {string} authVal - Selected auth value.
|
|
*/
|
|
function plugin_switchIdent_onAuthChange(proto, authVal) {
|
|
var isCustom = (authVal === '3'); // SMTP_AUTH_CUSTOM / SIEVE_AUTH_CUSTOM
|
|
var userFld = $("INPUT[name='_ident_switch.form." + proto + ".username']");
|
|
var passFld = $("INPUT[name='_ident_switch.form." + proto + ".password']");
|
|
if (isCustom) {
|
|
userFld.parentsUntil("TABLE", "TR").show();
|
|
passFld.parentsUntil("TABLE", "TR").show();
|
|
} else {
|
|
userFld.parentsUntil("TABLE", "TR").hide();
|
|
passFld.parentsUntil("TABLE", "TR").hide();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply or remove preconfig readonly state on form fields.
|
|
* Reads the hidden readonly field value and enables/disables fields accordingly.
|
|
*/
|
|
function plugin_switchIdent_processPreconfig() {
|
|
var disFld = $("INPUT[name='_ident_switch.form.common.readonly']");
|
|
disFld.parentsUntil("TABLE", "TR").hide();
|
|
|
|
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) {
|
|
$.each(lockedFields, function(_, sel) { $(sel).prop("disabled", true); });
|
|
}
|
|
if (disVal === 2) {
|
|
$("INPUT[name='_ident_switch.form.imap.username']").prop("disabled", true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle email field change: apply preconfig and manage domain restriction.
|
|
* @param {string} email - The email address entered by the user.
|
|
*/
|
|
function plugin_switchIdent_onEmailChange(email) {
|
|
var atPos = email.indexOf('@');
|
|
if (atPos < 0) return;
|
|
|
|
var domain = email.substring(atPos + 1).toLowerCase();
|
|
if (!domain) return;
|
|
|
|
// Update label and username placeholders to match current email
|
|
$("INPUT[name='_ident_switch.form.common.label']").attr('placeholder', email);
|
|
|
|
var preconfig = rcmail.env.ident_switch_preconfig || {};
|
|
var preconfigOnly = rcmail.env.ident_switch_preconfig_only || false;
|
|
var cfg = preconfig[domain] || preconfig['*'] || null;
|
|
|
|
// Update username placeholder to match current email
|
|
$("INPUT[name='_ident_switch.form.imap.username']").attr('placeholder', email);
|
|
|
|
// Show/hide domain warning and restrict "separate" option
|
|
var $modeSelect = $("SELECT[name='_ident_switch.form.common.mode']");
|
|
if (preconfigOnly && !cfg) {
|
|
var tpl = rcmail.env.ident_switch_warning_tpl || '';
|
|
$('#ident-switch-domain-warning').text(tpl.replace('%s', domain)).show();
|
|
// Disable "separate" option but keep alias options available
|
|
$modeSelect.find('option[value="separate"]').prop('disabled', true);
|
|
if ($modeSelect.val() === 'separate') {
|
|
$modeSelect.val('primary');
|
|
plugin_switchIdent_mode_onChange('primary');
|
|
}
|
|
return;
|
|
}
|
|
|
|
$('#ident-switch-domain-warning').hide();
|
|
$modeSelect.find('option[value="separate"]').prop('disabled', false);
|
|
|
|
// Only auto-fill for identities without an existing DB record
|
|
if (!rcmail.env.ident_switch_has_record && cfg) {
|
|
plugin_switchIdent_applyJsPreconfig(cfg, email);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply preconfig values from JS environment to the form fields.
|
|
* @param {object} cfg - Preconfig entry for the matched domain.
|
|
* @param {string} email - The full email address.
|
|
*/
|
|
function plugin_switchIdent_applyJsPreconfig(cfg, email) {
|
|
// Pre-fill server fields silently without changing mode.
|
|
// The user must explicitly select "Separate account" to see them.
|
|
|
|
// Apply protocol settings
|
|
$.each(['imap', 'smtp', 'sieve'], function(_, proto) {
|
|
if (!cfg[proto]) return;
|
|
|
|
// Set security first (updates port placeholder)
|
|
var security = cfg[proto].security || '';
|
|
$("SELECT[name='_ident_switch.form." + proto + ".security']").val(security);
|
|
plugin_switchIdent_onSecurityChange(proto, security);
|
|
|
|
// Set host
|
|
$("INPUT[name='_ident_switch.form." + proto + ".host']").val(cfg[proto].host || '');
|
|
|
|
// Set port (empty if matches default, so placeholder shows)
|
|
var port = cfg[proto].port;
|
|
var defaultPort = ident_switch_portDefaults[proto][security] || '';
|
|
$("INPUT[name='_ident_switch.form." + proto + ".port']").val(
|
|
(port && parseInt(port) !== defaultPort) ? port : ''
|
|
);
|
|
});
|
|
|
|
// Apply username from preconfig user mode
|
|
if (cfg.user) {
|
|
var username = '';
|
|
if (cfg.user.toUpperCase() === 'EMAIL') {
|
|
username = email;
|
|
} else if (cfg.user.toUpperCase() === 'MBOX') {
|
|
username = email.split('@')[0];
|
|
}
|
|
if (username) {
|
|
$("INPUT[name='_ident_switch.form.imap.username']").val(username);
|
|
}
|
|
}
|
|
|
|
// Apply readonly level
|
|
var readonlyLevel = 0;
|
|
if (cfg.readonly) {
|
|
var hasUser = cfg.user && ['EMAIL', 'MBOX'].indexOf(cfg.user.toUpperCase()) >= 0;
|
|
readonlyLevel = hasUser ? 2 : 1;
|
|
}
|
|
$("INPUT[name='_ident_switch.form.common.readonly']").val(readonlyLevel);
|
|
|
|
// Re-apply preconfig readonly state
|
|
plugin_switchIdent_processPreconfig();
|
|
}
|