Replace TLS checkbox with security dropdown (None/STARTTLS/SSL)
Replace the single IMAP TLS checkbox with a per-protocol security dropdown for IMAP, SMTP, and Sieve. The security scheme (ssl:// or tls://) is now stored directly in the host field, unifying the approach across all protocols. Smart form behavior: - Default ports update automatically when changing security type - Fields clear on blur when value matches the placeholder - Warning shown when selecting no encryption - Defaults: IMAP SSL/993, SMTP STARTTLS/587, Sieve STARTTLS/4190 The DB_SECURE_IMAP_TLS flag is no longer written but still read for backward compatibility with existing records.
This commit is contained in:
@@ -43,15 +43,15 @@ class IdentSwitchForm
|
||||
* Build the IMAP form fields for identity settings.
|
||||
*
|
||||
* @param array $record Identity record data used for placeholders.
|
||||
* @return array Form field definitions for IMAP host, port, TLS, username, password, delimiter.
|
||||
* @return array Form field definitions for IMAP host, port, security, username, password, delimiter.
|
||||
*/
|
||||
public static function get_imap_fields(array &$record): array
|
||||
public function get_imap_fields(array &$record): array
|
||||
{
|
||||
$prefix = 'ident_switch.form.imap.';
|
||||
return [
|
||||
$prefix . 'host' => ['type' => 'text', 'size' => 64, 'placeholder' => 'localhost'],
|
||||
$prefix . 'port' => ['type' => 'text', 'size' => 5, 'placeholder' => 143],
|
||||
$prefix . 'tls' => ['type' => 'checkbox'],
|
||||
$prefix . 'security' => ['value' => $this->build_security_select($prefix, $record, 'ssl')],
|
||||
$prefix . 'port' => ['type' => 'text', 'size' => 5, 'placeholder' => 993],
|
||||
$prefix . 'username' => ['type' => 'text', 'size' => 64, 'placeholder' => $record['email'] ?? ''],
|
||||
$prefix . 'password' => ['type' => 'password', 'size' => 64],
|
||||
$prefix . 'delimiter' => ['type' => 'text', 'size' => 1, 'placeholder' => 'Auto'],
|
||||
@@ -74,6 +74,7 @@ class IdentSwitchForm
|
||||
|
||||
return [
|
||||
$prefix . 'host' => ['type' => 'text', 'size' => 64, 'placeholder' => 'localhost'],
|
||||
$prefix . 'security' => ['value' => $this->build_security_select($prefix, $record, 'tls')],
|
||||
$prefix . 'port' => ['type' => 'text', 'size' => 5, 'placeholder' => 587],
|
||||
$prefix . 'auth' => ['value' => $authType->show([$record['ident_switch.form.smtp.auth'] ?? null])],
|
||||
];
|
||||
@@ -95,6 +96,7 @@ class IdentSwitchForm
|
||||
|
||||
return [
|
||||
$prefix . 'host' => ['type' => 'text', 'size' => 64, 'placeholder' => 'localhost'],
|
||||
$prefix . 'security' => ['value' => $this->build_security_select($prefix, $record, 'tls')],
|
||||
$prefix . 'port' => ['type' => 'text', 'size' => 5, 'placeholder' => 4190],
|
||||
$prefix . 'auth' => ['value' => $authType->show([$record['ident_switch.form.sieve.auth'] ?? null])],
|
||||
];
|
||||
@@ -142,6 +144,67 @@ class IdentSwitchForm
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a security dropdown select for a protocol section.
|
||||
*
|
||||
* @param string $prefix Form field prefix (e.g. 'ident_switch.form.imap.').
|
||||
* @param array $record Identity record data.
|
||||
* @param string $default Default security value ('', 'tls', 'ssl').
|
||||
* @return string Rendered HTML select element.
|
||||
*/
|
||||
private function build_security_select(string $prefix, array &$record, string $default): string
|
||||
{
|
||||
$select = new html_select(['name' => "_{$prefix}security"]);
|
||||
$select->add($this->plugin->gettext('form.security.none'), '');
|
||||
$select->add($this->plugin->gettext('form.security.starttls'), 'tls');
|
||||
$select->add($this->plugin->gettext('form.security.ssl'), 'ssl');
|
||||
|
||||
$current = $record[$prefix . 'security'] ?? $default;
|
||||
$proto = str_replace('ident_switch.form.', '', rtrim($prefix, '.'));
|
||||
$hidden = $current !== '' ? ' style="display:none"' : '';
|
||||
$warning = '<div id="ident-switch-security-warning-' . $proto . '" class="boxwarning"' . $hidden . '>'
|
||||
. rcube::Q($this->plugin->gettext('form.security.none_warning'))
|
||||
. '</div>';
|
||||
|
||||
return $select->show($current) . $warning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse scheme prefix from a host string.
|
||||
*
|
||||
* @param string $host Host string, optionally prefixed with ssl:// or tls://.
|
||||
* @return array{scheme: string, host: string} Parsed scheme and bare host.
|
||||
*/
|
||||
private static function parse_host_scheme(string $host): array
|
||||
{
|
||||
$lower = strtolower($host);
|
||||
if (str_starts_with($lower, 'ssl://')) {
|
||||
return ['scheme' => 'ssl', 'host' => substr($host, 6)];
|
||||
}
|
||||
if (str_starts_with($lower, 'tls://')) {
|
||||
return ['scheme' => 'tls', 'host' => substr($host, 6)];
|
||||
}
|
||||
return ['scheme' => '', 'host' => $host];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a host string with scheme prefix.
|
||||
*
|
||||
* @param string|null $host Bare host name.
|
||||
* @param string|null $security Security type ('', 'tls', 'ssl').
|
||||
* @return string|null Host with scheme prefix, or null if host is empty.
|
||||
*/
|
||||
private static function compose_host_scheme(?string $host, ?string $security): ?string
|
||||
{
|
||||
if ($host === null || $host === '') {
|
||||
return $host;
|
||||
}
|
||||
if ($security === 'ssl' || $security === 'tls') {
|
||||
return $security . '://' . $host;
|
||||
}
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle identity_form hook: add plugin-specific fields to the identity editor.
|
||||
*
|
||||
@@ -205,7 +268,22 @@ class IdentSwitchForm
|
||||
|
||||
// Parse flags
|
||||
$record['ident_switch.form.common.enabled'] = (bool)($row['flags'] & ident_switch::DB_ENABLED);
|
||||
$record['ident_switch.form.imap.tls'] = (bool)($row['flags'] & ident_switch::DB_SECURE_IMAP_TLS);
|
||||
|
||||
// Parse host schemes into separate security fields
|
||||
foreach (['imap', 'smtp', 'sieve'] as $proto) {
|
||||
$key = "ident_switch.form.{$proto}.host";
|
||||
$hostVal = $record[$key] ?? '';
|
||||
if ($hostVal !== '') {
|
||||
$parsed = self::parse_host_scheme($hostVal);
|
||||
$record[$key] = $parsed['host'];
|
||||
$record["ident_switch.form.{$proto}.security"] = $parsed['scheme'];
|
||||
}
|
||||
}
|
||||
|
||||
// Backward compat: if IMAP host had no scheme but TLS flag was set
|
||||
if (empty($record['ident_switch.form.imap.security']) && ($row['flags'] & ident_switch::DB_SECURE_IMAP_TLS)) {
|
||||
$record['ident_switch.form.imap.security'] = 'tls';
|
||||
}
|
||||
|
||||
// Set readonly if needed
|
||||
$cfg = $preconfig->get($record['email']);
|
||||
@@ -225,7 +303,7 @@ class IdentSwitchForm
|
||||
];
|
||||
$args['form']['ident_switch.imap'] = [
|
||||
'name' => $this->plugin->gettext('form.imap.caption'),
|
||||
'content' => self::get_imap_fields($record),
|
||||
'content' => $this->get_imap_fields($record),
|
||||
];
|
||||
$args['form']['ident_switch.smtp'] = [
|
||||
'name' => $this->plugin->gettext('form.smtp.caption'),
|
||||
@@ -414,11 +492,6 @@ class IdentSwitchForm
|
||||
$data[$dataKey] = $record[$recordKey];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle TLS flag from preconfig
|
||||
if (!empty($record['ident_switch.form.imap.tls'])) {
|
||||
$data['flags'] |= ident_switch::DB_SECURE_IMAP_TLS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,7 +512,10 @@ class IdentSwitchForm
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
// Validate and compose IMAP host with security scheme
|
||||
$retVal['imap.host'] = self::get_field_value('imap', 'host');
|
||||
$imapSecurity = self::get_field_value('imap', 'security') ?? '';
|
||||
$retVal['imap.host'] = self::compose_host_scheme($retVal['imap.host'], $imapSecurity);
|
||||
if (strlen($retVal['imap.host'] ?? '') > 64) {
|
||||
$retVal['err'] = 'host.long';
|
||||
return $retVal;
|
||||
@@ -467,7 +543,10 @@ class IdentSwitchForm
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
// Validate and compose SMTP host with security scheme
|
||||
$retVal['smtp.host'] = self::get_field_value('smtp', 'host');
|
||||
$smtpSecurity = self::get_field_value('smtp', 'security') ?? '';
|
||||
$retVal['smtp.host'] = self::compose_host_scheme($retVal['smtp.host'], $smtpSecurity);
|
||||
if (strlen($retVal['smtp.host'] ?? '') > 64) {
|
||||
$retVal['err'] = 'host.long';
|
||||
return $retVal;
|
||||
@@ -489,7 +568,10 @@ class IdentSwitchForm
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
// Validate and compose Sieve host with security scheme
|
||||
$retVal['sieve.host'] = self::get_field_value('sieve', 'host');
|
||||
$sieveSecurity = self::get_field_value('sieve', 'security') ?? '';
|
||||
$retVal['sieve.host'] = self::compose_host_scheme($retVal['sieve.host'], $sieveSecurity);
|
||||
if (strlen($retVal['sieve.host'] ?? '') > 64) {
|
||||
$retVal['err'] = 'host.long';
|
||||
return $retVal;
|
||||
@@ -526,14 +608,9 @@ class IdentSwitchForm
|
||||
// Get also password
|
||||
$retVal['imap.pass'] = self::get_field_value('imap', 'password', false, true);
|
||||
|
||||
// Parse secure settings
|
||||
// Flags: only enabled, security is now in host field scheme
|
||||
$retVal['flags'] = ident_switch::DB_ENABLED;
|
||||
|
||||
$tls = self::get_field_value('imap', 'tls', false);
|
||||
if ($tls) {
|
||||
$retVal['flags'] |= ident_switch::DB_SECURE_IMAP_TLS;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,57 +73,24 @@ class IdentSwitchPreconfig
|
||||
if (is_array($cfg)) {
|
||||
ident_switch::write_log("Applying predefined configuration for '{$email}'.");
|
||||
|
||||
// IMAP: use imap_host, fallback to host
|
||||
$imapUrl = $cfg['imap_host'] ?? $cfg['host'] ?? '';
|
||||
if (!empty($imapUrl)) {
|
||||
$urlArr = parse_url($imapUrl);
|
||||
// Parse each protocol URL into host, security, and port
|
||||
$protocols = [
|
||||
'imap' => $cfg['imap_host'] ?? $cfg['host'] ?? '',
|
||||
'smtp' => $cfg['smtp_host'] ?? $cfg['host'] ?? '',
|
||||
'sieve' => $cfg['sieve_host'] ?? '',
|
||||
];
|
||||
|
||||
foreach ($protocols as $proto => $url) {
|
||||
if (empty($url)) {
|
||||
continue;
|
||||
}
|
||||
$urlArr = parse_url($url);
|
||||
$host = !empty($urlArr['host']) ? rcube::Q($urlArr['host'], 'url') : '';
|
||||
$scheme = strtolower($urlArr['scheme'] ?? '');
|
||||
|
||||
if ($scheme === 'ssl') {
|
||||
$record['ident_switch.form.imap.host'] = 'ssl://' . $host;
|
||||
$record['ident_switch.form.imap.tls'] = false;
|
||||
} elseif ($scheme === 'tls') {
|
||||
$record['ident_switch.form.imap.host'] = $host;
|
||||
$record['ident_switch.form.imap.tls'] = true;
|
||||
} else {
|
||||
$record['ident_switch.form.imap.host'] = $host;
|
||||
$record['ident_switch.form.imap.tls'] = false;
|
||||
}
|
||||
|
||||
$record['ident_switch.form.imap.port'] = !empty($urlArr['port']) ? intval($urlArr['port']) : '';
|
||||
}
|
||||
|
||||
// SMTP: use smtp_host, fallback to host
|
||||
$smtpUrl = $cfg['smtp_host'] ?? $cfg['host'] ?? '';
|
||||
if (!empty($smtpUrl)) {
|
||||
$urlArr = parse_url($smtpUrl);
|
||||
$host = !empty($urlArr['host']) ? rcube::Q($urlArr['host'], 'url') : '';
|
||||
$scheme = strtolower($urlArr['scheme'] ?? '');
|
||||
|
||||
if ($scheme === 'tls' || $scheme === 'ssl') {
|
||||
$record['ident_switch.form.smtp.host'] = $scheme . '://' . $host;
|
||||
} else {
|
||||
$record['ident_switch.form.smtp.host'] = $host;
|
||||
}
|
||||
|
||||
$record['ident_switch.form.smtp.port'] = !empty($urlArr['port']) ? intval($urlArr['port']) : '';
|
||||
}
|
||||
|
||||
// Sieve: use sieve_host only (no fallback — sieve is optional)
|
||||
$sieveUrl = $cfg['sieve_host'] ?? '';
|
||||
if (!empty($sieveUrl)) {
|
||||
$urlArr = parse_url($sieveUrl);
|
||||
$host = !empty($urlArr['host']) ? rcube::Q($urlArr['host'], 'url') : '';
|
||||
$scheme = strtolower($urlArr['scheme'] ?? '');
|
||||
|
||||
if ($scheme === 'tls' || $scheme === 'ssl') {
|
||||
$record['ident_switch.form.sieve.host'] = $scheme . '://' . $host;
|
||||
} else {
|
||||
$record['ident_switch.form.sieve.host'] = $host;
|
||||
}
|
||||
|
||||
$record['ident_switch.form.sieve.port'] = !empty($urlArr['port']) ? intval($urlArr['port']) : '';
|
||||
$record["ident_switch.form.{$proto}.host"] = $host;
|
||||
$record["ident_switch.form.{$proto}.security"] = in_array($scheme, ['ssl', 'tls']) ? $scheme : '';
|
||||
$record["ident_switch.form.{$proto}.port"] = !empty($urlArr['port']) ? intval($urlArr['port']) : '';
|
||||
}
|
||||
|
||||
$loginSet = false;
|
||||
|
||||
@@ -95,18 +95,24 @@ class IdentSwitchSwitcher
|
||||
}
|
||||
}
|
||||
|
||||
$def_port = 143;
|
||||
$ssl = null;
|
||||
if ($r['flags'] & ident_switch::DB_SECURE_IMAP_TLS) {
|
||||
$ssl = 'tls';
|
||||
}
|
||||
$port = $r['imap_port'] ?: $def_port;
|
||||
|
||||
$host = $r['imap_host'] ?: 'localhost';
|
||||
if ($ssl && !str_starts_with(strtolower($host), "{$ssl}://")) {
|
||||
$host = "{$ssl}://" . $host;
|
||||
$ssl = null;
|
||||
|
||||
// Parse scheme from host field
|
||||
$hostLower = strtolower($host);
|
||||
if (str_starts_with($hostLower, 'ssl://')) {
|
||||
$ssl = 'ssl';
|
||||
} elseif (str_starts_with($hostLower, 'tls://')) {
|
||||
$ssl = 'tls';
|
||||
} elseif ($r['flags'] & ident_switch::DB_SECURE_IMAP_TLS) {
|
||||
// Backward compat: old records without scheme in host
|
||||
$ssl = 'tls';
|
||||
$host = 'tls://' . $host;
|
||||
}
|
||||
|
||||
$def_port = ($ssl === 'ssl') ? 993 : 143;
|
||||
$port = $r['imap_port'] ?: $def_port;
|
||||
|
||||
$delimiter = $r['imap_delimiter'] ?: null;
|
||||
|
||||
$_SESSION['storage_host'] = $host;
|
||||
@@ -164,7 +170,7 @@ class IdentSwitchSwitcher
|
||||
|
||||
$rc = rcmail::get_instance();
|
||||
|
||||
$sql = 'SELECT smtp_host, flags, smtp_port, username, smtp_auth, password FROM ' . $rc->db->table_name(ident_switch::TABLE) . ' WHERE iid = ? AND user_id = ?';
|
||||
$sql = 'SELECT smtp_host, smtp_port, username, smtp_auth, password FROM ' . $rc->db->table_name(ident_switch::TABLE) . ' WHERE iid = ? AND user_id = ?';
|
||||
$q = $rc->db->query($sql, $iid, $rc->user->ID);
|
||||
$r = $rc->db->fetch_assoc($q);
|
||||
if (is_array($r)) {
|
||||
@@ -179,17 +185,8 @@ class IdentSwitchSwitcher
|
||||
$args['smtp_user'] = $r['username'];
|
||||
$args['smtp_pass'] = $r['smtp_auth'] == ident_switch::SMTP_AUTH_IMAP ? $rc->decrypt($r['password']) : '';
|
||||
|
||||
// In RC 1.6+ smtp_server was renamed to smtp_host and includes port
|
||||
// Host already contains scheme (ssl:// or tls://) from form
|
||||
$smtpHost = $r['smtp_host'] ?: 'localhost';
|
||||
|
||||
if ($r['flags'] & ident_switch::DB_SECURE_IMAP_TLS) {
|
||||
if (str_contains($smtpHost, ':')) {
|
||||
ident_switch::write_log('SMTP server already contains protocol, ignoring TLS flag.');
|
||||
} else {
|
||||
$smtpHost = 'tls://' . $smtpHost;
|
||||
}
|
||||
}
|
||||
|
||||
$smtpPort = $r['smtp_port'] ?: 587;
|
||||
$args['smtp_host'] = $smtpHost . ':' . $smtpPort;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user