/ src / views / elements / ServersElement.php
<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\views\elements;

use seekquarry\yioop as B;
use seekquarry\yioop\configs as C;

/**
 * Element used to draw forms to set up the various external servers
 * that might be connected with a Yioop installation
 *
 * @author Chris Pollett
 */
class ServersElement extends Element
{
    /**
     * Method that draw forms to set up the various external servers
     * that might be connected with a Yioop installation
     *
     * @param array $data holds data on the profile elements which have been
     *     filled in as well as data about which form fields to display
     */
    public function render($data)
    {
        $admin_url = htmlentities(B\controllerUrl('admin', true));
        $token_string = C\p('CSRF_TOKEN')."=".$data[C\p('CSRF_TOKEN')];
        $pre_base_url = $admin_url . $token_string;
        $base_url = $pre_base_url . "&a=servers";
        ?>
        <div class="current-activity">
        <form id="serversForm" method="post">
        <input type="hidden" name="c" value="admin" >
        <input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>" value="<?=
            $data[C\p('CSRF_TOKEN')] ?>" >
        <input type="hidden" name="a" value="servers" >
        <input type="hidden" name="arg" value="update" >
        <h2><?= tl('servers_element_server_settings')?></h2>
        <div class="bold">
            <div class="top-margin">
            <fieldset><legend><?=tl('servers_element_name_server') .
                "&nbsp;" . $this->view->helper("helpbutton")->render(
                "Name Server Setup", $data[C\p('CSRF_TOKEN')]) ?></legend><?php
                if (!empty($this->view->controller_object->web_site) &&
                    $this->view->controller_object->web_site->isCli()) {
                    ?>
                    [<a href="<?=$base_url . '&arg=restart' ?>"><?=
                    tl('servers_element_restart');?></a>]
                    <?php
                }
                ?><div ><b><label for="queue-fetcher-salt"><?=
                    tl('servers_element_name_server_key')
                    ?></label></b>
                    <input type="text" id="queue-fetcher-salt" name="AUTH_KEY"
                        value="<?= $data['AUTH_KEY'] ?>"
                        class="wide-field" >
                </div>
                <div class="top-margin"><b><label for="name-server-url"><?=
                    tl('servers_element_name_server_url')
                    ?></label></b>
                    <input type="url" id="name-server-url" name="NAME_SERVER"
                        value="<?= $data['NAME_SERVER'] ?>"
                        class="extra-wide-field" >
                </div>
                <div id="filecache">
                <div class="top-margin"><label for="use-filecache"><b><?=
                    tl('servers_element_use_filecache')?></b></label>
                    <input type="checkbox" id="use-filecache"
                        name="USE_FILECACHE" value="true" <?=
                        $data['USE_FILECACHE'] ? "checked='checked'" :
                            "" ?> >
                    [<a href="<?= $base_url . '&arg=clearcache'
                    ?>"><?=tl('servers_element_clear_cache')
                    ?></a>]
                </div>
                </div>
            </fieldset>
            </div>
            <div class="top-margin">
            <fieldset><legend><?=tl('servers_element_database_setup').
                "&nbsp;" .$this->view->helper("helpbutton")->render(
                "Database Setup", $data[C\p('CSRF_TOKEN')]) ?>
                </legend>
                <div ><label for="database-system"><b><?=
                    tl('servers_element_database_system')
                    ?></b></label>
                    <?php $this->view->helper("options")->render(
                        "database-system", "DBMS",
                        $data['DBMSS'], $data['DBMS']);
                ?>
                </div>
                <div class="top-margin"><b><label for="database-name"><?=
                    tl('servers_element_databasename') ?></label></b>
                    <input type="text" id="database-name" name="DB_NAME"
                        value="<?= $data['DB_NAME'] ?>"
                        class="wide-field" >
                </div>
                <div id="login-dbms">
                    <div class="top-margin"><b><label for="database-host"><?=
                        tl('servers_element_databasehost')
                        ?></label></b>
                        <input type="text" id="database-host" name="DB_HOST"
                            value="<?= $data['DB_HOST'] ?>"
                            class="wide-field" >
                    </div>
                    <div class="top-margin"><b><label for="database-user"><?=
                        tl('servers_element_databaseuser')
                        ?></label></b>
                        <input type="text" id="database-user" name="DB_USER"
                            value="<?= $data['DB_USER'] ?>"
                            class="wide-field" >
                    </div>
                    <div class="top-margin"><b><label
                        for="database-password"><?=
                        tl('servers_element_databasepassword')
                        ?></label></b>
                        <input type="password" id="database-password"
                            name="DB_PASSWORD" value="<?=
                            $data['DB_PASSWORD'] ?>" class="wide-field" >
                    </div>
                </div>
            </fieldset>
            </div>
            <div class = "top-margin">
            </div>
            <div class="top-margin">
            <fieldset>
                <legend><label for="acme-on"><?=
                tl('servers_element_web_server')
                ?></label>
                <?= $this->view->helper("helpbutton")->render(
                    "Web Server", $data[C\p('CSRF_TOKEN')])
                    ?></legend>
                <div class="top-margin"><b><label for="acme-on"><?=
                    tl('servers_element_acme_on')
                    ?></label></b>
                    <input type="checkbox" id="acme-on"
                        name="ACME_ON" value="true" <?=
                        (!empty($data['ACME_ON']) &&
                        $data['ACME_ON'] == true) ?
                        "checked='checked'" : "" ?> >
                </div>
                <div class="top-margin" id="secure-domains-row"><b><label
                        for="secure-domain-add"><?=
                    tl('servers_element_secure_domains')
                    ?></label></b>
                    <ul class="mail-domain-list"
                        id="secure-domain-list"
                        data-route-url="<?= $pre_base_url .
                        '&a=servers&arg=updateroute' ?>"
                        data-routing-label="<?= tl(
                        'servers_element_domain_routing'
                        ) ?>"
                        data-default-label="<?= tl(
                        'servers_element_routing_default'
                        ) ?>"
                        data-custom-label="<?= tl(
                        'servers_element_routing_custom'
                        ) ?>"
                        data-group-label="<?= tl(
                        'servers_element_routing_group'
                        ) ?>"
                        data-edit-label="<?= tl(
                        'servers_element_domain_edit'
                        ) ?>"
                        data-apply-label="<?= tl(
                        'servers_element_route_apply'
                        ) ?>"><?php
                        $secure_csv = trim((string)
                            ($data['SECURE_DOMAINS'] ?? ''));
                        $secure_entries = $secure_csv === '' ?
                            [] : array_filter(array_map('trim',
                            explode(',', $secure_csv)));
                        foreach ($secure_entries as $secure_domain) {
                            $route_group = $data['DOMAIN_ROUTES'][
                                strtolower($secure_domain)] ?? '';
                            $is_custom = ($route_group !== '');
                            ?><li class="mail-domain-item"><span
                                class="mail-domain-text"><?=
                                $secure_domain
                                ?></span><button type="button"
                                class="secure-domain-edit"
                                data-domain="<?= $secure_domain
                                ?>" title="<?= tl(
                                'servers_element_domain_edit'
                                ) ?>">&#9998;</button><button
                                type="button"
                                class="secure-domain-remove mail-domain-remove"
                                data-domain="<?= $secure_domain
                                ?>" title="<?= tl(
                                'servers_element_mail_domain_remove'
                                ) ?>">&times;</button>
                            <div class="domain-route-panel"
                                data-domain="<?=
                                strtolower($secure_domain) ?>"
                                style="display:none;">
                                <label><?= tl(
                                    'servers_element_domain_routing'
                                    ) ?> <select
                                    class="domain-route-mode">
                                    <option value="default"<?=
                                        $is_custom ? "" : " selected"
                                    ?>><?= tl(
                                    'servers_element_routing_default'
                                    ) ?></option>
                                    <option value="custom"<?=
                                        $is_custom ? " selected" : ""
                                    ?>><?= tl(
                                    'servers_element_routing_custom'
                                    ) ?></option>
                                </select></label>
                                <label class="domain-route-group-label"
                                    <?= $is_custom ? "" :
                                    "style=\"display:none;\"" ?>><?=
                                    tl(
                                    'servers_element_routing_group'
                                    ) ?> <input type="text"
                                    class="domain-route-group"
                                    value="<?= e($route_group) ?>">
                                </label>
                                <button type="button"
                                    class="domain-route-apply"
                                    title="<?= tl(
                                    'servers_element_route_apply'
                                    ) ?>">&rarr;</button>
                                <div class="robots-config-link"><a href="<?=
                                    $pre_base_url .
                                    '&a=servers&arg=robots'.
                                    '&domain=' . urlencode(strtolower(
                                    $secure_domain)) ?>"><?= tl(
                                    'servers_element_configure_robots'
                                    ) ?></a></div>
                            </div></li><?php
                        }
                        ?></ul>
                    <div class="mail-domain-add-row">
                        <input type="text" id="secure-domain-add"
                            class="wide-field"
                            placeholder="<?= tl(
                            'servers_element_secure_domain_placeholder'
                            ) ?>" >
                        <button type="button" id="secure-domain-add-button"
                            class="button-box"
                            data-remove-label="<?= tl(
                            'servers_element_mail_domain_remove'
                            ) ?>"><?= tl(
                            'servers_element_mail_domain_add'
                            ) ?></button>
                    </div>
                    <input type="hidden" id="secure-domains"
                        name="SECURE_DOMAINS"
                        value="<?= $secure_csv ?>" >
                </div>
                <div id="web-server-atto-config">
                <div class="top-margin"><b><label for="www-user"><?=
                    tl('servers_element_www_user')
                    ?></label></b>
                    <input type="text" id="www-user" name="WWW_USER"
                        class="wide-field"
                        value="<?= $data['WWW_USER'] ?? '' ?>" >
                </div>
                <div class="top-margin"><b><label><?=
                    tl('servers_element_alpn_protocols')
                    ?></label></b><?php
                    $alpn_csv = trim((string)
                        ($data['ALPN_PROTOCOLS'] ?? ''));
                    $alpn_selected = $alpn_csv === '' ? [] :
                        array_filter(array_map('trim',
                        explode(',', $alpn_csv)));
                    foreach ($data['ALPN_PROTOCOL_OPTIONS'] as
                        $alpn_option) {
                        $alpn_id = "alpn-" . str_replace(["/", "."],
                            ["-", "-"], $alpn_option);
                        ?><span class="alpn-option"><input
                            type="checkbox" class="alpn-protocol-box"
                            id="<?= $alpn_id ?>"
                            value="<?= $alpn_option ?>" <?=
                            in_array($alpn_option, $alpn_selected) ?
                            "checked='checked'" : "" ?>
                            onchange="updateAlpnProtocols();" ><label
                            for="<?= $alpn_id ?>"><?= $alpn_option
                            ?></label></span><?php
                    }
                    ?><input type="hidden" id="alpn-protocols"
                        name="ALPN_PROTOCOLS"
                        value="<?= $alpn_csv ?>" >
                </div>
                </div>
            </fieldset>
            </div>
            <div class="top-margin">
            <fieldset >
                <legend><label for="mail-mode"><?=
                tl('servers_element_mail_services') ?>
                </label>
                <?= $this->view->helper("helpbutton")->render(
                    "Mail Services", $data[C\p('CSRF_TOKEN')])
                    ?></legend>
                <?php $this->view->helper("options")->render(
                    "mail-mode", "MAIL_MODE",
                    $data['MAIL_MODES'],
                    $data['MAIL_MODE']);
                ?>
                <div class="top-margin"><b><label for="mail-host-name"><?=
                    tl('servers_element_mail_banner')
                    ?></label></b>
                    <input type="text" id="mail-host-name"
                        name="MAIL_HOST_NAME" class="wide-field"
                        value="<?= $data['MAIL_HOST_NAME'] ?? '' ?>"
                        placeholder="<?= $data['MAIL_HOST_NAME_DEFAULT']
                        ?? '' ?>" >
                </div>
                <div id="external-config">
                <div class="top-margin"><b><label for="mail-log-enabled"><?=
                    tl('servers_element_mail_log_enabled')
                    ?></label></b>
                    <input type="checkbox" id="mail-log-enabled"
                        name="MAIL_LOG_ENABLED"
                        value="true" <?php if (
                            $data['MAIL_LOG_ENABLED'] == true) {
                            e("checked='checked'");
                            } ?> >
                    &nbsp;[<a href="<?=
                        $admin_url . "a=manageMachines&" .
                        $token_string . "&arg=log&" .
                        "name=NAME_SERVER&type=Mail&" .
                        "id=0" ?>"><?=
                        tl('servers_element_mail_log_view')
                        ?></a>]
                </div>
                </div>
                <div id="mailsite-config">
                <div class="top-margin"><b><label for="mail-domain-add"><?=
                    tl('servers_element_mail_domains')
                    ?></label></b>
                    <ul class="mail-domain-list" id="mail-domain-list"><?php
                        $domain_csv = trim((string)
                            $data['MAIL_DOMAINS']);
                        $domain_entries = $domain_csv === '' ?
                            [] : array_filter(array_map('trim',
                            explode(',', $domain_csv)));
                        foreach ($domain_entries as $domain) {
                            ?><li class="mail-domain-item"><span
                                class="mail-domain-text"><?=
                                e($domain)
                                ?></span><button type="button"
                                class="mail-domain-remove"
                                data-domain="<?= e(
                                $domain) ?>" title="<?= tl(
                                'servers_element_mail_domain_remove'
                                ) ?>">&times;</button></li><?php
                        }
                        ?></ul>
                    <div class="mail-domain-add-row">
                        <input type="text" id="mail-domain-add"
                            class="wide-field"
                            placeholder="<?= tl(
                            'servers_element_mail_domain_placeholder'
                            ) ?>" >
                        <button type="button" id="mail-domain-add-button"
                            class="button-box"
                            data-remove-label="<?= tl(
                            'servers_element_mail_domain_remove'
                            ) ?>"><?= tl(
                            'servers_element_mail_domain_add'
                            ) ?></button>
                    </div>
                    <input type="hidden" id="mail-domains"
                        name="MAIL_DOMAINS"
                        value="<?= e(
                        $domain_csv) ?>" >
                </div>
                <div class="top-margin" id="mail-smtp-port-row"><b><label
                        for="mail-smtp-port"><?=
                    tl('servers_element_mail_smtp_port')
                    ?></label></b>
                    <input type="number" id="mail-smtp-port"
                        name="MAIL_SMTP_PORT" min="1" max="65535"
                        value="<?= $data['MAIL_SMTP_PORT'] ?>"
                        class="narrow-field" >
                </div>
                <div class="top-margin" id="mail-submission-port-row"><b><label
                        for="mail-submission-port"><?=
                    tl('servers_element_mail_submission_port')
                    ?></label></b>
                    <input type="number" id="mail-submission-port"
                        name="MAIL_SUBMISSION_PORT" min="1" max="65535"
                        value="<?= $data['MAIL_SUBMISSION_PORT'] ?>"
                        class="narrow-field" >
                </div>
                <?php
                if (!empty($data['MAIL_TLS_AVAILABLE'])) { ?>
                <div class="top-margin"><b><label for="mail-smtps-port"><?=
                    tl('servers_element_mail_smtps_port')
                    ?></label></b>
                    <input type="number" id="mail-smtps-port"
                        name="MAIL_SMTPS_PORT" min="1" max="65535"
                        value="<?= $data['MAIL_SMTPS_PORT'] ?>"
                        class="narrow-field" >
                </div><?php
                } ?>
                <div class="top-margin" id="mail-imap-port-row"><b><label
                        for="mail-imap-port"><?=
                    tl('servers_element_mail_imap_port')
                    ?></label></b>
                    <input type="number" id="mail-imap-port"
                        name="MAIL_IMAP_PORT" min="1" max="65535"
                        value="<?= $data['MAIL_IMAP_PORT'] ?>"
                        class="narrow-field" >
                </div>
                <?php
                if (!empty($data['MAIL_TLS_AVAILABLE'])) { ?>
                <div class="top-margin"><b><label for="mail-imaps-port"><?=
                    tl('servers_element_mail_imaps_port')
                    ?></label></b>
                    <input type="number" id="mail-imaps-port"
                        name="MAIL_IMAPS_PORT" min="1" max="65535"
                        value="<?= $data['MAIL_IMAPS_PORT'] ?>"
                        class="narrow-field" >
                </div><?php
                } ?>
                <ul class="mail-port-links"><li><a
                        href="javascript:setMailDefaultPorts()"><?=
                    tl('servers_element_mail_use_default_ports')
                    ?></a></li><?php
                    if (!empty($data['MAIL_DNS_RECORDS'])) { ?>
                    <li><a
                        href="javascript:toggleDisplay('mail-dns-records')"><?=
                    tl('servers_element_mail_dns_records')
                    ?></a></li><?php
                    } ?>
                </ul><?php
                if (!empty($data['MAIL_DNS_RECORDS'])) { ?>
                <pre class="mail-dns-records" id="mail-dns-records"><?php
                    e(tl('servers_element_mail_dns_help'));
                    foreach ($data['MAIL_DNS_RECORDS'] as
                        $dns_domain => $dns_rows) {
                        e("\n\n" . $dns_domain . "\n");
                        e(tl('servers_element_mail_dns_host') .
                            "\t" .
                            tl('servers_element_mail_dns_type') .
                            "\t" .
                            tl('servers_element_mail_dns_value'));
                        foreach ($dns_rows as $dns_row) {
                            e("\n" . $dns_row['host'] . "\t" .
                                $dns_row['type'] . "\t" .
                                $dns_row['value']);
                        }
                    } ?></pre><?php
                } ?>
                <?php
                if (empty($data['MAIL_TLS_AVAILABLE'])) { ?>
                <div class="top-margin red"><?=
                    tl('servers_element_mail_no_certificate')
                    ?></div><?php
                } ?>
                <?php
                if (!empty($data['MAIL_TLS_AVAILABLE'])) { ?>
                <div class="top-margin"><b><label
                        for="mail-use-starttls"><?=
                    tl('servers_element_mail_use_starttls')
                    ?></label></b>
                    <input type="checkbox"
                        id="mail-use-starttls"
                        name="MAIL_USE_STARTTLS"
                        value="true" <?php if (
                            $data['MAIL_USE_STARTTLS'] == true) {
                            e("checked='checked'");
                            } ?> >
                </div><?php
                } ?>
                <div class="top-margin"><b><label
                        for="mail-dmarc-enforce"><?=
                    tl('servers_element_mail_dmarc_enforce')
                    ?></label></b>
                    <input type="checkbox"
                        id="mail-dmarc-enforce"
                        name="MAIL_DMARC_ENFORCE"
                        value="true" <?php if (!empty(
                            $data['MAIL_DMARC_ENFORCE']) &&
                            $data['MAIL_DMARC_ENFORCE'] == true) {
                            e("checked='checked'");
                            } ?> >
                </div>
                <div class="top-margin"><b><label
                        for="mail-delivery-security"><?=
                    tl('servers_element_mail_delivery_security')
                    ?></label></b><?php
                    if (empty($data['MAIL_SECURITY_AVAILABLE'])) {
                        /* Without a certificate and clean URLs the
                           secure postures cannot operate, so the
                           choice is forced to "insecure" and locked.
                           A disabled control is not submitted, so a
                           hidden field carries the value. */
                        ?>
                    <input type="hidden" name="MAIL_DELIVERY_SECURITY"
                        value="insecure">
                    <select id="mail-delivery-security"
                        disabled="disabled">
                        <option value="insecure"
                        selected="selected"><?=
                        tl('system_component_mail_security_insecure')
                        ?></option>
                    </select><?php
                    } else {
                        ?>
                    <select id="mail-delivery-security"
                        name="MAIL_DELIVERY_SECURITY"><?php
                        foreach ($data['MAIL_SECURITY_OPTIONS'] as
                            $option_value => $option_label) {
                            $selected =
                                ($data['MAIL_DELIVERY_SECURITY'] ==
                                $option_value) ?
                                ' selected="selected"' : '';
                            e('<option value="' . $option_value .
                                '"' . $selected . '>' .
                                $option_label . '</option>');
                        } ?>
                    </select><?php
                    } ?>
                </div>
                <div class="top-margin"><b><label for="mail-external"><?=
                    tl('servers_element_mail_external')
                    ?></label></b>
                    <input type="checkbox" id="mail-external"
                        name="MAIL_EXTERNAL"
                        value="true" <?php if (
                            $data['MAIL_EXTERNAL'] == true) {
                            e("checked='checked'");
                            } ?> >
                </div>
                <div class="top-margin"><b><label
                        for="mail-test-mode"><?=
                    tl('servers_element_mail_test_mode')
                    ?></label></b>
                    <input type="checkbox"
                        id="mail-test-mode"
                        name="MAIL_TEST_MODE"
                        value="true" <?php if (
                            $data['MAIL_TEST_MODE'] == true) {
                            e("checked='checked'");
                            } ?> >
                </div>
                <div class="top-margin"
                        id="mail-test-mode-port-row"><b><label
                        for="mail-test-mode-fallback-port"><?=
                    tl(
                    'servers_element_mail_test_mode_fallback_port'
                    ) ?></label></b>
                    <input type="number"
                        id="mail-test-mode-fallback-port"
                        name="MAIL_TEST_MODE_FALLBACK_PORT"
                        min="0" max="65535"
                        value="<?= (int)
                        $data['MAIL_TEST_MODE_FALLBACK_PORT']
                        ?>" class="narrow-field" >
                </div>
                </div>
            </fieldset>
            </div>
            <div class="top-margin">
            <fieldset><legend><?=tl('servers_element_proxy_title') .
                "&nbsp;" .$this->view->helper("helpbutton")->render(
                        "Proxy Server", $data[C\p('CSRF_TOKEN')]) ?></legend>
                <div ><b><label for="tor-proxies"><?=
                    tl('servers_element_tor_proxy')?></label></b>
                    <input type="text" id="tor-proxies" name="TOR_PROXY"
                        value="<?=$data['TOR_PROXY'] ?>"
                        class="wide-field" >
                </div>
                <div class="top-margin"><label for="use-proxy"><b><?=
                    tl('servers_element_use_proxy_servers')
                        ?></b></label>
                        <input type="checkbox" id="use-proxy"
                            name="USE_PROXY" value="true" <?=
                            $data['USE_PROXY'] ? "checked='checked'" :
                            ""  ?> >
                </div>
                <div id="proxy">
                    <div class="top-margin"><label for="proxy-servers" ><b><?=
                    tl('servers_element_proxy_servers') ?></b></label>
                    </div>
                    <textarea class="short-text-area" id="proxy-servers"
                    name="PROXY_SERVERS"><?=$data['PROXY_SERVERS'] ?></textarea>
                </div>
            </fieldset>
            </div>
            <div class="top-margin center">
            <button class="button-box" type="submit"><?=
                tl('servers_element_save') ?></button>
            </div>
            </div>
        </form>
        </div>
        <script>
        window.onload = function()
        {
            initMailDomainList();
            initMailInsecureToggle();
            initSecureDomainList();
            showHideWebServer();
        }
        /*
         * Wires the mail-domain Add and Remove buttons. The Add button
         * reads the input field, trims whitespace, skips duplicates,
         * appends a new list item with its own Remove button, clears
         * the input, and rebuilds the hidden CSV. The Remove buttons
         * (including those rendered server-side for already-saved
         * domains) remove their list item and rebuild the CSV. The
         * hidden input feeds the form submit; pressing Save persists
         * whatever is in the list at that moment.
         */
        function initMailDomainList()
        {
            var add_button = elt('mail-domain-add-button');
            var add_input = elt('mail-domain-add');
            var list = elt('mail-domain-list');
            if (!add_button || !add_input || !list) {
                return;
            }
            listen(add_button, 'click', function () {
                var value = add_input.value.trim();
                if (value === '' || value.indexOf(',') !== -1) {
                    return;
                }
                var spans = list.querySelectorAll(
                    '.mail-domain-text');
                for (var i = 0; i < spans.length; i++) {
                    if (spans[i].textContent === value) {
                        add_input.value = '';
                        return;
                    }
                }
                var item = ce('li');
                item.className = 'mail-domain-item';
                var text = ce('span');
                text.className = 'mail-domain-text';
                text.textContent = value;
                var remove = ce('button');
                remove.type = 'button';
                remove.className = 'mail-domain-remove';
                remove.setAttribute('data-domain', value);
                remove.textContent = '\u00D7';
                remove.title = add_button.getAttribute(
                    'data-remove-label') || 'Remove';
                wireMailDomainRemove(remove);
                item.appendChild(text);
                item.appendChild(remove);
                list.appendChild(item);
                add_input.value = '';
                syncMailDomainsHidden();
            });
            listen(add_input, 'keydown', function (e) {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    add_button.click();
                }
            });
            var removes = list.querySelectorAll(
                '.mail-domain-remove');
            for (var i = 0; i < removes.length; i++) {
                wireMailDomainRemove(removes[i]);
            }
            var settings_form = add_input.form;
            if (settings_form) {
                listen(settings_form, 'submit', function () {
                    if (add_input.value.trim() !== '') {
                        add_button.click();
                    }
                });
            }
        }
        /*
         * Attaches the click handler to one .mail-domain-remove button.
         * Factored out so both server-rendered and dynamically-added
         * buttons share the same wiring path.
         */
        function wireMailDomainRemove(button)
        {
            listen(button, 'click', function () {
                var item = button.closest('.mail-domain-item');
                if (item && item.parentNode) {
                    item.parentNode.removeChild(item);
                    syncMailDomainsHidden();
                }
            });
        }
        /*
         * Rebuilds the comma-separated value of the hidden
         * MAIL_DOMAINS input from the current set of list items so a
         * form submit reflects the latest UI state.
         */
        function syncMailDomainsHidden()
        {
            var hidden = elt('mail-domains');
            if (!hidden) {
                return;
            }
            var spans = sel(
                '#mail-domain-list .mail-domain-text');
            var parts = [];
            for (var i = 0; i < spans.length; i++) {
                parts.push(spans[i].textContent);
            }
            hidden.value = parts.join(',');
        }
        /*
         * Shows or hides the plaintext SMTP and IMAP port rows
         * based on the "allow insecure connections" checkbox. When
         * insecure connections are not allowed those listeners are
         * never bound, so their port fields would only mislead;
         * they are hidden until the box is checked. Runs once on
         * load to set the initial state and again whenever the box
         * is toggled.
         */
        /*
         * Shows the plaintext SMTP and IMAP port rows only when
         * the delivery-security choice is "insecure"; the other
         * postures refuse cleartext connections, so those port
         * fields would only mislead. Runs once on load and again
         * whenever the dropdown changes. When the dropdown is the
         * locked single-option control (no certificate), its value
         * is "insecure" so the rows show.
         */
        function initMailInsecureToggle()
        {
            var picker = elt('mail-delivery-security');
            if (!picker) {
                return;
            }
            var apply = function () {
                var insecure = (picker.value == 'insecure');
                setDisplay('mail-smtp-port-row', insecure);
                setDisplay('mail-imap-port-row', insecure);
            };
            listen(picker, 'change', apply);
            apply();
        }
        /*
         * Wires the secure-domain Add and Remove buttons, mirroring
         * the mail-domain list: Add appends a trimmed, de-duplicated
         * entry with its own Remove button and rebuilds the hidden
         * SECURE_DOMAINS comma-separated value; Remove drops an entry
         * and rebuilds. The hidden input feeds the form submit.
         */
        function initSecureDomainList()
        {
            var add_button = elt('secure-domain-add-button');
            var add_input = elt('secure-domain-add');
            var list = elt('secure-domain-list');
            if (!add_button || !add_input || !list) {
                return;
            }
            listen(add_button, 'click', function () {
                var value = add_input.value.trim();
                if (value === '' || value.indexOf(',') !== -1) {
                    return;
                }
                var spans = list.querySelectorAll(
                    '.mail-domain-text');
                for (var i = 0; i < spans.length; i++) {
                    if (spans[i].textContent === value) {
                        add_input.value = '';
                        return;
                    }
                }
                var item = ce('li');
                item.className = 'mail-domain-item';
                var text = ce('span');
                text.className = 'mail-domain-text';
                text.textContent = value;
                var remove = ce('button');
                remove.type = 'button';
                remove.className = 'secure-domain-remove mail-domain-remove';
                remove.setAttribute('data-domain', value);
                remove.textContent = '\u00D7';
                remove.title = add_button.getAttribute(
                    'data-remove-label') || 'Remove';
                wireSecureDomainRemove(remove);
                var edit = ce('button');
                edit.type = 'button';
                edit.className = 'secure-domain-edit';
                edit.setAttribute('data-domain', value);
                edit.textContent = '\u270E';
                edit.title = list.getAttribute('data-edit-label') || '';
                wireSecureDomainEdit(edit);
                var panel = buildDomainRoutePanel(list,
                    value.toLowerCase());
                item.appendChild(text);
                item.appendChild(edit);
                item.appendChild(remove);
                item.appendChild(panel);
                list.appendChild(item);
                add_input.value = '';
                syncSecureDomainsHidden();
            });
            listen(add_input, 'keydown', function (e) {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    add_button.click();
                }
            });
            var removes = list.querySelectorAll(
                '.secure-domain-remove');
            for (var i = 0; i < removes.length; i++) {
                wireSecureDomainRemove(removes[i]);
            }
            var edits = list.querySelectorAll('.secure-domain-edit');
            for (var j = 0; j < edits.length; j++) {
                wireSecureDomainEdit(edits[j]);
            }
            var panels = list.querySelectorAll('.domain-route-panel');
            for (var k = 0; k < panels.length; k++) {
                wireDomainRoutePanel(panels[k]);
            }
            var settings_form = add_input.form;
            if (settings_form) {
                listen(settings_form, 'submit', function () {
                    if (add_input.value.trim() !== '') {
                        add_button.click();
                    }
                });
            }
        }
        /*
         * Attaches the click handler to one .secure-domain-remove
         * button, shared by server-rendered and added buttons.
         */
        function wireSecureDomainRemove(button)
        {
            listen(button, 'click', function () {
                var item = button.closest('.mail-domain-item');
                if (item && item.parentNode) {
                    item.parentNode.removeChild(item);
                    syncSecureDomainsHidden();
                }
            });
        }
        /*
         * Rebuilds the comma-separated value of the hidden
         * SECURE_DOMAINS input from the current list items.
         */
        function syncSecureDomainsHidden()
        {
            var hidden = elt('secure-domains');
            if (!hidden) {
                return;
            }
            var spans = sel(
                '#secure-domain-list .mail-domain-text');
            var parts = [];
            for (var i = 0; i < spans.length; i++) {
                parts.push(spans[i].textContent);
            }
            hidden.value = parts.join(',');
        }
        /*
         * Builds the routing panel for one secure-domain row. The
         * panel holds a Default/Custom routing select, a group-name
         * field shown for Custom, and an apply button that requests
         * the route change. Labels come from data- attributes on the
         * list element so they stay localized.
         */
        function buildDomainRoutePanel(list, domain)
        {
            var panel = ce('div');
            panel.className = 'domain-route-panel';
            panel.setAttribute('data-domain', domain);
            panel.style.display = 'none';
            var mode_label = ce('label');
            mode_label.textContent =
                (list.getAttribute('data-routing-label') || '') + ' ';
            var mode = ce('select');
            mode.className = 'domain-route-mode';
            var default_option = ce('option');
            default_option.value = 'default';
            default_option.textContent =
                list.getAttribute('data-default-label') || 'Default';
            var custom_option = ce('option');
            custom_option.value = 'custom';
            custom_option.textContent =
                list.getAttribute('data-custom-label') || 'Custom';
            mode.appendChild(default_option);
            mode.appendChild(custom_option);
            mode_label.appendChild(mode);
            var group_label = ce('label');
            group_label.className = 'domain-route-group-label';
            group_label.style.display = 'none';
            group_label.textContent =
                (list.getAttribute('data-group-label') || '') + ' ';
            var group_input = ce('input');
            group_input.type = 'text';
            group_input.className = 'domain-route-group';
            group_label.appendChild(group_input);
            var apply = ce('button');
            apply.type = 'button';
            apply.className = 'domain-route-apply';
            apply.textContent = '\u2192';
            apply.title = list.getAttribute('data-apply-label') || '';
            panel.appendChild(mode_label);
            panel.appendChild(group_label);
            panel.appendChild(apply);
            wireDomainRoutePanel(panel);
            return panel;
        }
        /*
         * Wires one routing panel: switching the select between
         * Default and Custom shows or hides the group-name field,
         * and the apply button requests the route change for the
         * panel's domain (an empty or Default selection clears the
         * route). The request goes to the servers updateroute
         * action, which stores the route and redirects back with a
         * status message.
         */
        function wireDomainRoutePanel(panel)
        {
            var mode = panel.querySelector('.domain-route-mode');
            var group_label = panel.querySelector(
                '.domain-route-group-label');
            var group_input = panel.querySelector(
                '.domain-route-group');
            var apply = panel.querySelector('.domain-route-apply');
            if (!mode || !group_label || !group_input || !apply) {
                return;
            }
            listen(mode, 'change', function () {
                group_label.style.display =
                    (mode.value === 'custom') ? '' : 'none';
            });
            listen(apply, 'click', function () {
                var list = elt('secure-domain-list');
                var route_url = list ?
                    list.getAttribute('data-route-url') : '';
                var domain = panel.getAttribute('data-domain') || '';
                var route_group = (mode.value === 'custom') ?
                    group_input.value.trim() : '';
                if (route_url === '' || domain === '') {
                    return;
                }
                window.location = route_url + '&route_domain=' +
                    encodeURIComponent(domain) + '&route_group=' +
                    encodeURIComponent(route_group);
            });
        }
        /*
         * Attaches the click handler to one .secure-domain-edit
         * (pencil) button: it toggles visibility of the routing panel
         * in the button's list item.
         */
        function wireSecureDomainEdit(button)
        {
            listen(button, 'click', function () {
                var item = button.closest('.mail-domain-item');
                var panel = item ?
                    item.querySelector('.domain-route-panel') : null;
                if (panel) {
                    panel.style.display =
                        (panel.style.display === 'none') ? '' : 'none';
                }
            });
        }
        /*
         * Shows the Run As User and ALPN fields only when Yioop
         * serves over its own WebSite HTTP server, where those
         * apply; under Apache or nginx the front server owns the
         * ports and protocol so they are hidden. The under-WebSite
         * flag is fixed server-side. The Domains list is always
         * shown: it feeds certificates when Auto SSL Certs is on
         * and per-domain landing routes either way.
         */
        function showHideWebServer()
        {
            setDisplay('web-server-atto-config',
                <?= $data['show_web_server_atto'] ?>);
        }
        /*
         * Rebuilds the comma-separated value of the hidden
         * ALPN_PROTOCOLS input from the checked protocol boxes, in
         * the order they appear, so the saved list reflects the
         * current selection.
         */
        function updateAlpnProtocols()
        {
            var hidden = elt('alpn-protocols');
            if (!hidden) {
                return;
            }
            /* The checkboxes are shown in descending version order
               (h3, h2, http/1.1), which is also preference order
               (most modern first), so collecting in document order
               gives the right advertise order. */
            var boxes = sel(
                '.alpn-protocol-box');
            var parts = [];
            for (var i = 0; i < boxes.length; i++) {
                if (boxes[i].checked) {
                    parts.push(boxes[i].value);
                }
            }
            hidden.value = parts.join(',');
        }
        /*
         * Fills the mail port fields with their conventional
         * values: SMTP 25 (MX and STARTTLS), submission 587
         * (STARTTLS submission), IMAP 143 (STARTTLS), and the
         * wrapped implicit-TLS ports SMTPS 465 and IMAPS 993. All
         * five open together when a certificate is present, so the
         * defaults no longer depend on the "use STARTTLS" choice.
         * Invoked from the "Use Default Ports" link; the operator
         * can still edit any field afterward.
         */
        function setMailDefaultPorts()
        {
            var smtp = elt('mail-smtp-port');
            var submission = elt('mail-submission-port');
            var smtps = elt('mail-smtps-port');
            var imap = elt('mail-imap-port');
            var imaps = elt('mail-imaps-port');
            if (smtp) {
                smtp.value = 25;
            }
            if (submission) {
                submission.value = 587;
            }
            if (imap) {
                imap.value = 143;
            }
            if (smtps) {
                smtps.value = 465;
            }
            if (imaps) {
                imaps.value = 993;
            }
        }
        </script>
    <?php
    }
}
X