<?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;
/**
* ServersElement draws the Server Settings form, where a site owner
* says how Yioop reaches the other servers it works with: the machine
* that runs a crawl, the database, its own web server, the mail
* servers it sends and takes mail through, the relays a video call
* may use, and the proxies a fetcher goes out over.
*
* The Servers activity of the admin panel draws this element. Each
* group of settings is drawn by a render method of its own, so the
* form is read a group at a time.
*
* @author Chris Pollett
*/
class ServersElement extends Element
{
/**
* adminUrls builds the four web addresses the Server Settings form
* links back to itself and to Manage Machines with. Each render
* method that draws such a link builds them for itself by calling
* this, since a link drawn in one method cannot read a value set
* in another.
*
* The four are: the admin controller address, the token every link
* on the page carries, the two run together, and that with the
* Servers activity named. A caller takes the ones it needs by name
* from the array.
*
* @param array $data holds the token every form on the page
* carries under the CSRF_TOKEN key
* @return array the addresses under the keys admin_url,
* token_string, pre_base_url and base_url
*/
public function adminUrls($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;
return ['admin_url' => $admin_url,
'token_string' => $token_string,
'pre_base_url' => $pre_base_url,
'base_url' => $pre_base_url . "&a=servers"];
}
/**
* render draws the whole Server Settings form, calling one render
* method for each group of settings on it and closing with the
* Save button that sends them all.
*
* The Servers activity calls this method. It draws the form tag
* and the hidden fields every form on the page carries, so a
* group of settings has only its own controls to draw.
*
* @param array $data the settings the form shows, which form
* fields to draw, and the token every form on the page
* carries
*/
public function render($data)
{
?>
<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">
<?php $this->renderNameServer($data); ?>
</div>
<div class="top-margin">
<?php $this->renderDatabase($data); ?>
</div>
<div class = "top-margin">
</div>
<div class="top-margin">
<?php $this->renderWebServer($data); ?>
</div>
<div class="top-margin">
<?php $this->renderSystemEmails($data); ?>
</div>
<div class="top-margin">
<?php $this->renderMailServices($data); ?>
</div>
<div class="top-margin">
<?php $this->renderVideoCallServices($data); ?>
<?php $this->renderProxyServer($data); ?>
</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();
initCallServerList();
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();
}
});
}
/*
* Wires the Add and Remove buttons of the call relay list. The
* Add button reads the text field, trims spaces, passes over a
* relay the list already holds, appends a list item with its
* own Remove button, clears the field, and rebuilds the hidden
* input the form sends. The Remove buttons the server drew for
* already-saved relays are wired the same way. Pressing Save
* sends whatever the list holds at that moment.
*/
function initCallServerList()
{
var add_button = elt('call-server-add-button');
var add_input = elt('call-server-add');
var list = elt('call-server-list');
if (!add_button || !add_input || !list) {
return;
}
listen(add_button, 'click', function () {
var value = add_input.value.trim();
if (value === '') {
return;
}
var spans = sel(
'#call-server-list .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.textContent = '\u00D7';
remove.title = add_button.getAttribute(
'data-remove-label') || 'Remove';
wireCallServerRemove(remove);
item.appendChild(text);
item.appendChild(remove);
list.appendChild(item);
add_input.value = '';
syncCallServersHidden();
});
listen(add_input, 'keydown', function (event) {
if (event.key === 'Enter') {
event.preventDefault();
add_button.click();
}
});
var removes = sel(
'#call-server-list .mail-domain-remove');
for (var i = 0; i < removes.length; i++) {
wireCallServerRemove(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 Remove button of the call
* relay list. Both the buttons the server drew and the ones
* added while the screen is open are wired through here, so
* the two take a relay off the list the same way.
*/
function wireCallServerRemove(button)
{
listen(button, 'click', function () {
var item = button.closest('.mail-domain-item');
if (item && item.parentNode) {
item.parentNode.removeChild(item);
syncCallServersHidden();
}
});
}
/*
* Rebuilds the hidden CALL_SERVERS input from the relays the
* list now holds, one to a line, so what the form sends is
* what the screen shows.
*/
function syncCallServersHidden()
{
var hidden = elt('call-servers');
if (!hidden) {
return;
}
var spans = sel(
'#call-server-list .mail-domain-text');
var parts = [];
for (var i = 0; i < spans.length; i++) {
parts.push(spans[i].textContent);
}
hidden.value = parts.join("\n");
}
/*
* 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
}
/**
* renderNameServer draws the fieldset (the box a
* form draws round a group of settings) naming the machine that runs
* the crawl and how a fetcher reaches it.
*
* render calls it, so the servers form is read one group of
* settings at a time rather than in one long method.
*
* @param array $data the settings the form shows, and the
* token every form on the page carries
*/
public function renderNameServer($data)
{
$base_url = $this->adminUrls($data)['base_url'];
?>
<fieldset><legend><?=tl('servers_element_name_server') .
" " . $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>
<?php
}
/**
* renderDatabase draws the fieldset (the box a
* form draws round a group of settings) naming which database Yioop
* keeps its own records in and how it signs in to it.
*
* render calls it, so the servers form is read one group of
* settings at a time rather than in one long method.
*
* @param array $data the settings the form shows, and the
* token every form on the page carries
*/
public function renderDatabase($data)
{
?>
<fieldset><legend><?=tl('servers_element_database_setup').
" " .$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>
<?php
}
/**
* renderWebServer draws the fieldset (the box a
* form draws round a group of settings) naming how Yioop's own web
* server listens, including its certificate and its ports.
*
* render calls it, so the servers form is read one group of
* settings at a time rather than in one long method.
*
* @param array $data the settings the form shows, and the
* token every form on the page carries
*/
public function renderWebServer($data)
{
$pre_base_url = $this->adminUrls($data)['pre_base_url'];
?>
<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'
) ?>">✎</button><button
type="button"
class="secure-domain-remove mail-domain-remove"
data-domain="<?= $secure_domain
?>" title="<?= tl(
'servers_element_mail_domain_remove'
) ?>">×</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'
) ?>">→</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>
<?php
}
/**
* renderSystemEmails draws the fieldset (the box a
* form draws round a group of settings) naming the address Yioop
* writes from and how it reaches a mail server.
*
* render calls it, so the servers form is read one group of
* settings at a time rather than in one long method.
*
* @param array $data the settings the form shows, and the
* token every form on the page carries
*/
public function renderSystemEmails($data)
{
?>
<fieldset >
<legend><?=
tl('servers_element_system_emails') . " "
?><?= $this->view->helper("helpbutton")->render(
"System_Emails", $data[C\p('CSRF_TOKEN')])
?></legend>
<div class="top-margin"><b><label for="builtin-smtp"><?=
tl('servers_element_use_builtin_smtp')
?></label></b>
<input type="checkbox" id="builtin-smtp"
name="MAIL_BOT_HANDLED"
value="true" <?php if (
$data['MAIL_BOT_HANDLED'] == true) {
e("checked='checked'");
} ?> >
</div>
<div class="top-margin"><b><label for="use-a-queue"><?=
tl('servers_element_use_a_queue')
?></label></b>
<input type="checkbox" id="use-a-queue"
name="SEND_MAIL_MEDIA_UPDATER"
value="true" <?php if (
$data['SEND_MAIL_MEDIA_UPDATER'] == true) {
e("checked='checked'");
} ?> >
</div>
<div class="top-margin"><b><label for="mail-sender"><?=
tl('servers_element_sender_email') ?></label></b>
<input type="email" id="mail-sender" name="MAIL_SENDER"
value="<?= $data['MAIL_SENDER'] ?? '' ?>"
class="wide-field" >
<?php if (!empty($data['MAIL_SENDER_NOTE'])) { ?>
<div class="red"><?= $data['MAIL_SENDER_NOTE']
?></div>
<?php } ?>
</div>
<div id="mail-relay-fields">
<div class="top-margin"><b><label for="mail-server"><?=
tl('servers_element_smtp_server') ?></label></b>
<input type="text" id="mail-server" name="MAIL_SERVER"
value="<?= $data['MAIL_SERVER'] ?? '' ?>"
class="wide-field" >
</div>
<div class="top-margin"><b><label for="mail-port"><?=
tl('servers_element_server_port') ?></label></b>
<input type="text" id="mail-port"
name="MAIL_SERVERPORT"
value="<?= $data['MAIL_SERVERPORT'] ?? '' ?>"
class="wide-field" >
</div>
<div class="top-margin"><b><label for="mail-username"><?=
tl('servers_element_mail_username') ?></label></b>
<input type="text" id="mail-username"
name="MAIL_USERNAME"
value="<?= $data['MAIL_USERNAME'] ?? '' ?>"
class="wide-field" >
</div>
<div class="top-margin"><b><label for="mail-password"><?=
tl('servers_element_mail_password') ?></label></b>
<input type="password" id="mail-password"
name="MAIL_PASSWORD"
value="<?= $data['MAIL_PASSWORD'] ?? '' ?>"
class="wide-field" >
</div>
<div class="top-margin"><b><label for="mail-security"><?=
tl('servers_element_mail_security') ?></label></b>
<select id="mail-security" name="MAIL_SECURITY">
<?php
$security = $data['MAIL_SECURITY'] ?? 'starttls';
foreach (['plain' => 'mail_element_plain',
'starttls' => 'mail_element_starttls',
'smtps' => 'mail_element_smtps']
as $choice => $label) {
$chosen = ($security === $choice) ?
' selected="selected"' : '';
?><option value="<?= $choice ?>"<?= $chosen ?>><?=
tl($label) ?></option><?php
}
?>
</select>
</div>
</div>
</fieldset>
<?php
}
/**
* renderMailServices draws the fieldset (the box a
* form draws round a group of settings) naming the domains Yioop
* takes mail for and the settings its own mail server runs under.
*
* render calls it, so the servers form is read one group of
* settings at a time rather than in one long method.
*
* @param array $data the settings the form shows, and the
* token every form on the page carries
*/
public function renderMailServices($data)
{
['admin_url' => $admin_url, 'token_string' => $token_string] =
$this->adminUrls($data);
?>
<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" id="mail-banner-row"><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'");
} ?> >
[<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'
) ?>">×</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>
<?php
}
/**
* renderVideoCallServices draws the fieldset (the box a form draws
* round a group of settings) where a site owner says which relays a
* call between two people may use. A call between two
* people at home usually cannot be made directly, since each is
* behind a router that lets nothing in unasked, so a relay has to
* stand where both can reach it.
*
* The choice is between none, the relay Yioop runs itself, servers
* the owner names, or both. Where servers are named they are added
* one at a time, as the mail domains are. The controls that name
* them are shown only under the two choices that use them, since
* a call is handed a named server under no other choice.
*
* @param array $data the values the screen is drawn from, holding
* CALL_SERVICE_MODE and CALL_SERVERS
*/
public function renderVideoCallServices($data)
{
$modes = ["off" => tl('servers_element_calls_off'),
"turnsite" => tl('servers_element_calls_turnsite'),
"external" => tl('servers_element_calls_external'),
"both" => tl('servers_element_calls_both')];
$chosen = $data['CALL_SERVICE_MODE'];
$named = array_filter(array_map("trim",
preg_split("/[\n,]+/", (string)($data['CALL_SERVERS'] ?? ""))));
?>
<fieldset><legend><label for="call-service-mode"><?=
tl('servers_element_call_title') ?></label>
<?= $this->view->helper("helpbutton")->render(
"Video Call Services", $data[C\p('CSRF_TOKEN')])
?></legend>
<div><b><label for="call-service-mode"><?=
tl('servers_element_call_mode') ?></label></b>
<select id="call-service-mode" name="CALL_SERVICE_MODE"
class="wide-field"><?php
foreach ($modes as $value => $says) {
$picked = ($chosen == $value) ? " selected" : "";
?><option value="<?= $value ?>"<?= $picked ?>><?=
$says ?></option><?php
}
?></select>
</div>
<div class="top-margin" id="call-servers-row"><b><label
for="call-server-add"><?=
tl('servers_element_call_servers') ?></label></b>
<ul class="mail-domain-list" id="call-server-list"><?php
foreach ($named as $one) {
?><li class="mail-domain-item"><span
class="mail-domain-text"><?= $one
?></span><button type="button"
class="mail-domain-remove" title="<?= tl(
'servers_element_call_server_remove'
) ?>">×</button></li><?php
}
?></ul>
<div class="mail-domain-add-row">
<input type="text" id="call-server-add"
class="wide-field" placeholder="<?=
tl('servers_element_call_server_hint') ?>" >
<button type="button" id="call-server-add-button"
class="button-box" data-remove-label="<?= tl(
'servers_element_call_server_remove') ?>"><?=
tl('servers_element_call_server_add') ?></button>
</div>
<input type="hidden" id="call-servers" name="CALL_SERVERS"
value="<?= implode("\n", $named) ?>" >
</div>
</fieldset>
<?php
}
/**
* renderProxyServer draws the fieldset (the box a
* form draws round a group of settings) naming the proxies a fetcher
* reaches the web through.
*
* render calls it, so the servers form is read one group of
* settings at a time rather than in one long method.
*
* @param array $data the settings the form shows, and the
* token every form on the page carries
*/
public function renderProxyServer($data)
{
?>
<fieldset><legend><?=tl('servers_element_proxy_title') .
" " .$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>
<?php
}
}