diff --git a/actions/admin/settings/100.panel.php b/actions/admin/settings/100.panel.php
index 75e2ac9e..b1ac1522 100644
--- a/actions/admin/settings/100.panel.php
+++ b/actions/admin/settings/100.panel.php
@@ -269,7 +269,8 @@ return [
'traffic' => lng('menue.traffic.traffic'),
'traffic.http' => lng('menue.traffic.traffic') . " / HTTP",
'traffic.ftp' => lng('menue.traffic.traffic') . " / FTP",
- 'traffic.mail' => lng('menue.traffic.traffic') . " / Mail"
+ 'traffic.mail' => lng('menue.traffic.traffic') . " / Mail",
+ 'misc.documentation' => lng('admin.documentation'),
],
'save_method' => 'storeSettingField',
'advanced_mode' => true
diff --git a/actions/admin/settings/131.ssl.php b/actions/admin/settings/131.ssl.php
index 85f8c0be..05ef2fa3 100644
--- a/actions/admin/settings/131.ssl.php
+++ b/actions/admin/settings/131.ssl.php
@@ -241,6 +241,16 @@ return [
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField'
+ ],
+ 'system_le_domain_dnscheck_resolver' => [
+ 'label' => lng('serversettings.le_domain_dnscheck_resolver'),
+ 'settinggroup' => 'system',
+ 'varname' => 'le_domain_dnscheck_resolver',
+ 'type' => 'text',
+ 'string_regexp' => '/^(([0-9]+ [a-z0-9\-\._]+, ?)*[0-9]+ [a-z0-9\-\._]+)?$/i',
+ 'string_emptyallowed' => true,
+ 'default' => '',
+ 'save_method' => 'storeSettingField'
]
]
]
diff --git a/admin_configfiles.php b/admin_configfiles.php
index c3e3f0dd..ac4630db 100644
--- a/admin_configfiles.php
+++ b/admin_configfiles.php
@@ -92,6 +92,7 @@ if ($userinfo['change_serversettings'] == '1') {
if ($distribution != "" && isset($_POST['finish'])) {
unset($_POST['finish']);
+ unset($_POST['csrf_token']);
$params = $_POST;
$params['distro'] = $distribution;
$params['system'] = [];
@@ -121,8 +122,6 @@ if ($userinfo['change_serversettings'] == '1') {
'distribution' => $distribution
]);
} else {
- // @fixme check set distribution from settings
-
$cfg_formfield = [
'config' => [
'title' => lng('admin.configfiles.serverconfiguration'),
diff --git a/composer.json b/composer.json
index cf2e1942..67ab5f70 100644
--- a/composer.json
+++ b/composer.json
@@ -52,7 +52,8 @@
"voku/anti-xss": "^4.1",
"twig/twig": "^3.3",
"erusev/parsedown": "^1.7",
- "symfony/console": "^5.4"
+ "symfony/console": "^5.4",
+ "pear/net_dns2": "^1.5"
},
"require-dev": {
"phpunit/phpunit": "^9",
diff --git a/composer.lock b/composer.lock
index bacbbad2..f5e0d5fb 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "f8370edea3c85bcb7b681926a1fff04e",
+ "content-hash": "41e7a3bc0e13b47c4f245334b113c3be",
"packages": [
{
"name": "erusev/parsedown",
@@ -198,6 +198,57 @@
],
"time": "2022-06-09T08:53:42+00:00"
},
+ {
+ "name": "pear/net_dns2",
+ "version": "v1.5.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mikepultz/netdns2.git",
+ "reference": "dc8053772132a855b8bb6193422a959995f3a773"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mikepultz/netdns2/zipball/dc8053772132a855b8bb6193422a959995f3a773",
+ "reference": "dc8053772132a855b8bb6193422a959995f3a773",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Net_DNS2": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Mike Pultz",
+ "email": "mike@mikepultz.com",
+ "homepage": "https://mikepultz.com/",
+ "role": "lead"
+ }
+ ],
+ "description": "Native PHP DNS Resolver and Updater Library",
+ "homepage": "https://netdns2.com/",
+ "keywords": [
+ "PEAR",
+ "dns",
+ "network"
+ ],
+ "support": {
+ "issues": "https://github.com/mikepultz/netdns2/issues",
+ "source": "https://github.com/mikepultz/netdns2"
+ },
+ "time": "2022-11-28T19:16:31+00:00"
+ },
{
"name": "phpmailer/phpmailer",
"version": "v6.6.3",
diff --git a/customer_index.php b/customer_index.php
index c4a1ea80..02b65683 100644
--- a/customer_index.php
+++ b/customer_index.php
@@ -115,10 +115,14 @@ if ($page == 'overview') {
if ($usages) {
$userinfo['diskspace_bytes_used'] = $usages['webspace'] * 1024;
+ $userinfo['mailspace_used'] = $usages['mail'] * 1024;
+ $userinfo['dbspace_used'] = $usages['mysql'] * 1024;
$userinfo['total_bytes_used'] = ($usages['webspace'] + $usages['mail'] + $usages['mysql']) * 1024;
} else {
$userinfo['diskspace_bytes_used'] = 0;
$userinfo['total_bytes_used'] = 0;
+ $userinfo['mailspace_used'] = 0;
+ $userinfo['dbspace_used'] = 0;
}
UI::twig()->addGlobal('userinfo', $userinfo);
diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php
index 1f64b9ce..8dfaf5d0 100644
--- a/install/froxlor.sql.php
+++ b/install/froxlor.sql.php
@@ -642,7 +642,7 @@ opcache.validate_timestamps'),
('system', 'leprivatekey', 'unset'),
('system', 'lepublickey', 'unset'),
('system', 'letsencryptca', 'letsencrypt'),
- ('system', 'letsencryptchallengepath', '/var/www/froxlor'),
+ ('system', 'letsencryptchallengepath', '/var/www/html/froxlor'),
('system', 'letsencryptkeysize', '4096'),
('system', 'letsencryptreuseold', 0),
('system', 'leenabled', '0'),
@@ -670,6 +670,7 @@ opcache.validate_timestamps'),
('system', 'leaccount', ''),
('system', 'nssextrausers', '1'),
('system', 'le_domain_dnscheck', '1'),
+ ('system', 'le_domain_dnscheck_resolver', '1.1.1.1'),
('system', 'ssl_protocols', 'TLSv1.2'),
('system', 'tlsv13_cipher_list', ''),
('system', 'honorcipherorder', '0'),
@@ -696,7 +697,7 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
- ('system', 'update_notify_last', '2.0.7'),
+ ('system', 'update_notify_last', '2.0.9'),
('system', 'traffictool', 'goaccess'),
('api', 'enabled', '0'),
('2fa', 'enabled', '1'),
@@ -740,8 +741,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
- ('panel', 'version', '2.0.7'),
- ('panel', 'db_version', '202212060');
+ ('panel', 'version', '2.0.9'),
+ ('panel', 'db_version', '202301180');
DROP TABLE IF EXISTS `panel_tasks`;
diff --git a/install/updates/froxlor/update_2.x.inc.php b/install/updates/froxlor/update_2.x.inc.php
index b5ee6543..476770f3 100644
--- a/install/updates/froxlor/update_2.x.inc.php
+++ b/install/updates/froxlor/update_2.x.inc.php
@@ -38,10 +38,9 @@ if (!defined('_CRON_UPDATE')) {
// last 0.10.x release
if (Froxlor::isFroxlorVersion('0.10.38.3')) {
-
$update_to = '2.0.0-beta1';
- Update::showUpdateStep("Updating from 0.10.38.3 to ".$update_to, false);
+ Update::showUpdateStep("Updating from 0.10.38.3 to " . $update_to, false);
Update::showUpdateStep("Removing unused table");
Database::query("DROP TABLE IF EXISTS `panel_sessions`;");
@@ -146,7 +145,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
}
Update::showUpdateStep("Adding new settings");
- $panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int) $_POST['panel_settings_mode'] : 0;
+ $panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
Settings::AddNew("panel.settings_mode", $panel_settings_mode);
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : '';
Settings::AddNew("system.distribution", $system_distribution);
@@ -193,7 +192,6 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
}
if (Froxlor::isDatabaseVersion('202112310')) {
-
Update::showUpdateStep("Adjusting traffic tool settings");
$traffic_tool = Settings::Get('system.awstats_enabled') == 1 ? 'awstats' : 'webalizer';
Settings::AddNew("system.traffictool", $traffic_tool);
@@ -204,7 +202,6 @@ if (Froxlor::isDatabaseVersion('202112310')) {
}
if (Froxlor::isDatabaseVersion('202211030')) {
-
Update::showUpdateStep("Creating backward compatibility for cronjob");
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
@@ -225,8 +222,8 @@ EOF;
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
Update::lastStepStatus(0);
} else {
- $cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHO_EOL;
- $cron_run_cmd .= FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli').' froxlor:cron -r 99';
+ $cron_run_cmd = 'chmod +x ' . FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . PHP_EOL;
+ $cron_run_cmd .= FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -r 99';
Update::lastStepStatus(1, 'manual commands needed', 'Please run the following commands manually:
' . $cron_run_cmd . '
');
}
@@ -268,20 +265,27 @@ if (Froxlor::isFroxlorVersion('2.0.3')) {
$complete_filedir = Froxlor::getInstallDir() . '/scripts';
// check if compat. cronjob still exists (most likely didn't run successfully b/c of error from former 2.0 release)
- if (@file_exists($complete_filedir.'/froxlor_master_cronjob.php')) {
+ if (@file_exists($complete_filedir . '/froxlor_master_cronjob.php')) {
Update::showUpdateStep("Adjusting backward compatibility for cronjob");
- $newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
- $compCron = <<' . $cron_run_cmd . '
');
+ }
}
-
Froxlor::updateToVersion('2.0.4');
}
@@ -312,3 +316,64 @@ if (Froxlor::isFroxlorVersion('2.0.6')) {
Froxlor::updateToVersion('2.0.7');
}
+
+if (Froxlor::isDatabaseVersion('202212060')) {
+ Update::showUpdateStep("Validating acme.sh challenge path");
+ $acmesh_challenge_dir = Settings::Get('system.letsencryptchallengepath');
+ $system_letsencryptchallengepath_upd = isset($_POST['system_letsencryptchallengepath_upd']) ? $_POST['system_letsencryptchallengepath_upd'] : $acmesh_challenge_dir;
+ if ($acmesh_challenge_dir != $system_letsencryptchallengepath_upd) {
+ Settings::Set('system.letsencryptchallengepath', $system_letsencryptchallengepath_upd);
+ if ((int) Settings::Get('system.leenabled') == 1) {
+ // create JSON string for --apply
+ $dist = Settings::Get('system.distribution');
+ $webserver = Settings::Get('system.webserver');
+ if ($webserver == 'apache2') {
+ $webserver = 'apache22';
+ if (Settings::Get('system.apache24')) {
+ $webserver = 'apache24';
+ }
+ }
+ $apply_json = '{"http":"' . $webserver . '","dns":"x","smtp":"x","mail":"x","ftp":"x","distro":"' . $dist . '","system":[]}';
+ Update::lastStepStatus(1, 'manual commands needed',
+ "Please reconfigure webserver service using bin/froxlor-cli froxlor:config-services --apply='" . $apply_json . "'
" .
+ '
or adjust the path manually in ' . Settings::Get('system.letsencryptacmeconf') . '' .
+ '
In case you already have certificates issued, run the following command to validate and correct the webroot used for renewal:
' .
+ 'bin/froxlor-cli froxlor:validate-acme-webroot
'
+ );
+ } else {
+ Update::lastStepStatus(0);
+ }
+ } else {
+ Update::lastStepStatus(0);
+ }
+
+ Froxlor::updateToDbVersion('202301120');
+}
+
+if (Froxlor::isFroxlorVersion('2.0.7')) {
+ Update::showUpdateStep("Updating from 2.0.7 to 2.0.8", false);
+
+ // adjust file-logging to be set to froxlor/logs/
+ $logtypes = explode(',', Settings::Get('logger.logtypes'));
+ if (in_array('file', $logtypes)) {
+ Update::showUpdateStep("Adjusting froxlor logfile for system-logging to be stored in logs/froxlor.log");
+ Settings::Set('logger.logfile', 'froxlor.log');
+ Update::lastStepStatus(0);
+ }
+
+ Froxlor::updateToVersion('2.0.8');
+}
+
+if (Froxlor::isDatabaseVersion('202301120')) {
+ Update::showUpdateStep("Adding new setting for DNS resolver when using Let's Encrypt");
+ $system_le_domain_dnscheck_resolver = isset($_POST['system_le_domain_dnscheck_resolver']) ? $_POST['system_le_domain_dnscheck_resolver'] : '1.1.1.1';
+ Settings::AddNew("system.le_domain_dnscheck_resolver", $system_le_domain_dnscheck_resolver);
+ Update::lastStepStatus(0);
+
+ Froxlor::updateToDbVersion('202301180');
+}
+
+if (Froxlor::isFroxlorVersion('2.0.8')) {
+ Update::showUpdateStep("Updating from 2.0.8 to 2.0.9", false);
+ Froxlor::updateToVersion('2.0.9');
+}
diff --git a/install/updates/preconfig/preconfig_0.10.inc.php b/install/updates/preconfig/preconfig_0.10.inc.php
index 2a50eb7a..a1711fda 100644
--- a/install/updates/preconfig/preconfig_0.10.inc.php
+++ b/install/updates/preconfig/preconfig_0.10.inc.php
@@ -34,9 +34,14 @@ $return = [];
if (Update::versionInUpdate($current_db_version, '202004140')) {
$has_preconfig = true;
$description = 'Froxlor can now optionally validate the dns entries of domains that request Lets Encrypt certificates to reduce dns-related problems (e.g. freshly registered domain or updated a-record).';
- $return['system_le_domain_dnscheck_note'] = ['type' => 'infotext', 'value' => $description];
$question = 'Validate DNS of domains when using Lets Encrypt ';
- $return['system_le_domain_dnscheck'] = ['type' => 'checkbox', 'value' => 1, 'checked' => 1, 'label' => $question];
+ $return['system_le_domain_dnscheck'] = [
+ 'type' => 'checkbox',
+ 'value' => 1,
+ 'checked' => 1,
+ 'label' => $question,
+ 'prior_infotext' => $description
+ ];
}
$preconfig['fields'] = $return;
diff --git a/install/updates/preconfig/preconfig_2.x.inc.php b/install/updates/preconfig/preconfig_2.x.inc.php
index ff427cf5..5eb3d97d 100644
--- a/install/updates/preconfig/preconfig_2.x.inc.php
+++ b/install/updates/preconfig/preconfig_2.x.inc.php
@@ -27,6 +27,7 @@ use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Config\ConfigParser;
use Froxlor\Install\Update;
+use Froxlor\Settings;
$preconfig = [
'title' => '2.x updates',
@@ -36,7 +37,6 @@ $return = [];
if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
$description = 'We have rearranged the settings and split them into basic and advanced categories. This makes it easier for users who do not need all the detailed or very specific settings and options and gives a better overview of the basic/mostly used settings.';
- $return['panel_settings_mode_note'] = ['type' => 'infotext', 'value' => $description];
$question = 'Chose settings mode (you can change that at any time)';
$return['panel_settings_mode'] = [
'type' => 'select',
@@ -45,11 +45,11 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
1 => 'Advanced'
],
'selected' => 1,
- 'label' => $question
+ 'label' => $question,
+ 'prior_infotext' => $description
];
$description = 'The configuration page now can preselect a distribution, please select your current distribution';
- $return['system_distribution_note'] = ['type' => 'infotext', 'value' => $description];
$question = 'Select distribution';
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
// show list of available distro's
@@ -68,9 +68,44 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
'type' => 'select',
'select_var' => $distributions_select,
'selected' => '',
- 'label' => $question
+ 'label' => $question,
+ 'prior_infotext' => $description
];
}
+if (Update::versionInUpdate($current_db_version, '202301120')) {
+ $acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/");
+ $recommended = rtrim(FileDir::makeCorrectDir(Froxlor::getInstallDir()), "/");
+ if ((int) Settings::Get('system.leenabled') == 1 && $acmesh_challenge_dir != $recommended) {
+ $has_preconfig = true;
+ $description = 'ACME challenge docroot from settings differs from the current installation directory.';
+ $question = 'Validate Let\'s Encrypt challenge path (recommended value: ' . $recommended . ')';
+ $return['system_letsencryptchallengepath_upd'] = [
+ 'type' => 'text',
+ 'value' => $recommended,
+ 'placeholder' => $acmesh_challenge_dir,
+ 'label' => $question,
+ 'prior_infotext' => $description,
+ 'mandatory' => true,
+ ];
+ }
+}
+
+if (Update::versionInUpdate($current_db_version, '202301180')) {
+ if ((int) Settings::Get('system.leenabled') == 1) {
+ $has_preconfig = true;
+ $description = 'Froxlor now supports to set an external DNS resolver for the Let\'s Encrypt pre-check.';
+ $question = 'Specify a DNS resolver IP (recommended value: 1.1.1.1 or similar)';
+ $return['system_le_domain_dnscheck_resolver'] = [
+ 'type' => 'text',
+ 'pattern' => '^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$|^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$|^\s*$',
+ 'value' => '1.1.1.1',
+ 'placeholder' => '1.1.1.1',
+ 'label' => $question,
+ 'prior_infotext' => $description,
+ ];
+ }
+}
+
$preconfig['fields'] = $return;
return $preconfig;
diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php
index ee31f600..cbf85fef 100644
--- a/lib/Froxlor/Api/Commands/Domains.php
+++ b/lib/Froxlor/Api/Commands/Domains.php
@@ -559,7 +559,7 @@ class Domains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
- $domain_ips = PhpHelper::gethostbynamel6($domain);
+ $domain_ips = PhpHelper::gethostbynamel6($domain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
@@ -1523,7 +1523,7 @@ class Domains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
- $domain_ips = PhpHelper::gethostbynamel6($result['domain']);
+ $domain_ips = PhpHelper::gethostbynamel6($result['domain'], true, Settings::Get('system.le_domain_dnscheck_resolver'));
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
diff --git a/lib/Froxlor/Api/Commands/SubDomains.php b/lib/Froxlor/Api/Commands/SubDomains.php
index 6097caf0..98c05cb9 100644
--- a/lib/Froxlor/Api/Commands/SubDomains.php
+++ b/lib/Froxlor/Api/Commands/SubDomains.php
@@ -262,7 +262,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($domain_check['id']);
- $domain_ips = PhpHelper::gethostbynamel6($completedomain);
+ $domain_ips = PhpHelper::gethostbynamel6($completedomain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
}
@@ -738,7 +738,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
// validate dns if lets encrypt is enabled to check whether we can use it at all
if ($result['letsencrypt'] != $letsencrypt && $letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
$our_ips = Domain::getIpsOfDomain($result['parentdomainid']);
- $domain_ips = PhpHelper::gethostbynamel6($result['domain']);
+ $domain_ips = PhpHelper::gethostbynamel6($result['domain'], true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
Response::standardError('invaliddnsforletsencrypt', '', true);
}
diff --git a/lib/Froxlor/Cli/ValidateAcmeWebroot.php b/lib/Froxlor/Cli/ValidateAcmeWebroot.php
index f2aab971..1a634fae 100644
--- a/lib/Froxlor/Cli/ValidateAcmeWebroot.php
+++ b/lib/Froxlor/Cli/ValidateAcmeWebroot.php
@@ -44,7 +44,7 @@ final class ValidateAcmeWebroot extends CliCommand
protected function configure()
{
$this->setName('froxlor:validate-acme-webroot');
- $this->setDescription('Validates the Le_Webroot value is correct for froxlor managed domains with Let\s Encrypt certificate.');
+ $this->setDescription('Validates the Le_Webroot value is correct for froxlor managed domains with Let\'s Encrypt certificate.');
$this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
}
@@ -56,6 +56,11 @@ final class ValidateAcmeWebroot extends CliCommand
$io = new SymfonyStyle($input, $output);
+ if ((int) Settings::Get('system.leenabled') == 0) {
+ $io->info("Let's Encrypt not activated in froxlor settings.");
+ $result = self::INVALID;
+ }
+
if ($result == self::SUCCESS) {
$yestoall = $input->getOption('yes-to-all') !== false;
$helper = $this->getHelper('question');
@@ -64,9 +69,37 @@ final class ValidateAcmeWebroot extends CliCommand
$sel_stmt = Database::prepare("SELECT id, domain FROM panel_domains WHERE `letsencrypt` = '1' AND aliasdomain IS NULL ORDER BY id ASC");
Database::pexecute($sel_stmt);
$domains = $sel_stmt->fetchAll(PDO::FETCH_ASSOC);
+ // check for froxlor-vhost
+ if (Settings::Get('system.le_froxlor_enabled') == '1') {
+ $domains[] = [
+ 'id' => 0,
+ 'domain' => Settings::Get('system.hostname')
+ ];
+ }
$upd_stmt = Database::prepare("UPDATE domain_ssl_settings SET expirationdate=NULL WHERE `domainid` = :did");
$acmesh_dir = dirname(Settings::Get('system.acmeshpath'));
- $acmesh_challenge_dir = Settings::Get('system.letsencryptchallengepath');
+ $acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/");
+ $recommended = rtrim(FileDir::makeCorrectDir(Froxlor::getInstallDir()), "/");
+
+ if ($acmesh_challenge_dir != $recommended) {
+ $io->warning([
+ "ACME challenge docroot from settings differs from the current installation directory.",
+ "Settings: '" . $acmesh_challenge_dir . "'",
+ "Default/recommended value: '" . $recommended . "'",
+ ]);
+ $question = new ConfirmationQuestion('Fix ACME challenge docroot setting? [yes] ', true, '/^(y|j)/i');
+ if ($yestoall || $helper->ask($input, $output, $question)) {
+ Settings::Set('system.letsencryptchallengepath', $recommended);
+ $former_value = $acmesh_challenge_dir;
+ $acmesh_challenge_dir = $recommended;
+ // need to update the corresponding acme-alias config-file
+ $acme_alias_file = Settings::Get('system.letsencryptacmeconf');
+ $sed_params = "s@".$former_value."@" . $acmesh_challenge_dir . "@";
+ FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_alias_file));
+ $count_changes++;
+ }
+ }
+
foreach ($domains as $domain_arr) {
$domain = $domain_arr['domain'];
$acme_domain_conf = FileDir::makeCorrectFile($acmesh_dir . '/' . $domain . '/' . $domain . '.conf');
@@ -113,6 +146,7 @@ final class ValidateAcmeWebroot extends CliCommand
}
if ($count_changes > 0) {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
+ $io->info("Changes detected but froxlor has been updated. Inserting task to rebuild vhosts after update.");
Cronjob::inserttask(TaskId::REBUILD_VHOST);
} else {
$question = new ConfirmationQuestion('Changes detected. Force cronjob to refresh certificates? [yes] ', true, '/^(y|j)/i');
@@ -120,6 +154,8 @@ final class ValidateAcmeWebroot extends CliCommand
passthru(FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -f -d');
}
}
+ } else {
+ $io->success("No changes necessary.");
}
}
diff --git a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php
index e6c37473..122d6443 100644
--- a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php
+++ b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php
@@ -521,7 +521,7 @@ EOC;
foreach ($loop_domains as $idx => $domain) {
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain);
// ips according to NS
- $domain_ips = PhpHelper::gethostbynamel6($domain);
+ $domain_ips = PhpHelper::gethostbynamel6($domain, true, Settings::Get('system.le_domain_dnscheck_resolver'));
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
// no common ips...
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $domain . " due to no system known IP address via DNS check");
@@ -557,7 +557,7 @@ EOC;
if (Settings::Get('system.letsencryptreuseold') != '1') {
$acmesh_cmd .= " --always-force-new-domain-key";
}
- if (Settings::Get('system.letsencryptca') == 'letsencrypt_test') {
+ if (substr(Settings::Get('system.letsencryptca'), -5) == '_test') {
$acmesh_cmd .= " --staging";
}
if ($force) {
diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php
index dafed67a..c8ed31a8 100644
--- a/lib/Froxlor/Froxlor.php
+++ b/lib/Froxlor/Froxlor.php
@@ -31,10 +31,10 @@ final class Froxlor
{
// Main version variable
- const VERSION = '2.0.7';
+ const VERSION = '2.0.9';
// Database version (YYYYMMDDC where C is a daily counter)
- const DBVERSION = '202212060';
+ const DBVERSION = '202301180';
// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';
diff --git a/lib/Froxlor/FroxlorLogger.php b/lib/Froxlor/FroxlorLogger.php
index dcc2eb2f..4bbd5e09 100644
--- a/lib/Froxlor/FroxlorLogger.php
+++ b/lib/Froxlor/FroxlorLogger.php
@@ -100,11 +100,17 @@ class FroxlorLogger
self::$ml->pushHandler(new SyslogHandler('froxlor', LOG_USER, Logger::DEBUG));
break;
case 'file':
- $logger_logfile = Settings::Get('logger.logfile');
+ $logger_logfile = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/logs/' . Settings::Get('logger.logfile'));
// is_writable needs an existing file to check if it's actually writable
@touch($logger_logfile);
if (empty($logger_logfile) || !is_writable($logger_logfile)) {
- Settings::Set('logger.logfile', '/tmp/froxlor.log');
+ Settings::Set('logger.logfile', 'froxlor.log');
+ $logger_logfile = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/logs/froxlor.log');
+ @touch($logger_logfile);
+ if (empty($logger_logfile) || !is_writable($logger_logfile)) {
+ // not writable in our own directory? Skip
+ break;
+ }
}
self::$ml->pushHandler(new StreamHandler($logger_logfile, Logger::DEBUG));
break;
diff --git a/lib/Froxlor/Install/Preconfig.php b/lib/Froxlor/Install/Preconfig.php
index da5c943a..43e9942e 100644
--- a/lib/Froxlor/Install/Preconfig.php
+++ b/lib/Froxlor/Install/Preconfig.php
@@ -101,7 +101,7 @@ class Preconfig
$agree = [
'title' => 'Check',
'fields' => [
- 'update_changesagreed' => ['type' => 'checkbox', 'value' => 1, 'label' => 'I have read the update notifications above and I am aware of the changes made to my system.'],
+ 'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => 'I have read the update notifications above and I am aware of the changes made to my system.'],
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
]
];
diff --git a/lib/Froxlor/PhpHelper.php b/lib/Froxlor/PhpHelper.php
index 82dc70a9..820f14a7 100644
--- a/lib/Froxlor/PhpHelper.php
+++ b/lib/Froxlor/PhpHelper.php
@@ -27,6 +27,8 @@ namespace Froxlor;
use Exception;
use Froxlor\UI\Panel\UI;
+use Net_DNS2_Exception;
+use Net_DNS2_Resolver;
use Throwable;
use voku\helper\AntiXSS;
@@ -244,45 +246,60 @@ class PhpHelper
* ipv6 aware gethostbynamel function
*
* @param string $host
- * @param boolean $try_a
- * default true
+ * @param boolean $try_a default true
+ * @param string|null $nameserver set additional resolver nameserver to use (e.g. 1.1.1.1)
* @return boolean|array
*/
- public static function gethostbynamel6($host, $try_a = true)
+ public static function gethostbynamel6(string $host, bool $try_a = true, string $nameserver = null)
{
- $dns6 = @dns_get_record($host, DNS_AAAA);
- if (!is_array($dns6)) {
- // no record or failed to check
- $dns6 = [];
- }
- if ($try_a == true) {
- $dns4 = @dns_get_record($host, DNS_A);
- if (!is_array($dns4)) {
- // no record or failed to check
- $dns4 = [];
- }
- $dns = array_merge($dns4, $dns6);
- } else {
- $dns = $dns6;
- }
$ips = [];
- foreach ($dns as $record) {
- if ($record["type"] == "A") {
- // always use compressed ipv6 format
- $ip = inet_ntop(inet_pton($record["ip"]));
- $ips[] = $ip;
+
+ try {
+ // set the default nameservers to use, use the system default if none are provided
+ $resolver = new Net_DNS2_Resolver($nameserver ? ['nameservers' => [$nameserver]] : []);
+
+ // get all ip addresses from the A record and normalize them
+ if ($try_a) {
+ try {
+ $answer = $resolver->query($host, 'A')->answer;
+ foreach ($answer as $rr) {
+ $ips[] = inet_ntop(inet_pton($rr->address));
+ }
+ } catch (Net_DNS2_Exception $e) {
+ // we can't do anything here, just continue
+ }
}
- if ($record["type"] == "AAAA") {
- // always use compressed ipv6 format
- $ip = inet_ntop(inet_pton($record["ipv6"]));
- $ips[] = $ip;
+
+ // get all ip addresses from the AAAA record and normalize them
+ try {
+ $answer = $resolver->query($host, 'AAAA')->answer;
+ foreach ($answer as $rr) {
+ $ips[] = inet_ntop(inet_pton($rr->address));
+ }
+ } catch (Net_DNS2_Exception $e) {
+ // we can't do anything here, just continue
+ }
+ } catch (Net_DNS2_Exception $e) {
+ // fallback to php's dns_get_record if Net_DNS2 has no resolver available, but this may cause
+ // problems if the system's dns is not configured correctly; for example, the acme pre-check
+ // will fail because some providers put a local ip in /etc/hosts
+
+ // get all ip addresses from the A record and normalize them
+ if ($try_a) {
+ $answer = @dns_get_record($host, DNS_A);
+ foreach ($answer as $rr) {
+ $ips[] = inet_ntop(inet_pton($rr['ip']));
+ }
+ }
+
+ // get all ip addresses from the AAAA record and normalize them
+ $answer = @dns_get_record($host, DNS_AAAA);
+ foreach ($answer as $rr) {
+ $ips[] = inet_ntop(inet_pton($rr['ipv6']));
}
}
- if (count($ips) < 1) {
- return false;
- } else {
- return $ips;
- }
+
+ return count($ips) > 0 ? $ips : false;
}
/**
diff --git a/lib/Froxlor/UI/Panel/FroxlorTwig.php b/lib/Froxlor/UI/Panel/FroxlorTwig.php
index 20c2d4e6..d2f49175 100644
--- a/lib/Froxlor/UI/Panel/FroxlorTwig.php
+++ b/lib/Froxlor/UI/Panel/FroxlorTwig.php
@@ -87,6 +87,10 @@ class FroxlorTwig extends AbstractExtension
new TwigFunction('linker', [
$this,
'getLink'
+ ]),
+ new TwigFunction('mix', [
+ $this,
+ 'getMix'
])
];
}
@@ -158,4 +162,9 @@ class FroxlorTwig extends AbstractExtension
{
return 'froxlortwig';
}
+
+ public function getMix($mix = '')
+ {
+ return mix($mix);
+ }
}
diff --git a/lib/config.example.inc.php b/lib/config.example.inc.php
index 26d1be34..7f34f735 100644
--- a/lib/config.example.inc.php
+++ b/lib/config.example.inc.php
@@ -4,5 +4,11 @@
* change the options below to either true or false
*/
return [
- 'enable_webupdate' => false
+ /**
+ * enable/disable the possibility to update froxlor from within the web-interface,
+ * recommended value for debian/ubuntu package users is false to rely on apt and not have version mixup.
+ * This is also useful for providers that manage the servers but give admin access to froxlor to handle
+ * updates the way the providers does it (e.g. automation, etc.)
+ */
+ 'enable_webupdate' => false,
];
diff --git a/lib/configfiles/bionic.xml b/lib/configfiles/bionic.xml
index 6fd9a68b..f91b0d55 100644
--- a/lib/configfiles/bionic.xml
+++ b/lib/configfiles/bionic.xml
@@ -3458,11 +3458,7 @@ ssl_key = <
# auth_ssl_username_from_cert=yes.
#ssl_cert_username_field = commonName
-# SSL DH parameters
-# Generate new params with `openssl dhparam -out /etc/dovecot/dh.pem 4096`
-# Or migrate from old ssl-parameters.dat file with the command dovecot
-# gives on startup when ssl_dh is unset.
-ssl_dh = lng('admin.domain_add'),
'image' => 'fa-solid fa-globe',
'self_overview' => ['section' => 'domains', 'page' => 'domains'],
+ 'id' => 'domain_add',
'sections' => [
'section_a' => [
'title' => lng('domains.domainsettings'),
diff --git a/lib/formfields/admin/domains/formfield.domains_edit.php b/lib/formfields/admin/domains/formfield.domains_edit.php
index fe5aaf7a..48eadb60 100644
--- a/lib/formfields/admin/domains/formfield.domains_edit.php
+++ b/lib/formfields/admin/domains/formfield.domains_edit.php
@@ -30,6 +30,7 @@ return [
'title' => lng('admin.domain_edit'),
'image' => 'fa-solid fa-globe',
'self_overview' => ['section' => 'domains', 'page' => 'domains'],
+ 'id' => 'domain_edit',
'sections' => [
'section_a' => [
'title' => lng('domains.domainsettings'),
diff --git a/lib/functions.php b/lib/functions.php
index bca5eb0e..4119bfa3 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -26,6 +26,14 @@
use Froxlor\Language;
use Froxlor\UI\Request;
+/**
+ * Render a template with the given data.
+ * Mostly used if we have no template-engine (twig).
+ *
+ * @param $template
+ * @param $attributes
+ * @return array|false|string|string[]
+ */
function view($template, $attributes)
{
$view = file_get_contents(dirname(__DIR__) . '/templates/' . $template);
@@ -33,11 +41,26 @@ function view($template, $attributes)
return str_replace(array_keys($attributes), array_values($attributes), $view);
}
+/**
+ * Get the current translation for a given string.
+ *
+ * @param string $identifier
+ * @param array $arguments
+ * @return array|string
+ */
function lng(string $identifier, array $arguments = [])
{
return Language::getTranslation($identifier, $arguments);
}
+/**
+ * Get the value of a request variable.
+ *
+ * @param string $identifier
+ * @param string|null $default
+ * @param string|null $session
+ * @return mixed|string|null
+ */
function old(string $identifier, string $default = null, string $session = null)
{
if ($session && isset($_SESSION[$session])) {
@@ -45,3 +68,26 @@ function old(string $identifier, string $default = null, string $session = null)
}
return Request::any($identifier, $default);
}
+
+/**
+ * Loading the mix manifest file from given theme.
+ * This file contains the hashed filenames of the assets.
+ * It must be always placed in the theme assets folder.
+ *
+ * @param $filename
+ * @return mixed|string
+ */
+function mix($filename)
+{
+ if (preg_match('/templates\/(.+)\/assets\/(.+)\/(.+)/', $filename, $matches)) {
+ $mixManifest = dirname(__DIR__) . '/templates/' . $matches[1] . '/assets/mix-manifest.json';
+ if (file_exists($mixManifest)) {
+ $manifest = json_decode(file_get_contents($mixManifest), true);
+ $key = '/' . $matches[2] . '/' . $matches[3];
+ if ($manifest && !empty($manifest[$key])) {
+ $filename = 'templates/' . $matches[1] . '/assets' . $manifest[$key];
+ }
+ }
+ }
+ return $filename;
+}
diff --git a/lib/init.php b/lib/init.php
index 5bf42af6..16170a01 100644
--- a/lib/init.php
+++ b/lib/init.php
@@ -277,14 +277,14 @@ if (is_array($_themeoptions) && array_key_exists('js', $_themeoptions['variants'
if (is_array($_themeoptions['variants'][$themevariant]['js'])) {
foreach ($_themeoptions['variants'][$themevariant]['js'] as $jsfile) {
if (file_exists('templates/' . $theme . '/assets/js/' . $jsfile)) {
- $js .= '' . "\n";
+ $js .= '' . "\n";
}
}
}
if (is_array($_themeoptions['variants'][$themevariant]['css'])) {
foreach ($_themeoptions['variants'][$themevariant]['css'] as $cssfile) {
if (file_exists('templates/' . $theme . '/assets/css/' . $cssfile)) {
- $css .= '' . "\n";
+ $css .= '' . "\n";
}
}
}
diff --git a/lib/navigation/00.froxlor.main.php b/lib/navigation/00.froxlor.main.php
index 94a08198..d65819fb 100644
--- a/lib/navigation/00.froxlor.main.php
+++ b/lib/navigation/00.froxlor.main.php
@@ -158,6 +158,7 @@ return [
'docs' => [
'label' => lng('admin.documentation'),
'icon' => 'fa-solid fa-circle-info',
+ 'show_element' => (!Settings::IsInList('panel.customer_hide_options', 'misc.documentation')),
'elements' => [
[
'url' => 'https://docs.froxlor.org/v2/user-guide/',
diff --git a/lng/de.lng.php b/lng/de.lng.php
index 56b0dafa..25282a0d 100644
--- a/lng/de.lng.php
+++ b/lng/de.lng.php
@@ -1494,7 +1494,10 @@ Vielen Dank, Ihr Administrator',
'title' => 'Log-Art(en)',
'description' => 'Wählen Sie hier die gewünschten Logtypen. Für Mehrfachauswahl, halten Sie während der Auswahl STRG gedrückt
Mögliche Logtypen sind: syslog, file, mysql',
],
- 'logfile' => 'Log-Datei Pfad inklusive Dateinamen',
+ 'logfile' => [
+ 'title' => 'Dateiname der Logdatei',
+ 'description' => 'Wird nur verwendet, wenn die Log-Art "file" ausgewählt ist. Diese Datei wird unter froxlor/logs/ geschrieben. Dieser Ordner ist vor Webzugriff geschützt.',
+ ],
'logcron' => 'Logge Cronjobs',
'logcronoption' => [
'never' => 'Nie',
@@ -1962,6 +1965,10 @@ Vielen Dank, Ihr Administrator',
'title' => 'Validiere DNS der Domains wenn Let\'s Encrypt genutzt wird',
'description' => 'Wenn aktiviert wird froxlor überprüfen ob die DNS Einträge der Domains, welche ein Let\'s Encrypt Zertifikat beantragt, mindestens auf eine der System IP Adressen auflöst.',
],
+ 'le_domain_dnscheck_resolver' => [
+ 'title' => 'DNS Resolver für die DNS Überprüfung',
+ 'description' => 'IP Adresse des DNS Servers, welcher für die DNS Überprüfung genutzt werden soll. Wenn leer, wird der Standard DNS Resolver des Systems genutzt.',
+ ],
'phpsettingsforsubdomains' => [
'description' => 'Wenn ja, wird die gewählte PHP-Config für alle Subdomains übernommen',
],
@@ -2124,17 +2131,31 @@ Vielen Dank, Ihr Administrator',
],
'mb' => 'Traffic',
'day' => 'Tag',
- 'distribution' => 'FTP | HTTP | Mail',
- 'sumhttp' => 'Gesamt HTTP-Traffic',
- 'sumftp' => 'Gesamt FTP-Traffic',
- 'summail' => 'Gesamt Mail-Traffic',
+ 'sumtotal' => 'Gesamt Traffic',
+ 'sumhttp' => 'HTTP-Traffic',
+ 'sumftp' => 'FTP-Traffic',
+ 'summail' => 'Mail-Traffic',
'customer' => 'Kunde',
- 'trafficoverview' => 'Übersicht Traffic je',
+ 'trafficoverview' => 'Übersicht Traffic',
+ 'bycustomers' => 'Traffic nach Kunden',
'details' => 'Details',
'http' => 'HTTP',
'ftp' => 'FTP',
'mail' => 'Mail',
'nocustomers' => 'Es wird mindestens ein Kunde benötigt um die Traffic Statistiken anzuzeigen.',
+ 'top5customers' => 'Top 5 Kunden',
+ 'nodata' => 'Keine Daten im angegebenen Zeitraum.',
+ 'ranges' => [
+ 'last24h' => 'die letzten 24 Std',
+ 'last7d' => 'die letzten 7 Tage',
+ 'last30d' => 'die letzten 30 Tage',
+ 'cm' => 'Aktueller Monat',
+ 'last3m' => 'die letzten 3 Monate',
+ 'last6m' => 'die letzten 6 Monate',
+ 'last12m' => 'die letzten 12 Monate',
+ 'cy' => 'Aktuelles Jahr',
+ ],
+ 'byrange' => 'Nach angegebenem Zeitraum',
],
'translator' => '',
'update' => [
diff --git a/lng/en.lng.php b/lng/en.lng.php
index 7e3c87a1..776dcddf 100644
--- a/lng/en.lng.php
+++ b/lng/en.lng.php
@@ -1613,7 +1613,10 @@ Yours sincerely, your administrator',
'title' => 'Log-type(s)',
'description' => 'Specify logtypes. To select multiple types, hold down CTRL while selecting.
Available logtypes are: syslog, file, mysql',
],
- 'logfile' => 'Logfile path including filename',
+ 'logfile' => [
+ 'title' => 'Filename for log',
+ 'description' => 'Only used if log-type includes "file". This file will be created in froxlor/logs/. This folder is protected against public access.',
+ ],
'logcron' => 'Log cronjobs',
'logcronoption' => [
'never' => 'Never',
@@ -2081,6 +2084,10 @@ Yours sincerely, your administrator',
'title' => 'Validate DNS of domains when using Let\'s Encrypt',
'description' => 'If activated, froxlor will validate whether the domain which requests a Let\'s Encrypt certificate resolves to at least one of the system ip addresses.',
],
+ 'le_domain_dnscheck_resolver' => [
+ 'title' => 'Use a external nameserver for DNS validation',
+ 'description' => 'If set, froxlor will use this DNS to validate the DNS of domains when using Let\'s Encrypt. If empty, the system\'s default DNS resolver will be used.',
+ ],
'phpsettingsforsubdomains' => [
'description' => 'If yes the chosen php-config will be updated to all subdomains',
],
@@ -2254,18 +2261,32 @@ Yours sincerely, your administrator',
'total' => 'Total',
],
'mb' => 'Traffic',
- 'distribution' => 'FTP | HTTP | Mail',
- 'sumhttp' => 'Total HTTP-Traffic',
- 'sumftp' => 'Total FTP-Traffic',
- 'summail' => 'Total Mail-Traffic',
+ 'sumtotal' => 'Total traffic',
+ 'sumhttp' => 'HTTP traffic',
+ 'sumftp' => 'FTP traffic',
+ 'summail' => 'Mail traffic',
'customer' => 'Customer',
'domain' => 'Domain',
- 'trafficoverview' => 'Traffic summary by',
+ 'trafficoverview' => 'Traffic summary',
+ 'bycustomers' => 'Traffic by customers',
'details' => 'Details',
'http' => 'HTTP',
'ftp' => 'FTP',
'mail' => 'Mail',
'nocustomers' => 'You need at least one customer to view the traffic reports.',
+ 'top5customers' => 'Top 5 customers',
+ 'nodata' => 'No data for given range found.',
+ 'ranges' => [
+ 'last24h' => 'last 24 hours',
+ 'last7d' => 'last 7 days',
+ 'last30d' => 'last 30 days',
+ 'cm' => 'Current month',
+ 'last3m' => 'last 3 months',
+ 'last6m' => 'last 6 months',
+ 'last12m' => 'last 12 months',
+ 'cy' => 'Current year',
+ ],
+ 'byrange' => 'Specified by range',
],
'translator' => '',
'update' => [
diff --git a/templates/Froxlor/base.html.twig b/templates/Froxlor/base.html.twig
index 881184eb..f422d95d 100644
--- a/templates/Froxlor/base.html.twig
+++ b/templates/Froxlor/base.html.twig
@@ -11,7 +11,7 @@
{% if theme_css is empty %}
-
+
{% else %}
{{ theme_css|raw }}
{% endif %}
@@ -19,7 +19,7 @@
{% if theme_js is empty %}
-
+
{% else %}
{{ theme_js|raw }}
{% endif %}
diff --git a/templates/Froxlor/form/form.html.twig b/templates/Froxlor/form/form.html.twig
index a149da8f..778686b3 100644
--- a/templates/Froxlor/form/form.html.twig
+++ b/templates/Froxlor/form/form.html.twig
@@ -50,7 +50,7 @@
{# add translation for custom validations #}
- {% if form_data.id is defined and form_data.id in ['customer_add', 'customer_edit'] %}
+ {% if form_data.id is defined and form_data.id in ['customer_add', 'customer_edit', 'domain_add', 'domain_edit'] %}
{% endif %}
{% endmacro %}
diff --git a/templates/Froxlor/form/formfields.html.twig b/templates/Froxlor/form/formfields.html.twig
index 8fe90d4b..0491a6a3 100644
--- a/templates/Froxlor/form/formfields.html.twig
+++ b/templates/Froxlor/form/formfields.html.twig
@@ -2,6 +2,9 @@
{% if field.visible is not defined or (field.visible is defined and field.visible) or nohide == true %}
{% if norow == false and (field.type != 'hidden' or (field.type == 'hidden' and field.display is defined and field.display is not empty)) %}