Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b542b140c6 | ||
|
|
ac89fc7120 | ||
|
|
150858485d | ||
|
|
e7810e2066 | ||
|
|
4879446567 | ||
|
|
43eff78088 | ||
|
|
55a2ae3801 | ||
|
|
a3b0332d13 | ||
|
|
4b1846883d | ||
|
|
778fd3ba65 | ||
|
|
00456a35e5 | ||
|
|
5958f0516b | ||
|
|
166ffedf04 | ||
|
|
36dfee1263 | ||
|
|
ec0026ecfd | ||
|
|
a721bb3f21 | ||
|
|
83de3dd719 | ||
|
|
5615decd96 | ||
|
|
0348b1ec7e | ||
|
|
1467dab58f | ||
|
|
3a8f48de35 | ||
|
|
46391c06ec | ||
|
|
7103f7dd51 | ||
|
|
9fc1dfee41 | ||
|
|
82dc76fdc6 | ||
|
|
02ae52e3df |
3
.github/workflows/build-docs.yml
vendored
3
.github/workflows/build-docs.yml
vendored
@@ -2,7 +2,8 @@ name: build-documentation
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
# only run for stable releases
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
build_docs:
|
||||
|
||||
@@ -60,7 +60,9 @@ if ($userinfo['change_serversettings'] == '1') {
|
||||
|
||||
if (!empty($distribution)) {
|
||||
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
|
||||
|
||||
@@ -43,7 +43,12 @@ require dirname(__DIR__) . '/lib/tables.inc.php';
|
||||
$application = new Application('froxlor-cli', Froxlor::getFullVersion());
|
||||
|
||||
// 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
|
||||
$cmd_files = glob(Froxlor::getInstallDir() . '/lib/Froxlor/Cli/*.php');
|
||||
|
||||
@@ -56,7 +61,7 @@ foreach ($cmd_files as $cmdFile) {
|
||||
// create class-name including namespace
|
||||
$cmdClass = "\\Froxlor\\Cli\\" . substr(basename($cmdFile), 0, -4);
|
||||
// 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
|
||||
$application->add(new $cmdClass());
|
||||
}
|
||||
|
||||
@@ -115,8 +115,8 @@ if ($page == 'overview') {
|
||||
$userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024;
|
||||
|
||||
if (Settings::Get('system.mail_quota_enabled')) {
|
||||
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 : -1;
|
||||
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024;
|
||||
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 * 1024 : -1;
|
||||
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024 * 1024;
|
||||
}
|
||||
|
||||
if ($usages) {
|
||||
|
||||
19
index.php
19
index.php
@@ -74,27 +74,26 @@ if ($action == '2fa_entercode') {
|
||||
$code = isset($_POST['2fa_code']) ? $_POST['2fa_code'] : null;
|
||||
// verify entered code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
|
||||
// get user-data
|
||||
$table = $_SESSION['uidtable_2fa'];
|
||||
$field = $_SESSION['uidfield_2fa'];
|
||||
$uid = $_SESSION['uid_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
|
||||
// which is temporarily stored for the customer when using email-2fa
|
||||
if ($result) {
|
||||
$sel_param = [
|
||||
'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);
|
||||
// whoops, no (valid) user? Start again
|
||||
if (empty($userinfo)) {
|
||||
|
||||
@@ -157,7 +157,7 @@ CREATE TABLE `panel_admins` (
|
||||
`api_allowed` tinyint(1) NOT NULL default '1',
|
||||
PRIMARY KEY (`adminid`),
|
||||
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`;
|
||||
@@ -299,7 +299,7 @@ CREATE TABLE `panel_domains` (
|
||||
KEY `customerid` (`customerid`),
|
||||
KEY `parentdomain` (`parentdomainid`),
|
||||
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`;
|
||||
@@ -678,7 +678,7 @@ opcache.validate_timestamps'),
|
||||
('system', 'distribution', ''),
|
||||
('system', 'update_channel', 'stable'),
|
||||
('system', 'updatecheck_data', ''),
|
||||
('system', 'update_notify_last', '2.1.0-rc3'),
|
||||
('system', 'update_notify_last', ''),
|
||||
('system', 'traffictool', 'goaccess'),
|
||||
('system', 'req_limit_per_interval', 60),
|
||||
('system', 'req_limit_interval', 60),
|
||||
@@ -726,8 +726,8 @@ opcache.validate_timestamps'),
|
||||
('panel', 'logo_overridecustom', '0'),
|
||||
('panel', 'settings_mode', '0'),
|
||||
('panel', 'menu_collapsed', '1'),
|
||||
('panel', 'version', '2.1.0-rc3'),
|
||||
('panel', 'db_version', '202311260');
|
||||
('panel', 'version', '2.1.3'),
|
||||
('panel', 'db_version', '202312120');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_tasks`;
|
||||
|
||||
@@ -38,6 +38,7 @@ if (!defined('_CRON_UPDATE')) {
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.0.24')) {
|
||||
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`;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
@@ -67,15 +68,18 @@ if (Froxlor::isFroxlorVersion('2.0.24')) {
|
||||
}
|
||||
|
||||
Update::showUpdateStep("Adjusting cronjobs");
|
||||
Database::query("
|
||||
$cfupd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
|
||||
`module`= 'froxlor/export',
|
||||
`cronfile` = 'export',
|
||||
`cronclass` = '\\Froxlor\\Cron\\System\\ExportCron',
|
||||
`cronclass` = :cc,
|
||||
`interval` = '1 HOUR',
|
||||
`desc_lng_key` = 'cron_export'
|
||||
WHERE `module` = 'froxlor/backup'
|
||||
");
|
||||
Database::pexecute($cfupd_stmt, [
|
||||
'cc' => '\\Froxlor\\Cron\\System\\ExportCron'
|
||||
]);
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Update::showUpdateStep("Adjusting system for data-export function");
|
||||
@@ -130,8 +134,8 @@ if (Froxlor::isDatabaseVersion('202305240')) {
|
||||
$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("ALTER TABLE `" . TABLE_PANEL_TEMPLATES . "` ADD `file_extension` varchar(50) NOT NULL default 'html';");
|
||||
if (strtolower(trim($current_fileextension)) != 'html') {
|
||||
$stmt = Database::prepare("UPDATE TABLE `" . TABLE_PANEL_TEMPLATES . "` SET `file_extension` = :ext WHERE `templategroup` = 'files'");
|
||||
if (!empty(trim($current_fileextension)) && strtolower(trim($current_fileextension)) != 'html') {
|
||||
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET `file_extension` = :ext WHERE `templategroup` = 'files'");
|
||||
Database::pexecute($stmt, ['ext' => strtolower(trim($current_fileextension))]);
|
||||
}
|
||||
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);
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -1053,7 +1053,7 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
$email = $this->getParam('email', true, $idna_convert->decode($result['email']));
|
||||
$name = $this->getParam('name', true, $result['name']);
|
||||
$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']);
|
||||
$street = $this->getParam('street', true, $result['street']);
|
||||
$zipcode = $this->getParam('zipcode', true, $result['zipcode']);
|
||||
|
||||
@@ -259,14 +259,15 @@ class Froxlor extends ApiCommand
|
||||
* returns a random password based on froxlor settings for min-length, included characters, etc.
|
||||
*
|
||||
* @param int $length
|
||||
* optional length of password, defaults to 10
|
||||
* optional length of password, defaults to 0 (panel.password_min_length)
|
||||
*
|
||||
* @access admin, customer
|
||||
* @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));
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ final class InstallCommand extends Command
|
||||
$ask_field = false;
|
||||
}
|
||||
$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 ($fielddata['type'] == 'password') {
|
||||
$this->formfielddata[$fieldname] = $this->io->askHidden($fielddata['label'], function ($value) use ($fielddata) {
|
||||
@@ -267,14 +267,16 @@ final class InstallCommand extends Command
|
||||
case 4:
|
||||
$section = $inst->formfield['install']['sections']['step' . $step] ?? [];
|
||||
$this->io->section($section['title']);
|
||||
$this->io->note($section['description']);
|
||||
$this->io->note($this->cliTextFormat($section['description']));
|
||||
$cmdfield = $section['fields']['system'];
|
||||
$this->io->success([
|
||||
$cmdfield['label'],
|
||||
$cmdfield['value']
|
||||
]);
|
||||
if (!empty($decoded_input) || $this->io->confirm('Execute command now?', false)) {
|
||||
passthru($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)) {
|
||||
passthru($cmdfield['value']);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -305,7 +307,7 @@ final class InstallCommand extends Command
|
||||
$json_output = [];
|
||||
foreach ($fields['install']['sections'] as $section => $section_fields) {
|
||||
foreach ($section_fields['fields'] as $name => $field) {
|
||||
if ($name == 'system' || $name == 'manual_config' || $name == 'target_servername') {
|
||||
if ($name == 'system' || $name == 'target_servername') {
|
||||
continue;
|
||||
}
|
||||
if ($field['type'] == 'text' || $field['type'] == 'email') {
|
||||
@@ -318,7 +320,7 @@ final class InstallCommand extends Command
|
||||
$fieldval = '******';
|
||||
} elseif ($field['type'] == 'select') {
|
||||
$fieldval = implode("|", array_keys($field['select_var']));
|
||||
} else if ($field['type'] == 'checkbox') {
|
||||
} elseif ($field['type'] == 'checkbox') {
|
||||
$fieldval = "1|0";
|
||||
} else {
|
||||
$fieldval = "?";
|
||||
@@ -346,4 +348,10 @@ final class InstallCommand extends Command
|
||||
curl_close($ch);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ abstract class DnsBase
|
||||
'zonefile' => '',
|
||||
'froxlorhost' => '1'
|
||||
];
|
||||
$domains['none'] = $hostname_arr;
|
||||
$domains[0] = $hostname_arr;
|
||||
}
|
||||
|
||||
if (empty($domains)) {
|
||||
|
||||
@@ -47,7 +47,7 @@ class TrafficCron extends FroxlorCron
|
||||
|
||||
public static function run()
|
||||
{
|
||||
self::runFork([self::class, 'handle']);
|
||||
self::runFork([self::class, 'handle'], [true]);
|
||||
}
|
||||
|
||||
public static function handle()
|
||||
|
||||
@@ -256,7 +256,7 @@ class Domain
|
||||
]);
|
||||
$result = [];
|
||||
while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$result = $entry['id'];
|
||||
$result[] = $entry['id'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ final class Froxlor
|
||||
{
|
||||
|
||||
// Main version variable
|
||||
const VERSION = '2.1.0-rc3';
|
||||
const VERSION = '2.1.3';
|
||||
|
||||
// Database version (YYYYMMDDC where C is a daily counter)
|
||||
const DBVERSION = '202311260';
|
||||
const DBVERSION = '202312120';
|
||||
|
||||
// Distribution branding-tag (used for Debian etc.)
|
||||
const BRANDING = '';
|
||||
|
||||
@@ -220,8 +220,11 @@ class PhpHelper
|
||||
if (is_dir($data_dirname)) {
|
||||
$data_dirhandle = opendir($data_dirname);
|
||||
while (false !== ($data_filename = readdir($data_dirhandle))) {
|
||||
if ($data_filename != '.' && $data_filename != '..' && $data_filename != '' && substr($data_filename,
|
||||
-4) == '.php') {
|
||||
if ($data_filename != '.'
|
||||
&& $data_filename != '..'
|
||||
&& $data_filename != ''
|
||||
&& substr($data_filename, -4) == '.php'
|
||||
) {
|
||||
$data_files[] = $data_dirname . $data_filename;
|
||||
}
|
||||
}
|
||||
@@ -415,8 +418,8 @@ class PhpHelper
|
||||
*/
|
||||
public static function recursive_array_search(
|
||||
string $needle,
|
||||
array $haystack,
|
||||
array &$keys = [],
|
||||
array $haystack,
|
||||
array &$keys = [],
|
||||
string $currentKey = ''
|
||||
): bool {
|
||||
foreach ($haystack as $key => $value) {
|
||||
@@ -458,6 +461,10 @@ class PhpHelper
|
||||
'directory_password',
|
||||
'ftp_password',
|
||||
'mysql_password',
|
||||
'mysql_root_pass',
|
||||
'mysql_unprivileged_pass',
|
||||
'admin_pass',
|
||||
'admin_pass_confirm',
|
||||
];
|
||||
if (!empty($global)) {
|
||||
$tmp = $global;
|
||||
@@ -557,4 +564,17 @@ class PhpHelper
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ class Form
|
||||
{
|
||||
$returnvalue = [];
|
||||
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']);
|
||||
}
|
||||
}
|
||||
@@ -236,8 +236,8 @@ class Form
|
||||
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
|
||||
// Prefetch form fields
|
||||
foreach ($groupdetails['fields'] as $fieldname => $fielddetails) {
|
||||
if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) {
|
||||
$groupdetails['fields'][$fieldname] = self::arrayMergePrefix($fielddetails, $fielddetails['type'], self::prefetchFormFieldData($fieldname, $fielddetails));
|
||||
if (!$only_enabledisable || isset($fielddetails['overview_option'])) {
|
||||
$groupdetails['fields'][$fieldname] = array_merge($fielddetails, self::prefetchFormFieldData($fieldname, $fielddetails));
|
||||
$form['groups'][$groupname]['fields'][$fieldname] = $groupdetails['fields'][$fieldname];
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,7 @@ class Form
|
||||
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
|
||||
// Save fields
|
||||
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 (($saved_field = self::saveFormField($fieldname, $fielddetails, self::manipulateFormFieldData($fieldname, $fielddetails, $changed_fields[$fieldname]))) !== false) {
|
||||
$saved_fields = array_merge($saved_fields, $saved_field);
|
||||
@@ -364,24 +364,7 @@ class Form
|
||||
// Save form
|
||||
return self::saveForm($form, $saved_fields);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getFormFieldData($fieldname, $fielddata, &$input)
|
||||
|
||||
@@ -265,4 +265,10 @@ class Data
|
||||
|
||||
return $returnvalue;
|
||||
}
|
||||
|
||||
public static function validateFormFieldImage($fieldname, $fielddata, $newfieldvalue)
|
||||
{
|
||||
// validation is handled in \Froxlor\Settings\Store::storeSettingImage()
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ class Validate
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,9 +112,14 @@ function vite($basehref, array $filenames): string
|
||||
$assetDirectory = '/templates/' . $matches[1] . '/build/';
|
||||
$viteManifest = dirname(__DIR__) . $assetDirectory . '/manifest.json';
|
||||
$manifest = json_decode(file_get_contents($viteManifest), true);
|
||||
$links[] = $basehref . ltrim($assetDirectory, '/') . $manifest[$filename]['file'];
|
||||
if (!empty($manifest[$filename]['file'])) {
|
||||
$links[] = $basehref . ltrim($assetDirectory, '/') . $manifest[$filename]['file'];
|
||||
} else {
|
||||
// additional asset from config.json that was not prebuilt on release (e.g. custom.css)
|
||||
$links[] = $filename;
|
||||
}
|
||||
} else {
|
||||
$links = $filenames;
|
||||
$links[] = $filename;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
lib/init.php
11
lib/init.php
@@ -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
|
||||
*/
|
||||
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 &&
|
||||
!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')) && !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
|
||||
$redirect_file = FileDir::getUnknownDomainTemplate($_SERVER['SERVER_NAME']);
|
||||
$redirect_file = FileDir::getUnknownDomainTemplate($req_host ?? "non-detectable http-host");
|
||||
header('Location: '.$redirect_file);
|
||||
die();
|
||||
}
|
||||
@@ -224,7 +225,7 @@ if (is_array($_themeoptions) && (!array_key_exists('variants', $_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
|
||||
|
||||
@@ -49,6 +49,7 @@ return [
|
||||
'field' => 'domains',
|
||||
'callback' => [PHPConf::class, 'domainList'],
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
],
|
||||
'fpmdesc' => [
|
||||
'label' => lng('admin.phpsettings.fpmdesc'),
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -19,7 +19,7 @@
|
||||
"postcss": "^8.1.14",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass": "^1.69.3",
|
||||
"vite": "^4.0.0",
|
||||
"vite": "^4.4.12",
|
||||
"vue": "^3.2.37"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1156,9 +1156,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.4.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz",
|
||||
"integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==",
|
||||
"version": "4.4.12",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.12.tgz",
|
||||
"integrity": "sha512-KtPlUbWfxzGVul8Nut8Gw2Qe8sBzWY+8QVc5SL8iRFnpnrcoCaNlzO40c1R6hPmcdTwIPEDkq0Y9+27a5tVbdQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"postcss": "^8.1.14",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass": "^1.69.3",
|
||||
"vite": "^4.0.0",
|
||||
"vite": "^4.4.12",
|
||||
"vue": "^3.2.37"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -8,18 +8,18 @@ export default function () {
|
||||
rules: {
|
||||
'name': {
|
||||
required: function () {
|
||||
return $('#company').val().length === 0 || $('#firstname').val().length > 0;
|
||||
return $('#company').val().trim().length === 0 || $('#firstname').val().trim().length > 0;
|
||||
}
|
||||
},
|
||||
'firstname': {
|
||||
required: function () {
|
||||
return $('#company').val().length === 0 || $('#name').val().length > 0;
|
||||
return $('#company').val().trim().length === 0 || $('#name').val().trim().length > 0;
|
||||
}
|
||||
},
|
||||
'company': {
|
||||
required: function () {
|
||||
return $('#name').val().length === 0
|
||||
&& $('#firstname').val().length === 0;
|
||||
return $('#name').val().trim().length === 0
|
||||
&& $('#firstname').val().trim().length === 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.list-group-item {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
@include color-mode(dark) {
|
||||
.card, .list-group-item {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"global": {
|
||||
"css": [
|
||||
"assets/css/custom.css",
|
||||
"assets/scss/app.scss"
|
||||
"assets/scss/app.scss",
|
||||
"assets/css/custom.css"
|
||||
],
|
||||
"js": [
|
||||
"assets/js/app.js",
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% set field = field|merge({'type':'file'}) %}
|
||||
{% set field = field|merge({'type':'file'})|merge({'value':''}) %}
|
||||
{{ _self.input(id, field) }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -51,8 +51,16 @@
|
||||
|
||||
<div class="row">
|
||||
{% if userinfo.adminsession == 1 %}
|
||||
<div
|
||||
class="col-12 col-lg-6">
|
||||
<div 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 #}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
@@ -135,22 +143,9 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
</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>
|
||||
{% else %}
|
||||
<div
|
||||
class="col-12 col-md-6 col-lg-4">
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
{# account info #}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
@@ -212,8 +207,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-12 col-md-6 col-lg-4">
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
{# customer details #}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
|
||||
Reference in New Issue
Block a user