Compare commits

...

26 Commits

Author SHA1 Message Date
Michael Kaufmann
b542b140c6 set version to 2.1.3 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-22 14:33:11 +01:00
Michael Kaufmann
ac89fc7120 adjust order of css files
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-22 14:30:23 +01:00
Michael Kaufmann
150858485d include custom.css from config.json if preset correctly
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-21 10:59:15 +01:00
Michael Kaufmann
e7810e2066 correctly merge fielddetails with prefetched-formfielddata in form-processing
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-20 09:39:01 +01:00
Michael Kaufmann
4879446567 domains in php-configs are not sortable
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-19 17:17:51 +01:00
Michael Kaufmann
43eff78088 use panel.password_min_length setting for Froxlor.generatePassword() default length parameter; allow '::1' as valid mysql localhost value; wrapper to clean output for cli installation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-16 20:20:58 +01:00
Gamerboy59
55a2ae3801 Add manual_config install var to cli (#1208)
Make the manual_config var, which is available to the web installer, usuable for the cli installer too. If manual_config is set to true skip else (not set or false) proceed with auto config.
2023-12-16 20:13:58 +01:00
Michael Kaufmann
a3b0332d13 set version to 2.1.2 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-15 09:41:16 +01:00
Michael Kaufmann
4b1846883d Merge pull request from GHSA-625g-fm5w-w7w4
* fix possibility to have empty name/surname and empty company

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

* let js validation for customer add/edit form also trim() entered data to avoid empty values pass the client-side validation

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

---------

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-15 09:36:06 +01:00
Michael Kaufmann
778fd3ba65 fix wrong size-unit for mailquota-dashboard-info; fixes #1207
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-15 09:32:02 +01:00
Michael Kaufmann
00456a35e5 fix 2fa login when using email validation, thx to wysiwtf; adjusting row-format of larger tables
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-13 16:20:28 +01:00
Michael Kaufmann
5958f0516b do not css-check/clean passwords of the installation process; fixes #1203
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-12 09:33:28 +01:00
Michael Kaufmann
166ffedf04 correctly merge themeoptions array to use correct image on login when using darkmode
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-12 08:59:56 +01:00
Michael Kaufmann
36dfee1263 fix non-empty value for file-input fields when using uploaded logos
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-12 08:58:33 +01:00
Michael Kaufmann
ec0026ecfd fix wrong type when dns zone for system-hostname is active
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-11 14:20:08 +01:00
Michael Kaufmann
a721bb3f21 remove old 0.10.x and 2.0.x distribution-config-xml's for updaters
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-10 20:47:31 +01:00
Michael Kaufmann
83de3dd719 handle unknown distribution if there's a now unsupported distribution selected for the config-templates
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-10 19:23:58 +01:00
Michael Kaufmann
5615decd96 set version to 2.1.1 for bugfix release (dns and install)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-10 08:20:12 +01:00
Michael Kaufmann
0348b1ec7e fix wrong result in Domain::getMainSubdomainIds(); fixes #1202
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-09 14:25:58 +01:00
Michael Kaufmann
1467dab58f set version to 2.1.0 for upcoming stable release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-08 11:48:32 +01:00
Michael Kaufmann
3a8f48de35 check subclass for cli commands to be \Symfony\Component\Console\Command\Command as the installcommand does not use \Froxlor\Cli\CliCommand
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-07 11:16:53 +01:00
Michael Kaufmann
46391c06ec Merge branch 'main' of github.com:Froxlor/Froxlor 2023-12-06 08:11:17 +01:00
dependabot[bot]
7103f7dd51 Bump vite from 4.4.11 to 4.4.12 (#1201)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.11 to 4.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 08:11:01 +01:00
Michael Kaufmann
9fc1dfee41 better check for invalid cli classes
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-05 12:50:57 +01:00
Michael Kaufmann
82dc76fdc6 fix wrong escaping of backslash in class-names when updating cronjobs_run table; add missing validateFormField-method for type 'image' (needs to be present but image-validation is handled elsewhere
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-05 11:16:41 +01:00
Michael Kaufmann
02ae52e3df remove old files in updater; avoid including old cli files in froxlor-cli; fix css for card list-groups
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-12-05 10:22:12 +01:00
28 changed files with 264 additions and 104 deletions

View File

@@ -2,7 +2,8 @@ name: build-documentation
on: on:
release: release:
types: [published] # only run for stable releases
types: [released]
jobs: jobs:
build_docs: build_docs:

View File

@@ -60,7 +60,9 @@ if ($userinfo['change_serversettings'] == '1') {
if (!empty($distribution)) { if (!empty($distribution)) {
if (!file_exists($config_dir . '/' . $distribution . ".xml")) { if (!file_exists($config_dir . '/' . $distribution . ".xml")) {
Response::dynamicError("Unknown distribution"); // unknown distribution -> redirect to select a valid distribution for config-templates
Settings::Set('system.distribution', '');
Response::redirectTo('admin_configfiles.php', ['reselect' => 1]);
} }
// update setting if different // update setting if different

View File

@@ -43,7 +43,12 @@ require dirname(__DIR__) . '/lib/tables.inc.php';
$application = new Application('froxlor-cli', Froxlor::getFullVersion()); $application = new Application('froxlor-cli', Froxlor::getFullVersion());
// files that are no commands // files that are no commands
$fileIgnoreList = ['CliCommand.php', 'index.html', 'install.functions.php']; $fileIgnoreList = [
// Current non-command files
'CliCommand.php',
'index.html',
'install.functions.php',
];
// directory of commands to include // directory of commands to include
$cmd_files = glob(Froxlor::getInstallDir() . '/lib/Froxlor/Cli/*.php'); $cmd_files = glob(Froxlor::getInstallDir() . '/lib/Froxlor/Cli/*.php');
@@ -56,7 +61,7 @@ foreach ($cmd_files as $cmdFile) {
// create class-name including namespace // create class-name including namespace
$cmdClass = "\\Froxlor\\Cli\\" . substr(basename($cmdFile), 0, -4); $cmdClass = "\\Froxlor\\Cli\\" . substr(basename($cmdFile), 0, -4);
// check whether it exists // check whether it exists
if (class_exists($cmdClass)) { if (class_exists($cmdClass) && is_subclass_of($cmdClass, '\Symfony\Component\Console\Command\Command')) {
// add to cli application // add to cli application
$application->add(new $cmdClass()); $application->add(new $cmdClass());
} }

View File

@@ -115,8 +115,8 @@ if ($page == 'overview') {
$userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024; $userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024;
if (Settings::Get('system.mail_quota_enabled')) { if (Settings::Get('system.mail_quota_enabled')) {
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 : -1; $userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 * 1024 : -1;
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024; $userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024 * 1024;
} }
if ($usages) { if ($usages) {

View File

@@ -74,27 +74,26 @@ if ($action == '2fa_entercode') {
$code = isset($_POST['2fa_code']) ? $_POST['2fa_code'] : null; $code = isset($_POST['2fa_code']) ? $_POST['2fa_code'] : null;
// verify entered code // verify entered code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname')); $tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
// get user-data // get user-data
$table = $_SESSION['uidtable_2fa']; $table = $_SESSION['uidtable_2fa'];
$field = $_SESSION['uidfield_2fa']; $field = $_SESSION['uidfield_2fa'];
$uid = $_SESSION['uid_2fa']; $uid = $_SESSION['uid_2fa'];
$isadmin = $_SESSION['unfo_2fa']; $isadmin = $_SESSION['unfo_2fa'];
if ($_SESSION['secret_2fa'] == 'email') {
// verify code set to user's data_2fa field
$sel_stmt = Database::prepare("SELECT `data_2fa` FROM " . $table . " WHERE `" . $field . "` = :uid");
$userinfo_code = Database::pexecute_first($sel_stmt, ['uid' => $uid]);
$result = $tfa->verifyCode($userinfo_code['data_2fa'], $code);
} else {
$result = $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3);
}
// either the code is valid when using authenticator-app, or we will select userdata by id and entered code // either the code is valid when using authenticator-app, or we will select userdata by id and entered code
// which is temporarily stored for the customer when using email-2fa // which is temporarily stored for the customer when using email-2fa
if ($result) { if ($result) {
$sel_param = [ $sel_param = [
'uid' => $uid 'uid' => $uid
]; ];
if ($_SESSION['secret_2fa'] == 'email') {
// verify code by selecting user by id and the temp. stored code,
// so only if it's the correct code, we get the user-data
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
$sel_param['code'] = $code;
} else {
// Authenticator-verification has already happened at this point, so just get the user-data
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid"); $sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
}
$userinfo = Database::pexecute_first($sel_stmt, $sel_param); $userinfo = Database::pexecute_first($sel_stmt, $sel_param);
// whoops, no (valid) user? Start again // whoops, no (valid) user? Start again
if (empty($userinfo)) { if (empty($userinfo)) {

View File

@@ -157,7 +157,7 @@ CREATE TABLE `panel_admins` (
`api_allowed` tinyint(1) NOT NULL default '1', `api_allowed` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`adminid`), PRIMARY KEY (`adminid`),
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_customers`; DROP TABLE IF EXISTS `panel_customers`;
@@ -299,7 +299,7 @@ CREATE TABLE `panel_domains` (
KEY `customerid` (`customerid`), KEY `customerid` (`customerid`),
KEY `parentdomain` (`parentdomainid`), KEY `parentdomain` (`parentdomainid`),
KEY `domain` (`domain`) KEY `domain` (`domain`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci; ) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
DROP TABLE IF EXISTS `panel_ipsandports`; DROP TABLE IF EXISTS `panel_ipsandports`;
@@ -678,7 +678,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.1.0-rc3'), ('system', 'update_notify_last', ''),
('system', 'traffictool', 'goaccess'), ('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60), ('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60), ('system', 'req_limit_interval', 60),
@@ -726,8 +726,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridecustom', '0'), ('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'), ('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'), ('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.1.0-rc3'), ('panel', 'version', '2.1.3'),
('panel', 'db_version', '202311260'); ('panel', 'db_version', '202312120');
DROP TABLE IF EXISTS `panel_tasks`; DROP TABLE IF EXISTS `panel_tasks`;

View File

@@ -38,6 +38,7 @@ if (!defined('_CRON_UPDATE')) {
if (Froxlor::isFroxlorVersion('2.0.24')) { if (Froxlor::isFroxlorVersion('2.0.24')) {
Update::showUpdateStep("Cleaning domains table"); Update::showUpdateStep("Cleaning domains table");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;"); Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
Update::lastStepStatus(0); Update::lastStepStatus(0);
@@ -67,15 +68,18 @@ if (Froxlor::isFroxlorVersion('2.0.24')) {
} }
Update::showUpdateStep("Adjusting cronjobs"); Update::showUpdateStep("Adjusting cronjobs");
Database::query(" $cfupd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
`module`= 'froxlor/export', `module`= 'froxlor/export',
`cronfile` = 'export', `cronfile` = 'export',
`cronclass` = '\\Froxlor\\Cron\\System\\ExportCron', `cronclass` = :cc,
`interval` = '1 HOUR', `interval` = '1 HOUR',
`desc_lng_key` = 'cron_export' `desc_lng_key` = 'cron_export'
WHERE `module` = 'froxlor/backup' WHERE `module` = 'froxlor/backup'
"); ");
Database::pexecute($cfupd_stmt, [
'cc' => '\\Froxlor\\Cron\\System\\ExportCron'
]);
Update::lastStepStatus(0); Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting system for data-export function"); Update::showUpdateStep("Adjusting system for data-export function");
@@ -130,8 +134,8 @@ if (Froxlor::isDatabaseVersion('202305240')) {
$current_fileextension = Settings::Get('system.index_file_extension'); $current_fileextension = Settings::Get('system.index_file_extension');
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup`= 'system' AND `varname`= 'index_file_extension'"); Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup`= 'system' AND `varname`= 'index_file_extension'");
Database::query("ALTER TABLE `" . TABLE_PANEL_TEMPLATES . "` ADD `file_extension` varchar(50) NOT NULL default 'html';"); Database::query("ALTER TABLE `" . TABLE_PANEL_TEMPLATES . "` ADD `file_extension` varchar(50) NOT NULL default 'html';");
if (strtolower(trim($current_fileextension)) != 'html') { if (!empty(trim($current_fileextension)) && strtolower(trim($current_fileextension)) != 'html') {
$stmt = Database::prepare("UPDATE TABLE `" . TABLE_PANEL_TEMPLATES . "` SET `file_extension` = :ext WHERE `templategroup` = 'files'"); $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET `file_extension` = :ext WHERE `templategroup` = 'files'");
Database::pexecute($stmt, ['ext' => strtolower(trim($current_fileextension))]); Database::pexecute($stmt, ['ext' => strtolower(trim($current_fileextension))]);
} }
Update::lastStepStatus(0); Update::lastStepStatus(0);
@@ -143,3 +147,130 @@ if (Froxlor::isFroxlorVersion('2.1.0-rc2')) {
Update::showUpdateStep("Updating from 2.1.0-rc2 to 2.1.0-rc3", false); Update::showUpdateStep("Updating from 2.1.0-rc2 to 2.1.0-rc3", false);
Froxlor::updateToVersion('2.1.0-rc3'); Froxlor::updateToVersion('2.1.0-rc3');
} }
if (Froxlor::isDatabaseVersion('202311260')) {
Update::showUpdateStep("Cleaning up old files");
$to_clean = array(
"install/updates/froxlor/update_2.x.inc.php",
"install/updates/preconfig/preconfig_2.x.inc.php",
"lib/Froxlor/Api/Commands/CustomerBackups.php",
"lib/Froxlor/Cli/Action",
"lib/Froxlor/Cli/Action.php",
"lib/Froxlor/Cli/CmdLineHandler.php",
"lib/Froxlor/Cli/ConfigServicesCmd.php",
"lib/Froxlor/Cli/PhpSessioncleanCmd.php",
"lib/Froxlor/Cli/SwitchServerIpCmd.php",
"lib/Froxlor/Cli/UpdateCliCmd.php",
"lib/Froxlor/Cron/System/BackupCron.php",
"lib/formfields/customer/extras/formfield.backup.php",
"lib/tablelisting/customer/tablelisting.backups.php",
"templates/Froxlor/assets/mix-manifest.json",
"templates/Froxlor/assets/css",
"templates/Froxlor/assets/webfonts",
"templates/Froxlor/assets/js/main.js",
"templates/Froxlor/assets/js/main.js.LICENSE.txt",
"templates/Froxlor/src",
"templates/Froxlor/user/change_language.html.twig",
"templates/Froxlor/user/change_password.html.twig",
"templates/Froxlor/user/change_theme.html.twig",
"tests/Backup/CustomerBackupsTest.php"
);
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
$del_list = "";
foreach ($to_clean as $filedir) {
$complete_filedir = Froxlor::getInstallDir() . $filedir;
if (file_exists($complete_filedir)) {
if ($exec_allowed) {
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
} else {
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
}
}
}
if ($exec_allowed) {
Update::lastStepStatus(0);
} else {
if (empty($del_list)) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(
1,
'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
);
}
}
Froxlor::updateToDbVersion('202312050');
}
if (Froxlor::isFroxlorVersion('2.1.0-rc3')) {
Update::showUpdateStep("Updating from 2.1.0-rc3 to 2.1.0 stable", false);
Froxlor::updateToVersion('2.1.0');
}
if (Froxlor::isFroxlorVersion('2.1.0')) {
Update::showUpdateStep("Updating from 2.1.0 to 2.1.1", false);
Froxlor::updateToVersion('2.1.1');
}
if (Froxlor::isDatabaseVersion('202312050')) {
Update::showUpdateStep("Cleaning up old files");
$to_clean = array(
"lib/configfiles/centos7.xml",
"lib/configfiles/centos8.xml",
"lib/configfiles/stretch.xml",
"lib/configfiles/xenial.xml",
"lib/configfiles/buster.xml",
"lib/configfiles/bionic.xml",
);
$disabled = explode(',', ini_get('disable_functions'));
$exec_allowed = !in_array('exec', $disabled);
$del_list = "";
foreach ($to_clean as $filedir) {
$complete_filedir = Froxlor::getInstallDir() . $filedir;
if (file_exists($complete_filedir)) {
if ($exec_allowed) {
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
} else {
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
}
}
}
if ($exec_allowed) {
Update::lastStepStatus(0);
} else {
if (empty($del_list)) {
// none of the files existed
Update::lastStepStatus(0);
} else {
Update::lastStepStatus(
1,
'manual commands needed',
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
);
}
}
Froxlor::updateToDbVersion('202312100');
}
if (Froxlor::isDatabaseVersion('202312100')) {
Update::showUpdateStep("Adjusting table row format of larger tables");
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` ROW_FORMAT=DYNAMIC;");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202312120');
}
if (Froxlor::isFroxlorVersion('2.1.1')) {
Update::showUpdateStep("Updating from 2.1.1 to 2.1.2", false);
Froxlor::updateToVersion('2.1.2');
}
if (Froxlor::isFroxlorVersion('2.1.2')) {
Update::showUpdateStep("Updating from 2.1.2 to 2.1.3", false);
Froxlor::updateToVersion('2.1.3');
}

View File

@@ -1053,7 +1053,7 @@ class Customers extends ApiCommand implements ResourceEntity
$email = $this->getParam('email', true, $idna_convert->decode($result['email'])); $email = $this->getParam('email', true, $idna_convert->decode($result['email']));
$name = $this->getParam('name', true, $result['name']); $name = $this->getParam('name', true, $result['name']);
$firstname = $this->getParam('firstname', true, $result['firstname']); $firstname = $this->getParam('firstname', true, $result['firstname']);
$company_required = empty($result['company']) && ((!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname))); $company_required = (!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname));
$company = $this->getParam('company', !$company_required, $result['company']); $company = $this->getParam('company', !$company_required, $result['company']);
$street = $this->getParam('street', true, $result['street']); $street = $this->getParam('street', true, $result['street']);
$zipcode = $this->getParam('zipcode', true, $result['zipcode']); $zipcode = $this->getParam('zipcode', true, $result['zipcode']);

View File

@@ -259,14 +259,15 @@ class Froxlor extends ApiCommand
* returns a random password based on froxlor settings for min-length, included characters, etc. * returns a random password based on froxlor settings for min-length, included characters, etc.
* *
* @param int $length * @param int $length
* optional length of password, defaults to 10 * optional length of password, defaults to 0 (panel.password_min_length)
* *
* @access admin, customer * @access admin, customer
* @return string * @return string
* @throws Exception
*/ */
public function generatePassword() public function generatePassword(): string
{ {
$length = $this->getParam('length', true, 10); $length = $this->getParam('length', true, 0);
return $this->response(Crypt::generatePassword($length)); return $this->response(Crypt::generatePassword($length));
} }

View File

@@ -211,7 +211,7 @@ final class InstallCommand extends Command
$ask_field = false; $ask_field = false;
} }
$fielddata['value'] = $this->formfielddata[$fieldname] ?? ($fielddata['value'] ?? null); $fielddata['value'] = $this->formfielddata[$fieldname] ?? ($fielddata['value'] ?? null);
$fielddata['label'] = strip_tags(str_replace("<br>", " ", $fielddata['label'])); $fielddata['label'] = $this->cliTextFormat($fielddata['label'], " ");
if ($ask_field) { if ($ask_field) {
if ($fielddata['type'] == 'password') { if ($fielddata['type'] == 'password') {
$this->formfielddata[$fieldname] = $this->io->askHidden($fielddata['label'], function ($value) use ($fielddata) { $this->formfielddata[$fieldname] = $this->io->askHidden($fielddata['label'], function ($value) use ($fielddata) {
@@ -267,15 +267,17 @@ final class InstallCommand extends Command
case 4: case 4:
$section = $inst->formfield['install']['sections']['step' . $step] ?? []; $section = $inst->formfield['install']['sections']['step' . $step] ?? [];
$this->io->section($section['title']); $this->io->section($section['title']);
$this->io->note($section['description']); $this->io->note($this->cliTextFormat($section['description']));
$cmdfield = $section['fields']['system']; $cmdfield = $section['fields']['system'];
$this->io->success([ $this->io->success([
$cmdfield['label'], $cmdfield['label'],
$cmdfield['value'] $cmdfield['value']
]); ]);
if (!isset($decoded_input['manual_config']) || (bool)$decoded_input['manual_config'] === false) {
if (!empty($decoded_input) || $this->io->confirm('Execute command now?', false)) { if (!empty($decoded_input) || $this->io->confirm('Execute command now?', false)) {
passthru($cmdfield['value']); passthru($cmdfield['value']);
} }
}
break; break;
} }
return $result; return $result;
@@ -305,7 +307,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' || $name == 'target_servername') { if ($name == 'system' || $name == 'target_servername') {
continue; continue;
} }
if ($field['type'] == 'text' || $field['type'] == 'email') { if ($field['type'] == 'text' || $field['type'] == 'email') {
@@ -318,7 +320,7 @@ final class InstallCommand extends Command
$fieldval = '******'; $fieldval = '******';
} elseif ($field['type'] == 'select') { } elseif ($field['type'] == 'select') {
$fieldval = implode("|", array_keys($field['select_var'])); $fieldval = implode("|", array_keys($field['select_var']));
} else if ($field['type'] == 'checkbox') { } elseif ($field['type'] == 'checkbox') {
$fieldval = "1|0"; $fieldval = "1|0";
} else { } else {
$fieldval = "?"; $fieldval = "?";
@@ -346,4 +348,10 @@ final class InstallCommand extends Command
curl_close($ch); curl_close($ch);
fclose($fp); fclose($fp);
} }
private function cliTextFormat(string $text, string $nl_char = "\n"): string
{
$text = str_replace(['<br>', '<br/>', '<br />'], [$nl_char, $nl_char, $nl_char], $text);
return strip_tags($text);
}
} }

View File

@@ -244,7 +244,7 @@ abstract class DnsBase
'zonefile' => '', 'zonefile' => '',
'froxlorhost' => '1' 'froxlorhost' => '1'
]; ];
$domains['none'] = $hostname_arr; $domains[0] = $hostname_arr;
} }
if (empty($domains)) { if (empty($domains)) {

View File

@@ -47,7 +47,7 @@ class TrafficCron extends FroxlorCron
public static function run() public static function run()
{ {
self::runFork([self::class, 'handle']); self::runFork([self::class, 'handle'], [true]);
} }
public static function handle() public static function handle()

View File

@@ -256,7 +256,7 @@ class Domain
]); ]);
$result = []; $result = [];
while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$result = $entry['id']; $result[] = $entry['id'];
} }
return $result; return $result;
} }

View File

@@ -31,10 +31,10 @@ final class Froxlor
{ {
// Main version variable // Main version variable
const VERSION = '2.1.0-rc3'; const VERSION = '2.1.3';
// Database version (YYYYMMDDC where C is a daily counter) // Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202311260'; const DBVERSION = '202312120';
// Distribution branding-tag (used for Debian etc.) // Distribution branding-tag (used for Debian etc.)
const BRANDING = ''; const BRANDING = '';

View File

@@ -220,8 +220,11 @@ class PhpHelper
if (is_dir($data_dirname)) { if (is_dir($data_dirname)) {
$data_dirhandle = opendir($data_dirname); $data_dirhandle = opendir($data_dirname);
while (false !== ($data_filename = readdir($data_dirhandle))) { while (false !== ($data_filename = readdir($data_dirhandle))) {
if ($data_filename != '.' && $data_filename != '..' && $data_filename != '' && substr($data_filename, if ($data_filename != '.'
-4) == '.php') { && $data_filename != '..'
&& $data_filename != ''
&& substr($data_filename, -4) == '.php'
) {
$data_files[] = $data_dirname . $data_filename; $data_files[] = $data_dirname . $data_filename;
} }
} }
@@ -458,6 +461,10 @@ class PhpHelper
'directory_password', 'directory_password',
'ftp_password', 'ftp_password',
'mysql_password', 'mysql_password',
'mysql_root_pass',
'mysql_unprivileged_pass',
'admin_pass',
'admin_pass_confirm',
]; ];
if (!empty($global)) { if (!empty($global)) {
$tmp = $global; $tmp = $global;
@@ -557,4 +564,17 @@ class PhpHelper
} }
return $tab . $str; return $tab . $str;
} }
public static function array_merge_recursive_distinct(array &$array1, array &$array2)
{
$merged = $array1;
foreach ($array2 as $key => &$value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = self::array_merge_recursive_distinct($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
} }

View File

@@ -217,7 +217,7 @@ class Form
{ {
$returnvalue = []; $returnvalue = [];
if (is_array($fielddata) && isset($fielddata['type']) && $fielddata['type'] == 'select') { if (is_array($fielddata) && isset($fielddata['type']) && $fielddata['type'] == 'select') {
if ((!isset($fielddata['select_var']) || !is_array($fielddata['select_var']) || empty($fielddata['select_var'])) && (isset($fielddata['option_options_method']))) { if ((!is_array($fielddata['select_var']) || empty($fielddata['select_var'])) && (isset($fielddata['option_options_method']))) {
$returnvalue['select_var'] = call_user_func($fielddata['option_options_method']); $returnvalue['select_var'] = call_user_func($fielddata['option_options_method']);
} }
} }
@@ -236,8 +236,8 @@ class Form
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) { if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
// Prefetch form fields // Prefetch form fields
foreach ($groupdetails['fields'] as $fieldname => $fielddetails) { foreach ($groupdetails['fields'] as $fieldname => $fielddetails) {
if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) { if (!$only_enabledisable || isset($fielddetails['overview_option'])) {
$groupdetails['fields'][$fieldname] = self::arrayMergePrefix($fielddetails, $fielddetails['type'], self::prefetchFormFieldData($fieldname, $fielddetails)); $groupdetails['fields'][$fieldname] = array_merge($fielddetails, self::prefetchFormFieldData($fieldname, $fielddetails));
$form['groups'][$groupname]['fields'][$fieldname] = $groupdetails['fields'][$fieldname]; $form['groups'][$groupname]['fields'][$fieldname] = $groupdetails['fields'][$fieldname];
} }
} }
@@ -347,7 +347,7 @@ class Form
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) { if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
// Save fields // Save fields
foreach ($groupdetails['fields'] as $fieldname => $fielddetails) { foreach ($groupdetails['fields'] as $fieldname => $fielddetails) {
if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) { if (!$only_enabledisable || (isset($fielddetails['overview_option']))) {
if (isset($changed_fields[$fieldname])) { if (isset($changed_fields[$fieldname])) {
if (($saved_field = self::saveFormField($fieldname, $fielddetails, self::manipulateFormFieldData($fieldname, $fielddetails, $changed_fields[$fieldname]))) !== false) { if (($saved_field = self::saveFormField($fieldname, $fielddetails, self::manipulateFormFieldData($fieldname, $fielddetails, $changed_fields[$fieldname]))) !== false) {
$saved_fields = array_merge($saved_fields, $saved_field); $saved_fields = array_merge($saved_fields, $saved_field);
@@ -364,24 +364,7 @@ class Form
// Save form // Save form
return self::saveForm($form, $saved_fields); return self::saveForm($form, $saved_fields);
} }
} return false;
private static function arrayMergePrefix($array1, $key_prefix, $array2)
{
if (is_array($array1) && is_array($array2)) {
if ($key_prefix != '') {
foreach ($array2 as $key => $value) {
$array1[$key_prefix . '_' . $key] = $value;
unset($array2[$key]);
}
unset($array2);
return $array1;
} else {
return array_merge($array1, $array2);
}
} else {
return $array1;
}
} }
public static function getFormFieldData($fieldname, $fielddata, &$input) public static function getFormFieldData($fieldname, $fielddata, &$input)

View File

@@ -265,4 +265,10 @@ class Data
return $returnvalue; return $returnvalue;
} }
public static function validateFormFieldImage($fieldname, $fielddata, $newfieldvalue)
{
// validation is handled in \Froxlor\Settings\Store::storeSettingImage()
return true;
}
} }

View File

@@ -176,7 +176,7 @@ class Validate
} }
// special case where localhost ip is allowed (mysql-access-hosts for example) // special case where localhost ip is allowed (mysql-access-hosts for example)
if ($allow_localhost && $ip == '127.0.0.1') { if ($allow_localhost && ($ip == '127.0.0.1' || $ip == '::1')) {
return $ip . $cidr; return $ip . $cidr;
} }

View File

@@ -112,9 +112,14 @@ function vite($basehref, array $filenames): string
$assetDirectory = '/templates/' . $matches[1] . '/build/'; $assetDirectory = '/templates/' . $matches[1] . '/build/';
$viteManifest = dirname(__DIR__) . $assetDirectory . '/manifest.json'; $viteManifest = dirname(__DIR__) . $assetDirectory . '/manifest.json';
$manifest = json_decode(file_get_contents($viteManifest), true); $manifest = json_decode(file_get_contents($viteManifest), true);
if (!empty($manifest[$filename]['file'])) {
$links[] = $basehref . ltrim($assetDirectory, '/') . $manifest[$filename]['file']; $links[] = $basehref . ltrim($assetDirectory, '/') . $manifest[$filename]['file'];
} else { } else {
$links = $filenames; // additional asset from config.json that was not prebuilt on release (e.g. custom.css)
$links[] = $filename;
}
} else {
$links[] = $filename;
} }
} }

View File

@@ -115,14 +115,15 @@ if (!isset($sql) || !is_array($sql)) {
/** /**
* Show nice note if requested domain is "unknown" to froxlor and thus is being lead to its vhost * Show nice note if requested domain is "unknown" to froxlor and thus is being lead to its vhost
*/ */
if ($_SERVER['SERVER_NAME'] != Settings::Get('system.hostname') && $req_host = UI::getCookieHost();
if ($req_host != Settings::Get('system.hostname') &&
Settings::Get('panel.is_configured') == 1 && Settings::Get('panel.is_configured') == 1 &&
!filter_var($_SERVER['SERVER_NAME'], FILTER_VALIDATE_IP) && ( !filter_var($req_host, FILTER_VALIDATE_IP) && (
empty(Settings::Get('system.froxloraliases')) || empty(Settings::Get('system.froxloraliases')) ||
(!empty(Settings::Get('system.froxloraliases')) && !in_array($_SERVER['SERVER_NAME'], array_map('trim', explode(',', Settings::Get('system.froxloraliases'))))) (!empty(Settings::Get('system.froxloraliases')) && !in_array($req_host, array_map('trim', explode(',', Settings::Get('system.froxloraliases')))))
)) { )) {
// not the froxlor system-hostname, show info page for domains not configured in froxlor // not the froxlor system-hostname, show info page for domains not configured in froxlor
$redirect_file = FileDir::getUnknownDomainTemplate($_SERVER['SERVER_NAME']); $redirect_file = FileDir::getUnknownDomainTemplate($req_host ?? "non-detectable http-host");
header('Location: '.$redirect_file); header('Location: '.$redirect_file);
die(); die();
} }
@@ -224,7 +225,7 @@ if (is_array($_themeoptions) && (!array_key_exists('variants', $_themeoptions) |
} }
if (array_key_exists('global', $_themeoptions)) { if (array_key_exists('global', $_themeoptions)) {
$_themeoptions['variants'][$themevariant] = array_merge_recursive($_themeoptions['variants'][$themevariant], $_themeoptions['global']); $_themeoptions['variants'][$themevariant] = PhpHelper::array_merge_recursive_distinct($_themeoptions['global'], $_themeoptions['variants'][$themevariant]);
} }
// check for custom header-graphic // check for custom header-graphic

View File

@@ -49,6 +49,7 @@ return [
'field' => 'domains', 'field' => 'domains',
'callback' => [PHPConf::class, 'domainList'], 'callback' => [PHPConf::class, 'domainList'],
'searchable' => false, 'searchable' => false,
'sortable' => false,
], ],
'fpmdesc' => [ 'fpmdesc' => [
'label' => lng('admin.phpsettings.fpmdesc'), 'label' => lng('admin.phpsettings.fpmdesc'),

8
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"postcss": "^8.1.14", "postcss": "^8.1.14",
"resolve-url-loader": "^5.0.0", "resolve-url-loader": "^5.0.0",
"sass": "^1.69.3", "sass": "^1.69.3",
"vite": "^4.0.0", "vite": "^4.4.12",
"vue": "^3.2.37" "vue": "^3.2.37"
}, },
"engines": { "engines": {
@@ -1156,9 +1156,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "4.4.11", "version": "4.4.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.12.tgz",
"integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==", "integrity": "sha512-KtPlUbWfxzGVul8Nut8Gw2Qe8sBzWY+8QVc5SL8iRFnpnrcoCaNlzO40c1R6hPmcdTwIPEDkq0Y9+27a5tVbdQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.18.10", "esbuild": "^0.18.10",

View File

@@ -19,7 +19,7 @@
"postcss": "^8.1.14", "postcss": "^8.1.14",
"resolve-url-loader": "^5.0.0", "resolve-url-loader": "^5.0.0",
"sass": "^1.69.3", "sass": "^1.69.3",
"vite": "^4.0.0", "vite": "^4.4.12",
"vue": "^3.2.37" "vue": "^3.2.37"
}, },
"engines": { "engines": {

View File

@@ -8,18 +8,18 @@ export default function () {
rules: { rules: {
'name': { 'name': {
required: function () { required: function () {
return $('#company').val().length === 0 || $('#firstname').val().length > 0; return $('#company').val().trim().length === 0 || $('#firstname').val().trim().length > 0;
} }
}, },
'firstname': { 'firstname': {
required: function () { required: function () {
return $('#company').val().length === 0 || $('#name').val().length > 0; return $('#company').val().trim().length === 0 || $('#name').val().trim().length > 0;
} }
}, },
'company': { 'company': {
required: function () { required: function () {
return $('#name').val().length === 0 return $('#name').val().trim().length === 0
&& $('#firstname').val().length === 0; && $('#firstname').val().trim().length === 0;
} }
} }
}, },

View File

@@ -22,6 +22,9 @@
} }
} }
} }
.list-group-item {
background: $white;
}
@include color-mode(dark) { @include color-mode(dark) {
.card, .list-group-item { .card, .list-group-item {

View File

@@ -1,8 +1,8 @@
{ {
"global": { "global": {
"css": [ "css": [
"assets/css/custom.css", "assets/scss/app.scss",
"assets/scss/app.scss" "assets/css/custom.css"
], ],
"js": [ "js": [
"assets/js/app.js", "assets/js/app.js",

View File

@@ -184,7 +184,7 @@
</label> </label>
</div> </div>
{% endif %} {% endif %}
{% set field = field|merge({'type':'file'}) %} {% set field = field|merge({'type':'file'})|merge({'value':''}) %}
{{ _self.input(id, field) }} {{ _self.input(id, field) }}
{% endmacro %} {% endmacro %}

View File

@@ -51,8 +51,16 @@
<div class="row"> <div class="row">
{% if userinfo.adminsession == 1 %} {% if userinfo.adminsession == 1 %}
<div <div class="col-12 col-lg-6">
class="col-12 col-lg-6">
{% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
<div class="card mb-3">
<div class="card-body">
{{ userinfo.custom_notes|markdown|raw }}
</div>
</div>
{% endif %}
{# system infos #} {# system infos #}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
@@ -135,22 +143,9 @@
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
{% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
<div class="card mb-3">
<ul class="list-group list-group-flush">
<li class="list-group-item list-group-item-info d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
{{ userinfo.custom_notes|markdown|raw }}
</div>
</li>
</ul>
</div>
{% endif %}
</div> </div>
{% else %} {% else %}
<div <div class="col-12 col-md-6 col-lg-4">
class="col-12 col-md-6 col-lg-4">
{# account info #} {# account info #}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
@@ -212,8 +207,7 @@
</ul> </ul>
</div> </div>
</div> </div>
<div <div class="col-12 col-md-6 col-lg-4">
class="col-12 col-md-6 col-lg-4">
{# customer details #} {# customer details #}
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">