/** * 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); } // 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(); }); /** * 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; // 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 $("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(); }