Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5bece64ce | ||
|
|
0034681412 | ||
|
|
bd5b99dc1c | ||
|
|
2feb802094 | ||
|
|
7b08a71c59 | ||
|
|
2a84e9c120 | ||
|
|
d854e8e991 | ||
|
|
0a363910d6 | ||
|
|
b23d5cd909 | ||
|
|
3b753aa69d | ||
|
|
492cd288bc | ||
|
|
47938c5082 | ||
|
|
d090e48544 | ||
|
|
314e4407a0 | ||
|
|
dff7530cc5 | ||
|
|
19423c9644 | ||
|
|
42b3f1e59d | ||
|
|
1b77632fa8 | ||
|
|
867b7b1390 | ||
|
|
4c6ebde58c | ||
|
|
1e013d9e9a | ||
|
|
c56bc651b9 | ||
|
|
6cbdf45a7c | ||
|
|
715667e227 | ||
|
|
41de161555 | ||
|
|
1f1ea370c0 | ||
|
|
090cfc26f2 | ||
|
|
529890b5d2 | ||
|
|
d4a6ab146d | ||
|
|
e3f02879cf | ||
|
|
b52d6df777 | ||
|
|
9e671100ae | ||
|
|
7e801ea502 | ||
|
|
b68522f7d5 | ||
|
|
86852942e0 | ||
|
|
ec05c84f4d | ||
|
|
9e13c077e9 | ||
|
|
da8d315e77 | ||
|
|
cb67e3ae63 | ||
|
|
82d15c4dc2 | ||
|
|
6d048e2cee | ||
|
|
87bd80eea1 | ||
|
|
80e442e396 | ||
|
|
489ad375bd | ||
|
|
c420196e73 | ||
|
|
cc6d8d5f8b | ||
|
|
24f47bc58b | ||
|
|
c769c074e0 | ||
|
|
2ecb8eb034 | ||
|
|
6827c100c3 | ||
|
|
c402acd1bd | ||
|
|
c4ec2509fa | ||
|
|
0f382586ce | ||
|
|
9c2f12ecb1 | ||
|
|
12da117cab |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@ install/update.log
|
|||||||
install/*.json
|
install/*.json
|
||||||
lib/userdata.inc.php
|
lib/userdata.inc.php
|
||||||
lib/userdata.inc.php.bak
|
lib/userdata.inc.php.bak
|
||||||
|
lib/config.inc.php
|
||||||
logs/*
|
logs/*
|
||||||
!logs/index.html
|
!logs/index.html
|
||||||
.buildpath
|
.buildpath
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ May be found in [COPYING](COPYING)
|
|||||||
### Tarball
|
### Tarball
|
||||||
https://files.froxlor.org/releases/froxlor-latest.tar.gz [MD5](https://files.froxlor.org/releases/froxlor-latest.tar.gz.md5) [SHA1](https://files.froxlor.org/releases/froxlor-latest.tar.gz.sha1)
|
https://files.froxlor.org/releases/froxlor-latest.tar.gz [MD5](https://files.froxlor.org/releases/froxlor-latest.tar.gz.md5) [SHA1](https://files.froxlor.org/releases/froxlor-latest.tar.gz.sha1)
|
||||||
|
|
||||||
### Debian / Ubutnu repository
|
### Debian / Ubuntu repository
|
||||||
|
|
||||||
[HowTo](https://docs.froxlor.org/latest/general/installation/apt-package.html)
|
[HowTo](https://docs.froxlor.org/latest/general/installation/apt-package.html)
|
||||||
|
|
||||||
|
|||||||
@@ -269,7 +269,8 @@ return [
|
|||||||
'traffic' => lng('menue.traffic.traffic'),
|
'traffic' => lng('menue.traffic.traffic'),
|
||||||
'traffic.http' => lng('menue.traffic.traffic') . " / HTTP",
|
'traffic.http' => lng('menue.traffic.traffic') . " / HTTP",
|
||||||
'traffic.ftp' => lng('menue.traffic.traffic') . " / FTP",
|
'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',
|
'save_method' => 'storeSettingField',
|
||||||
'advanced_mode' => true
|
'advanced_mode' => true
|
||||||
|
|||||||
@@ -180,7 +180,9 @@ return [
|
|||||||
'letsencrypt' => 'Let\'s Encrypt (Live)',
|
'letsencrypt' => 'Let\'s Encrypt (Live)',
|
||||||
'buypass_test' => 'Buypass (Test / Staging)',
|
'buypass_test' => 'Buypass (Test / Staging)',
|
||||||
'buypass' => 'Buypass (Live)',
|
'buypass' => 'Buypass (Live)',
|
||||||
'zerossl' => 'ZeroSSL (Live)'
|
'zerossl' => 'ZeroSSL (Live)',
|
||||||
|
'google' => 'Google (Live)',
|
||||||
|
'google_test' => 'Google (Test / Staging)',
|
||||||
],
|
],
|
||||||
'save_method' => 'storeSettingField'
|
'save_method' => 'storeSettingField'
|
||||||
],
|
],
|
||||||
@@ -239,6 +241,16 @@ return [
|
|||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'default' => true,
|
'default' => true,
|
||||||
'save_method' => 'storeSettingField'
|
'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'
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ if ($userinfo['change_serversettings'] == '1') {
|
|||||||
|
|
||||||
if ($distribution != "" && isset($_POST['finish'])) {
|
if ($distribution != "" && isset($_POST['finish'])) {
|
||||||
unset($_POST['finish']);
|
unset($_POST['finish']);
|
||||||
|
unset($_POST['csrf_token']);
|
||||||
$params = $_POST;
|
$params = $_POST;
|
||||||
$params['distro'] = $distribution;
|
$params['distro'] = $distribution;
|
||||||
$params['system'] = [];
|
$params['system'] = [];
|
||||||
@@ -121,8 +122,6 @@ if ($userinfo['change_serversettings'] == '1') {
|
|||||||
'distribution' => $distribution
|
'distribution' => $distribution
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
// @fixme check set distribution from settings
|
|
||||||
|
|
||||||
$cfg_formfield = [
|
$cfg_formfield = [
|
||||||
'config' => [
|
'config' => [
|
||||||
'title' => lng('admin.configfiles.serverconfiguration'),
|
'title' => lng('admin.configfiles.serverconfiguration'),
|
||||||
|
|||||||
@@ -43,12 +43,6 @@ use PHPMailer\PHPMailer\PHPMailer;
|
|||||||
const AREA = 'admin';
|
const AREA = 'admin';
|
||||||
require __DIR__ . '/lib/init.php';
|
require __DIR__ . '/lib/init.php';
|
||||||
|
|
||||||
// get sql-root access data
|
|
||||||
Database::needRoot(true);
|
|
||||||
Database::needSqlData();
|
|
||||||
$sql_root = Database::getSqlData();
|
|
||||||
Database::needRoot(false);
|
|
||||||
|
|
||||||
if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
|
if ($page == 'overview' && $userinfo['change_serversettings'] == '1') {
|
||||||
$settings_data = PhpHelper::loadConfigArrayDir('./actions/admin/settings/');
|
$settings_data = PhpHelper::loadConfigArrayDir('./actions/admin/settings/');
|
||||||
Settings::loadSettingsInto($settings_data);
|
Settings::loadSettingsInto($settings_data);
|
||||||
|
|||||||
@@ -253,6 +253,9 @@ if ($action == '') {
|
|||||||
if (isset($_POST['prepare']) && $_POST['prepare'] == 'prepare') {
|
if (isset($_POST['prepare']) && $_POST['prepare'] == 'prepare') {
|
||||||
// email templates
|
// email templates
|
||||||
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect'));
|
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect'));
|
||||||
|
if (!array_key_exists($language, $languages)) {
|
||||||
|
Response::standardError('templatelanguageinvalid');
|
||||||
|
}
|
||||||
$template = Validate::validate($_POST['template'], 'template');
|
$template = Validate::validate($_POST['template'], 'template');
|
||||||
|
|
||||||
$result_stmt = Database::prepare("
|
$result_stmt = Database::prepare("
|
||||||
@@ -288,6 +291,9 @@ if ($action == '') {
|
|||||||
} elseif (isset($_POST['send']) && $_POST['send'] == 'send' && !isset($_POST['filesend'])) {
|
} elseif (isset($_POST['send']) && $_POST['send'] == 'send' && !isset($_POST['filesend'])) {
|
||||||
// email templates
|
// email templates
|
||||||
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect'));
|
$language = htmlentities(Validate::validate($_POST['language'], 'language', '/^[^\r\n\0"\']+$/', 'nolanguageselect'));
|
||||||
|
if (!array_key_exists($language, $languages)) {
|
||||||
|
Response::standardError('templatelanguageinvalid');
|
||||||
|
}
|
||||||
$template = Validate::validate($_POST['template'], 'template');
|
$template = Validate::validate($_POST['template'], 'template');
|
||||||
$subject = Validate::validate($_POST['subject'], 'subject', '/^[^\r\n\0]+$/', 'nosubjectcreate');
|
$subject = Validate::validate($_POST['subject'], 'subject', '/^[^\r\n\0]+$/', 'nosubjectcreate');
|
||||||
$mailbody = Validate::validate($_POST['mailbody'], 'mailbody', '/^[^\0]+$/', 'nomailbodycreate');
|
$mailbody = Validate::validate($_POST['mailbody'], 'mailbody', '/^[^\0]+$/', 'nomailbodycreate');
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ use Froxlor\Cli\UpdateCommand;
|
|||||||
use Froxlor\Cli\InstallCommand;
|
use Froxlor\Cli\InstallCommand;
|
||||||
use Froxlor\Cli\MasterCron;
|
use Froxlor\Cli\MasterCron;
|
||||||
use Froxlor\Cli\UserCommand;
|
use Froxlor\Cli\UserCommand;
|
||||||
|
use Froxlor\Cli\ValidateAcmeWebroot;
|
||||||
use Froxlor\Froxlor;
|
use Froxlor\Froxlor;
|
||||||
|
|
||||||
// validate correct php version
|
// validate correct php version
|
||||||
@@ -59,4 +60,5 @@ $application->add(new UpdateCommand());
|
|||||||
$application->add(new InstallCommand());
|
$application->add(new InstallCommand());
|
||||||
$application->add(new MasterCron());
|
$application->add(new MasterCron());
|
||||||
$application->add(new UserCommand());
|
$application->add(new UserCommand());
|
||||||
|
$application->add(new ValidateAcmeWebroot());
|
||||||
$application->run();
|
$application->run();
|
||||||
|
|||||||
@@ -52,7 +52,8 @@
|
|||||||
"voku/anti-xss": "^4.1",
|
"voku/anti-xss": "^4.1",
|
||||||
"twig/twig": "^3.3",
|
"twig/twig": "^3.3",
|
||||||
"erusev/parsedown": "^1.7",
|
"erusev/parsedown": "^1.7",
|
||||||
"symfony/console": "^5.4"
|
"symfony/console": "^5.4",
|
||||||
|
"pear/net_dns2": "^1.5"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^9",
|
"phpunit/phpunit": "^9",
|
||||||
|
|||||||
53
composer.lock
generated
53
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f8370edea3c85bcb7b681926a1fff04e",
|
"content-hash": "41e7a3bc0e13b47c4f245334b113c3be",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "erusev/parsedown",
|
"name": "erusev/parsedown",
|
||||||
@@ -198,6 +198,57 @@
|
|||||||
],
|
],
|
||||||
"time": "2022-06-09T08:53:42+00:00"
|
"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",
|
"name": "phpmailer/phpmailer",
|
||||||
"version": "v6.6.3",
|
"version": "v6.6.3",
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ if ($page == 'overview' || $page == 'domains') {
|
|||||||
$domain_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.domains.php';
|
$domain_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.domains.php';
|
||||||
$collection = (new Collection(SubDomains::class, $userinfo))
|
$collection = (new Collection(SubDomains::class, $userinfo))
|
||||||
->withPagination($domain_list_data['domain_list']['columns'], $domain_list_data['domain_list']['default_sorting']);
|
->withPagination($domain_list_data['domain_list']['columns'], $domain_list_data['domain_list']['default_sorting']);
|
||||||
$parentDomainCollection = (new Collection(SubDomains::class, $userinfo, ['sql_search' => ['d.parentdomainid' => 0]]));
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Response::dynamicError($e->getMessage());
|
Response::dynamicError($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ use Froxlor\UI\Response;
|
|||||||
use Froxlor\CurrentUser;
|
use Froxlor\CurrentUser;
|
||||||
|
|
||||||
// redirect if this customer page is hidden via settings
|
// redirect if this customer page is hidden via settings
|
||||||
if (Settings::IsInList('panel.customer_hide_options', 'ftp') || $userinfo['ftps'] == 0) {
|
if (Settings::IsInList('panel.customer_hide_options', 'ftp')) {
|
||||||
Response::redirectTo('customer_index.php');
|
Response::redirectTo('customer_index.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,10 +115,14 @@ if ($page == 'overview') {
|
|||||||
|
|
||||||
if ($usages) {
|
if ($usages) {
|
||||||
$userinfo['diskspace_bytes_used'] = $usages['webspace'] * 1024;
|
$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;
|
$userinfo['total_bytes_used'] = ($usages['webspace'] + $usages['mail'] + $usages['mysql']) * 1024;
|
||||||
} else {
|
} else {
|
||||||
$userinfo['diskspace_bytes_used'] = 0;
|
$userinfo['diskspace_bytes_used'] = 0;
|
||||||
$userinfo['total_bytes_used'] = 0;
|
$userinfo['total_bytes_used'] = 0;
|
||||||
|
$userinfo['mailspace_used'] = 0;
|
||||||
|
$userinfo['dbspace_used'] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
UI::twig()->addGlobal('userinfo', $userinfo);
|
UI::twig()->addGlobal('userinfo', $userinfo);
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ if ($page == 'overview' || $page == 'mysqls') {
|
|||||||
$result = json_decode($json_result, true)['data'];
|
$result = json_decode($json_result, true)['data'];
|
||||||
|
|
||||||
if (isset($result['databasename']) && $result['databasename'] != '') {
|
if (isset($result['databasename']) && $result['databasename'] != '') {
|
||||||
Database::needRoot(true, $result['dbserver']);
|
Database::needRoot(true, $result['dbserver'], false);
|
||||||
Database::needSqlData();
|
Database::needSqlData();
|
||||||
$sql_root = Database::getSqlData();
|
$sql_root = Database::getSqlData();
|
||||||
Database::needRoot(false);
|
Database::needRoot(false);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ use Froxlor\Froxlor;
|
|||||||
use Froxlor\UI\Panel\UI;
|
use Froxlor\UI\Panel\UI;
|
||||||
use Froxlor\UI\Request;
|
use Froxlor\UI\Request;
|
||||||
use Froxlor\UI\Response;
|
use Froxlor\UI\Response;
|
||||||
|
use Froxlor\Database\Database;
|
||||||
|
|
||||||
// This file is being included in admin_domains and customer_domains
|
// This file is being included in admin_domains and customer_domains
|
||||||
// and therefore does not need to require lib/init.php
|
// and therefore does not need to require lib/init.php
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ CREATE TABLE `panel_customers` (
|
|||||||
`allowed_mysqlserver` text NOT NULL,
|
`allowed_mysqlserver` text NOT NULL,
|
||||||
PRIMARY KEY (`customerid`),
|
PRIMARY KEY (`customerid`),
|
||||||
UNIQUE KEY `loginname` (`loginname`)
|
UNIQUE KEY `loginname` (`loginname`)
|
||||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
|
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `panel_databases`;
|
DROP TABLE IF EXISTS `panel_databases`;
|
||||||
@@ -642,7 +642,7 @@ opcache.validate_timestamps'),
|
|||||||
('system', 'leprivatekey', 'unset'),
|
('system', 'leprivatekey', 'unset'),
|
||||||
('system', 'lepublickey', 'unset'),
|
('system', 'lepublickey', 'unset'),
|
||||||
('system', 'letsencryptca', 'letsencrypt'),
|
('system', 'letsencryptca', 'letsencrypt'),
|
||||||
('system', 'letsencryptchallengepath', '/var/www/froxlor'),
|
('system', 'letsencryptchallengepath', '/var/www/html/froxlor'),
|
||||||
('system', 'letsencryptkeysize', '4096'),
|
('system', 'letsencryptkeysize', '4096'),
|
||||||
('system', 'letsencryptreuseold', 0),
|
('system', 'letsencryptreuseold', 0),
|
||||||
('system', 'leenabled', '0'),
|
('system', 'leenabled', '0'),
|
||||||
@@ -670,6 +670,7 @@ opcache.validate_timestamps'),
|
|||||||
('system', 'leaccount', ''),
|
('system', 'leaccount', ''),
|
||||||
('system', 'nssextrausers', '1'),
|
('system', 'nssextrausers', '1'),
|
||||||
('system', 'le_domain_dnscheck', '1'),
|
('system', 'le_domain_dnscheck', '1'),
|
||||||
|
('system', 'le_domain_dnscheck_resolver', '1.1.1.1'),
|
||||||
('system', 'ssl_protocols', 'TLSv1.2'),
|
('system', 'ssl_protocols', 'TLSv1.2'),
|
||||||
('system', 'tlsv13_cipher_list', ''),
|
('system', 'tlsv13_cipher_list', ''),
|
||||||
('system', 'honorcipherorder', '0'),
|
('system', 'honorcipherorder', '0'),
|
||||||
@@ -696,7 +697,7 @@ opcache.validate_timestamps'),
|
|||||||
('system', 'distribution', ''),
|
('system', 'distribution', ''),
|
||||||
('system', 'update_channel', 'stable'),
|
('system', 'update_channel', 'stable'),
|
||||||
('system', 'updatecheck_data', ''),
|
('system', 'updatecheck_data', ''),
|
||||||
('system', 'update_notify_last', '2.0.3'),
|
('system', 'update_notify_last', '2.0.10'),
|
||||||
('system', 'traffictool', 'goaccess'),
|
('system', 'traffictool', 'goaccess'),
|
||||||
('api', 'enabled', '0'),
|
('api', 'enabled', '0'),
|
||||||
('2fa', 'enabled', '1'),
|
('2fa', 'enabled', '1'),
|
||||||
@@ -740,8 +741,8 @@ opcache.validate_timestamps'),
|
|||||||
('panel', 'logo_overridetheme', '0'),
|
('panel', 'logo_overridetheme', '0'),
|
||||||
('panel', 'logo_overridecustom', '0'),
|
('panel', 'logo_overridecustom', '0'),
|
||||||
('panel', 'settings_mode', '0'),
|
('panel', 'settings_mode', '0'),
|
||||||
('panel', 'version', '2.0.3'),
|
('panel', 'version', '2.0.10'),
|
||||||
('panel', 'db_version', '202212060');
|
('panel', 'db_version', '202301180');
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `panel_tasks`;
|
DROP TABLE IF EXISTS `panel_tasks`;
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ if (!defined('_CRON_UPDATE')) {
|
|||||||
|
|
||||||
// last 0.10.x release
|
// last 0.10.x release
|
||||||
if (Froxlor::isFroxlorVersion('0.10.38.3')) {
|
if (Froxlor::isFroxlorVersion('0.10.38.3')) {
|
||||||
|
|
||||||
$update_to = '2.0.0-beta1';
|
$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);
|
||||||
@@ -66,8 +65,8 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
|
|||||||
KEY customerid (customerid)
|
KEY customerid (customerid)
|
||||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;";
|
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;";
|
||||||
Database::query($sql);
|
Database::query($sql);
|
||||||
Database::query("SET SESSION innodb_strict_mode=OFF;");
|
|
||||||
// new customer allowed_mysqlserver field
|
// new customer allowed_mysqlserver field
|
||||||
|
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ROW_FORMAT=DYNAMIC;");
|
||||||
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` CHANGE COLUMN `customernumber` `customernumber` varchar(100) NOT NULL default '';");
|
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` CHANGE COLUMN `customernumber` `customernumber` varchar(100) NOT NULL default '';");
|
||||||
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` CHANGE COLUMN `allowed_phpconfigs` `allowed_phpconfigs` text NOT NULL;");
|
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` CHANGE COLUMN `allowed_phpconfigs` `allowed_phpconfigs` text NOT NULL;");
|
||||||
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `allowed_mysqlserver` text NOT NULL;");
|
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `allowed_mysqlserver` text NOT NULL;");
|
||||||
@@ -183,17 +182,16 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
|
|||||||
Update::lastStepStatus(0);
|
Update::lastStepStatus(0);
|
||||||
|
|
||||||
Update::showUpdateStep("Updating email account password-hashes");
|
Update::showUpdateStep("Updating email account password-hashes");
|
||||||
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password` = REPLACE(`password`, '$1$', '{MD5-CRYPT}$1$') WHERE SUBSTRING(`password`, 1, 3) = '$1$'");
|
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$1$', '{MD5-CRYPT}$1$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$1$'");
|
||||||
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password` = REPLACE(`password`, '$5$', '{SHA256-CRYPT}$5$') WHERE SUBSTRING(`password`, 1, 3) = '$5$'");
|
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$5$', '{SHA256-CRYPT}$5$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$5$'");
|
||||||
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password` = REPLACE(`password`, '$6$', '{SHA512-CRYPT}$6$') WHERE SUBSTRING(`password`, 1, 3) = '$6$'");
|
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$6$', '{SHA512-CRYPT}$6$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$6$'");
|
||||||
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password` = REPLACE(`password`, '$2y$', '{BLF-CRYPT}$2y$') WHERE SUBSTRING(`password`, 1, 4) = '$2y$'");
|
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$2y$', '{BLF-CRYPT}$2y$') WHERE SUBSTRING(`password_enc`, 1, 4) = '$2y$'");
|
||||||
Update::lastStepStatus(0);
|
Update::lastStepStatus(0);
|
||||||
|
|
||||||
Froxlor::updateToVersion($update_to);
|
Froxlor::updateToVersion($update_to);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Froxlor::isDatabaseVersion('202112310')) {
|
if (Froxlor::isDatabaseVersion('202112310')) {
|
||||||
|
|
||||||
Update::showUpdateStep("Adjusting traffic tool settings");
|
Update::showUpdateStep("Adjusting traffic tool settings");
|
||||||
$traffic_tool = Settings::Get('system.awstats_enabled') == 1 ? 'awstats' : 'webalizer';
|
$traffic_tool = Settings::Get('system.awstats_enabled') == 1 ? 'awstats' : 'webalizer';
|
||||||
Settings::AddNew("system.traffictool", $traffic_tool);
|
Settings::AddNew("system.traffictool", $traffic_tool);
|
||||||
@@ -204,20 +202,30 @@ if (Froxlor::isDatabaseVersion('202112310')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Froxlor::isDatabaseVersion('202211030')) {
|
if (Froxlor::isDatabaseVersion('202211030')) {
|
||||||
|
|
||||||
Update::showUpdateStep("Creating backward compatibility for cronjob");
|
Update::showUpdateStep("Creating backward compatibility for cronjob");
|
||||||
|
$disabled = explode(',', ini_get('disable_functions'));
|
||||||
|
$exec_allowed = !in_array('exec', $disabled);
|
||||||
|
// check whether old files could be deleted in previous updates and if not,
|
||||||
|
// user should run cron to regenerate cron.d-file manually as he will run
|
||||||
|
// the other commands manually only after the update so this file would be deleted too
|
||||||
|
if ($exec_allowed) {
|
||||||
$complete_filedir = Froxlor::getInstallDir() . '/scripts';
|
$complete_filedir = Froxlor::getInstallDir() . '/scripts';
|
||||||
mkdir($complete_filedir, 0750, true);
|
mkdir($complete_filedir, 0750, true);
|
||||||
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
|
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
|
||||||
$compCron = <<<EOF
|
$compCron = <<<EOF
|
||||||
<?php
|
<?php
|
||||||
chmod($newCronBin, 0755);
|
chmod('$newCronBin', 0755);
|
||||||
// re-create cron.d configuration file
|
// re-create cron.d configuration file
|
||||||
exec('$newCronBin froxlor:cron -r 99');
|
exec('$newCronBin froxlor:cron -r 99');
|
||||||
exit;
|
exit;
|
||||||
EOF;
|
EOF;
|
||||||
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
|
file_put_contents($complete_filedir . '/froxlor_master_cronjob.php', $compCron);
|
||||||
Update::lastStepStatus(0);
|
Update::lastStepStatus(0);
|
||||||
|
} else {
|
||||||
|
$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:<br><pre>' . $cron_run_cmd . '</pre>');
|
||||||
|
}
|
||||||
|
|
||||||
Froxlor::updateToDbVersion('202212060');
|
Froxlor::updateToDbVersion('202212060');
|
||||||
}
|
}
|
||||||
@@ -251,3 +259,126 @@ if (Froxlor::isFroxlorVersion('2.0.2')) {
|
|||||||
Update::showUpdateStep("Updating from 2.0.2 to 2.0.3", false);
|
Update::showUpdateStep("Updating from 2.0.2 to 2.0.3", false);
|
||||||
Froxlor::updateToVersion('2.0.3');
|
Froxlor::updateToVersion('2.0.3');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Froxlor::isFroxlorVersion('2.0.3')) {
|
||||||
|
Update::showUpdateStep("Updating from 2.0.3 to 2.0.4", false);
|
||||||
|
|
||||||
|
$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')) {
|
||||||
|
Update::showUpdateStep("Adjusting backward compatibility for cronjob");
|
||||||
|
$disabled = explode(',', ini_get('disable_functions'));
|
||||||
|
$exec_allowed = !in_array('exec', $disabled);
|
||||||
|
if ($exec_allowed) {
|
||||||
|
$newCronBin = Froxlor::getInstallDir() . '/bin/froxlor-cli';
|
||||||
|
$compCron = <<<EOF
|
||||||
|
<?php
|
||||||
|
chmod('$newCronBin', 0755);
|
||||||
|
// re-create cron.d configuration file
|
||||||
|
exec('$newCronBin froxlor:cron -r 99');
|
||||||
|
exit;
|
||||||
|
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') . 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:<br><pre>' . $cron_run_cmd . '</pre>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Froxlor::updateToVersion('2.0.4');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Froxlor::isFroxlorVersion('2.0.4')) {
|
||||||
|
Update::showUpdateStep("Updating from 2.0.4 to 2.0.5", false);
|
||||||
|
Froxlor::updateToVersion('2.0.5');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Froxlor::isFroxlorVersion('2.0.5')) {
|
||||||
|
Update::showUpdateStep("Updating from 2.0.5 to 2.0.6", false);
|
||||||
|
|
||||||
|
Update::showUpdateStep("Updating possible missing email account password-hashes");
|
||||||
|
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$1$', '{MD5-CRYPT}$1$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$1$'");
|
||||||
|
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$5$', '{SHA256-CRYPT}$5$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$5$'");
|
||||||
|
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$6$', '{SHA512-CRYPT}$6$') WHERE SUBSTRING(`password_enc`, 1, 3) = '$6$'");
|
||||||
|
Database::query("UPDATE `" . TABLE_MAIL_USERS . "` SET `password_enc` = REPLACE(`password_enc`, '$2y$', '{BLF-CRYPT}$2y$') WHERE SUBSTRING(`password_enc`, 1, 4) = '$2y$'");
|
||||||
|
Update::lastStepStatus(0);
|
||||||
|
|
||||||
|
Froxlor::updateToVersion('2.0.6');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Froxlor::isFroxlorVersion('2.0.6')) {
|
||||||
|
Update::showUpdateStep("Updating from 2.0.6 to 2.0.7", false);
|
||||||
|
|
||||||
|
Update::showUpdateStep("Correcting allowed_mysqlserver for customers");
|
||||||
|
Database::query("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `allowed_mysqlserver` = '[0]' WHERE `allowed_mysqlserver` = ''");
|
||||||
|
Update::lastStepStatus(0);
|
||||||
|
|
||||||
|
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 <pre>bin/froxlor-cli froxlor:config-services --apply='" . $apply_json . "'</pre>" .
|
||||||
|
'<br>or adjust the path manually in <pre>' . Settings::Get('system.letsencryptacmeconf') . '</pre>' .
|
||||||
|
'<br><br>In case you already have certificates issued, run the following command to validate and correct the webroot used for renewal:<br>' .
|
||||||
|
'<pre>bin/froxlor-cli froxlor:validate-acme-webroot</pre><br>'
|
||||||
|
);
|
||||||
|
} 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Froxlor::isFroxlorVersion('2.0.9')) {
|
||||||
|
Update::showUpdateStep("Updating from 2.0.9 to 2.0.10", false);
|
||||||
|
Froxlor::updateToVersion('2.0.10');
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,9 +34,14 @@ $return = [];
|
|||||||
if (Update::versionInUpdate($current_db_version, '202004140')) {
|
if (Update::versionInUpdate($current_db_version, '202004140')) {
|
||||||
$has_preconfig = true;
|
$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).';
|
$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 = '<strong>Validate DNS of domains when using Lets Encrypt ';
|
$question = '<strong>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;
|
$preconfig['fields'] = $return;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ use Froxlor\Froxlor;
|
|||||||
use Froxlor\FileDir;
|
use Froxlor\FileDir;
|
||||||
use Froxlor\Config\ConfigParser;
|
use Froxlor\Config\ConfigParser;
|
||||||
use Froxlor\Install\Update;
|
use Froxlor\Install\Update;
|
||||||
|
use Froxlor\Settings;
|
||||||
|
|
||||||
$preconfig = [
|
$preconfig = [
|
||||||
'title' => '2.x updates',
|
'title' => '2.x updates',
|
||||||
@@ -36,7 +37,6 @@ $return = [];
|
|||||||
|
|
||||||
if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
|
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.';
|
$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 = '<strong>Chose settings mode (you can change that at any time)</strong>';
|
$question = '<strong>Chose settings mode (you can change that at any time)</strong>';
|
||||||
$return['panel_settings_mode'] = [
|
$return['panel_settings_mode'] = [
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
@@ -45,11 +45,11 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
|
|||||||
1 => 'Advanced'
|
1 => 'Advanced'
|
||||||
],
|
],
|
||||||
'selected' => 1,
|
'selected' => 1,
|
||||||
'label' => $question
|
'label' => $question,
|
||||||
|
'prior_infotext' => $description
|
||||||
];
|
];
|
||||||
|
|
||||||
$description = 'The configuration page now can preselect a distribution, please select your current distribution';
|
$description = 'The configuration page now can preselect a distribution, please select your current distribution';
|
||||||
$return['system_distribution_note'] = ['type' => 'infotext', 'value' => $description];
|
|
||||||
$question = '<strong>Select distribution</strong>';
|
$question = '<strong>Select distribution</strong>';
|
||||||
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
|
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
|
||||||
// show list of available distro's
|
// show list of available distro's
|
||||||
@@ -68,9 +68,44 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
|
|||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'select_var' => $distributions_select,
|
'select_var' => $distributions_select,
|
||||||
'selected' => '',
|
'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 = '<strong>Validate Let\'s Encrypt challenge path (recommended value: ' . $recommended . ')</strong>';
|
||||||
|
$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 = '<strong>Specify a DNS resolver IP (recommended value: 1.1.1.1 or similar)</strong>';
|
||||||
|
$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;
|
$preconfig['fields'] = $return;
|
||||||
return $preconfig;
|
return $preconfig;
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ class Ajax
|
|||||||
$content = preg_replace("/[\r\n]+/", " ", strip_tags($item->description));
|
$content = preg_replace("/[\r\n]+/", " ", strip_tags($item->description));
|
||||||
$content = substr($content, 0, 150) . "...";
|
$content = substr($content, 0, 150) . "...";
|
||||||
|
|
||||||
$items .= UI::twig()->render($this->theme . '/user/newsfeeditem.html.twig', [
|
$items .= UI::twig()->render(UI::validateThemeTemplate('/user/newsfeeditem.html.twig', $this->theme), [
|
||||||
'link' => $link,
|
'link' => $link,
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'date' => $date,
|
'date' => $date,
|
||||||
@@ -201,7 +201,7 @@ class Ajax
|
|||||||
$result['last_update_check'] = $uc_data['ts'];
|
$result['last_update_check'] = $uc_data['ts'];
|
||||||
$result['channel'] = Settings::Get('system.update_channel');
|
$result['channel'] = Settings::Get('system.update_channel');
|
||||||
|
|
||||||
$result_rendered = UI::twig()->render($this->theme . '/misc/version_top.html.twig', $result);
|
$result_rendered = UI::twig()->render(UI::validateThemeTemplate('/misc/version_top.html.twig', $this->theme), $result);
|
||||||
return $this->jsonResponse($result_rendered);
|
return $this->jsonResponse($result_rendered);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
// don't display anything if just not allowed due to permissions
|
// don't display anything if just not allowed due to permissions
|
||||||
|
|||||||
@@ -117,6 +117,6 @@ class Api
|
|||||||
|
|
||||||
private function stripcslashesDeep($value)
|
private function stripcslashesDeep($value)
|
||||||
{
|
{
|
||||||
return is_array($value) ? array_map([$this, 'stripcslashesDeep'], $value) : stripcslashes($value);
|
return is_array($value) ? array_map([$this, 'stripcslashesDeep'], $value) : (!empty($value) ? stripcslashes($value) : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ use Froxlor\Cron\TaskId;
|
|||||||
use Froxlor\Database\Database;
|
use Froxlor\Database\Database;
|
||||||
use Froxlor\FroxlorLogger;
|
use Froxlor\FroxlorLogger;
|
||||||
use Froxlor\System\Cronjob;
|
use Froxlor\System\Cronjob;
|
||||||
|
use Froxlor\UI\Response;
|
||||||
use Froxlor\Validate\Validate;
|
use Froxlor\Validate\Validate;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
@@ -41,6 +42,14 @@ use PDO;
|
|||||||
class Cronjobs extends ApiCommand implements ResourceEntity
|
class Cronjobs extends ApiCommand implements ResourceEntity
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private array $allowed_intervals = [
|
||||||
|
'MINUTE',
|
||||||
|
'HOUR',
|
||||||
|
'DAY',
|
||||||
|
'WEEK',
|
||||||
|
'MONTH'
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You cannot add new cronjobs yet.
|
* You cannot add new cronjobs yet.
|
||||||
*/
|
*/
|
||||||
@@ -118,6 +127,10 @@ class Cronjobs extends ApiCommand implements ResourceEntity
|
|||||||
$interval_value = Validate::validate($interval_value, 'interval_value', '/^([0-9]+)$/Di', 'stringisempty', [], true);
|
$interval_value = Validate::validate($interval_value, 'interval_value', '/^([0-9]+)$/Di', 'stringisempty', [], true);
|
||||||
$interval_interval = Validate::validate($interval_interval, 'interval_interval', '', '', [], true);
|
$interval_interval = Validate::validate($interval_interval, 'interval_interval', '', '', [], true);
|
||||||
|
|
||||||
|
if (!in_array(strtoupper($interval_interval), $this->allowed_intervals)) {
|
||||||
|
Response::standardError('invalidcronjobintervalvalue', implode(", ", $this->allowed_intervals), true);
|
||||||
|
}
|
||||||
|
|
||||||
// put together interval value
|
// put together interval value
|
||||||
$interval = $interval_value . ' ' . strtoupper($interval_interval);
|
$interval = $interval_value . ' ' . strtoupper($interval_interval);
|
||||||
|
|
||||||
|
|||||||
@@ -157,16 +157,15 @@ class DirOptions extends ApiCommand implements ResourceEntity
|
|||||||
* this functions validates a given value as ErrorDocument
|
* this functions validates a given value as ErrorDocument
|
||||||
* refs #267
|
* refs #267
|
||||||
*
|
*
|
||||||
* @param
|
* @param string $errdoc
|
||||||
* string error-document-string
|
|
||||||
* @param bool $throw_exception
|
* @param bool $throw_exception
|
||||||
*
|
*
|
||||||
* @return string error-document-string
|
* @return string error-document-string
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private function correctErrorDocument($errdoc = null, $throw_exception = false)
|
private function correctErrorDocument(string $errdoc, $throw_exception = false)
|
||||||
{
|
{
|
||||||
if ($errdoc !== null && $errdoc != '') {
|
if (trim($errdoc) != '') {
|
||||||
// not a URL
|
// not a URL
|
||||||
if ((strtoupper(substr($errdoc, 0, 5)) != 'HTTP:' && strtoupper(substr($errdoc, 0, 6)) != 'HTTPS:') || !Validate::validateUrl($errdoc)) {
|
if ((strtoupper(substr($errdoc, 0, 5)) != 'HTTP:' && strtoupper(substr($errdoc, 0, 6)) != 'HTTPS:') || !Validate::validateUrl($errdoc)) {
|
||||||
// a file
|
// a file
|
||||||
@@ -176,14 +175,14 @@ class DirOptions extends ApiCommand implements ResourceEntity
|
|||||||
if (!substr($errdoc, 0, 1) == '/') {
|
if (!substr($errdoc, 0, 1) == '/') {
|
||||||
$errdoc = '/' . $errdoc;
|
$errdoc = '/' . $errdoc;
|
||||||
}
|
}
|
||||||
} else {
|
} elseif (preg_match('/^"([^\r\n\t\f\0"]+)"$/', $errdoc)) {
|
||||||
// a string (check for ending ")
|
// a string (check for ending ")
|
||||||
// string won't work for lighty
|
// string won't work for lighty
|
||||||
if (Settings::Get('system.webserver') == 'lighttpd') {
|
if (Settings::Get('system.webserver') == 'lighttpd') {
|
||||||
Response::standardError('stringerrordocumentnotvalidforlighty', '', $throw_exception);
|
Response::standardError('stringerrordocumentnotvalidforlighty', '', $throw_exception);
|
||||||
} elseif (substr($errdoc, -1) != '"') {
|
|
||||||
$errdoc .= '"';
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Response::standardError('invaliderrordocumentvalue', '', $throw_exception);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Settings::Get('system.webserver') == 'lighttpd') {
|
if (Settings::Get('system.webserver') == 'lighttpd') {
|
||||||
@@ -191,7 +190,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $errdoc;
|
return trim($errdoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ class DirProtections extends ApiCommand implements ResourceEntity
|
|||||||
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
|
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
|
||||||
$username = Validate::validate($username, 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/', '', [], true);
|
$username = Validate::validate($username, 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/', '', [], true);
|
||||||
$authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
|
$authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
|
||||||
Validate::validate($password, 'password', '', '', [], true);
|
$password = Validate::validate($password, 'password', '', '', [], true);
|
||||||
|
$password = Crypt::validatePassword($password, true);
|
||||||
|
|
||||||
// check for duplicate usernames for the path
|
// check for duplicate usernames for the path
|
||||||
$username_path_check_stmt = Database::prepare("
|
$username_path_check_stmt = Database::prepare("
|
||||||
@@ -244,7 +245,8 @@ class DirProtections extends ApiCommand implements ResourceEntity
|
|||||||
|
|
||||||
// validation
|
// validation
|
||||||
$authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
|
$authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
|
||||||
Validate::validate($password, 'password', '', '', [], true);
|
$password = Validate::validate($password, 'password', '', '', [], true);
|
||||||
|
$password = Crypt::validatePassword($password, true);
|
||||||
|
|
||||||
$upd_query = "";
|
$upd_query = "";
|
||||||
$upd_params = [
|
$upd_params = [
|
||||||
|
|||||||
@@ -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
|
// 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') {
|
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);
|
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
|
||||||
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
|
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
|
||||||
Response::standardError('invaliddnsforletsencrypt', '', true);
|
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
|
// 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') {
|
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);
|
$selected_ips = $this->getIpsFromIdArray($ssl_ipandports);
|
||||||
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
|
if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) {
|
||||||
Response::standardError('invaliddnsforletsencrypt', '', true);
|
Response::standardError('invaliddnsforletsencrypt', '', true);
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
|||||||
$password = $this->getParam('mysql_password');
|
$password = $this->getParam('mysql_password');
|
||||||
|
|
||||||
// parameters
|
// parameters
|
||||||
$dbserver = $this->getParam('mysql_server', true, 0);
|
|
||||||
$databasedescription = $this->getParam('description', true, '');
|
$databasedescription = $this->getParam('description', true, '');
|
||||||
$databasename = $this->getParam('custom_suffix', true, '');
|
$databasename = $this->getParam('custom_suffix', true, '');
|
||||||
$sendinfomail = $this->getBoolParam('sendinfomail', true, 0);
|
$sendinfomail = $this->getBoolParam('sendinfomail', true, 0);
|
||||||
// get needed customer info to reduce the mysql-usage-counter by one
|
// get needed customer info to reduce the mysql-usage-counter by one
|
||||||
$customer = $this->getCustomerData('mysqls');
|
$customer = $this->getCustomerData('mysqls');
|
||||||
|
$dbserver = $this->getParam('mysql_server', true, $this->getDefaultMySqlServer($customer));
|
||||||
|
|
||||||
// validation
|
// validation
|
||||||
$password = Validate::validate($password, 'password', '', '', [], true);
|
$password = Validate::validate($password, 'password', '', '', [], true);
|
||||||
@@ -90,7 +90,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
|||||||
|
|
||||||
// validate whether the dbserver exists
|
// validate whether the dbserver exists
|
||||||
$dbserver = Validate::validate($dbserver, html_entity_decode(lng('mysql.mysql_server')), '/^[0-9]+$/', '', 0, true);
|
$dbserver = Validate::validate($dbserver, html_entity_decode(lng('mysql.mysql_server')), '/^[0-9]+$/', '', 0, true);
|
||||||
Database::needRoot(true, $dbserver);
|
Database::needRoot(true, $dbserver, false);
|
||||||
Database::needSqlData();
|
Database::needSqlData();
|
||||||
$sql_root = Database::getSqlData();
|
$sql_root = Database::getSqlData();
|
||||||
Database::needRoot(false);
|
Database::needRoot(false);
|
||||||
@@ -150,7 +150,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
|||||||
$pma = Settings::Get('panel.phpmyadmin_url');
|
$pma = Settings::Get('panel.phpmyadmin_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
Database::needRoot(true, $dbserver);
|
Database::needRoot(true, $dbserver, false);
|
||||||
Database::needSqlData();
|
Database::needSqlData();
|
||||||
$sql_root = Database::getSqlData();
|
$sql_root = Database::getSqlData();
|
||||||
Database::needRoot(false);
|
Database::needRoot(false);
|
||||||
@@ -287,7 +287,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
|||||||
}
|
}
|
||||||
$result = Database::pexecute_first($result_stmt, $params, true, true);
|
$result = Database::pexecute_first($result_stmt, $params, true, true);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
Database::needRoot(true, $result['dbserver']);
|
Database::needRoot(true, $result['dbserver'], false);
|
||||||
$mbdata_stmt = Database::prepare("
|
$mbdata_stmt = Database::prepare("
|
||||||
SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES
|
SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES
|
||||||
WHERE table_schema = :table_schema
|
WHERE table_schema = :table_schema
|
||||||
@@ -364,7 +364,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Begin root-session
|
// Begin root-session
|
||||||
Database::needRoot(true, $result['dbserver']);
|
Database::needRoot(true, $result['dbserver'], false);
|
||||||
$dbmgr = new DbManager($this->logger());
|
$dbmgr = new DbManager($this->logger());
|
||||||
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
|
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
|
||||||
$dbmgr->getManager()->grantPrivilegesTo($result['databasename'], $password, $mysql_access_host, false, true);
|
$dbmgr->getManager()->grantPrivilegesTo($result['databasename'], $password, $mysql_access_host, false, true);
|
||||||
@@ -449,7 +449,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
|||||||
'dbserver' => $_dbserver['dbserver']
|
'dbserver' => $_dbserver['dbserver']
|
||||||
], $query_fields), true, true);
|
], $query_fields), true, true);
|
||||||
// Begin root-session
|
// Begin root-session
|
||||||
Database::needRoot(true, $_dbserver['dbserver']);
|
Database::needRoot(true, $_dbserver['dbserver'], false);
|
||||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
$mbdata_stmt = Database::prepare("
|
$mbdata_stmt = Database::prepare("
|
||||||
SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES
|
SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES
|
||||||
@@ -536,7 +536,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
|||||||
$id = $result['id'];
|
$id = $result['id'];
|
||||||
|
|
||||||
// Begin root-session
|
// Begin root-session
|
||||||
Database::needRoot(true, $result['dbserver']);
|
Database::needRoot(true, $result['dbserver'], false);
|
||||||
$dbm = new DbManager($this->logger());
|
$dbm = new DbManager($this->logger());
|
||||||
$dbm->getManager()->deleteDatabase($result['databasename']);
|
$dbm->getManager()->deleteDatabase($result['databasename']);
|
||||||
Database::needRoot(false);
|
Database::needRoot(false);
|
||||||
@@ -558,4 +558,13 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
|||||||
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted database '" . $result['databasename'] . "'");
|
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted database '" . $result['databasename'] . "'");
|
||||||
return $this->response($result);
|
return $this->response($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getDefaultMySqlServer(array $customer) {
|
||||||
|
$allowed_mysqlservers = json_decode($customer['allowed_mysqlserver'] ?? '[]', true);
|
||||||
|
asort($allowed_mysqlservers, SORT_NUMERIC);
|
||||||
|
if (count($allowed_mysqlservers) == 1 && $allowed_mysqlservers[0] != 0) {
|
||||||
|
return (int) $allowed_mysqlservers[0];
|
||||||
|
}
|
||||||
|
return (int) array_shift($allowed_mysqlservers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// 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') {
|
if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
|
||||||
$our_ips = Domain::getIpsOfDomain($domain_check['id']);
|
$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) {
|
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
|
||||||
Response::standardError('invaliddnsforletsencrypt', '', true);
|
Response::standardError('invaliddnsforletsencrypt', '', true);
|
||||||
}
|
}
|
||||||
@@ -701,11 +701,13 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
|||||||
$wwwserveralias = ($selectserveralias == '1') ? '1' : '0';
|
$wwwserveralias = ($selectserveralias == '1') ? '1' : '0';
|
||||||
|
|
||||||
// if allowed, check for 'is email domain'-flag
|
// if allowed, check for 'is email domain'-flag
|
||||||
if ($result['parentdomainid'] != '0' && ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $isemaildomain != $result['isemaildomain']) {
|
if ($isemaildomain != $result['isemaildomain']) {
|
||||||
|
if ($result['parentdomainid'] != '0' && ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2')) {
|
||||||
$isemaildomain = intval($isemaildomain);
|
$isemaildomain = intval($isemaildomain);
|
||||||
} elseif ($result['parentdomainid'] != '0') {
|
} elseif ($result['parentdomainid'] != '0') {
|
||||||
$isemaildomain = $result['subcanemaildomain'] == '3' ? 1 : 0;
|
$isemaildomain = $result['subcanemaildomain'] == '3' ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check changes of openbasedir-path variable
|
// check changes of openbasedir-path variable
|
||||||
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
|
if ($openbasedir_path > 2 && $openbasedir_path < 0) {
|
||||||
@@ -736,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
|
// 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') {
|
if ($result['letsencrypt'] != $letsencrypt && $letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') {
|
||||||
$our_ips = Domain::getIpsOfDomain($result['parentdomainid']);
|
$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) {
|
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
|
||||||
Response::standardError('invaliddnsforletsencrypt', '', true);
|
Response::standardError('invaliddnsforletsencrypt', '', true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
class CliCommand extends Command
|
class CliCommand extends Command
|
||||||
{
|
{
|
||||||
|
|
||||||
protected function validateRequirements(InputInterface $input, OutputInterface $output): int
|
protected function validateRequirements(InputInterface $input, OutputInterface $output, bool $ignore_has_updates = false): int
|
||||||
{
|
{
|
||||||
if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
|
if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
|
||||||
$output->writeln("<error>Could not find froxlor's userdata.inc.php file. You should use this script only with an installed froxlor system.</>");
|
$output->writeln("<error>Could not find froxlor's userdata.inc.php file. You should use this script only with an installed froxlor system.</>");
|
||||||
@@ -51,7 +51,7 @@ class CliCommand extends Command
|
|||||||
$output->writeln("<error>" . $e->getMessage() . "</>");
|
$output->writeln("<error>" . $e->getMessage() . "</>");
|
||||||
return self::INVALID;
|
return self::INVALID;
|
||||||
}
|
}
|
||||||
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
|
if (!$ignore_has_updates && (Froxlor::hasUpdates() || Froxlor::hasDbUpdates())) {
|
||||||
if ((int)Settings::Get('system.cron_allowautoupdate') == 1) {
|
if ((int)Settings::Get('system.cron_allowautoupdate') == 1) {
|
||||||
return $this->runUpdate($output);
|
return $this->runUpdate($output);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -246,8 +246,11 @@ final class InstallCommand extends Command
|
|||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->io->error($e->getMessage());
|
$this->io->error($e->getMessage());
|
||||||
|
if ($this->io->confirm('Retry?', empty($decoded_input))) {
|
||||||
return $this->showStep($step, $extended, $decoded_input);
|
return $this->showStep($step, $extended, $decoded_input);
|
||||||
}
|
}
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
if ($step == 3) {
|
if ($step == 3) {
|
||||||
// do actual install with data from $this->formfielddata
|
// do actual install with data from $this->formfielddata
|
||||||
$core = new Core($this->formfielddata);
|
$core = new Core($this->formfielddata);
|
||||||
@@ -297,7 +300,7 @@ final class InstallCommand extends Command
|
|||||||
$json_output = [];
|
$json_output = [];
|
||||||
foreach ($fields['install']['sections'] as $section => $section_fields) {
|
foreach ($fields['install']['sections'] as $section => $section_fields) {
|
||||||
foreach ($section_fields['fields'] as $name => $field) {
|
foreach ($section_fields['fields'] as $name => $field) {
|
||||||
if ($name == 'system' || $name == 'manual_config') {
|
if ($name == 'system' || $name == 'manual_config' || $name == 'target_servername') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($field['type'] == 'text' || $field['type'] == 'email') {
|
if ($field['type'] == 'text' || $field['type'] == 'email') {
|
||||||
|
|||||||
165
lib/Froxlor/Cli/ValidateAcmeWebroot.php
Normal file
165
lib/Froxlor/Cli/ValidateAcmeWebroot.php
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is part of the Froxlor project.
|
||||||
|
* Copyright (c) 2010 the Froxlor Team (see authors).
|
||||||
|
*
|
||||||
|
* 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 2
|
||||||
|
* 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, you can also view it online at
|
||||||
|
* https://files.froxlor.org/misc/COPYING.txt
|
||||||
|
*
|
||||||
|
* @copyright the authors
|
||||||
|
* @author Froxlor team <team@froxlor.org>
|
||||||
|
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Froxlor\Cli;
|
||||||
|
|
||||||
|
use Froxlor\Cron\TaskId;
|
||||||
|
use Froxlor\Database\Database;
|
||||||
|
use Froxlor\FileDir;
|
||||||
|
use Froxlor\Froxlor;
|
||||||
|
use Froxlor\Settings;
|
||||||
|
use Froxlor\System\Cronjob;
|
||||||
|
use PDO;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
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->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$result = self::SUCCESS;
|
||||||
|
|
||||||
|
$result = $this->validateRequirements($input, $output, true);
|
||||||
|
|
||||||
|
$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');
|
||||||
|
$count_changes = 0;
|
||||||
|
// get all Let's Encrypt enabled domains
|
||||||
|
$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 = 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');
|
||||||
|
if (file_exists($acme_domain_conf)) {
|
||||||
|
$io->text("Getting info from " . $acme_domain_conf);
|
||||||
|
$conf_content = file_get_contents($acme_domain_conf);
|
||||||
|
} else {
|
||||||
|
$acme_domain_conf = FileDir::makeCorrectFile($acmesh_dir . '/' . $domain . '_ecc/' . $domain . '.conf');
|
||||||
|
if (file_exists($acme_domain_conf)) {
|
||||||
|
$io->text("Getting info from " . $acme_domain_conf);
|
||||||
|
$conf_content = file_get_contents($acme_domain_conf);
|
||||||
|
} else {
|
||||||
|
$io->info("No domain configuration file found in '" . $acmesh_dir . "'");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($conf_content)) {
|
||||||
|
$lines = explode("\n", $conf_content);
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$val_key = explode("=", $line);
|
||||||
|
if ($val_key[0] == 'Le_Webroot') {
|
||||||
|
$domain_webroot = trim(trim($val_key[1], "'"), '"');
|
||||||
|
if ($domain_webroot != $acmesh_challenge_dir) {
|
||||||
|
$io->warning("Domain '" . $domain . "' has old/wrong Le_Webroot setting: '" . $domain_webroot . ' <> ' . $acmesh_challenge_dir . "'");
|
||||||
|
$question = new ConfirmationQuestion('Fix Le_Webroot? [yes] ', true, '/^(y|j)/i');
|
||||||
|
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||||
|
$sed_params = "s@Le_Webroot=.*@Le_Webroot='" . $acmesh_challenge_dir . "'@";
|
||||||
|
FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_domain_conf));
|
||||||
|
Database::pexecute($upd_stmt, ['did' => $domain_arr['id']]);
|
||||||
|
$io->success("Correction of Le_Webroot successful");
|
||||||
|
$count_changes++;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$io->info("Domain '" . $domain . "' Le_Webroot value is correct");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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');
|
||||||
|
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||||
|
passthru(FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/bin/froxlor-cli') . ' froxlor:cron -f -d');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$io->success("No changes necessary.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -148,7 +148,7 @@ class ConfigDisplay
|
|||||||
if ($lasttype != '' && $lasttype != $_action['type']) {
|
if ($lasttype != '' && $lasttype != $_action['type']) {
|
||||||
$commands = trim($commands);
|
$commands = trim($commands);
|
||||||
$numbrows = count(explode("\n", $commands));
|
$numbrows = count(explode("\n", $commands));
|
||||||
$configpage .= UI::twig()->render(self::$theme . '/settings/conf/command.html.twig', [
|
$configpage .= UI::twig()->render(UI::validateThemeTemplate('/settings/conf/command.html.twig', self::$theme), [
|
||||||
'commands' => $commands,
|
'commands' => $commands,
|
||||||
'numbrows' => $numbrows
|
'numbrows' => $numbrows
|
||||||
]);
|
]);
|
||||||
@@ -182,7 +182,7 @@ class ConfigDisplay
|
|||||||
$commands = trim($commands_pre);
|
$commands = trim($commands_pre);
|
||||||
if ($commands != "") {
|
if ($commands != "") {
|
||||||
$numbrows = count(explode("\n", $commands));
|
$numbrows = count(explode("\n", $commands));
|
||||||
$commands_pre = UI::twig()->render(self::$theme . '/settings/conf/command.html.twig', [
|
$commands_pre = UI::twig()->render(UI::validateThemeTemplate('/settings/conf/command.html.twig', self::$theme), [
|
||||||
'commands' => $commands,
|
'commands' => $commands,
|
||||||
'numbrows' => $numbrows
|
'numbrows' => $numbrows
|
||||||
]);
|
]);
|
||||||
@@ -190,12 +190,12 @@ class ConfigDisplay
|
|||||||
$commands = trim($commands_post);
|
$commands = trim($commands_post);
|
||||||
if ($commands != "") {
|
if ($commands != "") {
|
||||||
$numbrows = count(explode("\n", $commands));
|
$numbrows = count(explode("\n", $commands));
|
||||||
$commands_post = UI::twig()->render(self::$theme . '/settings/conf/command.html.twig', [
|
$commands_post = UI::twig()->render(UI::validateThemeTemplate('/settings/conf/command.html.twig', self::$theme), [
|
||||||
'commands' => $commands,
|
'commands' => $commands,
|
||||||
'numbrows' => $numbrows
|
'numbrows' => $numbrows
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$configpage .= UI::twig()->render(self::$theme . '/settings/conf/fileblock.html.twig', [
|
$configpage .= UI::twig()->render(UI::validateThemeTemplate('/settings/conf/fileblock.html.twig', self::$theme), [
|
||||||
'realname' => $realname,
|
'realname' => $realname,
|
||||||
'commands_pre' => $commands_pre,
|
'commands_pre' => $commands_pre,
|
||||||
'commands_file' => $commands_file,
|
'commands_file' => $commands_file,
|
||||||
@@ -210,7 +210,7 @@ class ConfigDisplay
|
|||||||
$commands = trim($commands);
|
$commands = trim($commands);
|
||||||
if ($commands != '') {
|
if ($commands != '') {
|
||||||
$numbrows = count(explode("\n", $commands));
|
$numbrows = count(explode("\n", $commands));
|
||||||
$configpage .= UI::twig()->render(self::$theme . '/settings/conf/command.html.twig', [
|
$configpage .= UI::twig()->render(UI::validateThemeTemplate('/settings/conf/command.html.twig', self::$theme), [
|
||||||
'commands' => $commands,
|
'commands' => $commands,
|
||||||
'numbrows' => $numbrows
|
'numbrows' => $numbrows
|
||||||
]);
|
]);
|
||||||
@@ -233,7 +233,7 @@ class ConfigDisplay
|
|||||||
$file_content = htmlspecialchars($file_content);
|
$file_content = htmlspecialchars($file_content);
|
||||||
$numbrows = count(explode("\n", $file_content));
|
$numbrows = count(explode("\n", $file_content));
|
||||||
//eval("\$files=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_file") . "\";");
|
//eval("\$files=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_file") . "\";");
|
||||||
$files = UI::twig()->render(self::$theme . '/settings/conf/file.html.twig', [
|
$files = UI::twig()->render(UI::validateThemeTemplate('/settings/conf/file.html.twig', self::$theme), [
|
||||||
'distro_editor' => self::$editor,
|
'distro_editor' => self::$editor,
|
||||||
'realname' => $realname,
|
'realname' => $realname,
|
||||||
'numbrows' => $numbrows,
|
'numbrows' => $numbrows,
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ class AcmeSh extends FroxlorCron
|
|||||||
'letsencrypt_test' => "https://acme-staging-v02.api.letsencrypt.org/directory",
|
'letsencrypt_test' => "https://acme-staging-v02.api.letsencrypt.org/directory",
|
||||||
'buypass' => "https://api.buypass.com/acme/directory",
|
'buypass' => "https://api.buypass.com/acme/directory",
|
||||||
'buypass_test' => "https://api.test4.buypass.no/acme/directory",
|
'buypass_test' => "https://api.test4.buypass.no/acme/directory",
|
||||||
'zerossl' => "https://acme.zerossl.com/v2/DV90"
|
'zerossl' => "https://acme.zerossl.com/v2/DV90",
|
||||||
|
'google' => "https://dv.acme-v02.api.pki.goog/directory",
|
||||||
|
'google_test' => "https://dv.acme-v02.test-api.pki.goog/directory",
|
||||||
];
|
];
|
||||||
public static $no_inserttask = false;
|
public static $no_inserttask = false;
|
||||||
private static $apiserver = "";
|
private static $apiserver = "";
|
||||||
@@ -519,7 +521,7 @@ EOC;
|
|||||||
foreach ($loop_domains as $idx => $domain) {
|
foreach ($loop_domains as $idx => $domain) {
|
||||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain);
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Validating DNS of " . $domain);
|
||||||
// ips according to NS
|
// 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) {
|
if ($domain_ips == false || count(array_intersect($our_ips, $domain_ips)) <= 0) {
|
||||||
// no common ips...
|
// 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");
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $domain . " due to no system known IP address via DNS check");
|
||||||
@@ -555,7 +557,7 @@ EOC;
|
|||||||
if (Settings::Get('system.letsencryptreuseold') != '1') {
|
if (Settings::Get('system.letsencryptreuseold') != '1') {
|
||||||
$acmesh_cmd .= " --always-force-new-domain-key";
|
$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";
|
$acmesh_cmd .= " --staging";
|
||||||
}
|
}
|
||||||
if ($force) {
|
if ($force) {
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ class BackupCron extends FroxlorCron
|
|||||||
FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql')));
|
FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql')));
|
||||||
|
|
||||||
// get all customer database-names
|
// get all customer database-names
|
||||||
|
// @fixme respect multiple dbservers
|
||||||
$sel_stmt = Database::prepare("SELECT `databasename` FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :cid");
|
$sel_stmt = Database::prepare("SELECT `databasename` FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :cid");
|
||||||
Database::pexecute($sel_stmt, [
|
Database::pexecute($sel_stmt, [
|
||||||
'cid' => $data['customerid']
|
'cid' => $data['customerid']
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class TrafficCron extends FroxlorCron
|
|||||||
|
|
||||||
while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) {
|
while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
if ($last_dbserver != $row_database['dbserver']) {
|
if ($last_dbserver != $row_database['dbserver']) {
|
||||||
Database::needRoot(true, $row_database['dbserver']);
|
Database::needRoot(true, $row_database['dbserver'], true);
|
||||||
$last_dbserver = $row_database['dbserver'];
|
$last_dbserver = $row_database['dbserver'];
|
||||||
|
|
||||||
$databases_list = [];
|
$databases_list = [];
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ namespace Froxlor\Database;
|
|||||||
use Exception;
|
use Exception;
|
||||||
use Froxlor\FileDir;
|
use Froxlor\FileDir;
|
||||||
use Froxlor\Froxlor;
|
use Froxlor\Froxlor;
|
||||||
|
use Froxlor\PhpHelper;
|
||||||
use Froxlor\Settings;
|
use Froxlor\Settings;
|
||||||
use Froxlor\UI\Panel\UI;
|
use Froxlor\UI\Panel\UI;
|
||||||
use PDO;
|
use PDO;
|
||||||
@@ -79,6 +80,8 @@ class Database
|
|||||||
|
|
||||||
private static $sqldata = null;
|
private static $sqldata = null;
|
||||||
|
|
||||||
|
private static $need_dbname = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for PDOStatement::execute so we can catch the PDOException
|
* Wrapper for PDOStatement::execute so we can catch the PDOException
|
||||||
* and display the error nicely on the panel - also fetches the
|
* and display the error nicely on the panel - also fetches the
|
||||||
@@ -135,7 +138,7 @@ class Database
|
|||||||
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
|
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
|
||||||
|
|
||||||
// le format
|
// le format
|
||||||
if (isset($sql['root_user']) && isset($sql['root_password']) && !is_array($sql_root)) {
|
if (isset($sql['root_user']) && isset($sql['root_password']) && empty($sql_root)) {
|
||||||
$sql_root = [
|
$sql_root = [
|
||||||
0 => [
|
0 => [
|
||||||
'caption' => 'Default',
|
'caption' => 'Default',
|
||||||
@@ -145,12 +148,20 @@ class Database
|
|||||||
'password' => $sql['root_password']
|
'password' => $sql['root_password']
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
unset($sql['root_user']);
|
||||||
|
unset($sql['root_password']);
|
||||||
|
// write new layout so this won't happen again
|
||||||
|
self::generateNewUserData($sql, $sql_root);
|
||||||
|
// re-read file
|
||||||
|
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
|
||||||
}
|
}
|
||||||
|
|
||||||
$substitutions = [
|
$substitutions = [
|
||||||
$sql['password'] => 'DB_UNPRIV_PWD',
|
$sql['password'] => 'DB_UNPRIV_PWD',
|
||||||
$sql_root[0]['password'] => 'DB_ROOT_PWD'
|
|
||||||
];
|
];
|
||||||
|
foreach ($sql_root as $dbserver => $sql_root_data) {
|
||||||
|
$substitutions[$sql_root_data[$dbserver]]['password'] = 'DB_ROOT_PWD';
|
||||||
|
}
|
||||||
|
|
||||||
// hide username/password in messages
|
// hide username/password in messages
|
||||||
$error_message = $error->getMessage();
|
$error_message = $error->getMessage();
|
||||||
@@ -341,12 +352,13 @@ class Database
|
|||||||
* @param int $dbserver
|
* @param int $dbserver
|
||||||
* optional
|
* optional
|
||||||
*/
|
*/
|
||||||
public static function needRoot($needroot = false, $dbserver = 0)
|
public static function needRoot(bool $needroot = false, int $dbserver = 0, bool $need_db = true)
|
||||||
{
|
{
|
||||||
// force re-connecting to the db with corresponding user
|
// force re-connecting to the db with corresponding user
|
||||||
// and set the $dbserver (mostly to 0 = default)
|
// and set the $dbserver (mostly to 0 = default)
|
||||||
self::setServer($dbserver);
|
self::setServer($dbserver);
|
||||||
self::$needroot = $needroot;
|
self::$needroot = $needroot;
|
||||||
|
self::$need_dbname = $need_db;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -405,7 +417,7 @@ class Database
|
|||||||
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
|
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
|
||||||
|
|
||||||
// le format
|
// le format
|
||||||
if (self::$needroot == true && isset($sql['root_user']) && isset($sql['root_password']) && (!isset($sql_root) || !is_array($sql_root))) {
|
if (isset($sql['root_user']) && isset($sql['root_password']) && (!isset($sql_root) || !is_array($sql_root))) {
|
||||||
$sql_root = [
|
$sql_root = [
|
||||||
0 => [
|
0 => [
|
||||||
'caption' => 'Default',
|
'caption' => 'Default',
|
||||||
@@ -417,6 +429,10 @@ class Database
|
|||||||
];
|
];
|
||||||
unset($sql['root_user']);
|
unset($sql['root_user']);
|
||||||
unset($sql['root_password']);
|
unset($sql['root_password']);
|
||||||
|
// write new layout so this won't happen again
|
||||||
|
self::generateNewUserData($sql, $sql_root);
|
||||||
|
// re-read file
|
||||||
|
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
|
||||||
}
|
}
|
||||||
|
|
||||||
// either root or unprivileged user
|
// either root or unprivileged user
|
||||||
@@ -465,10 +481,11 @@ class Database
|
|||||||
'ATTR_ERRMODE' => 'ERRMODE_EXCEPTION'
|
'ATTR_ERRMODE' => 'ERRMODE_EXCEPTION'
|
||||||
];
|
];
|
||||||
|
|
||||||
$dbconf["dsn"] = [
|
$dbconf["dsn"] = ['charset' => 'utf8'];
|
||||||
'dbname' => $sql["db"],
|
|
||||||
'charset' => 'utf8'
|
if (self::$need_dbname) {
|
||||||
];
|
$dbconf["dsn"]['dbname'] = $sql["db"];
|
||||||
|
}
|
||||||
|
|
||||||
if ($socket != null) {
|
if ($socket != null) {
|
||||||
$dbconf["dsn"]['unix_socket'] = FileDir::makeCorrectFile($socket);
|
$dbconf["dsn"]['unix_socket'] = FileDir::makeCorrectFile($socket);
|
||||||
@@ -578,4 +595,22 @@ class Database
|
|||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* write new userdata.inc.php file
|
||||||
|
*/
|
||||||
|
private static function generateNewUserData(array $sql, array $sql_root)
|
||||||
|
{
|
||||||
|
$content = PhpHelper::parseArrayToPhpFile(
|
||||||
|
['sql' => $sql, 'sql_root' => $sql_root],
|
||||||
|
'automatically generated userdata.inc.php for froxlor'
|
||||||
|
);
|
||||||
|
chmod(Froxlor::getInstallDir() . "/lib/userdata.inc.php", 0700);
|
||||||
|
file_put_contents(Froxlor::getInstallDir() . "/lib/userdata.inc.php", $content);
|
||||||
|
chmod(Froxlor::getInstallDir() . "/lib/userdata.inc.php", 0400);
|
||||||
|
clearstatcache();
|
||||||
|
if (function_exists('opcache_invalidate')) {
|
||||||
|
@opcache_invalidate(Froxlor::getInstallDir() . "/lib/userdata.inc.php", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class DbManager
|
|||||||
$dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`");
|
$dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`");
|
||||||
while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {
|
while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
// require privileged access for target db-server
|
// require privileged access for target db-server
|
||||||
Database::needRoot(true, $dbserver['dbserver']);
|
Database::needRoot(true, $dbserver['dbserver'], false);
|
||||||
|
|
||||||
$dbm = new DbManager(FroxlorLogger::getInstanceOf());
|
$dbm = new DbManager(FroxlorLogger::getInstanceOf());
|
||||||
$users = $dbm->getManager()->getAllSqlUsers(false);
|
$users = $dbm->getManager()->getAllSqlUsers(false);
|
||||||
@@ -144,7 +144,7 @@ class DbManager
|
|||||||
*/
|
*/
|
||||||
public function createDatabase($loginname = null, $password = null, int $dbserver = 0, $last_accnumber = 0)
|
public function createDatabase($loginname = null, $password = null, int $dbserver = 0, $last_accnumber = 0)
|
||||||
{
|
{
|
||||||
Database::needRoot(true, $dbserver);
|
Database::needRoot(true, $dbserver, false);
|
||||||
|
|
||||||
// check whether we shall create a random username
|
// check whether we shall create a random username
|
||||||
if (strtoupper(Settings::Get('customer.mysqlprefix')) == 'RANDOM') {
|
if (strtoupper(Settings::Get('customer.mysqlprefix')) == 'RANDOM') {
|
||||||
@@ -169,18 +169,17 @@ class DbManager
|
|||||||
|
|
||||||
// now create the database itself
|
// now create the database itself
|
||||||
$this->getManager()->createDatabase($username);
|
$this->getManager()->createDatabase($username);
|
||||||
$this->log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "created database '" . $username . "'");
|
|
||||||
|
|
||||||
// and give permission to the user on every access-host we have
|
// and give permission to the user on every access-host we have
|
||||||
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
|
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
|
||||||
$this->getManager()->grantPrivilegesTo($username, $password, $mysql_access_host);
|
$this->getManager()->grantPrivilegesTo($username, $password, $mysql_access_host);
|
||||||
$this->log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "grant all privileges for '" . $username . "'@'" . $mysql_access_host . "'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->getManager()->flushPrivileges();
|
$this->getManager()->flushPrivileges();
|
||||||
|
|
||||||
Database::needRoot(false);
|
Database::needRoot(false);
|
||||||
|
|
||||||
|
$this->log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "created database '" . $username . "'");
|
||||||
|
|
||||||
return $username;
|
return $username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,9 +147,9 @@ class FileDir
|
|||||||
*/
|
*/
|
||||||
public static function makeSecurePath($path)
|
public static function makeSecurePath($path)
|
||||||
{
|
{
|
||||||
// check for bad characters, some are allowed with escaping
|
// check for bad characters, some are allowed with escaping,
|
||||||
// but we generally don't want them in our directory-names,
|
// but we generally don't want them in our directory-names,
|
||||||
// thx to aaronmueller for this snipped
|
// thx to aaronmueller for this snippet
|
||||||
$badchars = [
|
$badchars = [
|
||||||
':',
|
':',
|
||||||
';',
|
';',
|
||||||
@@ -161,7 +161,11 @@ class FileDir
|
|||||||
'$',
|
'$',
|
||||||
'~',
|
'~',
|
||||||
'?',
|
'?',
|
||||||
"\0"
|
"\0",
|
||||||
|
"\n",
|
||||||
|
"\r",
|
||||||
|
"\t",
|
||||||
|
"\f"
|
||||||
];
|
];
|
||||||
foreach ($badchars as $bc) {
|
foreach ($badchars as $bc) {
|
||||||
$path = str_replace($bc, "", $path);
|
$path = str_replace($bc, "", $path);
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ final class Froxlor
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Main version variable
|
// Main version variable
|
||||||
const VERSION = '2.0.3';
|
const VERSION = '2.0.10';
|
||||||
|
|
||||||
// Database version (YYYYMMDDC where C is a daily counter)
|
// Database version (YYYYMMDDC where C is a daily counter)
|
||||||
const DBVERSION = '202212060';
|
const DBVERSION = '202301180';
|
||||||
|
|
||||||
// Distribution branding-tag (used for Debian etc.)
|
// Distribution branding-tag (used for Debian etc.)
|
||||||
const BRANDING = '';
|
const BRANDING = '';
|
||||||
|
|||||||
@@ -100,11 +100,17 @@ class FroxlorLogger
|
|||||||
self::$ml->pushHandler(new SyslogHandler('froxlor', LOG_USER, Logger::DEBUG));
|
self::$ml->pushHandler(new SyslogHandler('froxlor', LOG_USER, Logger::DEBUG));
|
||||||
break;
|
break;
|
||||||
case 'file':
|
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
|
// is_writable needs an existing file to check if it's actually writable
|
||||||
@touch($logger_logfile);
|
@touch($logger_logfile);
|
||||||
if (empty($logger_logfile) || !is_writable($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));
|
self::$ml->pushHandler(new StreamHandler($logger_logfile, Logger::DEBUG));
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class Preconfig
|
|||||||
$agree = [
|
$agree = [
|
||||||
'title' => 'Check',
|
'title' => 'Check',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'update_changesagreed' => ['type' => 'checkbox', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
|
'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
|
||||||
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
|
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ namespace Froxlor;
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Froxlor\UI\Panel\UI;
|
use Froxlor\UI\Panel\UI;
|
||||||
|
use Net_DNS2_Exception;
|
||||||
|
use Net_DNS2_Resolver;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use voku\helper\AntiXSS;
|
use voku\helper\AntiXSS;
|
||||||
|
|
||||||
@@ -244,47 +246,66 @@ class PhpHelper
|
|||||||
* ipv6 aware gethostbynamel function
|
* ipv6 aware gethostbynamel function
|
||||||
*
|
*
|
||||||
* @param string $host
|
* @param string $host
|
||||||
* @param boolean $try_a
|
* @param boolean $try_a default true
|
||||||
* default true
|
* @param string|null $nameserver set additional resolver nameserver to use (e.g. 1.1.1.1)
|
||||||
* @return boolean|array
|
* @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 = [];
|
$ips = [];
|
||||||
foreach ($dns as $record) {
|
|
||||||
if ($record["type"] == "A") {
|
try {
|
||||||
// always use compressed ipv6 format
|
// set the default nameservers to use, use the system default if none are provided
|
||||||
$ip = inet_ntop(inet_pton($record["ip"]));
|
$resolver = new Net_DNS2_Resolver($nameserver ? ['nameservers' => [$nameserver]] : []);
|
||||||
$ips[] = $ip;
|
|
||||||
}
|
// get all ip addresses from the A record and normalize them
|
||||||
if ($record["type"] == "AAAA") {
|
if ($try_a) {
|
||||||
// always use compressed ipv6 format
|
try {
|
||||||
$ip = inet_ntop(inet_pton($record["ipv6"]));
|
$answer = $resolver->query($host, 'A')->answer;
|
||||||
$ips[] = $ip;
|
foreach ($answer as $rr) {
|
||||||
|
if ($rr instanceof Net_DNS2_RR_A) {
|
||||||
|
$ips[] = inet_ntop(inet_pton($rr->address));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (count($ips) < 1) {
|
} catch (Net_DNS2_Exception $e) {
|
||||||
return false;
|
// we can't do anything here, just continue
|
||||||
} else {
|
|
||||||
return $ips;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get all ip addresses from the AAAA record and normalize them
|
||||||
|
try {
|
||||||
|
$answer = $resolver->query($host, 'AAAA')->answer;
|
||||||
|
foreach ($answer as $rr) {
|
||||||
|
if ($rr instanceof Net_DNS2_RR_AAAA) {
|
||||||
|
$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']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($ips) > 0 ? $ips : false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function randomStr
|
* Function randomStr
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class Mysql
|
|||||||
public static function dbserver(array $attributes): string
|
public static function dbserver(array $attributes): string
|
||||||
{
|
{
|
||||||
// get sql-root access data
|
// get sql-root access data
|
||||||
Database::needRoot(true, (int)$attributes['data']);
|
Database::needRoot(true, (int)$attributes['data'], false);
|
||||||
Database::needSqlData();
|
Database::needSqlData();
|
||||||
$sql_root = Database::getSqlData();
|
$sql_root = Database::getSqlData();
|
||||||
Database::needRoot(false);
|
Database::needRoot(false);
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class Text
|
|||||||
$result = $attributes['fields'];
|
$result = $attributes['fields'];
|
||||||
$apikey_data = include Froxlor::getInstallDir() . '/lib/formfields/formfield.api_key.php';
|
$apikey_data = include Froxlor::getInstallDir() . '/lib/formfields/formfield.api_key.php';
|
||||||
|
|
||||||
$body = UI::twig()->render(UI::getTheme() . '/user/inline-form.html.twig', [
|
$body = UI::twig()->render(UI::validateThemeTemplate('/user/inline-form.html.twig'), [
|
||||||
'formaction' => $linker->getLink(['section' => 'index', 'page' => 'apikeys']),
|
'formaction' => $linker->getLink(['section' => 'index', 'page' => 'apikeys']),
|
||||||
'formdata' => $apikey_data['apikey'],
|
'formdata' => $apikey_data['apikey'],
|
||||||
'editid' => $attributes['fields']['id']
|
'editid' => $attributes['fields']['id']
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ class FroxlorTwig extends AbstractExtension
|
|||||||
new TwigFunction('linker', [
|
new TwigFunction('linker', [
|
||||||
$this,
|
$this,
|
||||||
'getLink'
|
'getLink'
|
||||||
|
]),
|
||||||
|
new TwigFunction('mix', [
|
||||||
|
$this,
|
||||||
|
'getMix'
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -158,4 +162,9 @@ class FroxlorTwig extends AbstractExtension
|
|||||||
{
|
{
|
||||||
return 'froxlortwig';
|
return 'froxlortwig';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMix($mix = '')
|
||||||
|
{
|
||||||
|
return mix($mix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class UI
|
|||||||
session_set_cookie_params([
|
session_set_cookie_params([
|
||||||
'lifetime' => self::$install_mode ? 7200 : 600, // will be renewed based on settings in lib/init.php
|
'lifetime' => self::$install_mode ? 7200 : 600, // will be renewed based on settings in lib/init.php
|
||||||
'path' => '/',
|
'path' => '/',
|
||||||
'domain' => $_SERVER['HTTP_HOST'],
|
'domain' => explode(':', $_SERVER['HTTP_HOST'])[0],
|
||||||
'secure' => self::requestIsHttps(),
|
'secure' => self::requestIsHttps(),
|
||||||
'httponly' => true,
|
'httponly' => true,
|
||||||
'samesite' => 'Strict'
|
'samesite' => 'Strict'
|
||||||
@@ -260,11 +260,28 @@ class UI
|
|||||||
*/
|
*/
|
||||||
public static function twigBuffer($name, array $context = [])
|
public static function twigBuffer($name, array $context = [])
|
||||||
{
|
{
|
||||||
|
$template_file = self::validateThemeTemplate($name);
|
||||||
|
|
||||||
self::$twigbuf[] = [
|
self::$twigbuf[] = [
|
||||||
self::getTheme() . '/' . $name => $context
|
$template_file => $context
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function validateThemeTemplate(string $name, string $theme = "") {
|
||||||
|
if (empty(trim($theme))) {
|
||||||
|
$theme = self::getTheme();
|
||||||
|
}
|
||||||
|
$template_file = $theme . '/' . $name;
|
||||||
|
if (!file_exists(Froxlor::getInstallDir() . '/templates/' . $template_file)) {
|
||||||
|
PhpHelper::phpErrHandler(E_USER_WARNING, "Template '" . $template_file . "' could not be found, trying fallback theme", __FILE__, __LINE__);
|
||||||
|
$template_file = self::$default_theme . '/'. $name;
|
||||||
|
if (!file_exists(Froxlor::getInstallDir() . '/templates/' . $template_file)) {
|
||||||
|
PhpHelper::phpErrHandler(E_USER_ERROR, "Unknown template '" . $template_file . "'", __FILE__, __LINE__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $template_file;
|
||||||
|
}
|
||||||
|
|
||||||
public static function getTheme()
|
public static function getTheme()
|
||||||
{
|
{
|
||||||
// fallback
|
// fallback
|
||||||
|
|||||||
14
lib/config.example.inc.php
Normal file
14
lib/config.example.inc.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* change the options below to either true or false
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
];
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* change the options below to either true or false
|
|
||||||
*/
|
|
||||||
return [
|
|
||||||
'enable_webupdate' => false
|
|
||||||
];
|
|
||||||
@@ -3458,11 +3458,7 @@ ssl_key = <<SSL_KEY_FILE>
|
|||||||
# auth_ssl_username_from_cert=yes.
|
# auth_ssl_username_from_cert=yes.
|
||||||
#ssl_cert_username_field = commonName
|
#ssl_cert_username_field = commonName
|
||||||
|
|
||||||
# SSL DH parameters
|
ssl_dh_parameters_length = 2048
|
||||||
# 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 = </usr/share/dovecot/dh.pem
|
|
||||||
|
|
||||||
# SSL protocols to use
|
# SSL protocols to use
|
||||||
#ssl_protocols = !SSLv3
|
#ssl_protocols = !SSLv3
|
||||||
@@ -4678,7 +4674,7 @@ aliases: files
|
|||||||
<command><![CDATA[mkdir -p {{settings.system.mod_fcgid_configdir}}]]></command>
|
<command><![CDATA[mkdir -p {{settings.system.mod_fcgid_configdir}}]]></command>
|
||||||
<command><![CDATA[mkdir -p {{settings.system.mod_fcgid_tmpdir}}]]></command>
|
<command><![CDATA[mkdir -p {{settings.system.mod_fcgid_tmpdir}}]]></command>
|
||||||
<command><![CDATA[chmod 1777 {{settings.system.mod_fcgid_tmpdir}}]]></command>
|
<command><![CDATA[chmod 1777 {{settings.system.mod_fcgid_tmpdir}}]]></command>
|
||||||
<command><![CDATA[a2dismod php7.2]]></command>
|
<command><![CDATA[a2dismod php7.4]]></command>
|
||||||
</commands>
|
</commands>
|
||||||
<!-- instead of just restarting apache, we let the cronjob do all the
|
<!-- instead of just restarting apache, we let the cronjob do all the
|
||||||
dirty work -->
|
dirty work -->
|
||||||
@@ -4711,7 +4707,7 @@ aliases: files
|
|||||||
</visibility>
|
</visibility>
|
||||||
<visibility mode="true">{{settings.phpfpm.enabled_ownvhost}}
|
<visibility mode="true">{{settings.phpfpm.enabled_ownvhost}}
|
||||||
</visibility>
|
</visibility>
|
||||||
<command><![CDATA[a2dismod php7.2]]></command>
|
<command><![CDATA[a2dismod php7.4]]></command>
|
||||||
</commands>
|
</commands>
|
||||||
<!-- instead of just restarting apache, we let the cronjob do all the
|
<!-- instead of just restarting apache, we let the cronjob do all the
|
||||||
dirty work -->
|
dirty work -->
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ return [
|
|||||||
'title' => lng('admin.domain_add'),
|
'title' => lng('admin.domain_add'),
|
||||||
'image' => 'fa-solid fa-globe',
|
'image' => 'fa-solid fa-globe',
|
||||||
'self_overview' => ['section' => 'domains', 'page' => 'domains'],
|
'self_overview' => ['section' => 'domains', 'page' => 'domains'],
|
||||||
|
'id' => 'domain_add',
|
||||||
'sections' => [
|
'sections' => [
|
||||||
'section_a' => [
|
'section_a' => [
|
||||||
'title' => lng('domains.domainsettings'),
|
'title' => lng('domains.domainsettings'),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ return [
|
|||||||
'title' => lng('admin.domain_edit'),
|
'title' => lng('admin.domain_edit'),
|
||||||
'image' => 'fa-solid fa-globe',
|
'image' => 'fa-solid fa-globe',
|
||||||
'self_overview' => ['section' => 'domains', 'page' => 'domains'],
|
'self_overview' => ['section' => 'domains', 'page' => 'domains'],
|
||||||
|
'id' => 'domain_edit',
|
||||||
'sections' => [
|
'sections' => [
|
||||||
'section_a' => [
|
'section_a' => [
|
||||||
'title' => lng('domains.domainsettings'),
|
'title' => lng('domains.domainsettings'),
|
||||||
|
|||||||
@@ -33,10 +33,16 @@ return [
|
|||||||
'value' => $result['databasename']
|
'value' => $result['databasename']
|
||||||
],
|
],
|
||||||
'mysql_server' => [
|
'mysql_server' => [
|
||||||
|
'visible' => count($mysql_servers) > 1,
|
||||||
|
'type' => 'hidden',
|
||||||
|
'value' => $result['dbserver'] ?? 0,
|
||||||
|
],
|
||||||
|
'mysql_server_info' => [
|
||||||
'visible' => count($mysql_servers) > 1,
|
'visible' => count($mysql_servers) > 1,
|
||||||
'label' => lng('mysql.mysql_server'),
|
'label' => lng('mysql.mysql_server'),
|
||||||
'type' => 'label',
|
'type' => 'label',
|
||||||
'value' => $mysql_servers[$result['dbserver']] ?? 'unknown db server'
|
'disabled' => true,
|
||||||
|
'value' => $mysql_servers[$result['dbserver']] ?? 'unknown db server',
|
||||||
],
|
],
|
||||||
'description' => [
|
'description' => [
|
||||||
'label' => lng('mysql.databasedescription'),
|
'label' => lng('mysql.databasedescription'),
|
||||||
|
|||||||
@@ -26,6 +26,14 @@
|
|||||||
use Froxlor\Language;
|
use Froxlor\Language;
|
||||||
use Froxlor\UI\Request;
|
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)
|
function view($template, $attributes)
|
||||||
{
|
{
|
||||||
$view = file_get_contents(dirname(__DIR__) . '/templates/' . $template);
|
$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);
|
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 = [])
|
function lng(string $identifier, array $arguments = [])
|
||||||
{
|
{
|
||||||
return Language::getTranslation($identifier, $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)
|
function old(string $identifier, string $default = null, string $session = null)
|
||||||
{
|
{
|
||||||
if ($session && isset($_SESSION[$session])) {
|
if ($session && isset($_SESSION[$session])) {
|
||||||
@@ -45,3 +68,26 @@ function old(string $identifier, string $default = null, string $session = null)
|
|||||||
}
|
}
|
||||||
return Request::any($identifier, $default);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -277,14 +277,14 @@ if (is_array($_themeoptions) && array_key_exists('js', $_themeoptions['variants'
|
|||||||
if (is_array($_themeoptions['variants'][$themevariant]['js'])) {
|
if (is_array($_themeoptions['variants'][$themevariant]['js'])) {
|
||||||
foreach ($_themeoptions['variants'][$themevariant]['js'] as $jsfile) {
|
foreach ($_themeoptions['variants'][$themevariant]['js'] as $jsfile) {
|
||||||
if (file_exists('templates/' . $theme . '/assets/js/' . $jsfile)) {
|
if (file_exists('templates/' . $theme . '/assets/js/' . $jsfile)) {
|
||||||
$js .= '<script type="text/javascript" src="templates/' . $theme . '/assets/js/' . $jsfile . '"></script>' . "\n";
|
$js .= '<script type="text/javascript" src="' . mix('templates/' . $theme . '/assets/js/' . $jsfile) . '"></script>' . "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is_array($_themeoptions['variants'][$themevariant]['css'])) {
|
if (is_array($_themeoptions['variants'][$themevariant]['css'])) {
|
||||||
foreach ($_themeoptions['variants'][$themevariant]['css'] as $cssfile) {
|
foreach ($_themeoptions['variants'][$themevariant]['css'] as $cssfile) {
|
||||||
if (file_exists('templates/' . $theme . '/assets/css/' . $cssfile)) {
|
if (file_exists('templates/' . $theme . '/assets/css/' . $cssfile)) {
|
||||||
$css .= '<link href="templates/' . $theme . '/assets/css/' . $cssfile . '" rel="stylesheet" type="text/css" />' . "\n";
|
$css .= '<link href="' . mix('templates/' . $theme . '/assets/css/' . $cssfile) . '" rel="stylesheet" type="text/css" />' . "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,7 +332,7 @@ if (CurrentUser::hasSession()) {
|
|||||||
$cookie_params = [
|
$cookie_params = [
|
||||||
'expires' => time() + Settings::Get('session.sessiontimeout'),
|
'expires' => time() + Settings::Get('session.sessiontimeout'),
|
||||||
'path' => '/',
|
'path' => '/',
|
||||||
'domain' => $_SERVER['HTTP_HOST'],
|
'domain' => explode(':', $_SERVER['HTTP_HOST'])[0],
|
||||||
'secure' => UI::requestIsHttps(),
|
'secure' => UI::requestIsHttps(),
|
||||||
'httponly' => true,
|
'httponly' => true,
|
||||||
'samesite' => 'Strict'
|
'samesite' => 'Strict'
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ return [
|
|||||||
'docs' => [
|
'docs' => [
|
||||||
'label' => lng('admin.documentation'),
|
'label' => lng('admin.documentation'),
|
||||||
'icon' => 'fa-solid fa-circle-info',
|
'icon' => 'fa-solid fa-circle-info',
|
||||||
|
'show_element' => (!Settings::IsInList('panel.customer_hide_options', 'misc.documentation')),
|
||||||
'elements' => [
|
'elements' => [
|
||||||
[
|
[
|
||||||
'url' => 'https://docs.froxlor.org/v2/user-guide/',
|
'url' => 'https://docs.froxlor.org/v2/user-guide/',
|
||||||
|
|||||||
@@ -110,6 +110,11 @@ return [
|
|||||||
'class' => 'text-center',
|
'class' => 'text-center',
|
||||||
'callback' => [Text::class, 'boolean'],
|
'callback' => [Text::class, 'boolean'],
|
||||||
],
|
],
|
||||||
|
'c.lastlogin_succ' => [
|
||||||
|
'label' => lng('admin.lastlogin_succ'),
|
||||||
|
'field' => 'lastlogin_succ',
|
||||||
|
'callback' => [Text::class, 'timestamp'],
|
||||||
|
],
|
||||||
'c.phpenabled' => [
|
'c.phpenabled' => [
|
||||||
'label' => lng('admin.phpenabled'),
|
'label' => lng('admin.phpenabled'),
|
||||||
'field' => 'phpenabled',
|
'field' => 'phpenabled',
|
||||||
@@ -138,7 +143,7 @@ return [
|
|||||||
'class' => 'text-center',
|
'class' => 'text-center',
|
||||||
'callback' => [Text::class, 'boolean'],
|
'callback' => [Text::class, 'boolean'],
|
||||||
],
|
],
|
||||||
'api_allowed' => [
|
'c.api_allowed' => [
|
||||||
'label' => lng('usersettings.api_allowed.title'),
|
'label' => lng('usersettings.api_allowed.title'),
|
||||||
'field' => 'api_allowed',
|
'field' => 'api_allowed',
|
||||||
'class' => 'text-center',
|
'class' => 'text-center',
|
||||||
|
|||||||
@@ -773,6 +773,7 @@ return [
|
|||||||
'destinationiswrong' => 'Die Weiterleitungsadresse "%s" enthält ungültige Zeichen oder ist nicht vollständig.',
|
'destinationiswrong' => 'Die Weiterleitungsadresse "%s" enthält ungültige Zeichen oder ist nicht vollständig.',
|
||||||
'backupfoldercannotbedocroot' => 'Der Ordner für Backups darf nicht das Heimatverzeichnis sein, wählen Sie einen Ordner unterhalb des Heimatverzeichnisses, z.B. /backups',
|
'backupfoldercannotbedocroot' => 'Der Ordner für Backups darf nicht das Heimatverzeichnis sein, wählen Sie einen Ordner unterhalb des Heimatverzeichnisses, z.B. /backups',
|
||||||
'templatelanguagecombodefined' => 'Die gewählte Kombination aus Sprache und Vorlage ist bereits definiert.',
|
'templatelanguagecombodefined' => 'Die gewählte Kombination aus Sprache und Vorlage ist bereits definiert.',
|
||||||
|
'templatelanguageinvalid' => 'Die gewählte Sprache existiert nicht',
|
||||||
'ipstillhasdomains' => 'Die IP/Port-Kombination, die Sie löschen wollen, ist noch bei einer oder mehreren Domains eingetragen. Bitte ändern Sie die Domains vorher auf eine andere IP/Port-Kombination, um diese löschen zu können.',
|
'ipstillhasdomains' => 'Die IP/Port-Kombination, die Sie löschen wollen, ist noch bei einer oder mehreren Domains eingetragen. Bitte ändern Sie die Domains vorher auf eine andere IP/Port-Kombination, um diese löschen zu können.',
|
||||||
'cantdeletedefaultip' => 'Sie können die Standard-IP/Port-Kombination für Reseller nicht löschen. Bitte setzen Sie eine andere IP/Port-Kombination als Standard, um diese löschen zu können.',
|
'cantdeletedefaultip' => 'Sie können die Standard-IP/Port-Kombination für Reseller nicht löschen. Bitte setzen Sie eine andere IP/Port-Kombination als Standard, um diese löschen zu können.',
|
||||||
'cantdeletesystemip' => 'Sie können die letzte System-IP-Adresse nicht löschen. Entweder legen Sie eine neue IP/Port-Kombination an oder Sie ändern die System-IP-Adresse.',
|
'cantdeletesystemip' => 'Sie können die letzte System-IP-Adresse nicht löschen. Entweder legen Sie eine neue IP/Port-Kombination an oder Sie ändern die System-IP-Adresse.',
|
||||||
@@ -836,6 +837,7 @@ return [
|
|||||||
'notrequiredpasswordcomplexity' => 'Die vorgegebene Passwort-Komplexität wurde nicht erfüllt.<br />Bitte kontaktieren Sie Ihren Administrator, wenn Sie Fragen zur Komplexitäts-Vorgabe haben.',
|
'notrequiredpasswordcomplexity' => 'Die vorgegebene Passwort-Komplexität wurde nicht erfüllt.<br />Bitte kontaktieren Sie Ihren Administrator, wenn Sie Fragen zur Komplexitäts-Vorgabe haben.',
|
||||||
'stringerrordocumentnotvalidforlighty' => 'Ein Text als Fehlerdokument funktioniert leider in LigHTTPd nicht, bitte geben Sie einen Pfad zu einer Datei an',
|
'stringerrordocumentnotvalidforlighty' => 'Ein Text als Fehlerdokument funktioniert leider in LigHTTPd nicht, bitte geben Sie einen Pfad zu einer Datei an',
|
||||||
'urlerrordocumentnotvalidforlighty' => 'Eine URL als Fehlerdokument funktioniert leider in LigHTTPd nicht, bitte geben Sie einen Pfad zu einer Datei an',
|
'urlerrordocumentnotvalidforlighty' => 'Eine URL als Fehlerdokument funktioniert leider in LigHTTPd nicht, bitte geben Sie einen Pfad zu einer Datei an',
|
||||||
|
'invaliderrordocumentvalue' => 'Der angegebene Wert für das Fehlederdokument ist keine gültige Datei, URL oder Text-Zeile.',
|
||||||
'intvaluetoolow' => 'Die angegebene Zahl ist zu klein (Feld "%s")',
|
'intvaluetoolow' => 'Die angegebene Zahl ist zu klein (Feld "%s")',
|
||||||
'intvaluetoohigh' => 'Die angegebene Zahl ist zu groß (Feld "%s")',
|
'intvaluetoohigh' => 'Die angegebene Zahl ist zu groß (Feld "%s")',
|
||||||
'phpfpmstillenabled' => 'PHP-FPM ist derzeit aktiviert. Bitte deaktivieren Sie es, um FCGID zu aktivieren',
|
'phpfpmstillenabled' => 'PHP-FPM ist derzeit aktiviert. Bitte deaktivieren Sie es, um FCGID zu aktivieren',
|
||||||
@@ -919,6 +921,7 @@ return [
|
|||||||
'pathmustberelative' => 'Der Benutzer hat nicht die benötigten Berechtigungen, um Pfade außerhalb des Kunden-Heimatverzeichnisses anzugeben. Bitte einen relativen Pfad angeben (kein führendes /).',
|
'pathmustberelative' => 'Der Benutzer hat nicht die benötigten Berechtigungen, um Pfade außerhalb des Kunden-Heimatverzeichnisses anzugeben. Bitte einen relativen Pfad angeben (kein führendes /).',
|
||||||
'mysqlserverstillhasdbs' => 'Datenbank-Server kann für den Kunden nicht entfernt werden, da sich dort noch Datenbanken befinden.',
|
'mysqlserverstillhasdbs' => 'Datenbank-Server kann für den Kunden nicht entfernt werden, da sich dort noch Datenbanken befinden.',
|
||||||
'domaincannotbeedited' => 'Keine Berechtigung, um die Domain %s zu bearbeiten',
|
'domaincannotbeedited' => 'Keine Berechtigung, um die Domain %s zu bearbeiten',
|
||||||
|
'invalidcronjobintervalvalue' => 'Cronjob Intervall muss einer der folgenden Werte sein: %s',
|
||||||
],
|
],
|
||||||
'extras' => [
|
'extras' => [
|
||||||
'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.<br />Die Änderungen sind erst nach einer kurzen Zeit wirksam.',
|
'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.<br />Die Änderungen sind erst nach einer kurzen Zeit wirksam.',
|
||||||
@@ -1494,7 +1497,10 @@ Vielen Dank, Ihr Administrator',
|
|||||||
'title' => 'Log-Art(en)',
|
'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<br />Mögliche Logtypen sind: syslog, file, mysql',
|
'description' => 'Wählen Sie hier die gewünschten Logtypen. Für Mehrfachauswahl, halten Sie während der Auswahl STRG gedrückt<br />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',
|
'logcron' => 'Logge Cronjobs',
|
||||||
'logcronoption' => [
|
'logcronoption' => [
|
||||||
'never' => 'Nie',
|
'never' => 'Nie',
|
||||||
@@ -1962,6 +1968,10 @@ Vielen Dank, Ihr Administrator',
|
|||||||
'title' => 'Validiere DNS der Domains wenn Let\'s Encrypt genutzt wird',
|
'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.',
|
'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' => [
|
'phpsettingsforsubdomains' => [
|
||||||
'description' => 'Wenn ja, wird die gewählte PHP-Config für alle Subdomains übernommen',
|
'description' => 'Wenn ja, wird die gewählte PHP-Config für alle Subdomains übernommen',
|
||||||
],
|
],
|
||||||
@@ -2124,17 +2134,31 @@ Vielen Dank, Ihr Administrator',
|
|||||||
],
|
],
|
||||||
'mb' => 'Traffic',
|
'mb' => 'Traffic',
|
||||||
'day' => 'Tag',
|
'day' => 'Tag',
|
||||||
'distribution' => '<span color="#019522">FTP</span> | <span color="#0000FF">HTTP</span> | <span color="#800000">Mail</span>',
|
'sumtotal' => 'Gesamt Traffic',
|
||||||
'sumhttp' => 'Gesamt HTTP-Traffic',
|
'sumhttp' => 'HTTP-Traffic',
|
||||||
'sumftp' => 'Gesamt FTP-Traffic',
|
'sumftp' => 'FTP-Traffic',
|
||||||
'summail' => 'Gesamt Mail-Traffic',
|
'summail' => 'Mail-Traffic',
|
||||||
'customer' => 'Kunde',
|
'customer' => 'Kunde',
|
||||||
'trafficoverview' => 'Übersicht Traffic je',
|
'trafficoverview' => 'Übersicht Traffic',
|
||||||
|
'bycustomers' => 'Traffic nach Kunden',
|
||||||
'details' => 'Details',
|
'details' => 'Details',
|
||||||
'http' => 'HTTP',
|
'http' => 'HTTP',
|
||||||
'ftp' => 'FTP',
|
'ftp' => 'FTP',
|
||||||
'mail' => 'Mail',
|
'mail' => 'Mail',
|
||||||
'nocustomers' => 'Es wird mindestens ein Kunde benötigt um die Traffic Statistiken anzuzeigen.',
|
'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' => '',
|
'translator' => '',
|
||||||
'update' => [
|
'update' => [
|
||||||
|
|||||||
@@ -839,6 +839,7 @@ return [
|
|||||||
'destinationiswrong' => 'The forwarder %s contains invalid character(s) or is incomplete.',
|
'destinationiswrong' => 'The forwarder %s contains invalid character(s) or is incomplete.',
|
||||||
'backupfoldercannotbedocroot' => 'The folder for backups cannot be your homedir, please chose a folder within your homedir, e.g. /backups',
|
'backupfoldercannotbedocroot' => 'The folder for backups cannot be your homedir, please chose a folder within your homedir, e.g. /backups',
|
||||||
'templatelanguagecombodefined' => 'The selected language/template combination has already been defined.',
|
'templatelanguagecombodefined' => 'The selected language/template combination has already been defined.',
|
||||||
|
'templatelanguageinvalid' => 'The selected language does not exist',
|
||||||
'ipstillhasdomains' => 'The IP/Port combination you want to delete still has domains assigned to it, please reassign those to other IP/Port combinations before deleting this IP/Port combination.',
|
'ipstillhasdomains' => 'The IP/Port combination you want to delete still has domains assigned to it, please reassign those to other IP/Port combinations before deleting this IP/Port combination.',
|
||||||
'cantdeletedefaultip' => 'You cannot delete the default IP/Port combination, please make another IP/Port combination default for before deleting this IP/Port combination.',
|
'cantdeletedefaultip' => 'You cannot delete the default IP/Port combination, please make another IP/Port combination default for before deleting this IP/Port combination.',
|
||||||
'cantdeletesystemip' => 'You cannot delete the last system IP, either create a new IP/Port combination for the system IP or change the system IP.',
|
'cantdeletesystemip' => 'You cannot delete the last system IP, either create a new IP/Port combination for the system IP or change the system IP.',
|
||||||
@@ -904,6 +905,7 @@ return [
|
|||||||
'notrequiredpasswordcomplexity' => 'The specified password-complexity was not satisfied.<br />Please contact your administrator if you have any questions about the complexity-specification',
|
'notrequiredpasswordcomplexity' => 'The specified password-complexity was not satisfied.<br />Please contact your administrator if you have any questions about the complexity-specification',
|
||||||
'stringerrordocumentnotvalidforlighty' => 'A string as ErrorDocument does not work in lighttpd, please specify a path to a file',
|
'stringerrordocumentnotvalidforlighty' => 'A string as ErrorDocument does not work in lighttpd, please specify a path to a file',
|
||||||
'urlerrordocumentnotvalidforlighty' => 'An URL as ErrorDocument does not work in lighttpd, please specify a path to a file',
|
'urlerrordocumentnotvalidforlighty' => 'An URL as ErrorDocument does not work in lighttpd, please specify a path to a file',
|
||||||
|
'invaliderrordocumentvalue' => 'The value given as ErrorDocument does not seem to be a valid file, URL or string.',
|
||||||
'intvaluetoolow' => 'The given number is too low (field %s)',
|
'intvaluetoolow' => 'The given number is too low (field %s)',
|
||||||
'intvaluetoohigh' => 'The given number is too high (field %s)',
|
'intvaluetoohigh' => 'The given number is too high (field %s)',
|
||||||
'phpfpmstillenabled' => 'PHP-FPM is currently active. Please deactivate it before activating FCGID',
|
'phpfpmstillenabled' => 'PHP-FPM is currently active. Please deactivate it before activating FCGID',
|
||||||
@@ -988,6 +990,7 @@ return [
|
|||||||
'pathmustberelative' => 'The user does not have the permission to specify directories outside the customers home-directory. Please specify a relative path (no leading /).',
|
'pathmustberelative' => 'The user does not have the permission to specify directories outside the customers home-directory. Please specify a relative path (no leading /).',
|
||||||
'mysqlserverstillhasdbs' => 'Cannot remove database server from customers allow-list as there are still databases on it.',
|
'mysqlserverstillhasdbs' => 'Cannot remove database server from customers allow-list as there are still databases on it.',
|
||||||
'domaincannotbeedited' => 'You are not permitted to edit the domain %s',
|
'domaincannotbeedited' => 'You are not permitted to edit the domain %s',
|
||||||
|
'invalidcronjobintervalvalue' => 'Cronjob interval must be one of: %s',
|
||||||
],
|
],
|
||||||
'extras' => [
|
'extras' => [
|
||||||
'description' => 'Here you can add some extras, for example directory protection.<br />The system will need some time to apply the new settings after every change.',
|
'description' => 'Here you can add some extras, for example directory protection.<br />The system will need some time to apply the new settings after every change.',
|
||||||
@@ -1613,7 +1616,10 @@ Yours sincerely, your administrator',
|
|||||||
'title' => 'Log-type(s)',
|
'title' => 'Log-type(s)',
|
||||||
'description' => 'Specify logtypes. To select multiple types, hold down CTRL while selecting.<br />Available logtypes are: syslog, file, mysql',
|
'description' => 'Specify logtypes. To select multiple types, hold down CTRL while selecting.<br />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',
|
'logcron' => 'Log cronjobs',
|
||||||
'logcronoption' => [
|
'logcronoption' => [
|
||||||
'never' => 'Never',
|
'never' => 'Never',
|
||||||
@@ -2081,6 +2087,10 @@ Yours sincerely, your administrator',
|
|||||||
'title' => 'Validate DNS of domains when using Let\'s Encrypt',
|
'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.',
|
'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' => [
|
'phpsettingsforsubdomains' => [
|
||||||
'description' => 'If yes the chosen php-config will be updated to all subdomains',
|
'description' => 'If yes the chosen php-config will be updated to all subdomains',
|
||||||
],
|
],
|
||||||
@@ -2254,18 +2264,32 @@ Yours sincerely, your administrator',
|
|||||||
'total' => 'Total',
|
'total' => 'Total',
|
||||||
],
|
],
|
||||||
'mb' => 'Traffic',
|
'mb' => 'Traffic',
|
||||||
'distribution' => '<font color="#019522">FTP</font> | <font color="#0000FF">HTTP</font> | <font color="#800000">Mail</font>',
|
'sumtotal' => 'Total traffic',
|
||||||
'sumhttp' => 'Total HTTP-Traffic',
|
'sumhttp' => 'HTTP traffic',
|
||||||
'sumftp' => 'Total FTP-Traffic',
|
'sumftp' => 'FTP traffic',
|
||||||
'summail' => 'Total Mail-Traffic',
|
'summail' => 'Mail traffic',
|
||||||
'customer' => 'Customer',
|
'customer' => 'Customer',
|
||||||
'domain' => 'Domain',
|
'domain' => 'Domain',
|
||||||
'trafficoverview' => 'Traffic summary by',
|
'trafficoverview' => 'Traffic summary',
|
||||||
|
'bycustomers' => 'Traffic by customers',
|
||||||
'details' => 'Details',
|
'details' => 'Details',
|
||||||
'http' => 'HTTP',
|
'http' => 'HTTP',
|
||||||
'ftp' => 'FTP',
|
'ftp' => 'FTP',
|
||||||
'mail' => 'Mail',
|
'mail' => 'Mail',
|
||||||
'nocustomers' => 'You need at least one customer to view the traffic reports.',
|
'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' => '',
|
'translator' => '',
|
||||||
'update' => [
|
'update' => [
|
||||||
|
|||||||
36
package-lock.json
generated
36
package-lock.json
generated
@@ -5103,9 +5103,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/img-loader/node_modules/json5": {
|
"node_modules/img-loader/node_modules/json5": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
@@ -5437,9 +5437,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/json5": {
|
"node_modules/json5": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
@@ -8448,9 +8448,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue-style-loader/node_modules/json5": {
|
"node_modules/vue-style-loader/node_modules/json5": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
@@ -12919,9 +12919,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
@@ -13165,9 +13165,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"jsonfile": {
|
"jsonfile": {
|
||||||
@@ -15379,9 +15379,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<!-- CSS -->
|
<!-- CSS -->
|
||||||
{% if theme_css is empty %}
|
{% if theme_css is empty %}
|
||||||
<link href="{{ basehref|default('') }}templates/Froxlor/assets/css/main.css" rel="stylesheet" type="text/css" />
|
<link href="{{ basehref|default('') }}{{ mix('templates/Froxlor/assets/css/main.css') }}" rel="stylesheet" type="text/css" />
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ theme_css|raw }}
|
{{ theme_css|raw }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
{% if theme_js is empty %}
|
{% if theme_js is empty %}
|
||||||
<script type="text/javascript" src="{{ basehref|default('') }}templates/Froxlor/assets/js/main.js"></script>
|
<script type="text/javascript" src="{{ basehref|default('') }}{{ mix('templates/Froxlor/assets/js/main.js') }}"></script>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ theme_js|raw }}
|
{{ theme_js|raw }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{# add translation for custom validations #}
|
{# 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'] %}
|
||||||
<script>$(function() { $.extend($.validator.messages, {required: "{{ lng('error.requiredfield') }}"}) });</script>
|
<script>$(function() { $.extend($.validator.messages, {required: "{{ lng('error.requiredfield') }}"}) });</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
{% if field.visible is not defined or (field.visible is defined and field.visible) or nohide == true %}
|
{% 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)) %}
|
{% if norow == false and (field.type != 'hidden' or (field.type == 'hidden' and field.display is defined and field.display is not empty)) %}
|
||||||
<div class="row g-0 formfield d-flex align-items-center">
|
<div class="row g-0 formfield d-flex align-items-center">
|
||||||
|
{% if field.prior_infotext is defined and field.prior_infotext|length > 0 %}
|
||||||
|
<h5>{{ field.prior_infotext }}</h5>
|
||||||
|
{% endif %}
|
||||||
{% if field.label is iterable %}
|
{% if field.label is iterable %}
|
||||||
<label for="{{ id }}" class="col-sm-6 col-form-label pe-3">
|
<label for="{{ id }}" class="col-sm-6 col-form-label pe-3">
|
||||||
{% if em %}
|
{% if em %}
|
||||||
@@ -51,6 +54,8 @@
|
|||||||
{{ _self.input_ul(id, field) }}
|
{{ _self.input_ul(id, field) }}
|
||||||
{% elseif field.type == 'checkbox' %}
|
{% elseif field.type == 'checkbox' %}
|
||||||
{{ _self.bool(id, field) }}
|
{{ _self.bool(id, field) }}
|
||||||
|
{% elseif field.type == 'checkrequired' %}
|
||||||
|
{{ _self.chk_required(id, field) }}
|
||||||
{% elseif field.type == 'select' %}
|
{% elseif field.type == 'select' %}
|
||||||
{{ _self.select(id, field) }}
|
{{ _self.select(id, field) }}
|
||||||
{% elseif field.type == 'textarea' %}
|
{% elseif field.type == 'textarea' %}
|
||||||
@@ -116,6 +121,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro chk_required(id, field) %}
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input type="checkbox" value="{{ field.value }}" id="{{ id }}" name="{{ id }}" class="form-check-input" {% if field.mandatory is defined and field.mandatory == 1 %} required {% endif %} />
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro infotext(id, field) %}
|
{% macro infotext(id, field) %}
|
||||||
{% if field.next_to is defined %}
|
{% if field.next_to is defined %}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -148,7 +159,7 @@
|
|||||||
{% if field.next_to is defined %}
|
{% if field.next_to is defined %}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="{{ field.type }}" {% if field.visible is defined and field.visible == false %} disabled {% endif %} {% if field.type == 'number' and field.min is defined %} min="{{ field.min }}" {% endif %} {% if field.type == 'number' and field.max is defined %} max="{{ field.max }}" {% endif %} {% if field.type != 'number' and field.maxlength is defined %} maxlength="{{ field.maxlength }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.type == 'file' and field.accept is defined %} accept="{{ field.accept }}" {% endif %}/>
|
<input type="{{ field.type }}" {% if field.visible is defined and field.visible == false %} disabled {% endif %} {% if field.type == 'number' and field.min is defined %} min="{{ field.min }}" {% endif %} {% if field.type == 'number' and field.max is defined %} max="{{ field.max }}" {% endif %} {% if field.type != 'number' and field.maxlength is defined %} maxlength="{{ field.maxlength }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.type == 'file' and field.accept is defined %} accept="{{ field.accept }}" {% endif %} {% if field.pattern is defined %} pattern="{{ field.pattern }}" {% endif %}/>
|
||||||
{% if field.type == 'hidden' and field.display is defined %}
|
{% if field.type == 'hidden' and field.display is defined %}
|
||||||
<input type="text" readonly class="form-control-plaintext" value="{{ field.display|raw }}">
|
<input type="text" readonly class="form-control-plaintext" value="{{ field.display|raw }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -165,7 +176,7 @@
|
|||||||
|
|
||||||
{% macro image(id, field) %}
|
{% macro image(id, field) %}
|
||||||
{% if field.value is not empty %}
|
{% if field.value is not empty %}
|
||||||
<img src="/{{ field.value }}" alt="Current Image" class="field-image-preview"><br>
|
<img src="{{ field.value }}" alt="Current Image" class="field-image-preview"><br>
|
||||||
<div class="form-check form-switch mb-2">
|
<div class="form-check form-switch mb-2">
|
||||||
<input type="checkbox" value="1" name="{{ id }}_delete" class="form-check-input">
|
<input type="checkbox" value="1" name="{{ id }}_delete" class="form-check-input">
|
||||||
<label class="form-check-label">
|
<label class="form-check-label">
|
||||||
@@ -197,7 +208,7 @@
|
|||||||
{% if field.next_to is defined %}
|
{% if field.next_to is defined %}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<select {% if field.visible is defined and field.visible == false %} disabled {% endif %} class="form-select {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" name="{{ id }}{% if field.select_mode is defined and field.select_mode == 'multiple' %}[]{% endif %}" id="{{ id }}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.select_mode is defined and field.select_mode == 'multiple' %} multiple="multiple" {% endif %}>
|
<select {% if field.visible is defined and field.visible == false %} disabled {% endif %} class="form-select {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" name="{{ id }}{% if field.select_mode is defined and field.select_mode == 'multiple' %}[]{% endif %}" id="{{ id }}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.select_mode is defined and field.select_mode == 'multiple' %} multiple="multiple" {% endif %}{% if field.readonly is defined and field.readonly %} readonly {% endif %}>
|
||||||
{% for val,txt in field.select_var %}
|
{% for val,txt in field.select_var %}
|
||||||
<option value="{{ val }}" {% if field.selected is defined and ((field.selected is not iterable and field.selected == val) or (field.selected is iterable and val in field.selected|keys)) %} selected="selected" {% endif %}>{{ txt|raw }}</option>
|
<option value="{{ val }}" {% if field.selected is defined and ((field.selected is not iterable and field.selected == val) or (field.selected is iterable and val in field.selected|keys)) %} selected="selected" {% endif %}>{{ txt|raw }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -112,7 +112,6 @@
|
|||||||
{{ lng('admin.configfiles.recommendednote') }}
|
{{ lng('admin.configfiles.recommendednote') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6 text-end">
|
<div class="col-12 col-md-6 text-end">
|
||||||
<input type="hidden" name="dist" value="{{ distribution }}"/>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" id="selectRecommendedConfig">{{ lng('admin.configfiles.selectrecommended') }}</button>
|
<button type="button" class="btn btn-outline-secondary" id="selectRecommendedConfig">{{ lng('admin.configfiles.selectrecommended') }}</button>
|
||||||
<button type="button" class="btn btn-outline-secondary" id="downloadSelectionAsJson">
|
<button type="button" class="btn btn-outline-secondary" id="downloadSelectionAsJson">
|
||||||
<i class="fa-solid fa-download"></i>
|
<i class="fa-solid fa-download"></i>
|
||||||
|
|||||||
@@ -21,4 +21,17 @@ $(document).ready(function() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
$('#domain_add,#domain_edit').each(function () {
|
||||||
|
$(this).validate({
|
||||||
|
rules: {
|
||||||
|
'ipandport[]': {
|
||||||
|
required: true,
|
||||||
|
minlength: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
errorPlacement: function(error, element) {
|
||||||
|
$(error).prependTo($(element).parent().parent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
@import "~@fortawesome/fontawesome-free/css/all";
|
@import "~@fortawesome/fontawesome-free/css/all";
|
||||||
|
|
||||||
// Generic
|
// Generic
|
||||||
|
.header-logo {
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.form-control-plaintext {
|
.form-control-plaintext {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% macro ditem(lngstr, available, used, assigned = null, formatbytes = false) %}
|
{% macro ditem(lngstr, available, used, assigned = null, formatbytes = false, byte_usage = null) %}
|
||||||
<div class="col border-end border-bottom p-3">
|
<div class="col border-end border-bottom p-3">
|
||||||
<div class="row mb-1">
|
<div class="row mb-1">
|
||||||
<div class="col text-truncate">{{ lng(lngstr) }}</div>
|
<div class="col text-truncate">{{ lng(lngstr) }}{% if byte_usage %} <small>({{ byte_usage|formatBytes }})</small>{% endif %}</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<small>{% if formatbytes %}{{ used|formatBytes }}{% else %}{{ used }}{% endif %}/{% if available < 0 %}{{ lng('panel.unlimited') }}{% else %}{% if formatbytes %}{{ available|formatBytes }}{% else %}{{ available }}{% endif %}{% endif %}</small>
|
<small>{% if formatbytes %}{{ used|formatBytes }}{% else %}{{ used }}{% endif %}/{% if available < 0 %}{{ lng('panel.unlimited') }}{% else %}{% if formatbytes %}{{ available|formatBytes }}{% else %}{{ available }}{% endif %}{% endif %}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,12 +33,13 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{# customer-resources #}
|
{# customer-resources #}
|
||||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-xl-4 g-0">
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-xl-4 g-0">
|
||||||
|
{{ dashboard.ditem('customer.total_diskspace', userinfo.diskspace_bytes, userinfo.total_bytes_used, null, true) }}
|
||||||
{{ dashboard.ditem('customer.diskspace', userinfo.diskspace_bytes, userinfo.diskspace_bytes_used, null, true) }}
|
{{ dashboard.ditem('customer.diskspace', userinfo.diskspace_bytes, userinfo.diskspace_bytes_used, null, true) }}
|
||||||
{{ dashboard.ditem('customer.traffic', userinfo.traffic_bytes, userinfo.traffic_bytes_used, null, true) }}
|
{{ dashboard.ditem('customer.traffic', userinfo.traffic_bytes, userinfo.traffic_bytes_used, null, true) }}
|
||||||
{{ dashboard.ditem('customer.subdomains', userinfo.subdomains, userinfo.subdomains_used) }}
|
{{ dashboard.ditem('customer.subdomains', userinfo.subdomains, userinfo.subdomains_used) }}
|
||||||
{{ dashboard.ditem('customer.mysqls', userinfo.mysqls, userinfo.mysqls_used) }}
|
{{ dashboard.ditem('customer.mysqls', userinfo.mysqls, userinfo.mysqls_used, null, false, userinfo.dbspace_used) }}
|
||||||
{{ dashboard.ditem('customer.emails', userinfo.emails, userinfo.emails_used) }}
|
{{ dashboard.ditem('customer.emails', userinfo.emails, userinfo.emails_used) }}
|
||||||
{{ dashboard.ditem('customer.accounts', userinfo.email_accounts, userinfo.email_accounts_used) }}
|
{{ dashboard.ditem('customer.accounts', userinfo.email_accounts, userinfo.email_accounts_used, null, false, userinfo.mailspace_used) }}
|
||||||
{{ dashboard.ditem('customer.forwarders', userinfo.email_forwarders, userinfo.email_forwarders_used) }}
|
{{ dashboard.ditem('customer.forwarders', userinfo.email_forwarders, userinfo.email_forwarders_used) }}
|
||||||
{{ dashboard.ditem('customer.ftps', userinfo.ftps, userinfo.ftps_used) }}
|
{{ dashboard.ditem('customer.ftps', userinfo.ftps, userinfo.ftps_used) }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,14 +18,14 @@
|
|||||||
<!-- TODO: set url on change. e.g.: ?param=days:7 -->
|
<!-- TODO: set url on change. e.g.: ?param=days:7 -->
|
||||||
<div class="d-flex justify-content-center justify-content-md-end">
|
<div class="d-flex justify-content-center justify-content-md-end">
|
||||||
<select class="form-select mb-3 mb-md-4 w-auto mt-md-n4" aria-label="select the traffic range" name="range" data-baseref="{{ linker({'section':'traffic'}) }}">
|
<select class="form-select mb-3 mb-md-4 w-auto mt-md-n4" aria-label="select the traffic range" name="range" data-baseref="{{ linker({'section':'traffic'}) }}">
|
||||||
<option value="hours:24" {% if range == 'hours:24' %}selected{% endif %}>last 24 hours</option>
|
<option value="hours:24" {% if range == 'hours:24' %}selected{% endif %}>{{ lng('traffic.ranges.last24h') }}</option>
|
||||||
<option value="days:7" {% if range == 'days:7' %}selected{% endif %}>last 7 days</option>
|
<option value="days:7" {% if range == 'days:7' %}selected{% endif %}>{{ lng('traffic.ranges.last7d') }}</option>
|
||||||
<option value="days:30" {% if range == 'days:30' %}selected{% endif %}>last 30 days</option>
|
<option value="days:30" {% if range == 'days:30' %}selected{% endif %}>{{ lng('traffic.ranges.last30d') }}</option>
|
||||||
<option value="currentmonth" {% if range == 'currentmonth' %}selected{% endif %}>current month</option>
|
<option value="currentmonth" {% if range == 'currentmonth' %}selected{% endif %}>{{ lng('traffic.ranges.cm') }}</option>
|
||||||
<option value="months:3" {% if range == 'months:3' %}selected{% endif %}>last 3 months</option>
|
<option value="months:3" {% if range == 'months:3' %}selected{% endif %}>{{ lng('traffic.ranges.last3m') }}</option>
|
||||||
<option value="months:6" {% if range == 'months:6' %}selected{% endif %}>last 6 months</option>
|
<option value="months:6" {% if range == 'months:6' %}selected{% endif %}>{{ lng('traffic.ranges.last6m') }}</option>
|
||||||
<option value="months:12" {% if range == 'months:12' %}selected{% endif %}>last 12 months</option>
|
<option value="months:12" {% if range == 'months:12' %}selected{% endif %}>{{ lng('traffic.ranges.last12m') }}</option>
|
||||||
<option value="currentyear" {% if range == 'currentyear' %}selected{% endif %}>current year</option>
|
<option value="currentyear" {% if range == 'currentyear' %}selected{% endif %}>{{ lng('traffic.ranges.cy') }}</option>
|
||||||
{% for yd in years_avail %}
|
{% for yd in years_avail %}
|
||||||
{% if yd.year != "now"|date('Y') %}
|
{% if yd.year != "now"|date('Y') %}
|
||||||
<option value="year:{{ yd.year }}" {% if range == 'year:' ~ yd.year %}selected{% endif %}>{{ yd.year }}</option>
|
<option value="year:{{ yd.year }}" {% if range == 'year:' ~ yd.year %}selected{% endif %}>{{ yd.year }}</option>
|
||||||
@@ -50,37 +50,36 @@
|
|||||||
<div class="row row-cols-2 row-cols-md-4 g-0">
|
<div class="row row-cols-2 row-cols-md-4 g-0">
|
||||||
<div class="col p-3 border-end">
|
<div class="col p-3 border-end">
|
||||||
<h3>{{ metrics.total|formatBytes }}</h3>
|
<h3>{{ metrics.total|formatBytes }}</h3>
|
||||||
<span>Total</span>
|
<span>{{ lng('traffic.months.total') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col p-3 border-end">
|
<div class="col p-3 border-end">
|
||||||
<h3>{{ metrics.http|formatBytes }}</h3>
|
<h3>{{ metrics.http|formatBytes }}</h3>
|
||||||
<span>HTTP</span>
|
<span>{{ lng('traffic.http') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col p-3 border-end">
|
<div class="col p-3 border-end">
|
||||||
<h3>{{ metrics.ftp|formatBytes }}</h3>
|
<h3>{{ metrics.ftp|formatBytes }}</h3>
|
||||||
<span>FTP</span>
|
<span>{{ lng('traffic.ftp') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col p-3 border-end">
|
<div class="col p-3 border-end">
|
||||||
<h3>{{ metrics.mail|formatBytes }}</h3>
|
<h3>{{ metrics.mail|formatBytes }}</h3>
|
||||||
<span>Mail</span>
|
<span>{{ lng('traffic.mail') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if userinfo.adminsession == 1 %}
|
{% if userinfo.adminsession == 1 %}
|
||||||
<!-- Overview for given range by user -->
|
<!-- Overview for given range by user -->
|
||||||
<h4 class="page-header">Traffic by customers</h4>
|
<h4 class="page-header">{{ lng('traffic.bycustomers') }}</h4>
|
||||||
{% if users is not empty %}
|
{% if users is not empty %}
|
||||||
<div class="card table-responsive">
|
<div class="card table-responsive">
|
||||||
<table class="table table-borderless table-striped align-middle mb-0 px-3">
|
<table class="table table-borderless table-striped align-middle mb-0 px-3">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{{ lng('login.username') }}</th>
|
<th scope="col">{{ lng('login.username') }}</th>
|
||||||
<th scope="col">Total</th>
|
<th scope="col">{{ lng('traffic.months.total') }}</th>
|
||||||
<th scope="col">HTTP</th>
|
<th scope="col">{{ lng('traffic.http') }}</th>
|
||||||
<th scope="col">FTP</th>
|
<th scope="col">{{ lng('traffic.ftp') }}</th>
|
||||||
<th scope="col">Mail
|
<th scope="col">{{ lng('traffic.mail') }}</th>
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -101,19 +100,19 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>No data for given range found.</p>
|
<p>{{ lng('traffic.nodata') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const labelsS = ['HTTP', 'FTP', 'Mail'];
|
const labelsS = ['{{ lng('traffic.http') }}', '{{ lng('traffic.ftp') }}', '{{ lng('traffic.mail') }}'];
|
||||||
|
|
||||||
const dataS = {
|
const dataS = {
|
||||||
labels: labelsS,
|
labels: labelsS,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Traffic summary',
|
label: '{{ lng('traffic.trafficoverview') }}',
|
||||||
backgroundColor: ['rgb(255, 99, 132)', 'rgb(200, 199, 132)', 'rgb(255, 99, 0)'],
|
backgroundColor: ['rgb(255, 99, 132)', 'rgb(200, 199, 132)', 'rgb(255, 99, 0)'],
|
||||||
data: [{value: '{{ metrics.http|default(0) }}', formatted: '{{ metrics.http|formatBytes }}'}, {value: '{{ metrics.ftp|default(0) }}', formatted: '{{ metrics.ftp|formatBytes }}'}, {value: '{{ metrics.mail|default(0) }}', formatted: '{{ metrics.mail|formatBytes }}'}]
|
data: [{value: '{{ metrics.http|default(0) }}', formatted: '{{ metrics.http|formatBytes }}'}, {value: '{{ metrics.ftp|default(0) }}', formatted: '{{ metrics.ftp|formatBytes }}'}, {value: '{{ metrics.mail|default(0) }}', formatted: '{{ metrics.mail|formatBytes }}'}]
|
||||||
}]
|
}]
|
||||||
@@ -130,7 +129,7 @@
|
|||||||
plugins: {
|
plugins: {
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: 'Total traffic'
|
text: '{{ lng('traffic.sumtotal') }}'
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
position: 'right'
|
position: 'right'
|
||||||
@@ -161,7 +160,7 @@
|
|||||||
const dataC = {
|
const dataC = {
|
||||||
labels: labelsC,
|
labels: labelsC,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Top 5 customers',
|
label: '{{ lng('traffic.top5customers') }}',
|
||||||
backgroundColor: ['rgb(255, 99, 132)', 'rgb(200, 199, 132)', 'rgb(255, 99, 0)', 'rgb(100, 100, 132)', 'rgb(240, 150, 232)'],
|
backgroundColor: ['rgb(255, 99, 132)', 'rgb(200, 199, 132)', 'rgb(255, 99, 0)', 'rgb(100, 100, 132)', 'rgb(240, 150, 232)'],
|
||||||
data: dataValues
|
data: dataValues
|
||||||
}]
|
}]
|
||||||
@@ -178,7 +177,7 @@
|
|||||||
plugins: {
|
plugins: {
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: 'Top 5 customers'
|
text: '{{ lng('traffic.top5customers') }}'
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
position: 'right'
|
position: 'right'
|
||||||
@@ -220,7 +219,7 @@
|
|||||||
labels: labelsC,
|
labels: labelsC,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'HTTP traffic',
|
label: '{{ lng('traffic.sumhttp') }}',
|
||||||
backgroundColor: 'rgb(255, 99, 132)',
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
{% if range starts with 'days' or range == 'currentmonth' %}
|
{% if range starts with 'days' or range == 'currentmonth' %}
|
||||||
data: [{% for d,dd in days %}{value: '{{ dd.http|default(0) }}', formatted: '{{ dd.http|formatBytes }}', axisv: '{{ d }}'},{% endfor %}],
|
data: [{% for d,dd in days %}{value: '{{ dd.http|default(0) }}', formatted: '{{ dd.http|formatBytes }}', axisv: '{{ d }}'},{% endfor %}],
|
||||||
@@ -234,7 +233,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'FTP traffic',
|
label: '{{ lng('traffic.sumftp') }}',
|
||||||
backgroundColor: 'rgb(200, 199, 132)',
|
backgroundColor: 'rgb(200, 199, 132)',
|
||||||
{% if range starts with 'days' or range == 'currentmonth' %}
|
{% if range starts with 'days' or range == 'currentmonth' %}
|
||||||
data: [{% for d,dd in days %}{value: '{{ dd.ftp|default(0) }}', formatted: '{{ dd.ftp|formatBytes }}', axisv: '{{ d }}'},{% endfor %}],
|
data: [{% for d,dd in days %}{value: '{{ dd.ftp|default(0) }}', formatted: '{{ dd.ftp|formatBytes }}', axisv: '{{ d }}'},{% endfor %}],
|
||||||
@@ -248,7 +247,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Mail traffic',
|
label: '{{ lng('traffic.summail') }}',
|
||||||
backgroundColor: 'rgb(255, 99, 0)',
|
backgroundColor: 'rgb(255, 99, 0)',
|
||||||
{% if range starts with 'days' or range == 'currentmonth' %}
|
{% if range starts with 'days' or range == 'currentmonth' %}
|
||||||
data: [{% for d,dd in days %}{value: '{{ dd.mail|default(0) }}', formatted: '{{ dd.mail|formatBytes }}', axisv: '{{ d }}'},{% endfor %}],
|
data: [{% for d,dd in days %}{value: '{{ dd.mail|default(0) }}', formatted: '{{ dd.mail|formatBytes }}', axisv: '{{ d }}'},{% endfor %}],
|
||||||
@@ -283,7 +282,7 @@
|
|||||||
plugins: {
|
plugins: {
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: 'Specified by range'
|
text: '{{ lng('traffic.byrange') }}'
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<a class="navbar-brand me-0 {% if block('heading') %}shadow-sm{% endif %}" href="{{ linker({'section': 'index'}) }}">
|
<a class="navbar-brand me-0 {% if block('heading') %}shadow-sm{% endif %}" href="{{ linker({'section': 'index'}) }}">
|
||||||
<img src="{{ header_logo }}" alt="" width="auto" height="24" class="d-inline-block align-text-top ms-md-3">
|
<img src="{{ header_logo }}" alt="logo" class="header-logo d-inline-block align-text-top ms-md-3">
|
||||||
</a>
|
</a>
|
||||||
<div class="order-0 order-md-1 d-flex flex-grow-0 flex-md-grow-1" id="navbar">
|
<div class="order-0 order-md-1 d-flex flex-grow-0 flex-md-grow-1" id="navbar">
|
||||||
<ul class="navbar-nav ms-md-auto me-3 me-lg-5">
|
<ul class="navbar-nav ms-md-auto me-3 me-lg-5">
|
||||||
|
|||||||
@@ -191,4 +191,49 @@ class DirOptionsTest extends TestCase
|
|||||||
$this->expectExceptionMessage("Directory option with id #1 could not be found");
|
$this->expectExceptionMessage("Directory option with id #1 could not be found");
|
||||||
DirOptions::getLocal($admin_userdata, $data)->get();
|
DirOptions::getLocal($admin_userdata, $data)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCustomerDirOptionsAddMalformed()
|
||||||
|
{
|
||||||
|
global $admin_userdata;
|
||||||
|
|
||||||
|
// get customer
|
||||||
|
$json_result = Customers::getLocal($admin_userdata, array(
|
||||||
|
'loginname' => 'test1'
|
||||||
|
))->get();
|
||||||
|
$customer_userdata = json_decode($json_result, true)['data'];
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'path' => '/testmalformed',
|
||||||
|
'error404path' => '/"'.PHP_EOL.'something/../../../../weird 404.html'.PHP_EOL.'#'
|
||||||
|
];
|
||||||
|
$json_result = DirOptions::getLocal($customer_userdata, $data)->add();
|
||||||
|
$result = json_decode($json_result, true)['data'];
|
||||||
|
$expected = '/"something/././././weird\ 404.html#';
|
||||||
|
$this->assertEquals($expected, $result['error404path']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustomerDirOptionsAddMalformedInvalid()
|
||||||
|
{
|
||||||
|
global $admin_userdata;
|
||||||
|
|
||||||
|
// get customer
|
||||||
|
$json_result = Customers::getLocal($admin_userdata, array(
|
||||||
|
'loginname' => 'test1'
|
||||||
|
))->get();
|
||||||
|
$customer_userdata = json_decode($json_result, true)['data'];
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'path' => '/testmalformed',
|
||||||
|
'error404path' => '"'.PHP_EOL.'IncludeOptional /something/else/'.PHP_EOL.'#'
|
||||||
|
];
|
||||||
|
$this->expectExceptionMessage("The value given as ErrorDocument does not seem to be a valid file, URL or string.");
|
||||||
|
DirOptions::getLocal($customer_userdata, $data)->add();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'path' => '/testmalformed',
|
||||||
|
'error404path' => '"something"oh no a quote within the string"'
|
||||||
|
];
|
||||||
|
$this->expectExceptionMessage("The value given as ErrorDocument does not seem to be a valid file, URL or string.");
|
||||||
|
DirOptions::getLocal($customer_userdata, $data)->add();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ mix
|
|||||||
.copyDirectory('node_modules/@fortawesome/fontawesome-free/webfonts', 'templates/Froxlor/assets/webfonts')
|
.copyDirectory('node_modules/@fortawesome/fontawesome-free/webfonts', 'templates/Froxlor/assets/webfonts')
|
||||||
.js('templates/Froxlor/src/js/main.js', 'js')
|
.js('templates/Froxlor/src/js/main.js', 'js')
|
||||||
.sass('templates/Froxlor/src/scss/main.scss', 'css')
|
.sass('templates/Froxlor/src/scss/main.scss', 'css')
|
||||||
.sass('templates/Froxlor/src/scss/dark.scss', 'css');
|
.sass('templates/Froxlor/src/scss/dark.scss', 'css')
|
||||||
|
.version();
|
||||||
|
|||||||
Reference in New Issue
Block a user