diff --git a/.github/workflows/build-mariadb.yml b/.github/workflows/build-mariadb.yml index 1b230752..1a69d731 100644 --- a/.github/workflows/build-mariadb.yml +++ b/.github/workflows/build-mariadb.yml @@ -19,7 +19,7 @@ jobs: with: php-version: ${{ matrix.php-versions }} tools: composer:v2 - extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp + extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg - name: Install tools run: sudo apt-get install -y ant diff --git a/.github/workflows/build-mysql.yml b/.github/workflows/build-mysql.yml index 6b8515e9..8015674c 100644 --- a/.github/workflows/build-mysql.yml +++ b/.github/workflows/build-mysql.yml @@ -19,7 +19,7 @@ jobs: with: php-version: ${{ matrix.php-versions }} tools: composer:v2 - extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp + extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg - name: Install tools run: sudo apt-get install -y ant diff --git a/.gitignore b/.gitignore index bbad0ca0..a9416c61 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ logs/* *~ .well-known .idea +.DS_Store *.iml img/ vendor/ diff --git a/actions/admin/settings/100.panel.php b/actions/admin/settings/100.panel.php index b1ac1522..40691a26 100644 --- a/actions/admin/settings/100.panel.php +++ b/actions/admin/settings/100.panel.php @@ -265,7 +265,7 @@ return [ 'extras.directoryprotection' => lng('menue.extras.extras') . " / " . lng('menue.extras.directoryprotection'), 'extras.pathoptions' => lng('menue.extras.extras') . " / " . lng('menue.extras.pathoptions'), 'extras.logger' => lng('menue.extras.extras') . " / " . lng('menue.logger.logger'), - 'extras.backup' => lng('menue.extras.extras') . " / " . lng('menue.extras.backup'), + 'extras.export' => lng('menue.extras.extras') . " / " . lng('menue.extras.export'), 'traffic' => lng('menue.traffic.traffic'), 'traffic.http' => lng('menue.traffic.traffic') . " / HTTP", 'traffic.ftp' => lng('menue.traffic.traffic') . " / FTP", @@ -337,7 +337,15 @@ return [ 'image_name' => 'logo_login', 'default' => '', 'save_method' => 'storeSettingImage' - ] + ], + 'panel_menu_collapsed' => [ + 'label' => lng('serversettings.panel_menu_collapsed'), + 'settinggroup' => 'panel', + 'varname' => 'menu_collapsed', + 'type' => 'checkbox', + 'default' => true, + 'save_method' => 'storeSettingField', + ], ] ] ] diff --git a/actions/admin/settings/110.accounts.php b/actions/admin/settings/110.accounts.php index 7a0b92ab..cff32356 100644 --- a/actions/admin/settings/110.accounts.php +++ b/actions/admin/settings/110.accounts.php @@ -230,13 +230,13 @@ return [ 'onlyif' => 1 ] ], - 'system_backupenabled' => [ - 'label' => lng('serversettings.backupenabled'), + 'system_exportenabled' => [ + 'label' => lng('serversettings.exportenabled'), 'settinggroup' => 'system', - 'varname' => 'backupenabled', + 'varname' => 'exportenabled', 'type' => 'checkbox', 'default' => false, - 'cronmodule' => 'froxlor/backup', + 'cronmodule' => 'froxlor/export', 'save_method' => 'storeSettingField' ], 'system_createstdsubdom_default' => [ diff --git a/actions/admin/settings/120.system.php b/actions/admin/settings/120.system.php index 339ed9a0..09f3da7d 100644 --- a/actions/admin/settings/120.system.php +++ b/actions/admin/settings/120.system.php @@ -107,7 +107,8 @@ return [ 'varname' => 'enabled', 'type' => 'checkbox', 'default' => false, - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'api_customer_default' => [ 'label' => lng('serversettings.api_customer_default'), diff --git a/actions/admin/settings/125.cronjob.php b/actions/admin/settings/125.cronjob.php index a04a8394..6c26bd11 100644 --- a/actions/admin/settings/125.cronjob.php +++ b/actions/admin/settings/125.cronjob.php @@ -46,7 +46,8 @@ return [ 'type' => 'text', 'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i', 'default' => '/usr/bin/nice -n 5 /usr/bin/php -q', - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_crondreload' => [ 'label' => lng('serversettings.system_crondreload'), @@ -55,7 +56,8 @@ return [ 'type' => 'text', 'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i', 'default' => '/etc/init.d/cron reload', - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_cron_allowautoupdate' => [ 'label' => lng('serversettings.system_cron_allowautoupdate'), @@ -63,7 +65,8 @@ return [ 'varname' => 'cron_allowautoupdate', 'type' => 'checkbox', 'default' => false, - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ] ] ] diff --git a/actions/admin/settings/130.webserver.php b/actions/admin/settings/130.webserver.php index a4fd26d5..84861fd9 100644 --- a/actions/admin/settings/130.webserver.php +++ b/actions/admin/settings/130.webserver.php @@ -181,7 +181,8 @@ return [ 'label' => lng('serversettings.logfiles_format'), 'settinggroup' => 'system', 'varname' => 'logfiles_format', - 'type' => 'text', + 'type' => (strpos(Settings::Get('system.logfiles_format'), '"') !== false ? 'textarea' : 'text'), + 'string_regexp' => '/^[^\0\r\n<>]*$/i', 'default' => '', 'string_emptyallowed' => true, 'save_method' => 'storeSettingField', @@ -307,7 +308,8 @@ return [ 'type' => 'text', 'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i', 'default' => '/etc/init.d/apache2 reload', - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_phpreload_command' => [ 'label' => lng('serversettings.phpreload_command'), @@ -319,7 +321,8 @@ return [ 'save_method' => 'storeSettingField', 'websrv_avail' => [ 'nginx' - ] + ], + 'required_otp' => true ], 'system_nginx_php_backend' => [ 'label' => lng('serversettings.nginx_php_backend'), diff --git a/actions/admin/settings/131.ssl.php b/actions/admin/settings/131.ssl.php index e2e00b0d..023f10a8 100644 --- a/actions/admin/settings/131.ssl.php +++ b/actions/admin/settings/131.ssl.php @@ -157,7 +157,8 @@ return [ 'string_type' => 'file', 'default' => '/root/.acme.sh/acme.sh', 'save_method' => 'storeSettingField', - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ], 'system_letsencryptacmeconf' => [ 'label' => lng('serversettings.letsencryptacmeconf'), diff --git a/actions/admin/settings/136.phpfpm.php b/actions/admin/settings/136.phpfpm.php index a408c481..68e64517 100644 --- a/actions/admin/settings/136.phpfpm.php +++ b/actions/admin/settings/136.phpfpm.php @@ -126,7 +126,8 @@ return [ 'type' => 'textarea', 'default' => '', 'save_method' => 'storeSettingField', - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ], 'phpfpm_ini_values' => [ 'label' => lng('phpfpm.ini_values'), @@ -135,7 +136,8 @@ return [ 'type' => 'textarea', 'default' => '', 'save_method' => 'storeSettingField', - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ], 'phpfpm_ini_admin_flags' => [ 'label' => lng('phpfpm.ini_admin_flags'), @@ -144,7 +146,8 @@ return [ 'type' => 'textarea', 'default' => '', 'save_method' => 'storeSettingField', - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ], 'phpfpm_ini_admin_values' => [ 'label' => lng('phpfpm.ini_admin_values'), @@ -153,7 +156,8 @@ return [ 'type' => 'textarea', 'default' => '', 'save_method' => 'storeSettingField', - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ] ] ] diff --git a/actions/admin/settings/160.nameserver.php b/actions/admin/settings/160.nameserver.php index 5d6906b0..184d5991 100644 --- a/actions/admin/settings/160.nameserver.php +++ b/actions/admin/settings/160.nameserver.php @@ -80,7 +80,8 @@ return [ 'type' => 'text', 'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i', 'default' => '/etc/init.d/bind9 reload', - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_nameservers' => [ 'label' => lng('serversettings.nameservers'), @@ -111,7 +112,8 @@ return [ 'string_delimiter' => ',', 'string_emptyallowed' => true, 'default' => '', - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_powerdns_mode' => [ 'label' => lng('serversettings.powerdns_mode'), diff --git a/actions/admin/settings/180.dkim.php b/actions/admin/settings/180.dkim.php index 7b2aada5..2feb67bc 100644 --- a/actions/admin/settings/180.dkim.php +++ b/actions/admin/settings/180.dkim.php @@ -137,7 +137,8 @@ return [ 'type' => 'text', 'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i', 'default' => '/etc/init.d/dkim-filter restart', - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ] ] ] diff --git a/actions/admin/settings/210.security.php b/actions/admin/settings/210.security.php index cf0712a8..c45fa615 100644 --- a/actions/admin/settings/210.security.php +++ b/actions/admin/settings/210.security.php @@ -37,7 +37,8 @@ return [ 'varname' => 'unix_names', 'type' => 'checkbox', 'default' => true, - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_mailpwcleartext' => [ 'label' => lng('serversettings.mailpwcleartext'), @@ -46,7 +47,8 @@ return [ 'type' => 'checkbox', 'default' => false, 'save_method' => 'storeSettingField', - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ], 'system_passwordcryptfunc' => [ 'label' => lng('serversettings.passwordcryptfunc'), @@ -59,7 +61,8 @@ return [ 'getAvailablePasswordHashes' ], 'save_method' => 'storeSettingField', - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ], 'system_allow_error_report_admin' => [ 'label' => lng('serversettings.allow_error_report_admin'), @@ -67,7 +70,8 @@ return [ 'varname' => 'allow_error_report_admin', 'type' => 'checkbox', 'default' => false, - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_allow_error_report_customer' => [ 'label' => lng('serversettings.allow_error_report_customer'), @@ -75,7 +79,8 @@ return [ 'varname' => 'allow_error_report_customer', 'type' => 'checkbox', 'default' => false, - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_allow_customer_shell' => [ 'label' => lng('serversettings.allow_allow_customer_shell'), @@ -84,7 +89,8 @@ return [ 'type' => 'checkbox', 'default' => false, 'save_method' => 'storeSettingField', - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ], 'system_available_shells' => [ 'label' => lng('serversettings.available_shells'), @@ -94,7 +100,8 @@ return [ 'string_emptyallowed' => true, 'default' => '', 'save_method' => 'storeSettingField', - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ], 'system_froxlorusergroup' => [ 'label' => lng('serversettings.froxlorusergroup'), @@ -108,7 +115,8 @@ return [ 'checkLocalGroup' ], 'visible' => Settings::Get('system.nssextrausers'), - 'advanced_mode' => true + 'advanced_mode' => true, + 'required_otp' => true ], ] ] diff --git a/actions/admin/settings/220.quota.php b/actions/admin/settings/220.quota.php index b7682bd1..8c0bbab3 100644 --- a/actions/admin/settings/220.quota.php +++ b/actions/admin/settings/220.quota.php @@ -44,24 +44,30 @@ return [ 'settinggroup' => 'system', 'varname' => 'diskquota_repquota_path', 'type' => 'text', + 'string_type' => 'file', 'default' => '/usr/sbin/repquota', - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_diskquota_quotatool_path' => [ 'label' => lng('serversettings.diskquota_quotatool_path.description'), 'settinggroup' => 'system', 'varname' => 'diskquota_quotatool_path', 'type' => 'text', + 'string_type' => 'file', 'default' => '/usr/bin/quotatool', - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ], 'system_diskquota_customer_partition' => [ 'label' => lng('serversettings.diskquota_customer_partition.description'), 'settinggroup' => 'system', 'varname' => 'diskquota_customer_partition', 'type' => 'text', + 'string_type' => 'file', 'default' => '/dev/root', - 'save_method' => 'storeSettingField' + 'save_method' => 'storeSettingField', + 'required_otp' => true ] ] ] diff --git a/admin_apcuinfo.php b/admin_apcuinfo.php index aba02807..f4a7e340 100644 --- a/admin_apcuinfo.php +++ b/admin_apcuinfo.php @@ -62,7 +62,7 @@ if ($action == 'delete' && function_exists('apcu_clear_cache') && $userinfo['cha } if (!function_exists('apcu_cache_info') || !function_exists('apcu_sma_info')) { - Response::standardError(lng('error.no_apcuinfo')); + Response::standardError('no_apcuinfo'); } if ($page == 'showinfo' && $userinfo['change_serversettings'] == '1') { diff --git a/admin_domains.php b/admin_domains.php index 59755968..4675e9fa 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -30,9 +30,9 @@ use Froxlor\Api\Commands\Customers as Customers; use Froxlor\Api\Commands\Domains as Domains; use Froxlor\Bulk\DomainBulkAction; use Froxlor\Cron\TaskId; +use Froxlor\CurrentUser; use Froxlor\Customer\Customer; use Froxlor\Database\Database; -use Froxlor\Domain\Domain; use Froxlor\FileDir; use Froxlor\FroxlorLogger; use Froxlor\Settings; @@ -45,7 +45,6 @@ use Froxlor\UI\Request; use Froxlor\UI\Response; use Froxlor\User; use Froxlor\Validate\Validate; -use Froxlor\CurrentUser; $id = (int)Request::any('id'); @@ -114,15 +113,11 @@ if ($page == 'domains' || $page == 'overview') { } elseif ($alias_check['count'] > 0) { Response::standardError('domains_cantdeletedomainwithaliases'); } else { - $showcheck = false; - if (Domain::domainHasMainSubDomains($id)) { - $showcheck = true; - } - HTML::askYesNoWithCheckbox('admin_domain_reallydelete', 'remove_subbutmain_domains', $filename, [ + HTML::askYesNo('admin_domain_reallydelete', $filename, [ 'id' => $id, 'page' => $page, 'action' => $action - ], $idna_convert->decode($result['domain']), $showcheck); + ], $idna_convert->decode($result['domain'])); } } } elseif ($action == 'add') { @@ -252,21 +247,6 @@ if ($page == 'domains' || $page == 'overview') { $domains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')'; } - $subtodomains = [ - 0 => lng('domains.nosubtomaindomain') - ]; - $result_domains_stmt = Database::prepare(" - SELECT `d`.`id`, `d`.`domain`, `c`.`loginname` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` - WHERE `d`.`aliasdomain` IS NULL AND `d`.`parentdomainid` = 0 AND `d`.`ismainbutsubto` = 0 " . $standardsubdomains . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid") . " - AND `d`.`customerid`=`c`.`customerid` ORDER BY `loginname`, `domain` ASC - "); - // params from above still valid - Database::pexecute($result_domains_stmt, $params); - - while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { - $subtodomains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')'; - } - $phpconfigs = []; $configs = Database::query(" SELECT c.*, fc.description as interpreter @@ -287,7 +267,7 @@ if ($page == 'domains' || $page == 'overview') { 1 => lng('domain.homedir'), 2 => lng('domain.docparent') ]; - + // create serveralias options $serveraliasoptions = [ 0 => lng('domains.serveraliasoption_wildcard'), @@ -469,27 +449,6 @@ if ($page == 'domains' || $page == 'overview') { $domains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']); } - $subtodomains = [ - 0 => lng('domains.nosubtomaindomain') - ]; - $result_domains_stmt = Database::prepare(" - SELECT `d`.`id`, `d`.`domain` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` - WHERE `d`.`aliasdomain` IS NULL AND `d`.`parentdomainid` = '0' AND `d`.`id` <> :id - AND `c`.`standardsubdomain`<>`d`.`id` AND `c`.`customerid`=`d`.`customerid`" . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid") . " - ORDER BY `d`.`domain` ASC - "); - $params = [ - 'id' => $result['id'] - ]; - if ($userinfo['customers_see_all'] == '0') { - $params['adminid'] = $userinfo['adminid']; - } - Database::pexecute($result_domains_stmt, $params); - - while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { - $subtodomains[$row_domain['id']] = $idna_convert->decode($row_domain['domain']); - } - if ($userinfo['ip'] == "-1") { $result_ipsandports_stmt = Database::query(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='0' ORDER BY `ip`, `port` ASC @@ -556,7 +515,7 @@ if ($page == 'domains' || $page == 'overview') { 1 => lng('domain.homedir'), 2 => lng('domain.docparent') ]; - + $serveraliasoptions = [ 0 => lng('domains.serveraliasoption_wildcard'), 1 => lng('domains.serveraliasoption_www'), @@ -676,6 +635,23 @@ if ($page == 'domains' || $page == 'overview') { 'alert_msg' => lng('domains.import_description') ]); } + } elseif ($action == 'duplicate') { + if (isset($_POST['send']) && $_POST['send'] == 'send') { + try { + Domains::getLocal($userinfo, $_POST)->duplicate(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + Response::redirectTo($filename, [ + 'page' => $page, + 'searchfield' => 'd.domain_ace', + 'searchtext' => Request::post('domain', "") + ]); + } else { + Response::redirectTo($filename, [ + 'page' => 'overview' + ]); + } } } elseif ($page == 'domainssleditor') { require_once __DIR__ . '/ssl_editor.php'; diff --git a/admin_index.php b/admin_index.php index adb9b70b..0cca79fa 100644 --- a/admin_index.php +++ b/admin_index.php @@ -31,6 +31,7 @@ use Froxlor\Api\Commands\Froxlor as Froxlor; use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\FroxlorLogger; +use Froxlor\Language; use Froxlor\Settings; use Froxlor\System\Cronjob; use Froxlor\System\Crypt; @@ -38,7 +39,6 @@ use Froxlor\UI\Panel\UI; use Froxlor\UI\Request; use Froxlor\UI\Response; use Froxlor\Validate\Validate; -use Froxlor\Language; $id = (int)Request::any('id'); @@ -197,107 +197,104 @@ if ($page == 'overview') { 'outstanding_tasks' => $outstanding_tasks, 'cron_last_runs' => $cron_last_runs ]); -} elseif ($page == 'change_password') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $old_password = Validate::validate($_POST['old_password'], 'old password'); +} elseif ($page == 'profile') { + $languages = Language::getLanguages(); - if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_ADMINS, 'adminid')) { - Response::standardError('oldpasswordnotcorrect'); - } + if (!empty($_POST)) { + if ($_POST['send'] == 'changepassword') { + $old_password = Validate::validate($_POST['old_password'], 'old password'); - try { - $new_password = Crypt::validatePassword($_POST['new_password'], 'new password'); - $new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm'); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } + if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_ADMINS, 'adminid')) { + Response::standardError('oldpasswordnotcorrect'); + } - if ($old_password == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.old_password' - ]); - } elseif ($new_password == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.new_password' - ]); - } elseif ($new_password_confirm == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.new_password_confirm' - ]); - } elseif ($new_password != $new_password_confirm) { - Response::standardError('newpasswordconfirmerror'); - } else { try { - Admins::getLocal($userinfo, [ - 'id' => $userinfo['adminid'], - 'admin_password' => $new_password - ])->update(); + $new_password = Crypt::validatePassword($_POST['new_password'], 'new password'); + $new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm'); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } - $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'changed password'); + + if ($old_password == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.old_password' + ]); + } elseif ($new_password == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.new_password' + ]); + } elseif ($new_password_confirm == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.new_password_confirm' + ]); + } elseif ($new_password != $new_password_confirm) { + Response::standardError('newpasswordconfirmerror'); + } else { + try { + Admins::getLocal($userinfo, [ + 'id' => $userinfo['adminid'], + 'admin_password' => $new_password + ])->update(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'changed password'); + Response::redirectTo($filename); + } + } elseif ($_POST['send'] == 'changetheme') { + if (Settings::Get('panel.allow_theme_change_admin') == 1) { + $theme = Validate::validate($_POST['theme'], 'theme'); + try { + Admins::getLocal($userinfo, [ + 'id' => $userinfo['adminid'], + 'theme' => $theme + ])->update(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + + $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her theme to '" . $theme . "'"); + } + Response::redirectTo($filename); + } elseif ($_POST['send'] == 'changelanguage') { + $def_language = Validate::validate($_POST['def_language'], 'default language'); + + if (isset($languages[$def_language])) { + try { + Admins::getLocal($userinfo, [ + 'id' => $userinfo['adminid'], + 'def_language' => $def_language + ])->update(); + CurrentUser::setField('language', $def_language); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + } + $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her default language to '" . $def_language . "'"); Response::redirectTo($filename); } } else { - UI::view('user/change_password.html.twig'); - } -} elseif ($page == 'change_language') { - $languages = Language::getLanguages(); - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $def_language = Validate::validate($_POST['def_language'], 'default language'); - - if (isset($languages[$def_language])) { - try { - Admins::getLocal($userinfo, [ - 'id' => $userinfo['adminid'], - 'def_language' => $def_language - ])->update(); - CurrentUser::setField('language', $def_language); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } + // change theme + $default_theme = Settings::Get('panel.default_theme'); + if ($userinfo['theme'] != '') { + $default_theme = $userinfo['theme']; } - $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her default language to '" . $def_language . "'"); - Response::redirectTo($filename); - } else { + $themes_avail = UI::getThemes(); + + // change language $default_lang = Settings::Get('panel.standardlanguage'); if ($userinfo['def_language'] != '') { $default_lang = $userinfo['def_language']; } - UI::view('user/change_language.html.twig', [ - 'languages' => $languages, - 'default_lang' => $default_lang - ]); - } -} elseif ($page == 'change_theme') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $theme = Validate::validate($_POST['theme'], 'theme'); - try { - Admins::getLocal($userinfo, [ - 'id' => $userinfo['adminid'], - 'theme' => $theme - ])->update(); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } - - $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her theme to '" . $theme . "'"); - Response::redirectTo($filename); - } else { - $default_theme = Settings::Get('panel.default_theme'); - if ($userinfo['theme'] != '') { - $default_theme = $userinfo['theme']; - } - - $themes_avail = UI::getThemes(); - - UI::view('user/change_theme.html.twig', [ + UI::view('user/profile.html.twig', [ 'themes' => $themes_avail, - 'default_theme' => $default_theme + 'default_theme' => $default_theme, + 'languages' => $languages, + 'default_lang' => $default_lang, ]); } } elseif ($page == 'send_error_report' && Settings::Get('system.allow_error_report_admin') == '1') { diff --git a/admin_opcacheinfo.php b/admin_opcacheinfo.php index 27654a44..eddace9e 100644 --- a/admin_opcacheinfo.php +++ b/admin_opcacheinfo.php @@ -33,9 +33,9 @@ const AREA = 'admin'; require __DIR__ . '/lib/init.php'; use Froxlor\FroxlorLogger; +use Froxlor\UI\HTML; use Froxlor\UI\Panel\UI; use Froxlor\UI\Response; -use Froxlor\UI\HTML; if ($action == 'reset' && function_exists('opcache_reset') && $userinfo['change_serversettings'] == '1') { if ($_POST['send'] == 'send') { @@ -57,252 +57,30 @@ if ($action == 'reset' && function_exists('opcache_reset') && $userinfo['change_ } } -if (!function_exists('opcache_get_configuration')) { - Response::standardError(lng('error.no_opcacheinfo')); +if (!extension_loaded('Zend OPcache')) { + Response::standardError('no_opcacheinfo'); +} + +$ocEnabled = ini_get('opcache.enable'); +if (empty($ocEnabled)) { + Response::standardError('inactive_opcacheinfo'); } if ($page == 'showinfo' && $userinfo['change_serversettings'] == '1') { $time = time(); $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "viewed OPcache info"); - $optimizationLevels = [ - 1 << 0 => 'CSE, STRING construction', - 1 << 1 => 'Constant conversion and jumps', - 1 << 2 => '++, +=, series of jumps', - 1 << 3 => 'INIT_FCALL_BY_NAME -> DO_FCALL', - 1 << 4 => 'CFG based optimization', - 1 << 5 => 'DFA based optimization', - 1 << 6 => 'CALL GRAPH optimization', - 1 << 7 => 'SCCP (constant propagation)', - 1 << 8 => 'TMP VAR usage', - 1 << 9 => 'NOP removal', - 1 << 10 => 'Merge equal constants', - 1 << 11 => 'Adjust used stack', - 1 << 12 => 'Remove unused variables', - 1 << 13 => 'DCE (dead code elimination)', - 1 << 14 => '(unsafe) Collect constants', - 1 << 15 => 'Inline functions' - ]; - - $jitModes = [ - [ - 'flag' => 'CPU-specific optimization', - 'value' => [ - 'Disable CPU-specific optimization', - 'Enable use of AVX, if the CPU supports it' - ] - ], - [ - 'flag' => 'Register allocation', - 'value' => [ - 'Do not perform register allocation', - 'Perform block-local register allocation', - 'Perform global register allocation' - ] - ], - [ - 'flag' => 'Trigger', - 'value' => [ - 'Compile all functions on script load', - 'Compile functions on first execution', - 'Profile functions on first request and compile the hottest functions afterwards', - 'Profile on the fly and compile hot functions', - 'Currently unused', - 'Use tracing JIT. Profile on the fly and compile traces for hot code segments' - ] - ], - [ - 'flag' => 'Optimization level', - 'value' => [ - 'No JIT', - 'Minimal JIT (call standard VM handlers)', - 'Inline VM handlers', - 'Use type inference', - 'Use call graph', - 'Optimize whole script' - ] - ] - ]; - - $jitModeMapping = [ - 'tracing' => 1254, - 'on' => 1254, - 'function' => 1205 - ]; - - $status = opcache_get_status(false); - $config = opcache_get_configuration(); - $missingConfig = array_diff_key(ini_get_all('zend opcache', false), $config['directives']); - if (!empty($missingConfig)) { - $config['directives'] = array_merge($config['directives'], $missingConfig); - } - - $files = []; - if (!empty($status['scripts'])) { - uasort($status['scripts'], static function ($a, $b) { - return $a['hits'] <=> $b['hits']; - }); - foreach ($status['scripts'] as &$file) { - $file['full_path'] = str_replace('\\', '/', $file['full_path']); - $file['readable'] = [ - 'hits' => number_format($file['hits']), - 'memory_consumption' => bsize($file['memory_consumption']) - ]; - } - $files = array_values($status['scripts']); - } - - if ($config['directives']['opcache.file_cache_only'] || !empty($status['file_cache_only'])) { - $overview = false; - } else { - $status['opcache_statistics']['start_time'] = $status['opcache_statistics']['start_time'] ?? time(); - $status['opcache_statistics']['last_restart_time'] = $status['opcache_statistics']['last_restart_time'] ?? time(); - - $overview = array_merge( - $status['memory_usage'], - $status['opcache_statistics'], - [ - 'total_memory' => $config['directives']['opcache.memory_consumption'], - 'used_memory_percentage' => round(100 * ( - ($status['memory_usage']['used_memory'] + $status['memory_usage']['wasted_memory']) - / $config['directives']['opcache.memory_consumption'] - )), - 'hit_rate_percentage' => round($status['opcache_statistics']['opcache_hit_rate']), - 'used_key_percentage' => round(100 * ($status['opcache_statistics']['num_cached_keys'] - / $status['opcache_statistics']['max_cached_keys'] - )), - 'wasted_percentage' => round($status['memory_usage']['current_wasted_percentage'], 2), - 'readable' => [ - 'total_memory' => bsize($config['directives']['opcache.memory_consumption']), - 'used_memory' => bsize($status['memory_usage']['used_memory']), - 'free_memory' => bsize($status['memory_usage']['free_memory']), - 'wasted_memory' => bsize($status['memory_usage']['wasted_memory']), - 'num_cached_scripts' => number_format($status['opcache_statistics']['num_cached_scripts']), - 'hits' => number_format($status['opcache_statistics']['hits']), - 'misses' => number_format($status['opcache_statistics']['misses']), - 'blacklist_miss' => number_format($status['opcache_statistics']['blacklist_misses']), - 'num_cached_keys' => number_format($status['opcache_statistics']['num_cached_keys']), - 'max_cached_keys' => number_format($status['opcache_statistics']['max_cached_keys']), - 'interned' => null, - 'start_time' => (new DateTimeImmutable("@{$status['opcache_statistics']['start_time']}")) - ->setTimezone(new DateTimeZone(date_default_timezone_get())) - ->format('Y-m-d H:i:s'), - 'last_restart_time' => ($status['opcache_statistics']['last_restart_time'] == 0 - ? 'never' - : (new DateTimeImmutable("@{$status['opcache_statistics']['last_restart_time']}")) - ->setTimezone(new DateTimeZone(date_default_timezone_get())) - ->format('Y-m-d H:i:s') - ) - ] - ] - ); - } - - $preload = []; - if (!empty($status['preload_statistics']['scripts'])) { - $preload = $status['preload_statistics']['scripts']; - sort($preload, SORT_STRING); - if ($overview) { - $overview['preload_memory'] = $status['preload_statistics']['memory_consumption']; - $overview['readable']['preload_memory'] = bsize($status['preload_statistics']['memory_consumption']); - } - } - - if (!empty($status['interned_strings_usage'])) { - $overview['readable']['interned'] = [ - 'buffer_size' => bsize($status['interned_strings_usage']['buffer_size']), - 'strings_used_memory' => bsize($status['interned_strings_usage']['used_memory']), - 'strings_free_memory' => bsize($status['interned_strings_usage']['free_memory']), - 'number_of_strings' => number_format($status['interned_strings_usage']['number_of_strings']) - ]; - } - - if ($overview && !empty($status['jit'])) { - $overview['jit_buffer_used_percentage'] = ($status['jit']['buffer_size'] - ? round(100 * (($status['jit']['buffer_size'] - $status['jit']['buffer_free']) / $status['jit']['buffer_size'])) - : 0 - ); - $overview['readable'] = array_merge($overview['readable'], [ - 'jit_buffer_size' => bsize($status['jit']['buffer_size']), - 'jit_buffer_free' => bsize($status['jit']['buffer_free']) - ]); - } - - $directives = []; - ksort($config['directives']); - foreach ($config['directives'] as $k => $v) { - if (in_array($k, ['opcache.max_file_size', 'opcache.memory_consumption', 'opcache.jit_buffer_size']) && $v) { - $v = bsize($v) . " ({$v})"; - } elseif ($k === 'opcache.optimization_level') { - $levels = []; - foreach ($optimizationLevels as $level => $info) { - if ($level & $v) { - $levels[] = "{$info} [{$level}]"; - } - } - $v = $levels ?: 'none'; - } elseif ($k === 'opcache.jit') { - if ($v === '1') { - $v = 'on'; - } - if (isset($jitModeMapping[$v]) || is_numeric($v)) { - $levels = []; - foreach (str_split((string)($jitModeMapping[$v] ?? $v)) as $type => $level) { - $levels[] = "{$level}: {$jitModes[$type]['value'][$level]} ({$jitModes[$type]['flag']})"; - } - $v = [$v, $levels]; - } elseif (empty($v) || strtolower($v) === 'off') { - $v = 'Off'; - } - } - $directives[] = [ - 'k' => $k, - 'v' => $v - ]; - } - - $version = array_merge( - $config['version'], - [ - 'php' => phpversion(), - 'server' => $_SERVER['SERVER_SOFTWARE'] ?: '', - 'host' => (function_exists('gethostname') - ? gethostname() - : (php_uname('n') - ?: (empty($_SERVER['SERVER_NAME']) - ? $_SERVER['HOST_NAME'] - : $_SERVER['SERVER_NAME'] - ) - ) - ) - ] - ); + $opcache = (new \Amnuts\Opcache\Service())->getData(); UI::view('settings/opcacheinfo.html.twig', [ 'opcacheinfo' => [ - 'version' => $version, - 'overview' => $overview, - 'files' => $files, - 'preload' => $preload, - 'directives' => $directives, - 'blacklist' => $config['blacklist'], - 'functions' => get_extension_funcs('Zend OPcache') + 'version' => $opcache['version'], + 'overview' => $opcache['overview'], + 'files' => $opcache['files'], + 'preload' => $opcache['preload'], + 'directives' => $opcache['directives'], + 'blacklist' => $opcache['blacklist'], + 'functions' => $opcache['functions'], ] ]); } - -function bsize($size) -{ - $i = 0; - $val = ['b', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - while (($size / 1024) > 1) { - $size /= 1024; - ++$i; - } - return sprintf( - '%.2f%s%s', - $size, - '', - $val[$i] - ); -} diff --git a/admin_settings.php b/admin_settings.php index 18a1d60a..8d6fe497 100644 --- a/admin_settings.php +++ b/admin_settings.php @@ -70,14 +70,15 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') { // check if the session timeout is too low #815 if (isset($_POST['session_sessiontimeout']) && $_POST['session_sessiontimeout'] < 60) { - Response::standardError(lng('error.session_timeout'), lng('error.session_timeout_desc')); + Response::standardError(['session_timeout', 'session_timeout_desc']); } try { if (Form::processForm($settings_data, $_POST, [ 'filename' => $filename, 'action' => $action, - 'page' => $page + 'page' => $page, + 'part' => $_part, ], $_part, $settings_all, $settings_part, $only_enabledisable)) { $log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "rebuild configfiles due to changed setting"); Cronjob::inserttask(TaskId::REBUILD_VHOST); @@ -132,7 +133,7 @@ if ($page == 'overview' && $userinfo['change_serversettings'] == '1') { } } } else { - Response::standardError(lng('error.no_phpinfo')); + Response::standardError('error.no_phpinfo'); } UI::view('settings/phpinfo.html.twig', [ 'phpversion' => PHP_VERSION, diff --git a/composer.json b/composer.json index aac3c0fd..f3d884fb 100644 --- a/composer.json +++ b/composer.json @@ -46,15 +46,17 @@ "ext-fileinfo": "*", "ext-gmp": "*", "ext-gd": "*", + "ext-gnupg": "*", "phpmailer/phpmailer": "~6.0", "monolog/monolog": "^1.24", "robthree/twofactorauth": "^1.6", "froxlor/idna-convert-legacy": "^2.1", "voku/anti-xss": "^4.1", "twig/twig": "^3.3", - "erusev/parsedown": "^1.7", "symfony/console": "^5.4", - "pear/net_dns2": "^1.5" + "pear/net_dns2": "^1.5", + "amnuts/opcache-gui": "^3.4", + "league/commonmark": "^2.4" }, "require-dev": { "phpunit/phpunit": "^9", diff --git a/composer.lock b/composer.lock index 9cf44dfa..c63fbb7a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,33 +4,31 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2de39e6b85579ce1f0c2f7a16d57ede3", + "content-hash": "cf7a156f58ab3f86204a4b4d263621e2", "packages": [ { - "name": "erusev/parsedown", - "version": "1.7.4", + "name": "amnuts/opcache-gui", + "version": "3.5.1", "source": { "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + "url": "https://github.com/amnuts/opcache-gui.git", + "reference": "c4af1edefef3494dd483de5b168af4f08179fda5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "url": "https://api.github.com/repos/amnuts/opcache-gui/zipball/c4af1edefef3494dd483de5b168af4f08179fda5", + "reference": "c4af1edefef3494dd483de5b168af4f08179fda5", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" + "ext-json": "*", + "ext-zend-opcache": "*", + "php": ">=7.1.0" }, "type": "library", "autoload": { - "psr-0": { - "Parsedown": "" + "psr-4": { + "Amnuts\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -39,22 +37,111 @@ ], "authors": [ { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" + "name": "Andrew Collington", + "email": "andy@amnuts.com", + "homepage": "https://blog.amnuts.com/", + "role": "Developer" + }, + { + "name": "Contributors", + "homepage": "https://github.com/amnuts/opcache-gui/graphs/contributors" } ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", + "description": "A clean, effective and responsive interface for Zend OPcache, with real(ish)-time monitoring, filtering and the ability to invalidate files", "keywords": [ - "markdown", - "parser" + "Opcache", + "cache", + "gui", + "interface", + "opcodes" ], "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" + "email": "andy@amnuts.com", + "issues": "https://github.com/amnuts/opcache-gui/issues", + "source": "https://github.com/amnuts/opcache-gui/tree/3.5.1" }, - "time": "2019-12-30T22:54:17+00:00" + "funding": [ + { + "url": "https://github.com/amnuts", + "type": "github" + } + ], + "time": "2023-08-25T18:02:59+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "f41715465d65213d644d3141a6a93081be5d3549" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", + "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + }, + "time": "2022-10-27T11:44:00+00:00" }, { "name": "froxlor/idna-convert-legacy", @@ -112,6 +199,194 @@ }, "time": "2019-12-31T12:16:30+00:00" }, + { + "name": "league/commonmark", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2023-08-30T16:55:00+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, { "name": "monolog/monolog", "version": "1.27.1", @@ -198,6 +473,154 @@ ], "time": "2022-06-09T08:53:42+00:00" }, + { + "name": "nette/schema", + "version": "v1.2.4", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab", + "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": "7.1 - 8.3" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.4" + }, + "time": "2023-08-05T18:56:25+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.10", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2", + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.4" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.10" + }, + "time": "2023-07-30T15:38:18+00:00" + }, { "name": "pear/net_dns2", "version": "v1.5.3", @@ -251,16 +674,16 @@ }, { "name": "phpmailer/phpmailer", - "version": "v6.8.0", + "version": "v6.8.1", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "df16b615e371d81fb79e506277faea67a1be18f1" + "reference": "e88da8d679acc3824ff231fdc553565b802ac016" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/df16b615e371d81fb79e506277faea67a1be18f1", - "reference": "df16b615e371d81fb79e506277faea67a1be18f1", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e88da8d679acc3824ff231fdc553565b802ac016", + "reference": "e88da8d679acc3824ff231fdc553565b802ac016", "shasum": "" }, "require": { @@ -270,13 +693,13 @@ "php": ">=5.5.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "doctrine/annotations": "^1.2.6 || ^1.13.3", "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.7.1", + "squizlabs/php_codesniffer": "^3.7.2", "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { @@ -319,7 +742,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.0" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.1" }, "funding": [ { @@ -327,7 +750,7 @@ "type": "github" } ], - "time": "2023-03-06T14:43:22+00:00" + "time": "2023-08-29T08:26:30+00:00" }, { "name": "psr/container", @@ -377,6 +800,56 @@ }, "time": "2021-11-05T16:50:12+00:00" }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "psr/log", "version": "1.1.4", @@ -499,16 +972,16 @@ }, { "name": "symfony/console", - "version": "v5.4.22", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8" + "reference": "f4f71842f24c2023b91237c72a365306f3c58827" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3cd51fd2e6c461ca678f84d419461281bd87a0a8", - "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8", + "url": "https://api.github.com/repos/symfony/console/zipball/f4f71842f24c2023b91237c72a365306f3c58827", + "reference": "f4f71842f24c2023b91237c72a365306f3c58827", "shasum": "" }, "require": { @@ -578,7 +1051,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.22" + "source": "https://github.com/symfony/console/tree/v5.4.28" }, "funding": [ { @@ -594,7 +1067,7 @@ "type": "tidelift" } ], - "time": "2023-03-25T09:27:28+00:00" + "time": "2023-08-07T06:12:30+00:00" }, { "name": "symfony/deprecation-contracts", @@ -665,16 +1138,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -689,7 +1162,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -727,7 +1200,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -743,20 +1216,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "927013f3aac555983a5059aada98e1907d842695" + "reference": "6de50471469b8c9afc38164452ab2b6170ee71c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/927013f3aac555983a5059aada98e1907d842695", - "reference": "927013f3aac555983a5059aada98e1907d842695", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/6de50471469b8c9afc38164452ab2b6170ee71c1", + "reference": "6de50471469b8c9afc38164452ab2b6170ee71c1", "shasum": "" }, "require": { @@ -771,7 +1244,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -810,7 +1283,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.28.0" }, "funding": [ { @@ -826,20 +1299,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -851,7 +1324,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -891,7 +1364,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -907,20 +1380,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -932,7 +1405,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -975,7 +1448,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -991,20 +1464,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -1019,7 +1492,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1058,7 +1531,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -1074,20 +1547,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", "shasum": "" }, "require": { @@ -1096,7 +1569,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1134,7 +1607,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" }, "funding": [ { @@ -1150,20 +1623,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", "shasum": "" }, "require": { @@ -1172,7 +1645,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1213,7 +1686,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" }, "funding": [ { @@ -1229,20 +1702,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -1251,7 +1724,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1296,7 +1769,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -1312,7 +1785,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/service-contracts", @@ -1399,16 +1872,16 @@ }, { "name": "symfony/string", - "version": "v5.4.22", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" + "reference": "1181fe9270e373537475e826873b5867b863883c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "url": "https://api.github.com/repos/symfony/string/zipball/1181fe9270e373537475e826873b5867b863883c", + "reference": "1181fe9270e373537475e826873b5867b863883c", "shasum": "" }, "require": { @@ -1465,7 +1938,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.22" + "source": "https://github.com/symfony/string/tree/v5.4.26" }, "funding": [ { @@ -1481,20 +1954,20 @@ "type": "tidelift" } ], - "time": "2023-03-14T06:11:53+00:00" + "time": "2023-06-28T12:46:07+00:00" }, { "name": "twig/twig", - "version": "v3.5.1", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15" + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15", - "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", "shasum": "" }, "require": { @@ -1503,15 +1976,10 @@ "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } - }, "autoload": { "psr-4": { "Twig\\": "src/" @@ -1545,7 +2013,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.5.1" + "source": "https://github.com/twigphp/Twig/tree/v3.7.1" }, "funding": [ { @@ -1557,20 +2025,20 @@ "type": "tidelift" } ], - "time": "2023-02-08T07:49:20+00:00" + "time": "2023-08-28T11:09:02+00:00" }, { "name": "voku/anti-xss", - "version": "4.1.41", + "version": "4.1.42", "source": { "type": "git", "url": "https://github.com/voku/anti-xss.git", - "reference": "55a403436494e44a2547a8d42de68e6cad4bca1d" + "reference": "bca1f8607e55a3c5077483615cd93bd8f11bd675" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/anti-xss/zipball/55a403436494e44a2547a8d42de68e6cad4bca1d", - "reference": "55a403436494e44a2547a8d42de68e6cad4bca1d", + "url": "https://api.github.com/repos/voku/anti-xss/zipball/bca1f8607e55a3c5077483615cd93bd8f11bd675", + "reference": "bca1f8607e55a3c5077483615cd93bd8f11bd675", "shasum": "" }, "require": { @@ -1616,7 +2084,7 @@ ], "support": { "issues": "https://github.com/voku/anti-xss/issues", - "source": "https://github.com/voku/anti-xss/tree/4.1.41" + "source": "https://github.com/voku/anti-xss/tree/4.1.42" }, "funding": [ { @@ -1640,7 +2108,7 @@ "type": "tidelift" } ], - "time": "2023-02-12T15:56:55+00:00" + "time": "2023-07-03T14:40:46+00:00" }, { "name": "voku/portable-ascii", @@ -2089,16 +2557,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.4", + "version": "v4.17.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", "shasum": "" }, "require": { @@ -2139,22 +2607,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" }, - "time": "2023-03-05T19:49:14+00:00" + "time": "2023-08-13T19:53:39+00:00" }, { "name": "pdepend/pdepend", - "version": "2.13.0", + "version": "2.14.0", "source": { "type": "git", "url": "https://github.com/pdepend/pdepend.git", - "reference": "31be7cd4f305f3f7b52af99c1cb13fc938d1cfad" + "reference": "1121d4b04af06e33e9659bac3a6741b91cab1de1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pdepend/pdepend/zipball/31be7cd4f305f3f7b52af99c1cb13fc938d1cfad", - "reference": "31be7cd4f305f3f7b52af99c1cb13fc938d1cfad", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/1121d4b04af06e33e9659bac3a6741b91cab1de1", + "reference": "1121d4b04af06e33e9659bac3a6741b91cab1de1", "shasum": "" }, "require": { @@ -2188,9 +2656,15 @@ "BSD-3-Clause" ], "description": "Official version of pdepend to be handled with Composer", + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" + ], "support": { "issues": "https://github.com/pdepend/pdepend/issues", - "source": "https://github.com/pdepend/pdepend/tree/2.13.0" + "source": "https://github.com/pdepend/pdepend/tree/2.14.0" }, "funding": [ { @@ -2198,7 +2672,7 @@ "type": "tidelift" } ], - "time": "2023-02-28T20:56:15+00:00" + "time": "2023-05-26T13:15:18+00:00" }, { "name": "phar-io/manifest", @@ -2520,16 +2994,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.14", + "version": "1.10.34", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c" + "reference": "7f806b6f1403e6914c778140e2ba07c293cb4901" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7f806b6f1403e6914c778140e2ba07c293cb4901", + "reference": "7f806b6f1403e6914c778140e2ba07c293cb4901", "shasum": "" }, "require": { @@ -2578,20 +3052,20 @@ "type": "tidelift" } ], - "time": "2023-04-19T13:47:27+00:00" + "time": "2023-09-13T09:49:47+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "9.2.28", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "7134a5ccaaf0f1c92a4f5501a6c9f98ac4dcc0ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7134a5ccaaf0f1c92a4f5501a6c9f98ac4dcc0ef", + "reference": "7134a5ccaaf0f1c92a4f5501a6c9f98ac4dcc0ef", "shasum": "" }, "require": { @@ -2647,7 +3121,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.28" }, "funding": [ { @@ -2655,7 +3130,7 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2023-09-12T14:36:20+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2900,16 +3375,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.7", + "version": "9.6.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" + "reference": "a122c2ebd469b751d774aa0f613dc0d67697653f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a122c2ebd469b751d774aa0f613dc0d67697653f", + "reference": "a122c2ebd469b751d774aa0f613dc0d67697653f", "shasum": "" }, "require": { @@ -2924,7 +3399,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-code-coverage": "^9.2.28", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -2983,7 +3458,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.12" }, "funding": [ { @@ -2999,7 +3474,7 @@ "type": "tidelift" } ], - "time": "2023-04-14T08:58:40+00:00" + "time": "2023-09-12T14:39:31+00:00" }, { "name": "sebastian/cli-parser", @@ -3301,16 +3776,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -3355,7 +3830,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -3363,7 +3838,7 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", @@ -3507,16 +3982,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -3559,7 +4034,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" }, "funding": [ { @@ -3567,7 +4042,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", @@ -4086,16 +4561,16 @@ }, { "name": "symfony/config", - "version": "v5.4.21", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "2a6b1111d038adfa15d52c0871e540f3b352d1e4" + "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/2a6b1111d038adfa15d52c0871e540f3b352d1e4", - "reference": "2a6b1111d038adfa15d52c0871e540f3b352d1e4", + "url": "https://api.github.com/repos/symfony/config/zipball/8109892f27beed9252bd1f1c1880aeb4ad842650", + "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650", "shasum": "" }, "require": { @@ -4145,7 +4620,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.21" + "source": "https://github.com/symfony/config/tree/v5.4.26" }, "funding": [ { @@ -4161,20 +4636,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-07-19T20:21:11+00:00" }, { "name": "symfony/dependency-injection", - "version": "v5.4.22", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e1b7c1432efb4ad1dd89d62906187271e2601ed9" + "reference": "addc22fed594f9ce04e73ef6a9d3e2416f77192d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e1b7c1432efb4ad1dd89d62906187271e2601ed9", - "reference": "e1b7c1432efb4ad1dd89d62906187271e2601ed9", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/addc22fed594f9ce04e73ef6a9d3e2416f77192d", + "reference": "addc22fed594f9ce04e73ef6a9d3e2416f77192d", "shasum": "" }, "require": { @@ -4234,7 +4709,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.4.22" + "source": "https://github.com/symfony/dependency-injection/tree/v5.4.28" }, "funding": [ { @@ -4250,20 +4725,20 @@ "type": "tidelift" } ], - "time": "2023-03-10T10:02:45+00:00" + "time": "2023-08-14T10:47:38+00:00" }, { "name": "symfony/filesystem", - "version": "v5.4.21", + "version": "v5.4.25", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e75960b1bbfd2b8c9e483e0d74811d555ca3de9f" + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e75960b1bbfd2b8c9e483e0d74811d555ca3de9f", - "reference": "e75960b1bbfd2b8c9e483e0d74811d555ca3de9f", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", "shasum": "" }, "require": { @@ -4298,7 +4773,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.21" + "source": "https://github.com/symfony/filesystem/tree/v5.4.25" }, "funding": [ { @@ -4314,20 +4789,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-05-31T13:04:02+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", "shasum": "" }, "require": { @@ -4336,7 +4811,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4377,7 +4852,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" }, "funding": [ { @@ -4393,7 +4868,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "theseer/tokenizer", @@ -4467,7 +4942,8 @@ "ext-openssl": "*", "ext-fileinfo": "*", "ext-gmp": "*", - "ext-gd": "*" + "ext-gd": "*", + "ext-gnupg": "*" }, "platform-dev": { "ext-pcntl": "*" diff --git a/customer_domains.php b/customer_domains.php index 933edd13..af3c0068 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -27,6 +27,7 @@ const AREA = 'customer'; require __DIR__ . '/lib/init.php'; use Froxlor\Api\Commands\SubDomains as SubDomains; +use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\Domain\Domain; use Froxlor\FileDir; @@ -40,7 +41,6 @@ use Froxlor\UI\Panel\UI; use Froxlor\UI\Request; use Froxlor\UI\Response; use Froxlor\Validate\Validate; -use Froxlor\CurrentUser; // redirect if this customer page is hidden via settings if (Settings::IsInList('panel.customer_hide_options', 'domains')) { @@ -51,7 +51,7 @@ $id = (int)Request::any('id'); if ($page == 'overview' || $page == 'domains') { if ($action == '') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_domains::domains"); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_domains::domains"); $parentdomain_id = (int)Request::any('pid', '0'); @@ -63,20 +63,32 @@ if ($page == 'overview' || $page == 'domains') { Response::dynamicError($e->getMessage()); } - $actions_links = false; + $actions_links = []; if (CurrentUser::canAddResource('subdomains')) { - $actions_links = [ - [ - 'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']), - 'label' => lng('domains.subdomain_add') - ] + $actions_links[] = [ + 'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']), + 'label' => lng('domains.subdomain_add') ]; } - UI::view('user/table.html.twig', [ + $actions_links[] = [ + 'href' => 'https://docs.froxlor.org/v2/user-guide/domains/', + 'target' => '_blank', + 'icon' => 'fa-solid fa-circle-info', + 'class' => 'btn-outline-secondary' + ]; + + $table_tpl = 'table.html.twig'; + if ($collection->count() == 0) { + $table_tpl = 'table-note.html.twig'; + } + UI::view('user/' . $table_tpl, [ 'listing' => Listing::format($collection, $domain_list_data, 'domain_list'), 'actions_links' => $actions_links, - 'entity_info' => lng('domains.description') + 'entity_info' => lng('domains.description'), + // alert-box + 'type' => 'warning', + 'alert_msg' => lng('domains.nodomainsassignedbyadmin') ]); } elseif ($action == 'delete' && $id != 0) { try { @@ -130,6 +142,7 @@ if ($page == 'overview' || $page == 'domains') { AND `parentdomainid` = '0' AND `email_only` = '0' AND `caneditdomain` = '1' + AND `deactivated` = '0' ORDER BY `domain` ASC"); Database::pexecute($stmt, [ "customerid" => $userinfo['customerid'] @@ -139,6 +152,14 @@ if ($page == 'overview' || $page == 'domains') { $domains[$row['domain']] = $idna_convert->decode($row['domain']); } + // check of there are any domains to be used + if (count($domains) <= 0) { + // no, possible direct URL access, redirect to overview + Response::redirectTo($filename, [ + 'page' => $page + ]); + } + $aliasdomains[0] = lng('domains.noaliasdomain'); $domains_stmt = Database::prepare("SELECT `d`.`id`, `d`.`domain` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` WHERE `d`.`aliasdomain` IS NULL @@ -223,7 +244,7 @@ if ($page == 'overview' || $page == 'domains') { if (isset($result['customerid']) && $result['customerid'] == $userinfo['customerid']) { - if ((int) $result['caneditdomain'] == 0) { + if ((int)$result['caneditdomain'] == 0) { Response::standardError('domaincannotbeedited', $result['domain']); } @@ -373,6 +394,23 @@ if ($page == 'overview' || $page == 'domains') { } else { Response::standardError('domains_canteditdomain'); } + } elseif ($action == 'jqSpeciallogfileNote') { + $domainid = intval($_POST['id']); + $newval = intval($_POST['newval']); + try { + $json_result = SubDomains::getLocal($userinfo, [ + 'id' => $domainid + ])->get(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; + if ($newval != $result['speciallogfile']) { + echo json_encode(['changed' => true, 'info' => lng('admin.speciallogwarning')]); + exit(); + } + echo 0; + exit(); } } elseif ($page == 'domainssleditor') { require_once __DIR__ . '/ssl_editor.php'; diff --git a/customer_email.php b/customer_email.php index a9944734..0373317f 100644 --- a/customer_email.php +++ b/customer_email.php @@ -27,9 +27,10 @@ const AREA = 'customer'; require __DIR__ . '/lib/init.php'; use Froxlor\Api\Commands\EmailAccounts; +use Froxlor\Api\Commands\EmailDomains; use Froxlor\Api\Commands\EmailForwarders; use Froxlor\Api\Commands\Emails; -use Froxlor\Api\Commands\EmailDomains; +use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\FroxlorLogger; use Froxlor\PhpHelper; @@ -41,7 +42,6 @@ use Froxlor\UI\Panel\UI; use Froxlor\UI\Request; use Froxlor\UI\Response; use Froxlor\Validate\Check; -use Froxlor\CurrentUser; // redirect if this customer page is hidden via settings if (Settings::IsInList('panel.customer_hide_options', 'email') || $userinfo['emails'] == 0) { @@ -67,14 +67,24 @@ if ($page == 'overview' || $page == 'emails') { Response::dynamicError($e->getMessage()); } + $actions_links = []; + if (CurrentUser::canAddResource('emails')) { + $actions_links[] = [ + 'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']), + 'label' => lng('emails.emails_add') + ]; + } + + $actions_links[] = [ + 'href' => 'https://docs.froxlor.org/v2/user-guide/emails/', + 'target' => '_blank', + 'icon' => 'fa-solid fa-circle-info', + 'class' => 'btn-outline-secondary' + ]; + UI::view('user/table.html.twig', [ 'listing' => Listing::format($collection, $emaildomain_list_data, 'emaildomain_list'), - 'actions_links' => CurrentUser::canAddResource('emails') ? [ - [ - 'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']), - 'label' => lng('emails.emails_add') - ] - ] : null, + 'actions_links' => $actions_links, ]); } else { // only emails for one domain -> show email address listing directly @@ -84,7 +94,7 @@ if ($page == 'overview' || $page == 'emails') { if ($page == 'email_domain') { $email_domainid = Request::any('domainid', 0); if ($action == '') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_email::emails"); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_email::emails"); $sql_search = []; if ($email_domainid > 0) { @@ -127,6 +137,12 @@ if ($page == 'email_domain') { 'label' => lng('emails.emails_add') ]; } + $actions_links[] = [ + 'href' => 'https://docs.froxlor.org/v2/user-guide/emails/', + 'target' => '_blank', + 'icon' => 'fa-solid fa-circle-info', + 'class' => 'btn-outline-secondary' + ]; UI::view('user/table.html.twig', [ 'listing' => Listing::format($collection, $email_list_data, 'email_list'), diff --git a/customer_extras.php b/customer_extras.php index 05ee93d7..2caae923 100644 --- a/customer_extras.php +++ b/customer_extras.php @@ -26,7 +26,7 @@ const AREA = 'customer'; require __DIR__ . '/lib/init.php'; -use Froxlor\Api\Commands\CustomerBackups as CustomerBackups; +use Froxlor\Api\Commands\DataDump as DataDump; use Froxlor\Api\Commands\DirOptions as DirOptions; use Froxlor\Api\Commands\DirProtections as DirProtections; use Froxlor\Customer\Customer; @@ -68,14 +68,22 @@ if ($page == 'overview' || $page == 'htpasswds') { Response::dynamicError($e->getMessage()); } + $actions_links = []; + $actions_links[] = [ + 'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']), + 'label' => lng('extras.directoryprotection_add') + ]; + + $actions_links[] = [ + 'href' => 'https://docs.froxlor.org/v2/user-guide/extras/', + 'target' => '_blank', + 'icon' => 'fa-solid fa-circle-info', + 'class' => 'btn-outline-secondary' + ]; + UI::view('user/table.html.twig', [ 'listing' => Listing::format($collection, $htpasswd_list_data, 'htpasswd_list'), - 'actions_links' => [ - [ - 'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']), - 'label' => lng('extras.directoryprotection_add') - ] - ], + 'actions_links' => $actions_links, 'entity_info' => lng('extras.description') ]); } elseif ($action == 'delete' && $id != 0) { @@ -185,14 +193,22 @@ if ($page == 'overview' || $page == 'htpasswds') { Response::dynamicError($e->getMessage()); } + $actions_links = []; + $actions_links[] = [ + 'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']), + 'label' => lng('extras.pathoptions_add') + ]; + + $actions_links[] = [ + 'href' => 'https://docs.froxlor.org/v2/user-guide/extras/', + 'target' => '_blank', + 'icon' => 'fa-solid fa-circle-info', + 'class' => 'btn-outline-secondary' + ]; + UI::view('user/table.html.twig', [ 'listing' => Listing::format($collection, $htaccess_list_data, 'htaccess_list'), - 'actions_links' => [ - [ - 'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']), - 'label' => lng('extras.pathoptions_add') - ] - ], + 'actions_links' => $actions_links, 'entity_info' => lng('extras.description') ]); } elseif ($action == 'delete' && $id != 0) { @@ -282,18 +298,18 @@ if ($page == 'overview' || $page == 'htpasswds') { } } } -} elseif ($page == 'backup') { +} elseif ($page == 'export') { // redirect if this customer sub-page is hidden via settings - if (Settings::IsInList('panel.customer_hide_options', 'extras.backup')) { + if (Settings::IsInList('panel.customer_hide_options', 'extras.export')) { Response::redirectTo('customer_index.php'); } - if (Settings::Get('system.backupenabled') == 1) { + if (Settings::Get('system.exportenabled') == 1) { if ($action == 'abort') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "customer_extras::backup - aborted scheduled backupjob"); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "customer_extras::export - aborted scheduled data export job"); try { - CustomerBackups::getLocal($userinfo, $_POST)->delete(); + DataDump::getLocal($userinfo, $_POST)->delete(); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } @@ -302,43 +318,53 @@ if ($page == 'overview' || $page == 'htpasswds') { 'action' => '' ]); } else { - HTML::askYesNo('extras_reallydelete_backup', $filename, [ - 'backup_job_entry' => $id, + HTML::askYesNo('extras_reallydelete_export', $filename, [ + 'job_entry' => $id, 'section' => 'extras', 'page' => $page, 'action' => $action ]); } } elseif ($action == '') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_extras::backup"); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_extras::export"); // check whether there is a backup-job for this customer try { - $backup_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.backups.php'; - $collection = (new Collection(CustomerBackups::class, $userinfo)); + $export_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.export.php'; + $collection = (new Collection(DataDump::class, $userinfo)); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } if (isset($_POST['send']) && $_POST['send'] == 'send') { try { - CustomerBackups::getLocal($userinfo, $_POST)->add(); + DataDump::getLocal($userinfo, $_POST)->add(); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } - Response::standardSuccess('backupscheduled'); + Response::standardSuccess('exportscheduled'); } else { $pathSelect = FileDir::makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']); - $backup_data = include_once dirname(__FILE__) . '/lib/formfields/customer/extras/formfield.backup.php'; + $export_data = include_once dirname(__FILE__) . '/lib/formfields/customer/extras/formfield.export.php'; + + $actions_links = [ + [ + 'href' => 'https://docs.froxlor.org/v2/user-guide/extras/', + 'target' => '_blank', + 'icon' => 'fa-solid fa-circle-info', + 'class' => 'btn-outline-secondary' + ] + ]; UI::view('user/form-datatable.html.twig', [ 'formaction' => $linker->getLink(['section' => 'extras']), - 'formdata' => $backup_data['backup'], - 'tabledata' => Listing::format($collection, $backup_list_data, 'backup_list'), + 'formdata' => $export_data['export'], + 'actions_links' => $actions_links, + 'tabledata' => Listing::format($collection, $export_list_data, 'export_list'), ]); } } } else { - Response::standardError('backupfunctionnotenabled'); + Response::standardError('exportfunctionnotenabled'); } } diff --git a/customer_ftp.php b/customer_ftp.php index 5fd78fe2..8ddb6199 100644 --- a/customer_ftp.php +++ b/customer_ftp.php @@ -27,6 +27,7 @@ const AREA = 'customer'; require __DIR__ . '/lib/init.php'; use Froxlor\Api\Commands\Ftps as Ftps; +use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\FileDir; use Froxlor\FroxlorLogger; @@ -37,7 +38,6 @@ use Froxlor\UI\Listing; use Froxlor\UI\Panel\UI; use Froxlor\UI\Request; use Froxlor\UI\Response; -use Froxlor\CurrentUser; // redirect if this customer page is hidden via settings if (Settings::IsInList('panel.customer_hide_options', 'ftp')) { @@ -57,15 +57,19 @@ if ($page == 'overview' || $page == 'accounts') { Response::dynamicError($e->getMessage()); } - $actions_links = false; + $actions_links = []; if (CurrentUser::canAddResource('ftps')) { $actions_links = [ - [ - 'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']), - 'label' => lng('ftp.account_add') - ] + 'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']), + 'label' => lng('ftp.account_add') ]; } + $actions_links[] = [ + 'href' => 'https://docs.froxlor.org/v2/user-guide/ftp-accounts/', + 'target' => '_blank', + 'icon' => 'fa-solid fa-circle-info', + 'class' => 'btn-outline-secondary' + ]; UI::view('user/table.html.twig', [ 'listing' => Listing::format($collection, $ftp_list_data, 'ftp_list'), diff --git a/customer_index.php b/customer_index.php index 228e73e3..9ea4ca83 100644 --- a/customer_index.php +++ b/customer_index.php @@ -27,21 +27,21 @@ const AREA = 'customer'; require __DIR__ . '/lib/init.php'; use Froxlor\Api\Commands\Customers as Customers; +use Froxlor\Cron\TaskId; use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\Froxlor; use Froxlor\FroxlorLogger; +use Froxlor\Language; use Froxlor\Settings; +use Froxlor\System\Cronjob; use Froxlor\System\Crypt; use Froxlor\UI\Panel\UI; use Froxlor\UI\Response; use Froxlor\Validate\Validate; -use Froxlor\Language; -use Froxlor\System\Cronjob; -use Froxlor\Cron\TaskId; if ($action == 'logout') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'logged out'); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, 'logged out'); unset($_SESSION['userinfo']); CurrentUser::setData(); @@ -66,7 +66,7 @@ if ($action == 'logout') { } if ($page == 'overview') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_index"); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_index"); $domain_stmt = Database::prepare("SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :customerid @@ -114,15 +114,20 @@ if ($page == 'overview') { $userinfo['traffic_bytes'] = ($userinfo['traffic'] > -1) ? $userinfo['traffic'] * 1024 : -1; $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; + } + if ($usages) { $userinfo['diskspace_bytes_used'] = $usages['webspace'] * 1024; - $userinfo['mailspace_used'] = $usages['mail'] * 1024; + $userinfo['mailspace_used'] = $usages['mail'] * 1024; $userinfo['dbspace_used'] = $usages['mysql'] * 1024; $userinfo['total_bytes_used'] = ($usages['webspace'] + $usages['mail'] + $usages['mysql']) * 1024; } else { $userinfo['diskspace_bytes_used'] = 0; $userinfo['total_bytes_used'] = 0; - $userinfo['mailspace_used'] = 0; + $userinfo['mailspace_used'] = 0; $userinfo['dbspace_used'] = 0; } @@ -131,141 +136,138 @@ if ($page == 'overview') { 'domains' => $domainArray, 'stdsubdomain' => $stdsubdomain ]); -} elseif ($page == 'change_password') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $old_password = Validate::validate($_POST['old_password'], 'old password'); +} elseif ($page == 'profile') { + $languages = Language::getLanguages(); - if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_CUSTOMERS, 'customerid')) { - Response::standardError('oldpasswordnotcorrect'); - } + if (!empty($_POST)) { + if ($_POST['send'] == 'changepassword') { + $old_password = Validate::validate($_POST['old_password'], 'old password'); - try { - $new_password = Crypt::validatePassword($_POST['new_password'], 'new password'); - $new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm'); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } + if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_CUSTOMERS, 'customerid')) { + Response::standardError('oldpasswordnotcorrect'); + } - if ($old_password == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.old_password' - ]); - } elseif ($new_password == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.new_password' - ]); - } elseif ($new_password_confirm == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.new_password_confirm' - ]); - } elseif ($new_password != $new_password_confirm) { - Response::standardError('newpasswordconfirmerror'); - } else { - // Update user password try { - Customers::getLocal($userinfo, [ - 'id' => $userinfo['customerid'], - 'new_customer_password' => $new_password - ])->update(); + $new_password = Crypt::validatePassword($_POST['new_password'], 'new password'); + $new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm'); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed password'); - // Update ftp password - if (isset($_POST['change_main_ftp']) && $_POST['change_main_ftp'] == 'true') { - $cryptPassword = Crypt::makeCryptPassword($new_password); - $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` + if ($old_password == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.old_password' + ]); + } elseif ($new_password == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.new_password' + ]); + } elseif ($new_password_confirm == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.new_password_confirm' + ]); + } elseif ($new_password != $new_password_confirm) { + Response::standardError('newpasswordconfirmerror'); + } else { + // Update user password + try { + Customers::getLocal($userinfo, [ + 'id' => $userinfo['customerid'], + 'new_customer_password' => $new_password + ])->update(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed password'); + + // Update ftp password + if (isset($_POST['change_main_ftp']) && $_POST['change_main_ftp'] == 'true') { + $cryptPassword = Crypt::makeCryptPassword($new_password); + $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` SET `password` = :password WHERE `customerid` = :customerid AND `username` = :username"); - $params = [ - "password" => $cryptPassword, - "customerid" => $userinfo['customerid'], - "username" => $userinfo['loginname'] - ]; - Database::pexecute($stmt, $params); - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed main ftp password'); - } + $params = [ + "password" => $cryptPassword, + "customerid" => $userinfo['customerid'], + "username" => $userinfo['loginname'] + ]; + Database::pexecute($stmt, $params); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed main ftp password'); + } - // Update statistics password - if (isset($_POST['change_stats']) && $_POST['change_stats'] == 'true') { - $new_stats_password = Crypt::makeCryptPassword($new_password, true); + // Update statistics password + if (isset($_POST['change_stats']) && $_POST['change_stats'] == 'true') { + $new_stats_password = Crypt::makeCryptPassword($new_password, true); - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_HTPASSWDS . "` + $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_HTPASSWDS . "` SET `password` = :password WHERE `customerid` = :customerid AND `username` = :username"); - $params = [ - "password" => $new_stats_password, - "customerid" => $userinfo['customerid'], - "username" => $userinfo['loginname'] - ]; - Database::pexecute($stmt, $params); - Cronjob::inserttask(TaskId::REBUILD_VHOST); - } + $params = [ + "password" => $new_stats_password, + "customerid" => $userinfo['customerid'], + "username" => $userinfo['loginname'] + ]; + Database::pexecute($stmt, $params); + Cronjob::inserttask(TaskId::REBUILD_VHOST); + } + Response::redirectTo($filename); + } + } elseif ($_POST['send'] == 'changetheme') { + if (Settings::Get('panel.allow_theme_change_customer') == 1) { + $theme = Validate::validate($_POST['theme'], 'theme'); + try { + Customers::getLocal($userinfo, [ + 'id' => $userinfo['customerid'], + 'theme' => $theme + ])->update(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default theme to '" . $theme . "'"); + } + Response::redirectTo($filename); + } elseif ($_POST['send'] == 'changelanguage') { + $def_language = Validate::validate($_POST['def_language'], 'default language'); + if (isset($languages[$def_language])) { + try { + Customers::getLocal($userinfo, [ + 'id' => $userinfo['customerid'], + 'def_language' => $def_language + ])->update(); + CurrentUser::setField('language', $def_language); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + } + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default language to '" . $def_language . "'"); Response::redirectTo($filename); } } else { - UI::view('user/change_password.html.twig'); - } -} elseif ($page == 'change_language') { - $languages = Language::getLanguages(); - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $def_language = Validate::validate($_POST['def_language'], 'default language'); - if (isset($languages[$def_language])) { - try { - Customers::getLocal($userinfo, [ - 'id' => $userinfo['customerid'], - 'def_language' => $def_language - ])->update(); - CurrentUser::setField('language', $def_language); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } + // change theme + $default_theme = Settings::Get('panel.default_theme'); + if ($userinfo['theme'] != '') { + $default_theme = $userinfo['theme']; } - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default language to '" . $def_language . "'"); - Response::redirectTo($filename); - } else { + $themes_avail = UI::getThemes(); + + // change language $default_lang = Settings::Get('panel.standardlanguage'); if ($userinfo['def_language'] != '') { $default_lang = $userinfo['def_language']; } - UI::view('user/change_language.html.twig', [ - 'languages' => $languages, - 'default_lang' => $default_lang - ]); - } -} elseif ($page == 'change_theme') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $theme = Validate::validate($_POST['theme'], 'theme'); - try { - Customers::getLocal($userinfo, [ - 'id' => $userinfo['customerid'], - 'theme' => $theme - ])->update(); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } - - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default theme to '" . $theme . "'"); - Response::redirectTo($filename); - } else { - $default_theme = Settings::Get('panel.default_theme'); - if ($userinfo['theme'] != '') { - $default_theme = $userinfo['theme']; - } - - $themes_avail = UI::getThemes(); - - UI::view('user/change_theme.html.twig', [ + UI::view('user/profile.html.twig', [ 'themes' => $themes_avail, - 'default_theme' => $default_theme + 'default_theme' => $default_theme, + 'languages' => $languages, + 'default_lang' => $default_lang, ]); } } elseif ($page == 'send_error_report' && Settings::Get('system.allow_error_report_customer') == '1') { diff --git a/customer_mysql.php b/customer_mysql.php index 1caa2eda..2330df7b 100644 --- a/customer_mysql.php +++ b/customer_mysql.php @@ -28,6 +28,7 @@ require __DIR__ . '/lib/init.php'; use Froxlor\Api\Commands\Mysqls; use Froxlor\Api\Commands\MysqlServer; +use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\FroxlorLogger; use Froxlor\Settings; @@ -37,7 +38,6 @@ use Froxlor\UI\Listing; use Froxlor\UI\Panel\UI; use Froxlor\UI\Request; use Froxlor\UI\Response; -use Froxlor\CurrentUser; // redirect if this customer page is hidden via settings or no resources given if (Settings::IsInList('panel.customer_hide_options', 'mysql') || $userinfo['mysqls'] == 0) { @@ -66,16 +66,21 @@ if ($page == 'overview' || $page == 'mysqls') { Response::dynamicError($e->getMessage()); } - $actions_links = false; + $actions_links = []; if (CurrentUser::canAddResource('mysqls')) { - $actions_links = [ - [ - 'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']), - 'label' => lng('mysql.database_create') - ] + $actions_links[] = [ + 'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']), + 'label' => lng('mysql.database_create') ]; } + $actions_links[] = [ + 'href' => 'https://docs.froxlor.org/v2/user-guide/databases/', + 'target' => '_blank', + 'icon' => 'fa-solid fa-circle-info', + 'class' => 'btn-outline-secondary' + ]; + UI::view('user/table.html.twig', [ 'listing' => Listing::format($collection, $mysql_list_data, 'mysql_list'), 'actions_links' => $actions_links, @@ -179,7 +184,7 @@ if ($page == 'overview' || $page == 'mysqls') { $result_json = MysqlServer::getLocal($userinfo)->listing(); $result_decoded = json_decode($result_json, true)['data']['list']; foreach ($result_decoded as $dbserver => $dbdata) { - $mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '').')'; + $mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '') . ')'; } } catch (Exception $e) { /* just none */ diff --git a/index.php b/index.php index 4370b767..827bdbf9 100644 --- a/index.php +++ b/index.php @@ -26,6 +26,7 @@ const AREA = 'login'; require __DIR__ . '/lib/init.php'; +use Froxlor\Api\FroxlorRPC; use Froxlor\CurrentUser; use Froxlor\Customer\Customer; use Froxlor\Database\Database; @@ -37,6 +38,7 @@ use Froxlor\PhpHelper; use Froxlor\Settings; use Froxlor\System\Crypt; use Froxlor\UI\Panel\UI; +use Froxlor\UI\Request; use Froxlor\UI\Response; use Froxlor\User; use Froxlor\Validate\Validate; @@ -734,6 +736,58 @@ if ($action == 'resetpwd') { } } +// one-time link login +if ($action == 'll') { + if (!Froxlor::hasUpdates() && !Froxlor::hasDbUpdates()) { + $loginname = Request::get('ln'); + $hash = Request::get('h'); + if ($loginname && $hash) { + $sel_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_LOGINLINKS . "` + WHERE `loginname` = :loginname AND `hash` = :hash + "); + try { + $entry = Database::pexecute_first($sel_stmt, ['loginname' => $loginname, 'hash' => $hash]); + } catch (Exception $e) { + $entry = false; + } + if ($entry) { + // delete entry + $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `loginname` = :loginname AND `hash` = :hash"); + Database::pexecute($del_stmt, ['loginname' => $loginname, 'hash' => $hash]); + if (time() <= $entry['valid_until']) { + $valid = true; + // validate source ip if specified + if (!empty($entry['allowed_from'])) { + $valid = false; + $ip_list = explode(",", $entry['allowed_from']); + if (FroxlorRPC::validateAllowedFrom($ip_list, $_SERVER['REMOTE_ADDR'])) { + $valid = true; + } + } + if ($valid) { + // login user / select only non-deactivated (in case the user got deactivated after generating the link) + $userinfo_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname`= :loginname AND `deactivated` = 0"); + try { + $userinfo = Database::pexecute_first($userinfo_stmt, [ + "loginname" => $loginname + ]); + } catch (Exception $e) { + $userinfo = false; + } + if ($userinfo) { + $userinfo['userid'] = $userinfo['customerid']; + $userinfo['adminsession'] = 0; + finishLogin($userinfo); + } + } + } + } + } + } + Response::redirectTo('index.php'); +} + function finishLogin($userinfo) { if (isset($userinfo['userid']) && $userinfo['userid'] != '') { diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index dec228d8..25734520 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -278,7 +278,6 @@ CREATE TABLE `panel_domains` ( `phpsettingid` INT( 11 ) UNSIGNED NOT NULL DEFAULT '1', `mod_fcgid_starter` int(4) default '-1', `mod_fcgid_maxrequests` int(4) default '-1', - `ismainbutsubto` int(11) unsigned NOT NULL default '0', `letsencrypt` tinyint(1) NOT NULL default '0', `hsts` varchar(10) NOT NULL default '0', `hsts_sub` tinyint(1) NOT NULL default '0', @@ -555,7 +554,7 @@ opcache.validate_timestamps'), ('system', 'defaultip', '1'), ('system', 'defaultsslip', ''), ('system', 'phpappendopenbasedir', '/tmp/'), - ('system', 'deactivateddocroot', ''), + ('system', 'deactivateddocroot', '/var/www/html/froxlor/templates/misc/deactivated/'), ('system', 'mailpwcleartext', '0'), ('system', 'last_tasks_run', '000000'), ('system', 'nameservers', ''), @@ -563,7 +562,7 @@ opcache.validate_timestamps'), ('system', 'mod_fcgid', '0'), ('system', 'apacheconf_vhost', '/etc/apache2/sites-enabled/'), ('system', 'apacheconf_diroptions', '/etc/apache2/sites-enabled/'), - ('system', 'apacheconf_htpasswddir', '/etc/apache2/htpasswd/'), + ('system', 'apacheconf_htpasswddir', '/etc/apache2/froxlor-htpasswd/'), ('system', 'webalizer_quiet', '2'), ('system', 'last_archive_run', '000000'), ('system', 'mod_fcgid_configdir', '/var/www/php-fcgi-scripts'), @@ -647,7 +646,7 @@ opcache.validate_timestamps'), ('system', 'letsencryptreuseold', 0), ('system', 'leenabled', '0'), ('system', 'leapiversion', '2'), - ('system', 'backupenabled', '0'), + ('system', 'exportenabled', '0'), ('system', 'dnsenabled', '0'), ('system', 'dns_server', 'Bind'), ('system', 'apacheglobaldiropt', ''), @@ -697,7 +696,7 @@ opcache.validate_timestamps'), ('system', 'distribution', ''), ('system', 'update_channel', 'stable'), ('system', 'updatecheck_data', ''), - ('system', 'update_notify_last', '2.0.24'), + ('system', 'update_notify_last', '2.1.0-dev1'), ('system', 'traffictool', 'goaccess'), ('system', 'req_limit_per_interval', 60), ('system', 'req_limit_interval', 60), @@ -744,8 +743,9 @@ opcache.validate_timestamps'), ('panel', 'logo_overridetheme', '0'), ('panel', 'logo_overridecustom', '0'), ('panel', 'settings_mode', '0'), - ('panel', 'version', '2.0.24'), - ('panel', 'db_version', '202304260'); + ('panel', 'menu_collapsed', '1'), + ('panel', 'version', '2.1.0-dev1'), + ('panel', 'db_version', '202305240'); DROP TABLE IF EXISTS `panel_tasks`; @@ -914,7 +914,7 @@ INSERT INTO `cronjobs_run` (`id`, `module`, `cronfile`, `cronclass`, `interval`, (3, 'froxlor/reports', 'usage_report', '\\Froxlor\\Cron\\Traffic\\ReportsCron', '1 DAY', '1', 'cron_usage_report'), (4, 'froxlor/core', 'mailboxsize', '\\Froxlor\\Cron\\System\\MailboxsizeCron', '6 HOUR', '1', 'cron_mailboxsize'), (5, 'froxlor/letsencrypt', 'letsencrypt', '\\Froxlor\\Cron\\Http\\LetsEncrypt\\AcmeSh', '5 MINUTE', '0', 'cron_letsencrypt'), - (6, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\System\\BackupCron', '1 DAY', '0', 'cron_backup'); + (6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 HOUR', '0', 'cron_export'); DROP TABLE IF EXISTS `ftp_quotalimits`; @@ -1052,4 +1052,14 @@ CREATE TABLE `panel_usercolumns` ( KEY adminid (adminid), KEY customerid (customerid) ) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci; + + +DROP TABLE IF EXISTS `panel_loginlinks`; +CREATE TABLE `panel_loginlinks` ( + `hash` varchar(500) NOT NULL, + `loginname` varchar(50) NOT NULL, + `valid_until` int(15) NOT NULL, + `allowed_from` text NOT NULL, + UNIQUE KEY `loginname` (`loginname`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; FROXLORSQL; diff --git a/install/updates/froxlor/update_0.10.inc.php b/install/updates/froxlor/update_0.10.inc.php index f1082917..d9c79e1f 100644 --- a/install/updates/froxlor/update_0.10.inc.php +++ b/install/updates/froxlor/update_0.10.inc.php @@ -23,11 +23,11 @@ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 */ -use Froxlor\Froxlor; -use Froxlor\FileDir; use Froxlor\Database\Database; -use Froxlor\Settings; +use Froxlor\FileDir; +use Froxlor\Froxlor; use Froxlor\Install\Update; +use Froxlor\Settings; use Froxlor\System\Cronjob; use Froxlor\System\IPTools; diff --git a/install/updates/froxlor/update_2.x.inc.php b/install/updates/froxlor/update_2.0.inc.php similarity index 100% rename from install/updates/froxlor/update_2.x.inc.php rename to install/updates/froxlor/update_2.0.inc.php diff --git a/install/updates/froxlor/update_2.1.inc.php b/install/updates/froxlor/update_2.1.inc.php new file mode 100644 index 00000000..feb126af --- /dev/null +++ b/install/updates/froxlor/update_2.1.inc.php @@ -0,0 +1,90 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +use Froxlor\Database\Database; +use Froxlor\FileDir; +use Froxlor\Froxlor; +use Froxlor\Install\Update; +use Froxlor\Settings; + +if (!defined('_CRON_UPDATE')) { + if (!defined('AREA') || (defined('AREA') && AREA != 'admin') || !isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) { + header('Location: ../../../../index.php'); + exit(); + } +} + +if (Froxlor::isFroxlorVersion('2.0.24')) { + Update::showUpdateStep("Cleaning domains table"); + Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;"); + Update::lastStepStatus(0); + + Update::showUpdateStep("Creating new tables and fields"); + Database::query("DROP TABLE IF EXISTS `panel_loginlinks`;"); + $sql = "CREATE TABLE `panel_loginlinks` ( + `hash` varchar(500) NOT NULL, + `loginname` varchar(50) NOT NULL, + `valid_until` int(15) NOT NULL, + `allowed_from` text NOT NULL, + UNIQUE KEY `loginname` (`loginname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + Database::query($sql); + Update::lastStepStatus(0); + + Update::showUpdateStep("Adding new settings"); + Settings::AddNew('panel.menu_collapsed', 1); + Update::lastStepStatus(0); + + Update::showUpdateStep("Adjusting setting for deactivated webroot"); + $current_deactivated_webroot = Settings::Get('system.deactivateddocroot'); + if (empty($current_deactivated_webroot)) { + Settings::Set('system.deactivateddocroot', FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/templates/misc/deactivated/')); + Update::lastStepStatus(0); + } else { + Update::lastStepStatus(1, 'Customized setting, not changing'); + } + + Update::showUpdateStep("Adjusting cronjobs"); + Database::query(" + UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET + `module`= 'froxlor/export', + `cronfile` = 'export', + `cronclass` = '\\Froxlor\\Cron\\System\\ExportCron', + `interval` = '1 HOUR', + `desc_lng_key` = 'cron_export' + WHERE `module` = 'froxlor/backup' + "); + Update::lastStepStatus(0); + + Update::showUpdateStep("Adjusting system for data-export function"); + Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `varname` = 'exportenabled' WHERE `settinggroup`= 'system' AND `varname`= 'backupenabled'"); + Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `value` = REPLACE(`value`, 'extras.backup', 'extras.export') WHERE `settinggroup` = 'panel' AND `varname` = 'customer_hide_options'"); + Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'backup_list'"); + Database::query("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'"); + Update::lastStepStatus(0); + + Froxlor::updateToDbVersion('202305240'); + Froxlor::updateToVersion('2.1.0-dev1'); +} diff --git a/install/updates/preconfig/preconfig_2.x.inc.php b/install/updates/preconfig/preconfig_2.0.inc.php similarity index 99% rename from install/updates/preconfig/preconfig_2.x.inc.php rename to install/updates/preconfig/preconfig_2.0.inc.php index d4e0e6e4..4e1fe409 100644 --- a/install/updates/preconfig/preconfig_2.x.inc.php +++ b/install/updates/preconfig/preconfig_2.0.inc.php @@ -30,7 +30,7 @@ use Froxlor\Install\Update; use Froxlor\Settings; $preconfig = [ - 'title' => '2.x updates', + 'title' => '2.0.x updates', 'fields' => [] ]; $return = []; diff --git a/install/updates/preconfig/preconfig_2.1.inc.php b/install/updates/preconfig/preconfig_2.1.inc.php new file mode 100644 index 00000000..88529296 --- /dev/null +++ b/install/updates/preconfig/preconfig_2.1.inc.php @@ -0,0 +1,43 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +use Froxlor\Froxlor; +use Froxlor\FileDir; +use Froxlor\Config\ConfigParser; +use Froxlor\Install\Update; +use Froxlor\Settings; + +$preconfig = [ + 'title' => '2.1.x updates', + 'fields' => [] +]; +$return = []; + +if (Update::versionInUpdate($current_version, '2.1.0-dev1')) { + +} + +$preconfig['fields'] = $return; +return $preconfig; diff --git a/install/updatesql.php b/install/updatesql.php index f759d159..47b45a44 100644 --- a/install/updatesql.php +++ b/install/updatesql.php @@ -53,7 +53,8 @@ try { if (Froxlor::isFroxlor()) { include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_0.10.inc.php')); - include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.x.inc.php')); + include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.0.inc.php')); + include_once(FileDir::makeCorrectFile(dirname(__FILE__) . '/updates/froxlor/update_2.1.inc.php')); // Check Froxlor - database integrity (only happens after all updates are done, so we know the db-layout is okay) Update::showUpdateStep("Checking database integrity"); diff --git a/lib/Froxlor/Api/ApiCommand.php b/lib/Froxlor/Api/ApiCommand.php index 210d28ac..c2a17f50 100644 --- a/lib/Froxlor/Api/ApiCommand.php +++ b/lib/Froxlor/Api/ApiCommand.php @@ -272,7 +272,8 @@ abstract class ApiCommand extends ApiParameter $ops = [ '<', '>', - '=' + '=', + '<>' ]; $first = true; foreach ($search as $field => $valoper) { @@ -396,6 +397,7 @@ abstract class ApiCommand extends ApiParameter $nat_fields = [ '`c`.`loginname`', + '`c`.`name`', '`a`.`loginname`', '`adminname`', '`databasename`', diff --git a/lib/Froxlor/Api/Commands/Customers.php b/lib/Froxlor/Api/Commands/Customers.php index b6ac3c89..f9cd52a8 100644 --- a/lib/Froxlor/Api/Commands/Customers.php +++ b/lib/Froxlor/Api/Commands/Customers.php @@ -100,7 +100,7 @@ class Customers extends ApiCommand implements ResourceEntity AND `id`<> :stdd "); $usages_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_DISKSPACE . "` + SELECT webspace, mail, mysql FROM `" . TABLE_PANEL_DISKSPACE . "` WHERE `customerid` = :cid ORDER BY `stamp` DESC LIMIT 1 "); @@ -109,11 +109,10 @@ class Customers extends ApiCommand implements ResourceEntity while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { if ($show_usages) { // get number of domains - Database::pexecute($domains_stmt, [ + $domains = Database::pexecute_first($domains_stmt, [ 'cid' => $row['customerid'], 'stdd' => $row['standardsubdomain'] ]); - $domains = $domains_stmt->fetch(PDO::FETCH_ASSOC); $row['domains'] = intval($domains['domains']); // get disk-space usages for web, mysql and mail $usages = Database::pexecute_first($usages_stmt, [ @@ -400,6 +399,10 @@ class Customers extends ApiCommand implements ResourceEntity } $allowed_phpconfigs = array_map('intval', $allowed_phpconfigs); + if (empty($allowed_phpconfigs) && $phpenabled == 1) { + Response::standardError('customerphpenabledbutnoconfig', '', true); + } + $allowed_mysqlserver = array(); if (! empty($p_allowed_mysqlserver) && is_array($p_allowed_mysqlserver)) { foreach ($p_allowed_mysqlserver as $allowed_ms) { @@ -1110,6 +1113,9 @@ class Customers extends ApiCommand implements ResourceEntity if (!empty($allowed_phpconfigs)) { $allowed_phpconfigs = array_map('intval', $allowed_phpconfigs); } + if (empty($allowed_phpconfigs) && $phpenabled == 1) { + Response::standardError('customerphpenabledbutnoconfig', '', true); + } // add permission for allowed mysql usage if customer was not allowed to use mysql prior if ($result['mysqls'] == 0 && ($mysqls == -1 || $mysqls > 0)) { @@ -1118,6 +1124,7 @@ class Customers extends ApiCommand implements ResourceEntity if (! empty($allowed_mysqlserver)) { $allowed_mysqlserver = array_map('intval', $allowed_mysqlserver); } + } $def_language = Validate::validate($def_language, 'default language', '', '', [], true); $theme = Validate::validate($theme, 'theme', '', '', [], true); diff --git a/lib/Froxlor/Api/Commands/CustomerBackups.php b/lib/Froxlor/Api/Commands/DataDump.php similarity index 62% rename from lib/Froxlor/Api/Commands/CustomerBackups.php rename to lib/Froxlor/Api/Commands/DataDump.php index 5dbc9392..fef7099d 100644 --- a/lib/Froxlor/Api/Commands/CustomerBackups.php +++ b/lib/Froxlor/Api/Commands/DataDump.php @@ -41,20 +41,22 @@ use PDO; /** * @since 0.10.0 */ -class CustomerBackups extends ApiCommand implements ResourceEntity +class DataDump extends ApiCommand implements ResourceEntity { /** - * add a new customer backup job + * add a new data dump job * * @param string $path - * path to store the backup to - * @param bool $backup_dbs - * optional whether to backup databases, default is 0 (false) - * @param bool $backup_mail - * optional whether to backup mail-data, default is 0 (false) - * @param bool $backup_web - * optional whether to backup web-data, default is 0 (false) + * path to store the dumped data to + * @param string $pgp_public_key + * optional pgp public key to encrypt the archive, default is empty + * @param bool $dump_dbs + * optional whether to include databases, default is 0 (false) + * @param bool $dump_mail + * optional whether to include mail-data, default is 0 (false) + * @param bool $dump_web + * optional whether to incoude web-data, default is 0 (false) * @param int $customerid * optional, required when called as admin (if $loginname is not specified) * @param string $loginname @@ -72,9 +74,10 @@ class CustomerBackups extends ApiCommand implements ResourceEntity $path = $this->getParam('path'); // parameter - $backup_dbs = $this->getBoolParam('backup_dbs', true, 0); - $backup_mail = $this->getBoolParam('backup_mail', true, 0); - $backup_web = $this->getBoolParam('backup_web', true, 0); + $pgp_public_key = $this->getParam('pgp_public_key', true, ''); + $dump_dbs = $this->getBoolParam('dump_dbs', true, 0); + $dump_mail = $this->getBoolParam('dump_mail', true, 0); + $dump_web = $this->getBoolParam('dump_web', true, 0); // get customer data $customer = $this->getCustomerData(); @@ -86,19 +89,32 @@ class CustomerBackups extends ApiCommand implements ResourceEntity // path cannot be the customers docroot if ($path == FileDir::makeCorrectDir($customer['documentroot'])) { - Response::standardError('backupfoldercannotbedocroot', '', true); + Response::standardError('dumpfoldercannotbedocroot', '', true); } - if ($backup_dbs != '1') { - $backup_dbs = '0'; + // pgp public key validation + if (!empty($pgp_public_key)) { + // check if gnupg extension is loaded + if (!extension_loaded('gnupg')) { + Response::standardError('gnupgextensionnotavailable', '', true); + } + // check if the pgp public key is a valid key + putenv('GNUPGHOME='.sys_get_temp_dir()); + if (gnupg_import(gnupg_init(), $pgp_public_key) === false) { + Response::standardError('invalidpgppublickey', '', true); + } } - if ($backup_mail != '1') { - $backup_mail = '0'; + if ($dump_dbs != '1') { + $dump_dbs = '0'; } - if ($backup_web != '1') { - $backup_web = '0'; + if ($dump_mail != '1') { + $dump_mail = '0'; + } + + if ($dump_web != '1') { + $dump_web = '0'; } $task_data = [ @@ -107,61 +123,63 @@ class CustomerBackups extends ApiCommand implements ResourceEntity 'gid' => $customer['guid'], 'loginname' => $customer['loginname'], 'destdir' => $path, - 'backup_dbs' => $backup_dbs, - 'backup_mail' => $backup_mail, - 'backup_web' => $backup_web + 'pgp_public_key' => $pgp_public_key, + 'dump_dbs' => $dump_dbs, + 'dump_mail' => $dump_mail, + 'dump_web' => $dump_web ]; - // schedule backup job - Cronjob::inserttask(TaskId::CREATE_CUSTOMER_BACKUP, $task_data); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer-backup job for '" . $customer['loginname'] . "'. Target directory: " . $userpath); + // schedule export job + Cronjob::inserttask(TaskId::CREATE_CUSTOMER_DATADUMP, $task_data); + + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer data export job for '" . $customer['loginname'] . "'. Target directory: " . $userpath); return $this->response($task_data); } /** - * check whether backup is enabled systemwide and if accessible for customer (hide_options) + * check whether data dump is enabled systemwide and if accessible for customer (hide_options) * * @throws Exception */ private function validateAccess() { - if (Settings::Get('system.backupenabled') != 1) { + if (Settings::Get('system.exportenabled') != 1) { throw new Exception("You cannot access this resource", 405); } if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { throw new Exception("You cannot access this resource", 405); } - if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.backup')) { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.export')) { throw new Exception("You cannot access this resource", 405); } } /** - * You cannot get a planned backup. - * Try CustomerBackups.listing() + * You cannot get a planned data export. + * Try DataDump.listing() */ public function get() { - throw new Exception('You cannot get a planned backup. Try CustomerBackups.listing()', 303); + throw new Exception('You cannot get a planned data export. Try DataDump.listing()', 303); } /** - * You cannot update a planned backup. + * You cannot update a planned data export. * You need to delete it and re-add it. */ public function update() { - throw new Exception('You cannot update a planned backup. You need to delete it and re-add it.', 303); + throw new Exception('You cannot update a planned data export. You need to delete it and re-add it.', 303); } /** - * list all planned backup-jobs, if called from an admin, list all planned backup-jobs of all customers you are + * list all planned data export jobs, if called from an admin, list all planned data export jobs of all customers you are * allowed to view, or specify id or loginname for one specific customer * * @param int $customerid - * optional, admin-only, select backup-jobs of a specific customer by id + * optional, admin-only, select data export jobs of a specific customer by id * @param string $loginname - * optional, admin-only, select backup-jobs of a specific customer by loginname + * optional, admin-only, select data export jobs of a specific customer by loginname * @param array $sql_search * optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =), * LIKE is used if left empty and 'value' => searchvalue @@ -181,9 +199,9 @@ class CustomerBackups extends ApiCommand implements ResourceEntity { $this->validateAccess(); - $customer_ids = $this->getAllowedCustomerIds('extras.backup'); + $customer_ids = $this->getAllowedCustomerIds('extras.export'); - // check whether there is a backup-job for this customer + // check whether there is a data export job for this customer $query_fields = []; $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit()); Database::pexecute($sel_stmt, $query_fields, true, true); @@ -194,7 +212,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity $result[] = $entry; } } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer-backups"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer data dump jobs"); return $this->response([ 'count' => count($result), 'list' => $result @@ -202,12 +220,12 @@ class CustomerBackups extends ApiCommand implements ResourceEntity } /** - * returns the total number of planned backups + * returns the total number of planned data exports * * @param int $customerid - * optional, admin-only, select backup-jobs of a specific customer by id + * optional, admin-only, select data export jobs of a specific customer by id * @param string $loginname - * optional, admin-only, select backup-jobs of a specific customer by loginname + * optional, admin-only, select data export jobs of a specific customer by loginname * * @access admin, customer * @return string json-encoded response message @@ -217,9 +235,9 @@ class CustomerBackups extends ApiCommand implements ResourceEntity { $this->validateAccess(); - $customer_ids = $this->getAllowedCustomerIds('extras.backup'); + $customer_ids = $this->getAllowedCustomerIds('extras.export'); - // check whether there is a backup-job for this customer + // check whether there is a data export job for this customer $result_count = 0; $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'"); Database::pexecute($sel_stmt, null, true, true); @@ -233,10 +251,10 @@ class CustomerBackups extends ApiCommand implements ResourceEntity } /** - * delete a planned backup-jobs by id, if called from an admin you need to specify the customerid/loginname + * delete a planned data export jobs by id, if called from an admin you need to specify the customerid/loginname * - * @param int $backup_job_entry - * id of backup job + * @param int $job_entry + * id of data export job * @param int $customerid * optional, required when called as admin (if $loginname is not specified) * @param string $loginname @@ -248,26 +266,26 @@ class CustomerBackups extends ApiCommand implements ResourceEntity */ public function delete() { - // get planned backups - $result = $this->apiCall('CustomerBackups.listing', $this->getParamList()); + // get planned exports + $result = $this->apiCall('DataDump.listing', $this->getParamList()); - $entry = $this->getParam('backup_job_entry'); - $customer_ids = $this->getAllowedCustomerIds('extras.backup'); + $entry = $this->getParam('job_entry'); + $customer_ids = $this->getAllowedCustomerIds('extras.export'); if ($result['count'] > 0 && $entry > 0) { // prepare statement $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :tid"); // check for the correct job - foreach ($result['list'] as $backupjob) { - if ($backupjob['id'] == $entry && in_array($backupjob['data']['customerid'], $customer_ids)) { + foreach ($result['list'] as $exportjob) { + if ($exportjob['id'] == $entry && in_array($exportjob['data']['customerid'], $customer_ids)) { Database::pexecute($del_stmt, [ 'tid' => $entry ], true, true); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted planned customer-backup #" . $entry); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted planned customer data export job #" . $entry); return $this->response(true); } } } - throw new Exception('Backup job with id #' . $entry . ' could not be found', 404); + throw new Exception('Data export job with id #' . $entry . ' could not be found', 404); } } diff --git a/lib/Froxlor/Api/Commands/DomainZones.php b/lib/Froxlor/Api/Commands/DomainZones.php index 3975fd4a..eebe6b8d 100644 --- a/lib/Froxlor/Api/Commands/DomainZones.php +++ b/lib/Froxlor/Api/Commands/DomainZones.php @@ -302,6 +302,8 @@ class DomainZones extends ApiCommand implements ResourceEntity } } elseif ($type == 'SSHFP' && !empty($content)) { $content = $content; + } elseif ($type == 'TLSA' && !empty($content)) { + $content = $content; } elseif ($type == 'TXT' && !empty($content)) { // check that TXT content is enclosed in " " $content = Dns::encloseTXTContent($content); diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php index f3a331e8..0123f29a 100644 --- a/lib/Froxlor/Api/Commands/Domains.php +++ b/lib/Froxlor/Api/Commands/Domains.php @@ -76,7 +76,7 @@ class Domains extends ApiCommand implements ResourceEntity $query_fields = []; $result_stmt = Database::prepare(" SELECT - `d`.*, `c`.`loginname`, `c`.`deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, `c`.`adminid` as customeradmin, + `d`.*, `c`.`loginname`, `c`.`deactivated` as `customer_deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, `c`.`adminid` as customeradmin, `ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain` FROM `" . TABLE_PANEL_DOMAINS . "` `d` LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) @@ -110,7 +110,7 @@ class Domains extends ApiCommand implements ResourceEntity * * @param number $domain_id * @param bool $ssl_only - * optional, return only ssl enabled ip's, default false + * optional, return only ssl enabled ips, default false * @return array */ private function getIpsForDomain($domain_id = 0, $ssl_only = false) @@ -190,9 +190,6 @@ class Domains extends ApiCommand implements ResourceEntity * optional, whether to create an exclusive web-logfile for this domain, default 0 (false) * @param int $alias * optional, domain-id of a domain that the new domain should be an alias of, default 0 (none) - * @param int $issubof - * optional, domain-id of a domain this domain is a subdomain of (required for webserver-cronjob to - * generate the correct order), default 0 (none) * @param string $registration_date * optional, date of domain registration in form of YYYY-MM-DD, default empty (none) * @param string $termination_date @@ -210,7 +207,7 @@ class Domains extends ApiCommand implements ResourceEntity * @param string $ssl_specialsettings * optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty * @param bool $include_specialsettings - * optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false + * optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false * @param bool $notryfiles * optional, [nginx only] do not generate the default try-files directive, default 0 (false) * @param bool $writeaccesslog @@ -219,7 +216,7 @@ class Domains extends ApiCommand implements ResourceEntity * optional, Enable writing an error-log file for this domain, default 1 (true) * @param string $documentroot * optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be - * aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also + * aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also * specifying a URL is possible here (redirect), default empty (autogenerated) * @param bool $phpenabled * optional, whether php is enabled for this domain, default 0 (false) @@ -244,7 +241,7 @@ class Domains extends ApiCommand implements ResourceEntity * optional, do NOT set the systems default ssl ip addresses if none are given via $ssl_ipandport * parameter * @param bool $sslenabled - * optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default + * optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default * 1 (true) * @param bool $http2 * optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default @@ -252,9 +249,9 @@ class Domains extends ApiCommand implements ResourceEntity * @param int $hsts_maxage * optional max-age value for HSTS header * @param bool $hsts_sub - * optional whether or not to add subdomains to the HSTS header + * optional whether to add subdomains to the HSTS header * @param bool $hsts_preload - * optional whether or not to preload HSTS header value + * optional whether to preload HSTS header value * @param bool $ocsp_stapling * optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL * @param bool $honorcipherorder @@ -263,7 +260,7 @@ class Domains extends ApiCommand implements ResourceEntity * optional whether to enable or disable TLS sessiontickets (RFC 5077) for this domain. default 1 * (true), requires SSL * @param bool $override_tls - * optional whether or not to override system-tls settings like protocol, ssl-ciphers and if applicable + * optional whether to override system-tls settings like protocol, ssl-ciphers and if applicable * tls-1.3 ciphers, requires change_serversettings flag for the admin, default false * @param array $ssl_protocols * optional list of allowed/used ssl/tls protocols, see system.ssl_protocols setting, only used/required @@ -298,7 +295,6 @@ class Domains extends ApiCommand implements ResourceEntity $serveraliasoption = $this->getParam('selectserveralias', true, Settings::Get('system.domaindefaultalias')); $speciallogfile = $this->getBoolParam('speciallogfile', true, 0); $aliasdomain = intval($this->getParam('alias', true, 0)); - $issubof = $this->getParam('issubof', true, 0); $registration_date = $this->getParam('registration_date', true, ''); $termination_date = $this->getParam('termination_date', true, ''); $caneditdomain = $this->getBoolParam('caneditdomain', true, 0); @@ -320,9 +316,9 @@ class Domains extends ApiCommand implements ResourceEntity $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1); $ssl_redirect = $this->getBoolParam('ssl_redirect', true, 0); $letsencrypt = $this->getBoolParam('letsencrypt', true, 0); + $sslenabled = $this->getBoolParam('sslenabled', true, 1); $dont_use_default_ssl_ipandport_if_empty = $this->getBoolParam('dont_use_default_ssl_ipandport_if_empty', true, 0); $p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $dont_use_default_ssl_ipandport_if_empty ? [] : explode(',', Settings::Get('system.defaultsslip'))); - $sslenabled = $this->getBoolParam('sslenabled', true, 1); $http2 = $this->getBoolParam('http2', true, 0); $hsts_maxage = $this->getParam('hsts_maxage', true, 0); $hsts_sub = $this->getBoolParam('hsts_sub', true, 0); @@ -548,6 +544,10 @@ class Domains extends ApiCommand implements ResourceEntity $ssl_specialsettings = Validate::validate(str_replace("\r\n", "\n", $ssl_specialsettings), 'ssl_specialsettings', '/^[^\0]*$/', '', [], true); } } + if (Settings::Get('system.use_ssl') == "1" && $sslenabled == 1 && empty($ssl_ipandports)) { + // enabled ssl for the domain but no ssl ip/port is selected + Response::standardError('nosslippportgiven', '', true); + } if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) { $ssl_redirect = 0; $letsencrypt = 0; @@ -665,10 +665,6 @@ class Domains extends ApiCommand implements ResourceEntity $serveraliasoption = '0'; } - if ($issubof <= 0) { - $issubof = '0'; - } - $idna_convert = new IdnaWrapper(); if ($domain == '') { Response::standardError([ @@ -723,7 +719,6 @@ class Domains extends ApiCommand implements ResourceEntity 'phpsettingid' => $phpsettingid, 'mod_fcgid_starter' => $mod_fcgid_starter, 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, - 'ismainbutsubto' => $issubof, 'letsencrypt' => $letsencrypt, 'http2' => $http2, 'hsts' => $hsts_maxage, @@ -777,7 +772,6 @@ class Domains extends ApiCommand implements ResourceEntity `phpsettingid` = :phpsettingid, `mod_fcgid_starter` = :mod_fcgid_starter, `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, - `ismainbutsubto` = :ismainbutsubto, `letsencrypt` = :letsencrypt, `http2` = :http2, `hsts` = :hsts, @@ -1069,9 +1063,6 @@ class Domains extends ApiCommand implements ResourceEntity * default 0 (false) * @param int $alias * optional, domain-id of a domain that the new domain should be an alias of, default 0 (none) - * @param int $issubof - * optional, domain-id of a domain this domain is a subdomain of (required for webserver-cronjob to - * generate the correct order), default 0 (none) * @param string $registration_date * optional, date of domain registration in form of YYYY-MM-DD, default empty (none) * @param string $termination_date @@ -1089,7 +1080,7 @@ class Domains extends ApiCommand implements ResourceEntity * @param string $ssl_specialsettings * optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty * @param bool $include_specialsettings - * optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false + * optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false * @param bool $specialsettingsforsubdomains * optional, whether to apply specialsettings to all subdomains of this domain, default is read from * setting system.apply_specialsettings_default @@ -1101,7 +1092,7 @@ class Domains extends ApiCommand implements ResourceEntity * optional, Enable writing an error-log file for this domain, default 1 (true) * @param string $documentroot * optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be - * aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also + * aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also * specifying a URL is possible here (redirect), default empty (autogenerated) * @param bool $phpenabled * optional, whether php is enabled for this domain, default 0 (false) @@ -1130,7 +1121,7 @@ class Domains extends ApiCommand implements ResourceEntity * optional, if set to true and no $ssl_ipandport value is given, the ip's get removed, otherwise, the * currently set value is used, default false * @param bool $sslenabled - * optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default + * optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default * 1 (true) * @param bool $http2 * optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default @@ -1138,9 +1129,9 @@ class Domains extends ApiCommand implements ResourceEntity * @param int $hsts_maxage * optional max-age value for HSTS header * @param bool $hsts_sub - * optional whether or not to add subdomains to the HSTS header + * optional whether to add subdomains to the HSTS header * @param bool $hsts_preload - * optional whether or not to preload HSTS header value + * optional whether to preload HSTS header value * @param bool $ocsp_stapling * optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL * @param bool $honorcipherorder @@ -1150,6 +1141,8 @@ class Domains extends ApiCommand implements ResourceEntity * (true), requires SSL * @param string $description * optional custom description (currently not used/shown in the frontend), default empty + * @param bool $deactivated + * optional, if 1 (true) the domain can be deactivated/suspended * * @access admin * @return string json-encoded array @@ -1191,7 +1184,6 @@ class Domains extends ApiCommand implements ResourceEntity $speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']); $speciallogverified = $this->getBoolParam('speciallogverified', true, 0); $aliasdomain = intval($this->getParam('alias', true, $result['aliasdomain'])); - $issubof = $this->getParam('issubof', true, $result['ismainbutsubto']); $registration_date = $this->getParam('registration_date', true, $result['registration_date']); $termination_date = $this->getParam('termination_date', true, $result['termination_date']); $caneditdomain = $this->getBoolParam('caneditdomain', true, $result['caneditdomain']); @@ -1219,7 +1211,7 @@ class Domains extends ApiCommand implements ResourceEntity $p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $remove_ssl_ipandport ? [ -1 ] : null); - $sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']); + $sslenabled = $remove_ssl_ipandport ? false : $this->getBoolParam('sslenabled', true, $result['ssl_enabled']); $http2 = $this->getBoolParam('http2', true, $result['http2']); $hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']); $hsts_sub = $this->getBoolParam('hsts_sub', true, $result['hsts_sub']); @@ -1246,6 +1238,7 @@ class Domains extends ApiCommand implements ResourceEntity $tlsv13_cipher_list = $result['tlsv13_cipher_list']; } $description = $this->getParam('description', true, $result['description']); + $deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']); // count subdomain usage of source-domain $subdomains_stmt = Database::prepare(" @@ -1528,6 +1521,10 @@ class Domains extends ApiCommand implements ResourceEntity if ($remove_ssl_ipandport || (!empty($p_ssl_ipandports) && $p_ssl_ipandports[0] == -1)) { $ssl_ipandports = []; } + if (Settings::Get('system.use_ssl') == "1" && $sslenabled && empty($ssl_ipandports)) { + // enabled ssl for the domain but no ssl ip/port is selected + Response::standardError('nosslippportgiven', '', true); + } if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) { $ssl_redirect = 0; $letsencrypt = 0; @@ -1564,7 +1561,7 @@ class Domains extends ApiCommand implements ResourceEntity } // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated - if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) { + if (($result['letsencrypt'] != $letsencrypt || $result['ssl_redirect'] != $ssl_redirect) && $ssl_redirect > 0 && $letsencrypt == 1) { $ssl_redirect = 2; } @@ -1640,10 +1637,6 @@ class Domains extends ApiCommand implements ResourceEntity Response::standardError('domainisaliasorothercustomer', '', true); } - if ($issubof <= 0) { - $issubof = '0'; - } - if ($serveraliasoption != '1' && $serveraliasoption != '2') { $serveraliasoption = '0'; } @@ -1666,7 +1659,6 @@ class Domains extends ApiCommand implements ResourceEntity || $writeaccesslog != $result['writeaccesslog'] || $writeerrorlog != $result['writeerrorlog'] || $aliasdomain != $result['aliasdomain'] - || $issubof != $result['ismainbutsubto'] || $email_only != $result['email_only'] || ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1') || $letsencrypt != $result['letsencrypt'] @@ -1837,7 +1829,6 @@ class Domains extends ApiCommand implements ResourceEntity $update_data['writeerrorlog'] = $writeerrorlog; $update_data['registration_date'] = $registration_date; $update_data['termination_date'] = $termination_date; - $update_data['ismainbutsubto'] = $issubof; $update_data['letsencrypt'] = $letsencrypt; $update_data['http2'] = $http2; $update_data['hsts'] = $hsts_maxage; @@ -1852,6 +1843,7 @@ class Domains extends ApiCommand implements ResourceEntity $update_data['honorcipherorder'] = $honorcipherorder; $update_data['sessiontickets'] = $sessiontickets; $update_data['description'] = $description; + $update_data['deactivated'] = $deactivated; $update_data['id'] = $id; $update_stmt = Database::prepare(" @@ -1885,7 +1877,6 @@ class Domains extends ApiCommand implements ResourceEntity `writeerrorlog` = :writeerrorlog, `registration_date` = :registration_date, `termination_date` = :termination_date, - `ismainbutsubto` = :ismainbutsubto, `letsencrypt` = :letsencrypt, `http2` = :http2, `hsts` = :hsts, @@ -1899,11 +1890,36 @@ class Domains extends ApiCommand implements ResourceEntity `ssl_enabled` = :sslenabled, `ssl_honorcipherorder` = :honorcipherorder, `ssl_sessiontickets` = :sessiontickets, - `description` = :description + `description` = :description, + `deactivated` = :deactivated WHERE `id` = :id "); Database::pexecute($update_stmt, $update_data, true, true); + // activate/deactivate domain-based services + if ($deactivated != $result['deactivated']) { + // deactivate email accounts + $yesno = ($deactivated ? 'N' : 'Y'); + $pop3 = ($deactivated ? '0' : (int)$customer['pop3']); + $imap = ($deactivated ? '0' : (int)$customer['imap']); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_USERS . "` + SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap + WHERE `customerid` = :customerid AND `domainid` = :domainid + "); + Database::pexecute($upd_stmt, [ + 'yesno' => $yesno, + 'pop3' => $pop3, + 'imap' => $imap, + 'customerid' => $customerid, + 'domainid' => $id + ]); + + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " domain '" . $result['domain'] . "'"); + Cronjob::inserttask(TaskId::REBUILD_VHOST); + } + $_update_data['customerid'] = $customerid; $_update_data['adminid'] = $adminid; $_update_data['phpenabled'] = $phpenabled; @@ -1921,6 +1937,7 @@ class Domains extends ApiCommand implements ResourceEntity $_update_data['honorcipherorder'] = $honorcipherorder; $_update_data['sessiontickets'] = $sessiontickets; $_update_data['parentdomainid'] = $id; + $_update_data['deactivated'] = $deactivated; // if php config is to be set for all subdomains, check here $update_phpconfig = ''; @@ -1953,7 +1970,8 @@ class Domains extends ApiCommand implements ResourceEntity `ssl_cipher_list` = :ssl_cipher_list, `tlsv13_cipher_list` = :tlsv13_cipher_list, `ssl_honorcipherorder` = :honorcipherorder, - `ssl_sessiontickets` = :sessiontickets + `ssl_sessiontickets` = :sessiontickets, + `deactivated` = :deactivated " . $update_phpconfig . $upd_specialsettings . $updatechildren . $update_sslredirect . " WHERE `parentdomainid` = :parentdomainid "); @@ -2073,9 +2091,6 @@ class Domains extends ApiCommand implements ResourceEntity * optional, the domain-id * @param string $domainname * optional, the domainname - * @param bool $delete_mainsubdomains - * optional, remove also domains that are subdomains of this domain but added as main domains; default - * false * @param bool $is_stdsubdomain * optional, default false, specify whether it's a std-subdomain you are deleting as it does not count * as subdomain-resource @@ -2091,7 +2106,6 @@ class Domains extends ApiCommand implements ResourceEntity $dn_optional = $id > 0; $domainname = $this->getParam('domainname', $dn_optional, ''); $is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0); - $remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0); $result = $this->apiCall('Domains.get', [ 'id' => $id, @@ -2099,15 +2113,10 @@ class Domains extends ApiCommand implements ResourceEntity ]); $id = $result['id']; - // check for deletion of main-domains which are logically subdomains, #329 - $rsd_sql = ''; - if ($remove_subbutmain_domains) { - $rsd_sql .= " OR `ismainbutsubto` = :id"; - } - $subresult_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE (`id` = :id OR `parentdomainid` = :id " . $rsd_sql . ")"); + SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE (`id` = :id OR `parentdomainid` = :id) + "); Database::pexecute($subresult_stmt, [ 'id' => $id ], true, true); @@ -2129,23 +2138,10 @@ class Domains extends ApiCommand implements ResourceEntity $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted domain/s from mail-tables"); } - // if mainbutsubto-domains are not to be deleted, re-assign the (ismainbutsubto value of the main - // domain which is being deleted) as their new ismainbutsubto value - if ($remove_subbutmain_domains !== 1) { - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `ismainbutsubto` = :newIsMainButSubtoValue - WHERE `ismainbutsubto` = :deletedMainDomainId - "); - Database::pexecute($upd_stmt, [ - 'newIsMainButSubtoValue' => $result['ismainbutsubto'], - 'deletedMainDomainId' => $id - ], true, true); - } - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `id` = :id OR `parentdomainid` = :id " . $rsd_sql); + DELETE FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `id` = :id OR `parentdomainid` = :id + "); Database::pexecute($del_stmt, [ 'id' => $id ], true, true); @@ -2230,4 +2226,118 @@ class Domains extends ApiCommand implements ResourceEntity } throw new Exception("Not allowed to execute given command.", 403); } + + /** + * duplicate domain entry by either id or domainname. All parameters from Domains.add() can be used + * to overwrite source entity values if necessary. + * + * @param int $id + * optional, the domain-id + * @param string $domainname + * optional, the domainname + * @param string $domain + * required, name of the new domain to be added + * + * @access admin + * @return string json-encoded array + * @throws Exception + */ + public function duplicate() + { + if ($this->isAdmin()) { + // parameters + $id = $this->getParam('id', true, 0); + $dn_optional = $id > 0; + $domainname = $this->getParam('domainname', $dn_optional, ''); + $p_domain = $this->getParam('domain'); + + // get requested domain + $result = $this->apiCall('Domains.get', [ + 'id' => $id, + 'domainname' => $domainname, + ]); + + // clear some defaults + unset($result['domain_ace']); + unset($result['adminid']); + unset($result['documentroot']); + unset($result['registration_date']); + unset($result['termination_date']); + unset($result['zonefile']); + // clear auto-generated values + unset($result['bindserial']); + unset($result['dkim_privkey']); + unset($result['dkim_pubkey']); + // clear api-call generated fields + unset($result['domain_hascert']); + + // set correct ip/port information + $domain_ips = $result['ipsandports']; + unset($result['ipsandports']); + $result['ipandport'] = []; + $result['ssl_ipandport'] = []; + foreach ($domain_ips as $dip) { + if ($dip['ssl'] == 1) { + $result['ssl_ipandport'][] = $dip['id']; + } else { + $result['ipandport'][] = $dip['id']; + } + } + + // check whether we are changing the customer/owner + if ($this->getParam('customerid', true, 0) == 0 && $this->getParam('loginname', true, '') == '') { + $customerid = $result['customerid']; + } else { + $customer = $this->getCustomerData(); + $customerid = $customer['customerid']; + } + + // check for alias-domain and whether it belongs to the target user + if (!empty($result['aliasdomain']) && $customerid == $result['customerid']) { + // duplicate alias entry + $result['alias'] = $result['aliasdomain']; + } + unset($result['aliasdomain']); + + // validate possible fpm configs and whether the customer is allowed to use them + if ($customerid != $result['customerid']) { + $allowed_phpconfigs = json_decode($customer['allowed_phpconfigs'] ?? '[]', true); + if (empty($allowed_phpconfigs)) { + // system defaults + unset($result['phpsettingid']); + } elseif (!in_array($result['phpsettingid'], $allowed_phpconfigs)) { + // use the first customer allowed config + $result['phpsettingid'] = array_shift($allowed_phpconfigs); + } + } + + // translate serveralias values + $result['selectserveralias'] = 2; + if ((int)$result['wwwserveralias'] == 1) { + $result['selectserveralias'] = 1; + } elseif ((int)$result['iswildcarddomain'] == 1) { + $result['selectserveralias'] = 0; + } + unset($result['wwwserveralias']); + unset($result['iswildcarddomain']); + + // translate sslenabled flag + $result['sslenabled'] = $result['ssl_enabled']; + unset($result['ssl_enabled']); + + $additional_params = $this->getParamList(); + // unset unneeded params from this call + unset($additional_params['id']); + unset($additional_params['domainname']); + unset($additional_params['domain']); + + // set new values and merge with optional add() parameters + $new_domain = array_merge($result, $additional_params); + $new_domain['domain'] = $p_domain; + + $result_new = $this->apiCall('Domains.add', $new_domain); + return $this->response($result_new); + } + throw new Exception("Not allowed to execute given command.", 403); + } } diff --git a/lib/Froxlor/Api/Commands/EmailAccounts.php b/lib/Froxlor/Api/Commands/EmailAccounts.php index 605a6daa..e560fd0d 100644 --- a/lib/Froxlor/Api/Commands/EmailAccounts.php +++ b/lib/Froxlor/Api/Commands/EmailAccounts.php @@ -95,9 +95,13 @@ class EmailAccounts extends ApiCommand implements ResourceEntity $customer = $this->getCustomerData('email_accounts'); // check for imap||pop3 == 1, see #1298 + // d00p, 6.5.2023 @revert this - if a customer has resources which allow email accounts + // it implicitly allowed SMTP, e.g. sending of emails which also requires an account to exist + /* if ($customer['imap'] != '1' && $customer['pop3'] != '1') { Response::standardError('notallowedtouseaccounts', '', true); } + */ if (!empty($emailaddr)) { $idna_convert = new IdnaWrapper(); diff --git a/lib/Froxlor/Api/Commands/Emails.php b/lib/Froxlor/Api/Commands/Emails.php index 221efa9b..85c19a49 100644 --- a/lib/Froxlor/Api/Commands/Emails.php +++ b/lib/Froxlor/Api/Commands/Emails.php @@ -88,9 +88,12 @@ class Emails extends ApiCommand implements ResourceEntity $domain_check = $this->apiCall('SubDomains.get', [ 'domainname' => $domain ], true); - if ($domain_check['isemaildomain'] == 0) { + if ((int)$domain_check['isemaildomain'] == 0) { Response::standardError('maindomainnonexist', $domain, true); } + if ((int)$domain_check['deactivated'] == 1) { + Response::standardError('maindomaindeactivated', $domain, true); + } if (Settings::Get('catchall.catchall_enabled') != '1') { $iscatchall = 0; diff --git a/lib/Froxlor/Api/Commands/FpmDaemons.php b/lib/Froxlor/Api/Commands/FpmDaemons.php index 26d4d493..d74daa6d 100644 --- a/lib/Froxlor/Api/Commands/FpmDaemons.php +++ b/lib/Froxlor/Api/Commands/FpmDaemons.php @@ -202,7 +202,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity // validation $description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true); - $reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\- ]+$/i', '', [], true); + $reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\-@ ]+$/i', '', [], true); $sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc"); $dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]); if ($dupcheck && $dupcheck['id']) { @@ -327,7 +327,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity // validation $description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true); - $reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\- ]+$/i', '', [], true); + $reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\-@ ]+$/i', '', [], true); $sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc"); $dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]); if ($dupcheck && $dupcheck['id'] != $id) { diff --git a/lib/Froxlor/Api/Commands/Froxlor.php b/lib/Froxlor/Api/Commands/Froxlor.php index 378f84c5..eaa657c1 100644 --- a/lib/Froxlor/Api/Commands/Froxlor.php +++ b/lib/Froxlor/Api/Commands/Froxlor.php @@ -37,6 +37,7 @@ use Froxlor\Settings; use Froxlor\SImExporter; use Froxlor\System\Cronjob; use Froxlor\System\Crypt; +use Froxlor\Validate\Validate; use PDO; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; @@ -269,6 +270,79 @@ class Froxlor extends ApiCommand return $this->response(Crypt::generatePassword($length)); } + /** + * return a one-time login link URL for a given user + * + * @param int $customerid optional, required if $loginname is not specified, user to create link for + * @param string $loginname optional, required if $customerid is not specified, user to create link for + * @param int $valid_time optional, value in seconds how long the link will be valid, default is 10 seconds, valid values are numbers from 10 to 120 + * @param string $allowed_from optional, comma separated list of ip addresses or networks to allow login from via this link + * + * @access admin + * @return string json-encoded array [base => domain, uri => relative link] + * @throws Exception + */ + public function generateLoginLink() + { + if ($this->isAdmin()) { + $customer = $this->getCustomerData(); + + // cannot create link for deactivated users + if ((int)$customer['deactivated'] == 1) { + throw new Exception("Cannot generate link for deactivated user", 406); + } + + $valid_time = (int)$this->getParam('valid_time', true, 10); + $allowed_from = $this->getParam('allowed_from', true, ''); + + $valid_time = Validate::validate($valid_time, 'valid time', '/^(1[0-1][0-9]|120|[1-9][0-9])$/', 'invalid_validtime', [10], true); + + // validate allowed_from + if (!empty($allowed_from)) { + $ip_list = array_map('trim', explode(",", $allowed_from)); + $_check_list = $ip_list; + foreach ($_check_list as $idx => $ip) { + if (Validate::validate_ip2($ip, true, 'invalidip', true, true, true) == false) { + throw new Exception('Invalid ip address', 406); + } + // check for cidr + if (strpos($ip, '/') !== false) { + $ipparts = explode("/", $ip); + // shorten IP + $ip = inet_ntop(inet_pton($ipparts[0])); + // re-add cidr + $ip .= '/' . $ipparts[1]; + } else { + // shorten IP + $ip = inet_ntop(inet_pton($ip)); + } + $ip_list[$idx] = $ip; + } + $allowed_from = implode(",", array_unique($ip_list)); + } + + $hash = hash('sha256', openssl_random_pseudo_bytes(64 * 64)); + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_LOGINLINKS . "` + SET `hash` = :hash, `loginname` = :loginname, `valid_until` = :validuntil, `allowed_from` = :allowedfrom + ON DUPLICATE KEY UPDATE `hash` = :hash, `valid_until` = :validuntil, `allowed_from` = :allowedfrom + "); + Database::pexecute($ins_stmt, [ + 'hash' => $hash, + 'loginname' => $customer['loginname'], + 'validuntil' => time() + $valid_time, + 'allowedfrom' => $allowed_from + ]); + + return $this->response([ + 'base' => 'https://' . Settings::Get('system.hostname') . '/' . (Settings::Get('system.froxlordirectlyviahostname') != 1 ? basename(\Froxlor\Froxlor::getInstallDir()) . '/' : ''), + 'uri' => 'index.php?action=ll&ln=' . $customer['loginname'] . '&h=' . $hash + ]); + } + throw new Exception("Not allowed to execute given command.", 403); + } + /** * can be used to remotely run the integritiy checks froxlor implements * diff --git a/lib/Froxlor/Api/Commands/Ftps.php b/lib/Froxlor/Api/Commands/Ftps.php index 41dc8cb9..e29ebf99 100644 --- a/lib/Froxlor/Api/Commands/Ftps.php +++ b/lib/Froxlor/Api/Commands/Ftps.php @@ -72,6 +72,8 @@ class Ftps extends ApiCommand implements ResourceEntity * optional whether to add additional usernames to the group * @param bool $is_defaultuser * optional whether this is the standard default ftp user which is being added so no usage is decreased + * @param bool $login_enabled + * optional whether to allow login (default) or not * * @access admin, customer * @return string json-encoded array @@ -84,6 +86,7 @@ class Ftps extends ApiCommand implements ResourceEntity } $is_defaultuser = $this->getBoolParam('is_defaultuser', true, 0); + $login_enabled = $this->getBoolParam('login_enabled', true, 1); if (($this->getUserDetail('ftps_used') < $this->getUserDetail('ftps') || $this->getUserDetail('ftps') == '-1') || $this->isAdmin() && $is_defaultuser == 1) { // required parameters @@ -176,13 +179,14 @@ class Ftps extends ApiCommand implements ResourceEntity $stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "` (`customerid`, `username`, `description`, `password`, `homedir`, `login_enabled`, `uid`, `gid`, `shell`) - VALUES (:customerid, :username, :description, :password, :homedir, 'y', :guid, :guid, :shell)"); + VALUES (:customerid, :username, :description, :password, :homedir, :loginenabled, :guid, :guid, :shell)"); $params = [ "customerid" => $customer['customerid'], "username" => $username, "description" => $description, "password" => $cryptPassword, "homedir" => $path, + "loginenabled" => $login_enabled ? 'Y' : 'N', "guid" => $customer['guid'], "shell" => $shell ]; @@ -389,6 +393,8 @@ class Ftps extends ApiCommand implements ResourceEntity * optional, description for ftp-user * @param string $shell * optional, default /bin/false (not changeable when deactivated) + * @param bool $login_enabled + * optional whether to allow login (default) or not * @param int $customerid * optional, required when called as admin (if $loginname is not specified) * @param string $loginname @@ -419,6 +425,7 @@ class Ftps extends ApiCommand implements ResourceEntity $password = $this->getParam('ftp_password', true, ''); $description = $this->getParam('ftp_description', true, $result['description']); $shell = $this->getParam('shell', true, $result['shell']); + $login_enabled = $this->getBoolParam('login_enabled', true, ($result['login_enabled'] == 'Y' ? 1 : 0)); // validation $password = Validate::validate($password, 'password', '', '', [], true); @@ -430,6 +437,10 @@ class Ftps extends ApiCommand implements ResourceEntity $shell = "/bin/false"; } + if ($login_enabled != 1) { + $login_enabled = 0; + } + // get needed customer info to reduce the ftp-user-counter by one $customer = $this->getCustomerData(); @@ -480,13 +491,14 @@ class Ftps extends ApiCommand implements ResourceEntity $stmt = Database::prepare(" UPDATE `" . TABLE_FTP_USERS . "` - SET `description` = :desc, `shell` = :shell + SET `description` = :desc, `shell` = :shell, `login_enabled` = :loginenabled WHERE `customerid` = :customerid AND `id` = :id "); Database::pexecute($stmt, [ "desc" => $description, "shell" => $shell, + "loginenabled" => $login_enabled ? 'Y' : 'N', "customerid" => $customer['customerid'], "id" => $id ], true, true); diff --git a/lib/Froxlor/Api/Commands/HostingPlans.php b/lib/Froxlor/Api/Commands/HostingPlans.php index ce5f4832..5954ff08 100644 --- a/lib/Froxlor/Api/Commands/HostingPlans.php +++ b/lib/Froxlor/Api/Commands/HostingPlans.php @@ -201,7 +201,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity // validation $name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true); - $description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT); + $description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT); if (Settings::Get('system.mail_quota_enabled') != '1') { $value_arr['email_quota'] = -1; @@ -383,7 +383,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity // validation $name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true); - $description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT); + $description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT); if (Settings::Get('system.mail_quota_enabled') != '1') { $value_arr['email_quota'] = -1; diff --git a/lib/Froxlor/Api/Commands/SubDomains.php b/lib/Froxlor/Api/Commands/SubDomains.php index ed8f49fc..1711c3ef 100644 --- a/lib/Froxlor/Api/Commands/SubDomains.php +++ b/lib/Froxlor/Api/Commands/SubDomains.php @@ -67,6 +67,8 @@ class SubDomains extends ApiCommand implements ResourceEntity * optional, php-settings-id, if empty the $domain value is used * @param int $redirectcode * optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES + * @param int $speciallogfile + * optional, whether to create an exclusive web-logfile for this domain (1) or not (0) or inherit value from parentdomain (2, default) * @param bool $sslenabled * optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default * 1 (true) @@ -107,6 +109,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $openbasedir_path = $this->getParam('openbasedir_path', true, 0); $phpsettingid = $this->getParam('phpsettingid', true, 0); $redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default')); + $speciallogfile = intval($this->getParam('speciallogfile', true, 2)); $isemaildomain = $this->getParam('isemaildomain', true, 0); if (Settings::Get('system.use_ssl')) { $sslenabled = $this->getBoolParam('sslenabled', true, 1); @@ -229,6 +232,9 @@ class SubDomains extends ApiCommand implements ResourceEntity } elseif ($completedomain_check && strtolower($completedomain_check['domain']) == strtolower($completedomain)) { // the domain does already exist as main-domain Response::standardError('domainexistalready', $completedomain, true); + } elseif ((int)$domain_check['deactivated'] == 1) { + // main domain is deactivated + Response::standardError('maindomaindeactivated', $domain, true); } // if allowed, check for 'is email domain'-flag @@ -273,6 +279,11 @@ class SubDomains extends ApiCommand implements ResourceEntity $ssl_redirect = 2; } + // validate speciallogfile value + if ($speciallogfile < 0 || $speciallogfile > 2) { + $speciallogfile = 2; // inherit from parent-domain + } + // get the phpsettingid from parentdomain, #107 $phpsid_stmt = Database::prepare(" SELECT `phpsettingid` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id @@ -351,7 +362,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "openbasedir" => $domain_check['openbasedir'], "openbasedir_path" => $openbasedir_path, "phpenabled" => $domain_check['phpenabled'], - "speciallogfile" => $domain_check['speciallogfile'], + "speciallogfile" => $speciallogfile == 2 ? $domain_check['speciallogfile'] : $speciallogfile, "specialsettings" => $domain_check['specialsettings'], "ssl_specialsettings" => $domain_check['ssl_specialsettings'], "include_specialsettings" => $domain_check['include_specialsettings'], @@ -588,6 +599,11 @@ class SubDomains extends ApiCommand implements ResourceEntity * optional, php-settings-id, if empty the $domain value is used * @param int $redirectcode * optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES + * @param bool $speciallogfile + * optional, whether to create an exclusive web-logfile for this domain + * @param bool $speciallogverified + * optional, when setting $speciallogfile to false, this needs to be set to true to confirm the action, + * default 0 (false) * @param bool $sslenabled * optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default * 1 (true) @@ -645,6 +661,8 @@ class SubDomains extends ApiCommand implements ResourceEntity $openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']); $phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']); $redirectcode = $this->getParam('redirectcode', true, Domain::getDomainRedirectId($id)); + $speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']); + $speciallogverified = $this->getBoolParam('speciallogverified', true, 0); if (Settings::Get('system.use_ssl')) { $sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']); $ssl_redirect = $this->getBoolParam('ssl_redirect', true, $result['ssl_redirect']); @@ -754,6 +772,10 @@ class SubDomains extends ApiCommand implements ResourceEntity $ssl_redirect = 2; } + if ($speciallogfile != $result['speciallogfile'] && $speciallogverified != '1') { + $speciallogfile = $result['speciallogfile']; + } + // is-email-domain flag changed - remove mail accounts and mail-addresses if (($result['isemaildomain'] == '1') && $isemaildomain == '0') { $params = [ @@ -786,7 +808,21 @@ class SubDomains extends ApiCommand implements ResourceEntity Domain::updateRedirectOfDomain($id, $redirectcode); } - if ($path != $result['documentroot'] || $isemaildomain != $result['isemaildomain'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $aliasdomain != (int)$result['aliasdomain'] || $openbasedir_path != $result['openbasedir_path'] || $ssl_redirect != $result['ssl_redirect'] || $letsencrypt != $result['letsencrypt'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $phpsettingid != $result['phpsettingid'] || $http2 != $result['http2']) { + if ($path != $result['documentroot'] + || $isemaildomain != $result['isemaildomain'] + || $wwwserveralias != $result['wwwserveralias'] + || $iswildcarddomain != $result['iswildcarddomain'] + || $aliasdomain != (int)$result['aliasdomain'] + || $openbasedir_path != $result['openbasedir_path'] + || $ssl_redirect != $result['ssl_redirect'] + || $letsencrypt != $result['letsencrypt'] + || $hsts_maxage != $result['hsts'] + || $hsts_sub != $result['hsts_sub'] + || $hsts_preload != $result['hsts_preload'] + || $phpsettingid != $result['phpsettingid'] + || $http2 != $result['http2'] + || ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1') + ) { $stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `documentroot` = :documentroot, @@ -802,7 +838,8 @@ class SubDomains extends ApiCommand implements ResourceEntity `hsts` = :hsts, `hsts_sub` = :hsts_sub, `hsts_preload` = :hsts_preload, - `phpsettingid` = :phpsettingid + `phpsettingid` = :phpsettingid, + `speciallogfile` = :speciallogfile WHERE `customerid`= :customerid AND `id`= :id "); $params = [ @@ -820,6 +857,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "hsts_sub" => $hsts_sub, "hsts_preload" => $hsts_preload, "phpsettingid" => $phpsettingid, + "speciallogfile" => $speciallogfile, "customerid" => $customer['customerid'], "id" => $id ]; @@ -865,7 +903,7 @@ class SubDomains extends ApiCommand implements ResourceEntity } /** - * lists all subdomain entries + * lists all customer domain/subdomain entries * * @param bool $with_ips * optional, default true @@ -910,17 +948,12 @@ class SubDomains extends ApiCommand implements ResourceEntity $custom_list_result = $_custom_list_result['list']; } $customer_ids = []; - $customer_stdsubs = []; foreach ($custom_list_result as $customer) { $customer_ids[] = $customer['customerid']; - $customer_stdsubs[$customer['customerid']] = $customer['standardsubdomain']; } if (empty($customer_ids)) { throw new Exception("Required resource unsatisfied.", 405); } - if (empty($customer_stdsubs)) { - throw new Exception("Required resource unsatisfied.", 405); - } $select_fields = [ '`d`.*' @@ -932,9 +965,6 @@ class SubDomains extends ApiCommand implements ResourceEntity $customer_ids = [ $this->getUserDetail('customerid') ]; - $customer_stdsubs = [ - $this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain') - ]; $select_fields = [ '`d`.`id`', @@ -949,7 +979,8 @@ class SubDomains extends ApiCommand implements ResourceEntity '`d`.`parentdomainid`', '`d`.`letsencrypt`', '`d`.`registration_date`', - '`d`.`termination_date`' + '`d`.`termination_date`', + '`d`.`deactivated`' ]; } $query_fields = []; @@ -963,7 +994,7 @@ class SubDomains extends ApiCommand implements ResourceEntity LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `pd` ON `pd`.`id`=`d`.`parentdomainid` WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ") AND `d`.`email_only` = '0' - AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit()); + " . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit()); $result = []; Database::pexecute($domains_stmt, $query_fields, true, true); @@ -1063,17 +1094,19 @@ class SubDomains extends ApiCommand implements ResourceEntity $this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain') ]; } - // prepare select statement - $domains_stmt = Database::prepare(" - SELECT COUNT(*) as num_subdom - FROM `" . TABLE_PANEL_DOMAINS . "` `d` - WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ") - AND `d`.`email_only` = '0' - AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ") - "); - $result = Database::pexecute_first($domains_stmt, null, true, true); - if ($result) { - return $this->response($result['num_subdom']); + if (!empty($customer_ids)) { + // prepare select statement + $domains_stmt = Database::prepare(" + SELECT COUNT(*) as num_subdom + FROM `" . TABLE_PANEL_DOMAINS . "` `d` + WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ") + AND `d`.`email_only` = '0' + AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ") + "); + $result = Database::pexecute_first($domains_stmt, null, true, true); + if ($result) { + return $this->response($result['num_subdom']); + } } return $this->response(0); } diff --git a/lib/Froxlor/Api/FroxlorRPC.php b/lib/Froxlor/Api/FroxlorRPC.php index 4a85872b..ed5398dd 100644 --- a/lib/Froxlor/Api/FroxlorRPC.php +++ b/lib/Froxlor/Api/FroxlorRPC.php @@ -112,11 +112,11 @@ class FroxlorRPC * * @return bool */ - private static function validateAllowedFrom(array $allowed_from, string $remote_addr): bool + public static function validateAllowedFrom(array $allowed_from, string $remote_addr): bool { // shorten IP for comparison $remote_addr = inet_ntop(inet_pton($remote_addr)); - // check for diret matches + // check for direct matches if (in_array($remote_addr, $allowed_from)) { return true; } diff --git a/lib/Froxlor/Cli/CliCommand.php b/lib/Froxlor/Cli/CliCommand.php index 0db8c5a2..83fcfe70 100644 --- a/lib/Froxlor/Cli/CliCommand.php +++ b/lib/Froxlor/Cli/CliCommand.php @@ -25,19 +25,18 @@ namespace Froxlor\Cli; -use PDO; use Exception; +use Froxlor\Database\Database; use Froxlor\Froxlor; use Froxlor\Settings; -use Froxlor\Database\Database; +use PDO; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class CliCommand extends Command { - protected function validateRequirements(InputInterface $input, OutputInterface $output, bool $ignore_has_updates = false): int + protected function validateRequirements(OutputInterface $output, bool $ignore_has_updates = false): int { if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) { $output->writeln("Could not find froxlor's userdata.inc.php file. You should use this script only with an installed froxlor system."); @@ -116,9 +115,11 @@ class CliCommand extends Command return $userinfo; } - private function runUpdate(OutputInterface $output): int + protected function runUpdate(OutputInterface $output, bool $manual = false): int { - $output->writeln('Automatic update is activated and we are going to proceed without any notices'); + if (!$manual) { + $output->writeln('Automatic update is activated and we are going to proceed without any notices'); + } include_once Froxlor::getInstallDir() . '/lib/tables.inc.php'; define('_CRON_UPDATE', 1); ob_start([ @@ -127,11 +128,11 @@ class CliCommand extends Command ]); include_once Froxlor::getInstallDir() . '/install/updatesql.php'; ob_end_flush(); - $output->writeln('Automatic update done - you should check your settings to be sure everything is fine'); + $output->writeln('' . ($manual ? 'Database' : 'Automatic') . ' update done - you should check your settings to be sure everything is fine'); return self::SUCCESS; } - private function cleanUpdateOutput($buffer) + private function cleanUpdateOutput($buffer): string { return strip_tags(preg_replace("//", "\n", $buffer)); } diff --git a/lib/Froxlor/Cli/ConfigDiff.php b/lib/Froxlor/Cli/ConfigDiff.php index 82340b6c..d7251617 100644 --- a/lib/Froxlor/Cli/ConfigDiff.php +++ b/lib/Froxlor/Cli/ConfigDiff.php @@ -45,6 +45,9 @@ final class ConfigDiff extends CliCommand ->addOption('diff-params', '', InputOption::VALUE_REQUIRED, 'Additional parameters for `diff`, e.g. --diff-params="--color=always"'); } + /** + * @throws \Exception + */ protected function execute(InputInterface $input, OutputInterface $output): int { require Froxlor::getInstallDir() . '/lib/functions.php'; diff --git a/lib/Froxlor/Cli/ConfigServices.php b/lib/Froxlor/Cli/ConfigServices.php index 155c0afd..ba6389ba 100644 --- a/lib/Froxlor/Cli/ConfigServices.php +++ b/lib/Froxlor/Cli/ConfigServices.php @@ -25,6 +25,7 @@ namespace Froxlor\Cli; +use Exception; use Froxlor\Config\ConfigParser; use Froxlor\Database\Database; use Froxlor\FileDir; @@ -40,9 +41,8 @@ use Symfony\Component\Console\Style\SymfonyStyle; final class ConfigServices extends CliCommand { - private $yes_to_all_supported = [ - /* 'bookworm', */ + 'bookworm', 'bionic', 'bullseye', 'buster', @@ -62,11 +62,9 @@ final class ConfigServices extends CliCommand ->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Install packages without asking questions (Debian/Ubuntu only currently)'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - $result = self::SUCCESS; - - $result = $this->validateRequirements($input, $output); + $result = $this->validateRequirements($output); require Froxlor::getInstallDir() . '/lib/functions.php'; @@ -93,7 +91,7 @@ final class ConfigServices extends CliCommand if ($result == self::SUCCESS) { $io = new SymfonyStyle($input, $output); if ($input->getOption('create')) { - $result = $this->createConfig($input, $output, $io); + $result = $this->createConfig($output, $io); } elseif ($input->getOption('apply')) { $result = $this->applyConfig($input, $output, $io); } elseif ($input->getOption('list') || $input->getOption('daemon')) { @@ -158,7 +156,10 @@ final class ConfigServices extends CliCommand fclose($fp); } - private function createConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io) + /** + * @throws Exception + */ + private function createConfig(OutputInterface $output, SymfonyStyle $io): int { $_daemons_config = [ 'distro' => "" @@ -285,7 +286,10 @@ final class ConfigServices extends CliCommand return self::SUCCESS; } - private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io) + /** + * @throws Exception + */ + private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io): int { $applyFile = $input->getOption('apply'); @@ -429,7 +433,10 @@ final class ConfigServices extends CliCommand } } - private function getReplacerArray() + /** + * @throws Exception + */ + private function getReplacerArray(): array { $customer_tmpdir = '/tmp/'; if (Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_tmpdir') != '') { @@ -438,7 +445,7 @@ final class ConfigServices extends CliCommand $customer_tmpdir = Settings::Get('phpfpm.tmpdir'); } - // try to convert namserver hosts to ip's + // try to convert nameserver hosts to ip's $ns_ips = ""; $known_ns_ips = []; if (Settings::Get('system.nameservers') != '') { @@ -484,12 +491,12 @@ final class ConfigServices extends CliCommand Database::needSqlData(); $sql = Database::getSqlData(); - $replace_arr = [ + return [ '' => $sql['user'], '' => $sql['passwd'], '' => $sql['db'], '' => $sql['host'], - '' => isset($sql['socket']) ? $sql['socket'] : null, + '' => $sql['socket'] ?? null, '' => Settings::Get('system.hostname'), '' => Settings::Get('system.ipaddress'), '' => Settings::Get('system.nameservers'), @@ -508,6 +515,5 @@ final class ConfigServices extends CliCommand '' => Settings::Get('system.ssl_cert_file'), '' => Settings::Get('system.ssl_key_file'), ]; - return $replace_arr; } } diff --git a/lib/Froxlor/Cli/InstallCommand.php b/lib/Froxlor/Cli/InstallCommand.php index 45209dc1..cf00b6ba 100644 --- a/lib/Froxlor/Cli/InstallCommand.php +++ b/lib/Froxlor/Cli/InstallCommand.php @@ -26,13 +26,13 @@ namespace Froxlor\Cli; use Exception; -use Froxlor\Froxlor; use Froxlor\Config\ConfigParser; +use Froxlor\Froxlor; use Froxlor\Install\Install; use Froxlor\Install\Install\Core; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -53,7 +53,10 @@ final class InstallCommand extends Command ->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process'); } - protected function execute(InputInterface $input, OutputInterface $output) + /** + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output): int { $result = self::SUCCESS; @@ -137,10 +140,12 @@ final class InstallCommand extends Command $decoded_input = []; } - $result = $this->showStep(0, $extended, $decoded_input); - return $result; + return $this->showStep(0, $extended, $decoded_input); } + /** + * @throws Exception + */ private function showStep(int $step = 0, bool $extended = false, array $decoded_input = []): int { $result = self::SUCCESS; diff --git a/lib/Froxlor/Cli/MasterCron.php b/lib/Froxlor/Cli/MasterCron.php index 72d43f70..4364f5a8 100644 --- a/lib/Froxlor/Cli/MasterCron.php +++ b/lib/Froxlor/Cli/MasterCron.php @@ -25,19 +25,20 @@ namespace Froxlor\Cli; -use PDO; -use Froxlor\Froxlor; -use Froxlor\FileDir; -use Froxlor\Settings; -use Froxlor\FroxlorLogger; -use Froxlor\Database\Database; -use Froxlor\System\Cronjob; -use Froxlor\Cron\TaskId; +use Exception; use Froxlor\Cron\CronConfig; use Froxlor\Cron\System\Extrausers; +use Froxlor\Cron\TaskId; +use Froxlor\Database\Database; +use Froxlor\FileDir; +use Froxlor\Froxlor; +use Froxlor\FroxlorLogger; +use Froxlor\Settings; +use Froxlor\System\Cronjob; +use PDO; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; final class MasterCron extends CliCommand @@ -52,15 +53,17 @@ final class MasterCron extends CliCommand $this->setDescription('Regulary perform tasks created by froxlor'); $this->addArgument('job', InputArgument::IS_ARRAY, 'Job(s) to run'); $this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 10 = re-set quotas, 99 = re-create cron.d-file]') - ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces re-generating of config-files (webserver, nameserver, etc.)') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces given job or, if none given, forces re-generating of config-files (webserver, nameserver, etc.)') ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Output debug information about what is going on to STDOUT.') ->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).'); } - protected function execute(InputInterface $input, OutputInterface $output) + /** + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output): int { - $result = self::SUCCESS; - $result = $this->validateRequirements($input, $output); + $result = $this->validateRequirements($output); if ($result != self::SUCCESS) { // requirements failed, exit @@ -71,12 +74,13 @@ final class MasterCron extends CliCommand // handle force option if ($input->getOption('force')) { - // rebuild all config files - Cronjob::inserttask(TaskId::REBUILD_VHOST); - Cronjob::inserttask(TaskId::REBUILD_DNS); - Cronjob::inserttask(TaskId::CREATE_QUOTA); - Cronjob::inserttask(TaskId::REBUILD_CRON); - array_push($jobs, 'tasks'); + if (empty($jobs) || in_array('tasks', $jobs)) { + Cronjob::inserttask(TaskId::REBUILD_VHOST); + Cronjob::inserttask(TaskId::REBUILD_DNS); + Cronjob::inserttask(TaskId::CREATE_QUOTA); + Cronjob::inserttask(TaskId::REBUILD_CRON); + $jobs[] = 'tasks'; + } define('CRON_IS_FORCED', 1); } // handle debug option @@ -91,9 +95,9 @@ final class MasterCron extends CliCommand if ($input->getOption('run-task')) { $tasks_to_run = $input->getOption('run-task'); foreach ($tasks_to_run as $ttr) { - if (in_array($ttr, [1, 4, 10, 99])) { + if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) { Cronjob::inserttask($ttr); - array_push($jobs, 'tasks'); + $jobs[] = 'tasks'; } else { $output->writeln('Unknown task number "' . $ttr . '"'); } @@ -139,12 +143,12 @@ final class MasterCron extends CliCommand $cronfile::run(); } // free the lockfile - $this->unlockJob($job); + $this->unlockJob(); } } // regenerate nss-extrausers files / invalidate nscd cache (if used) - $this->refreshUsers((int) $tasks_cnt['jobcnt']); + $this->refreshUsers((int)$tasks_cnt['jobcnt']); // we have to check the system's last guid with every cron run // in case the admin installed new software which added a new user @@ -156,40 +160,25 @@ final class MasterCron extends CliCommand CronConfig::checkCrondConfigurationFile(); // check for old/compatibility cronjob file - if (file_exists(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php')) { - @unlink(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php'); - @rmdir(Froxlor::getInstallDir().'/scripts'); + if (file_exists(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php')) { + @unlink(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php'); + @rmdir(Froxlor::getInstallDir() . '/scripts'); } // reset cronlog-flag if set to "once" - if ((int) Settings::Get('logger.log_cron') == 1) { + if ((int)Settings::Get('logger.log_cron') == 1) { FroxlorLogger::getInstanceOf()->setCronLog(0); } + // clean up possible old login-links + Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()"); + return $result; } - private function refreshUsers(int $jobcount = 0) - { - if ($jobcount > 0) { - if (Settings::Get('system.nssextrausers') == 1) { - Extrausers::generateFiles($this->cronLog); - return; - } - - // clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers - if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) { - $false_val = false; - FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [ - '>' - ]); - FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [ - '>' - ]); - } - } - } - + /** + * @throws Exception + */ private function validateOwnership(OutputInterface $output) { // when using fcgid or fpm for froxlor-vhost itself, we have to check @@ -216,21 +205,6 @@ final class MasterCron extends CliCommand $output->writeln('OK'); } - private function getCronModule(string $cronname, OutputInterface $output) - { - $upd_stmt = Database::prepare(" - SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron; - "); - $cron = Database::pexecute_first($upd_stmt, [ - 'cron' => $cronname - ]); - if ($cron) { - return $cron['cronclass']; - } - $output->writeln("Requested cronjob '" . $cronname . "' could not be found."); - return false; - } - private function lockJob(string $job, OutputInterface $output): bool { @@ -243,12 +217,12 @@ final class MasterCron extends CliCommand system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return); if ($check_pid_return == 1) { // Process does not seem to run, most likely it has died - $this->unlockJob($job); + $this->unlockJob(); } else { // cronjob still running, output info and stop $output->writeln([ 'Job "' . $jobinfo['job'] . '" is currently running.', - 'Started: ' . date('d.m.Y H:i', (int) $jobinfo['startts']), + 'Started: ' . date('d.m.Y H:i', (int)$jobinfo['startts']), 'PID: ' . $jobinfo['pid'] . '' ]); return false; @@ -264,8 +238,44 @@ final class MasterCron extends CliCommand return true; } - private function unlockJob(string $job): bool + private function unlockJob(): bool { return @unlink($this->lockFile); } + + private function getCronModule(string $cronname, OutputInterface $output) + { + $upd_stmt = Database::prepare(" + SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron; + "); + $cron = Database::pexecute_first($upd_stmt, [ + 'cron' => $cronname + ]); + if ($cron) { + return $cron['cronclass']; + } + $output->writeln("Requested cronjob '" . $cronname . "' could not be found."); + return false; + } + + private function refreshUsers(int $jobcount = 0) + { + if ($jobcount > 0) { + if (Settings::Get('system.nssextrausers') == 1) { + Extrausers::generateFiles($this->cronLog); + return; + } + + // clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers + if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) { + $false_val = false; + FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [ + '>' + ]); + FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [ + '>' + ]); + } + } + } } diff --git a/lib/Froxlor/Cli/PhpSessionclean.php b/lib/Froxlor/Cli/PhpSessionclean.php index 9c8fbd33..2993e19f 100644 --- a/lib/Froxlor/Cli/PhpSessionclean.php +++ b/lib/Froxlor/Cli/PhpSessionclean.php @@ -43,9 +43,9 @@ final class PhpSessionclean extends CliCommand $this->addArgument('max-lifetime', InputArgument::OPTIONAL, 'The number of seconds after which data will be seen as "garbage" and potentially cleaned up. Defaults to "1440"'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - $result = $this->validateRequirements($input, $output); + $result = $this->validateRequirements($output); if ($result == self::SUCCESS) { if ((int)Settings::Get('phpfpm.enabled') == 1) { @@ -89,7 +89,7 @@ final class PhpSessionclean extends CliCommand if (count($paths_to_clean) > 0) { foreach ($paths_to_clean as $ptc) { - // find all files older then maxlifetime and delete them + // find all files older than maxlifetime and delete them FileDir::safe_exec("find -O3 \"" . $ptc . "\" -ignore_readdir_race -depth -mindepth 1 -name 'sess_*' -type f -cmin \"+" . $maxlifetime . "\" -delete"); } } diff --git a/lib/Froxlor/Cli/RunApiCommand.php b/lib/Froxlor/Cli/RunApiCommand.php index 5a070f42..9e692de0 100644 --- a/lib/Froxlor/Cli/RunApiCommand.php +++ b/lib/Froxlor/Cli/RunApiCommand.php @@ -26,14 +26,12 @@ namespace Froxlor\Cli; use Exception; -use PDO; -use Symfony\Component\Console\Input\InputInterface; +use Froxlor\Froxlor; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Froxlor\Database\Database; -use Froxlor\Froxlor; final class RunApiCommand extends CliCommand { @@ -48,11 +46,9 @@ final class RunApiCommand extends CliCommand $this->addOption('show-params', 's', InputOption::VALUE_NONE, 'Show possible parameters for given api-command (given command will *not* be called)'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - $result = self::SUCCESS; - - $result = $this->validateRequirements($input, $output); + $result = $this->validateRequirements($output); require Froxlor::getInstallDir() . '/lib/functions.php'; @@ -110,6 +106,9 @@ final class RunApiCommand extends CliCommand return self::SUCCESS; } + /** + * @throws Exception + */ private function validateCommand(string $command): array { $command = explode(".", $command); diff --git a/lib/Froxlor/Cli/SwitchServerIp.php b/lib/Froxlor/Cli/SwitchServerIp.php index 5e1100cf..53f2b4ea 100644 --- a/lib/Froxlor/Cli/SwitchServerIp.php +++ b/lib/Froxlor/Cli/SwitchServerIp.php @@ -43,11 +43,9 @@ final class SwitchServerIp extends CliCommand ->addOption('list', 'l', InputOption::VALUE_NONE, 'List all IP addresses currently added for this server in froxlor'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - $result = self::SUCCESS; - - $result = $this->validateRequirements($input, $output); + $result = $this->validateRequirements($output); if ($result == self::SUCCESS && $input->getOption('list') == false && $input->getOption('switch') == false) { $output->writeln('Either --list or --switch option must be provided. Nothing to do, exiting.'); @@ -83,6 +81,7 @@ final class SwitchServerIp extends CliCommand $ip_list = $input->getOption('switch'); $has_error = false; + $ips_to_switch = []; foreach ($ip_list as $ips_combo) { $ip_pair = explode(",", $ips_combo); if (count($ip_pair) != 2) { diff --git a/lib/Froxlor/Cli/UpdateCommand.php b/lib/Froxlor/Cli/UpdateCommand.php index c97f6eaf..e49cb99e 100644 --- a/lib/Froxlor/Cli/UpdateCommand.php +++ b/lib/Froxlor/Cli/UpdateCommand.php @@ -27,9 +27,9 @@ namespace Froxlor\Cli; use Exception; use Froxlor\Froxlor; -use Froxlor\Settings; -use Froxlor\Install\Update; use Froxlor\Install\AutoUpdate; +use Froxlor\Install\Update; +use Froxlor\Settings; use Froxlor\System\Mailer; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -44,6 +44,7 @@ final class UpdateCommand extends CliCommand $this->setName('froxlor:update'); $this->setDescription('Check for newer version and update froxlor'); $this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit') + ->addOption('database', 'd', InputOption::VALUE_NONE, 'Only run database updates in case updates are done via apt or manually.') ->addOption('mail-notify', 'm', InputOption::VALUE_NONE, 'Additionally inform administrator via email if a newer version was found') ->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for download, extract and database-update, just do it (if not --check-only is set)') ->addOption('integer-return', 'i', InputOption::VALUE_NONE, 'Return integer whether a new version is available or not (implies --check-only). Useful for programmatic use.'); @@ -53,8 +54,36 @@ final class UpdateCommand extends CliCommand { $result = self::SUCCESS; + // database update only + if ($input->getOption('database')) { + $result = $this->validateRequirements($input, $output, true); + if ($result == self::SUCCESS) { + if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) { + $output->writeln('' . lng('updates.dbupdate_required') . ''); + if ($input->getOption('check-only')) { + $output->writeln('Doing nothing because of "check-only" flag.'); + } else { + $yestoall = $input->getOption('yes-to-all') !== false; + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i'); + if ($yestoall || $helper->ask($input, $output, $question)) { + $result = $this->runUpdate($output, true); + } + } + return $result; + } + $output->writeln('' . lng('update.noupdatesavail', (Settings::Get('system.update_channel') == 'testing' ? lng('serversettings.uc_testing') . ' ' : '')) . ''); + } + return $result; + } + $result = $this->validateRequirements($input, $output); + if ($result != self::SUCCESS) { + // requirements failed, exit + return $result; + } + require Froxlor::getInstallDir() . '/lib/functions.php'; // version check @@ -182,22 +211,4 @@ final class UpdateCommand extends CliCommand } } } - - private function updateDatabase() - { - include_once Froxlor::getInstallDir() . '/lib/tables.inc.php'; - define('_CRON_UPDATE', 1); - ob_start([ - $this, - 'cleanUpdateOutput' - ]); - include_once Froxlor::getInstallDir() . '/install/updatesql.php'; - ob_end_flush(); - return self::SUCCESS; - } - - private function cleanUpdateOutput($buffer) - { - return strip_tags(preg_replace("//", "\n", $buffer)); - } } diff --git a/lib/Froxlor/Cli/UserCommand.php b/lib/Froxlor/Cli/UserCommand.php index 1a7674b0..3da5c429 100644 --- a/lib/Froxlor/Cli/UserCommand.php +++ b/lib/Froxlor/Cli/UserCommand.php @@ -26,15 +26,15 @@ namespace Froxlor\Cli; use Exception; -use Symfony\Component\Console\Input\InputInterface; +use Froxlor\Api\Commands\Admins; +use Froxlor\Api\Commands\Customers; +use Froxlor\Froxlor; +use Froxlor\System\Crypt; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Froxlor\Api\Commands\Admins; -use Froxlor\Api\Commands\Customers; -use Froxlor\System\Crypt; -use Froxlor\Froxlor; final class UserCommand extends CliCommand { @@ -50,11 +50,11 @@ final class UserCommand extends CliCommand ->addOption('show-info', 's', InputOption::VALUE_NONE, 'Output information details of given user'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $result = self::SUCCESS; - $result = $this->validateRequirements($input, $output); + $result = $this->validateRequirements($output); require Froxlor::getInstallDir() . '/lib/functions.php'; diff --git a/lib/Froxlor/Cli/ValidateAcmeWebroot.php b/lib/Froxlor/Cli/ValidateAcmeWebroot.php index 4d906399..ef0b46ae 100644 --- a/lib/Froxlor/Cli/ValidateAcmeWebroot.php +++ b/lib/Froxlor/Cli/ValidateAcmeWebroot.php @@ -48,15 +48,16 @@ final class ValidateAcmeWebroot extends CliCommand $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) + /** + * @throws \Exception + */ + protected function execute(InputInterface $input, OutputInterface $output): int { - $result = self::SUCCESS; - - $result = $this->validateRequirements($input, $output, true); + $result = $this->validateRequirements($output, true); $io = new SymfonyStyle($input, $output); - if ((int) Settings::Get('system.leenabled') == 0) { + if ((int)Settings::Get('system.leenabled') == 0) { $io->info("Let's Encrypt not activated in froxlor settings."); $result = self::INVALID; } @@ -94,7 +95,7 @@ final class ValidateAcmeWebroot extends CliCommand $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 . "@"; + $sed_params = "s@" . $former_value . "@" . $acmesh_challenge_dir . "@"; FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_alias_file)); $count_changes++; } @@ -138,8 +139,6 @@ final class ValidateAcmeWebroot extends CliCommand $io->info("Domain '" . $domain . "' Le_Webroot value is correct"); } break; - } else { - continue; } } } diff --git a/lib/Froxlor/Cron/Dns/Bind.php b/lib/Froxlor/Cron/Dns/Bind.php index 939796d7..882b3d99 100644 --- a/lib/Froxlor/Cron/Dns/Bind.php +++ b/lib/Froxlor/Cron/Dns/Bind.php @@ -62,8 +62,8 @@ class Bind extends DnsBase $this->bindconf_file = '# ' . Settings::Get('system.bindconf_directory') . 'froxlor_bind.conf' . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n\n"; foreach ($domains as $domain) { - if ($domain['ismainbutsubto'] > 0) { - // domains with ismainbutsubto>0 are handled by recursion within walkDomainList() + if ($domain['is_child']) { + // domains that are subdomains to other main domains are handled by recursion within walkDomainList() continue; } $this->walkDomainList($domain, $domains); @@ -114,7 +114,7 @@ class Bind extends DnsBase $isFroxlorHostname = true; } - if ($domain['ismainbutsubto'] == 0) { + if (!$domain['is_child']) { $zoneContent = (string)Dns::createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname); $domain['zonefile'] = 'domains/' . $domain['domain'] . '.zone'; $zonefile_name = FileDir::makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']); diff --git a/lib/Froxlor/Cron/Dns/DnsBase.php b/lib/Froxlor/Cron/Dns/DnsBase.php index 5c9eb018..f9ceaede 100644 --- a/lib/Froxlor/Cron/Dns/DnsBase.php +++ b/lib/Froxlor/Cron/Dns/DnsBase.php @@ -26,6 +26,7 @@ namespace Froxlor\Cron\Dns; use Froxlor\Database\Database; +use Froxlor\Domain\Domain; use Froxlor\FileDir; use Froxlor\FroxlorLogger; use Froxlor\PhpHelper; @@ -210,7 +211,6 @@ abstract class DnsBase `d`.`dkim`, `d`.`dkim_id`, `d`.`dkim_pubkey`, - `d`.`ismainbutsubto`, `c`.`loginname`, `c`.`guid` FROM @@ -219,7 +219,7 @@ abstract class DnsBase WHERE `d`.`isbinddomain` = '1' ORDER BY - `d`.`domain` ASC + LENGTH(`d`.`domain`), `d`.`domain` ASC "); $domains = []; @@ -241,7 +241,6 @@ abstract class DnsBase 'bindserial' => date('Ymd') . '00', 'dkim' => '0', 'iswildcarddomain' => '1', - 'ismainbutsubto' => '0', 'zonefile' => '', 'froxlorhost' => '1' ]; @@ -257,18 +256,23 @@ abstract class DnsBase if (!isset($domains[$key]['children'])) { $domains[$key]['children'] = []; } - if ($domains[$key]['ismainbutsubto'] > 0) { - if (isset($domains[$domains[$key]['ismainbutsubto']])) { - $domains[$domains[$key]['ismainbutsubto']]['children'][] = $domains[$key]['id']; - } else { - $domains[$key]['ismainbutsubto'] = 0; + if (!isset($domains[$key]['is_child'])) { + $domains[$key]['is_child'] = false; + } + $children = Domain::getMainSubdomainIds($key); + if (count($children) > 0) { + foreach ($children as $child) { + if (isset($domains[$child])) { + $domains[$key]['children'][] = $domains[$child]['id']; + $domains[$child]['is_child'] = true; + } } } } - $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, str_pad('domId', 9, ' ') . str_pad('domain', 40, ' ') . 'ismainbutsubto ' . str_pad('parent domain', 40, ' ') . "list of child domain ids"); + $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, str_pad('domId', 9, ' ') . str_pad('domain', 40, ' ') . "list of child domain ids"); foreach ($domains as $domain) { - $logLine = str_pad($domain['id'], 9, ' ') . str_pad($domain['domain'], 40, ' ') . str_pad($domain['ismainbutsubto'], 15, ' ') . str_pad(((isset($domains[$domain['ismainbutsubto']])) ? $domains[$domain['ismainbutsubto']]['domain'] : '-'), 40, ' ') . join(', ', $domain['children']); + $logLine = str_pad($domain['id'], 9, ' ') . str_pad($domain['domain'], 40, ' ') . join(', ', $domain['children']); $this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, $logLine); } diff --git a/lib/Froxlor/Cron/Dns/PowerDNS.php b/lib/Froxlor/Cron/Dns/PowerDNS.php index c3d8f20b..65fb25ca 100644 --- a/lib/Froxlor/Cron/Dns/PowerDNS.php +++ b/lib/Froxlor/Cron/Dns/PowerDNS.php @@ -50,8 +50,8 @@ class PowerDNS extends DnsBase } foreach ($domains as $domain) { - if ($domain['ismainbutsubto'] > 0) { - // domains with ismainbutsubto>0 are handled by recursion within walkDomainList() + if ($domain['is_child']) { + // domains that are subdomains to other main domains are handled by recursion within walkDomainList() continue; } $this->walkDomainList($domain, $domains); @@ -108,7 +108,7 @@ class PowerDNS extends DnsBase $isFroxlorHostname = true; } - if ($domain['ismainbutsubto'] == 0) { + if (!$domain['is_child']) { $zoneContent = Dns::createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname); if (count($subzones)) { foreach ($subzones as $subzone) { diff --git a/lib/Froxlor/Cron/Forkable.php b/lib/Froxlor/Cron/Forkable.php new file mode 100644 index 00000000..dc928f8c --- /dev/null +++ b/lib/Froxlor/Cron/Forkable.php @@ -0,0 +1,57 @@ += $concurrentChildren) { + foreach ($childrenPids as $key => $pid) { + $res = pcntl_waitpid($pid, $status, WNOHANG); + // If the process has already exited + if ($res == -1 || $res > 0) { + unset($childrenPids[$key]); + } + } + sleep(1); + } + } + } + while (pcntl_waitpid(0, $status) != -1); + } else { + if (!defined('CRON_NOFORK_FLAG')) { + if (extension_loaded('pcntl')) { + $msg = "PHP compiled with pcntl but pcntl_fork function is not available."; + } else { + $msg = "PHP compiled without pcntl."; + } + FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, $msg . " Not forking " . self::class . ", this may take a long time!"); + } + foreach ($attributes as $closureAttributes) { + $closure($closureAttributes); + } + } + } +} diff --git a/lib/Froxlor/Cron/Http/Apache.php b/lib/Froxlor/Cron/Http/Apache.php index faa439ef..8fba3764 100644 --- a/lib/Froxlor/Cron/Http/Apache.php +++ b/lib/Froxlor/Cron/Http/Apache.php @@ -630,29 +630,6 @@ class Apache extends HttpConfigBase } } - /** - * Get the filename for the virtualhost - */ - protected function getVhostFilename($domain, $ssl_vhost = false) - { - if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) { - $vhost_no = '35'; - } elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) { - $vhost_no = '30'; - } else { - // number of dots in a domain specifies it's position (and depth of subdomain) starting at 29 going downwards on higher depth - $vhost_no = (string)(30 - substr_count($domain['domain'], ".") + 1); - } - - if ($ssl_vhost === true) { - $vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_ssl_vhost_' . $domain['domain'] . '.conf'); - } else { - $vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_normal_vhost_' . $domain['domain'] . '.conf'); - } - - return $vhost_filename; - } - /** * We compose the virtualhost entry for one domain */ diff --git a/lib/Froxlor/Cron/Http/HttpConfigBase.php b/lib/Froxlor/Cron/Http/HttpConfigBase.php index 7b400726..4226322a 100644 --- a/lib/Froxlor/Cron/Http/HttpConfigBase.php +++ b/lib/Froxlor/Cron/Http/HttpConfigBase.php @@ -28,6 +28,7 @@ namespace Froxlor\Cron\Http; use Froxlor\Cron\Http\LetsEncrypt\AcmeSh; use Froxlor\Cron\Http\Php\Fpm; use Froxlor\Database\Database; +use Froxlor\Domain\Domain; use Froxlor\FileDir; use Froxlor\Froxlor; use Froxlor\FroxlorLogger; @@ -187,4 +188,18 @@ class HttpConfigBase } return false; } + + /** + * Get the filename for the virtualhost + */ + protected function getVhostFilename(array $domain, bool $ssl_vhost = false, bool $filename_only = false) + { + // number of dots in a domain specifies its position (and depth of subdomain) starting at 35 going downwards on higher depth + $vhost_no = (string)(35 - substr_count($domain['domain'], ".") + 1); + $filename = $vhost_no . '_froxlor_' . ($ssl_vhost ? 'ssl' : 'normal') . '_vhost_' . $domain['domain'] . '.conf'; + if ($filename_only) { + return $filename; + } + return FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $filename); + } } diff --git a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php index 514dcc3d..659fb551 100644 --- a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php +++ b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php @@ -556,6 +556,10 @@ EOC; Settings::Set('system.le_froxlor_enabled', 0); } $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Let's Encrypt deactivated for domain " . $domain); + if (!defined('CRON_IS_FORCED') && !defined('CRON_DEBUG_FLAG')) { + // email info to admin that lets encrypt has been disabled for this domain + Cronjob::notifyMailToAdmin("Let's Encrypt has been deactivated for domain '" . $domain . "' due to failed dns validation (wrong or no IP address)"); + } } } } @@ -586,11 +590,20 @@ EOC; $acmesh_cmd .= " --debug"; } - $acme_result = FileDir::safe_exec($acmesh_cmd); + $exit_code = null; + $acme_result = FileDir::safe_exec($acmesh_cmd, $exit_code); // debug output of acme.sh run $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, implode("\n", $acme_result)); - self::certToDb($certrow, $cronlog, $acme_result); + if ($exit_code != 0) { + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Non-successful exit-code returned :("); + if (!defined('CRON_IS_FORCED') && !defined('CRON_DEBUG_FLAG')) { + Cronjob::notifyMailToAdmin("Let's Encrypt certificate could not be obtained for: " . implode(", ", $domains) . "\n\n" . implode("\n", $acme_result)); + } + } else { + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Successful exit-code returned - storing certificate"); + self::certToDb($certrow, $cronlog, $acme_result); + } } } diff --git a/lib/Froxlor/Cron/Http/Lighttpd.php b/lib/Froxlor/Cron/Http/Lighttpd.php index 233236a0..968ab62c 100644 --- a/lib/Froxlor/Cron/Http/Lighttpd.php +++ b/lib/Froxlor/Cron/Http/Lighttpd.php @@ -336,24 +336,9 @@ class Lighttpd extends HttpConfigBase $_pos = strrpos($_tmp_path, '/'); $_inc_path = substr($_tmp_path, $_pos + 1); - // maindomain - if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) { - $vhost_no = '50'; - } elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) { - // sub-but-main-domain - $vhost_no = '51'; - } else { - // subdomains - // number of dots in a domain specifies it's position (and depth of subdomain) starting at 89 going downwards on higher depth - $vhost_no = (string)(90 - substr_count($domain['domain'], ".") + 1); - } - - if ($ssl == '1') { - $vhost_no = (int)$vhost_no += 10; - } - - $vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/vhosts/' . $vhost_no . '_' . $domain['domain'] . '.conf'); - $included_vhosts[] = $_inc_path . '/vhosts/' . $vhost_no . '_' . $domain['domain'] . '.conf'; + $filename = self::getVhostFilename($domain, ($ssl == '1'), true); + $vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/vhosts/' . $filename); + $included_vhosts[] = $_inc_path . '/vhosts/' . $filename; } if (!isset($this->lighttpd_data[$vhost_filename])) { diff --git a/lib/Froxlor/Cron/Http/Nginx.php b/lib/Froxlor/Cron/Http/Nginx.php index 33d80e86..b9a345f2 100644 --- a/lib/Froxlor/Cron/Http/Nginx.php +++ b/lib/Froxlor/Cron/Http/Nginx.php @@ -467,26 +467,6 @@ class Nginx extends HttpConfigBase } } - protected function getVhostFilename($domain, $ssl_vhost = false) - { - if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) { - $vhost_no = '35'; - } elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) { - $vhost_no = '30'; - } else { - // number of dots in a domain specifies it's position (and depth of subdomain) starting at 29 going downwards on higher depth - $vhost_no = (string)(30 - substr_count($domain['domain'], ".") + 1); - } - - if ($ssl_vhost === true) { - $vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_ssl_vhost_' . $domain['domain'] . '.conf'); - } else { - $vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_normal_vhost_' . $domain['domain'] . '.conf'); - } - - return $vhost_filename; - } - protected function getVhostContent($domain, $ssl_vhost = false) { if ($ssl_vhost === true && $domain['ssl'] != '1' && $domain['ssl_redirect'] != '1') { diff --git a/lib/Froxlor/Cron/Http/Php/Fpm.php b/lib/Froxlor/Cron/Http/Php/Fpm.php index 1cf84901..3c96fe1b 100644 --- a/lib/Froxlor/Cron/Http/Php/Fpm.php +++ b/lib/Froxlor/Cron/Http/Php/Fpm.php @@ -289,7 +289,7 @@ pm.max_children = 1 } } - // now check if 'sendmail_path' has not beed set in the custom-php.ini + // now check if 'sendmail_path' has not been set in the custom-php.ini // if not we use our fallback-default as usual if (strpos($fpm_config, 'php_admin_value[sendmail_path]') === false) { $fpm_config .= 'php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f ' . $this->domain['email'] . "\n"; diff --git a/lib/Froxlor/Cron/System/BackupCron.php b/lib/Froxlor/Cron/System/ExportCron.php similarity index 65% rename from lib/Froxlor/Cron/System/BackupCron.php rename to lib/Froxlor/Cron/System/ExportCron.php index 5b818b56..8def5b7d 100644 --- a/lib/Froxlor/Cron/System/BackupCron.php +++ b/lib/Froxlor/Cron/System/ExportCron.php @@ -25,122 +25,84 @@ namespace Froxlor\Cron\System; +use Exception; +use Froxlor\Cron\Forkable; use Froxlor\Cron\FroxlorCron; use Froxlor\Database\Database; use Froxlor\FileDir; use Froxlor\FroxlorLogger; use Froxlor\Settings; -class BackupCron extends FroxlorCron +class ExportCron extends FroxlorCron { + use Forkable; public static function run() { - // Check Traffic-Lock - if (function_exists('pcntl_fork')) { - $BackupLock = FileDir::makeCorrectFile(dirname(self::getLockfile()) . "/froxlor_cron_backup.lock"); - if (file_exists($BackupLock) && is_numeric($BackupPid = file_get_contents($BackupLock))) { - if (function_exists('posix_kill')) { - $BackupPidStatus = @posix_kill($BackupPid, 0); - } else { - system("kill -CHLD " . $BackupPid . " 1> /dev/null 2> /dev/null", $BackupPidStatus); - $BackupPidStatus = !$BackupPidStatus; - } - if ($BackupPidStatus) { - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Backup run already in progress'); - return 1; - } - } - // Create Backup Log and Fork - // We close the database - connection before we fork, so we don't share resources with the child - Database::needRoot(false); // this forces the connection to be set to null - $BackupPid = pcntl_fork(); - // Parent - if ($BackupPid) { - file_put_contents($BackupLock, $BackupPid); - // unnecessary to recreate database connection here - return 0; - } elseif ($BackupPid == 0) { - // Child - posix_setsid(); - // re-create db - Database::needRoot(false); - } else { - // Fork failed - return 1; - } - } else { - if (extension_loaded('pcntl')) { - $msg = "PHP compiled with pcntl but pcntl_fork function is not available."; - } else { - $msg = "PHP compiled without pcntl."; - } - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, $msg . " Not forking backup-cron, this may take a long time!"); - } - - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'BackupCron: started - creating customer backup'); + FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'ExportCron: started - creating customer data export'); $result_tasks_stmt = Database::query(" SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20' ORDER BY `id` ASC "); - - $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :id"); - - $cronlog = FroxlorLogger::getInstanceOf(); $all_jobs = $result_tasks_stmt->fetchAll(); - foreach ($all_jobs as $row) { - if ($row['data'] != '') { - $row['data'] = json_decode($row['data'], true); - } - if (is_array($row['data'])) { - if (isset($row['data']['customerid']) && isset($row['data']['loginname']) && isset($row['data']['destdir'])) { - $row['data']['destdir'] = FileDir::makeCorrectDir($row['data']['destdir']); - $customerdocroot = FileDir::makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $row['data']['loginname'] . '/'); - - // create folder if not exists - if (!file_exists($row['data']['destdir']) && $row['data']['destdir'] != '/' && $row['data']['destdir'] != Settings::Get('system.documentroot_prefix') && $row['data']['destdir'] != $customerdocroot) { - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating backup-destination path for customer: ' . escapeshellarg($row['data']['destdir'])); - FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir'])); - } - - self::createCustomerBackup($row['data'], $customerdocroot, $cronlog); - } - } - - // remove entry - Database::pexecute($del_stmt, [ - 'id' => $row['id'] - ]); - } - - if (function_exists('pcntl_fork')) { - @unlink($BackupLock); - die(); + if (!empty($all_jobs)) { + self::runFork([self::class, 'handle'], $all_jobs); } } + public static function handle(array $row) + { + $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :id"); + $cronlog = FroxlorLogger::getInstanceOf(); + + if ($row['data'] != '') { + $row['data'] = json_decode($row['data'], true); + } + + if (is_array($row['data'])) { + if (isset($row['data']['customerid']) && isset($row['data']['loginname']) && isset($row['data']['destdir'])) { + $row['data']['destdir'] = FileDir::makeCorrectDir($row['data']['destdir']); + $customerdocroot = FileDir::makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $row['data']['loginname'] . '/'); + + // create folder if not exists + if (!file_exists($row['data']['destdir']) && $row['data']['destdir'] != '/' && $row['data']['destdir'] != Settings::Get('system.documentroot_prefix') && $row['data']['destdir'] != $customerdocroot) { + FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating data export destination path for customer: ' . escapeshellarg($row['data']['destdir'])); + FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir'])); + } + + self::createCustomerExport($row['data'], $customerdocroot, $cronlog); + } + } + + // remove entry + Database::pexecute($del_stmt, [ + 'id' => $row['id'] + ]); + } + /** - * depending on the give choice, the customers web-data, email-data and databases are being backup'ed + * depending on the give choice, the customers web-data, email-data and databases are being exported * * @param array $data * * @return void * + * @throws Exception */ - private static function createCustomerBackup($data = null, $customerdocroot = null, &$cronlog = null) + private static function createCustomerExport($data = null, $customerdocroot = null, &$cronlog = null) { - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating Backup for user "' . $data['loginname'] . '"'); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating data export for user "' . $data['loginname'] . '"'); // create tmp folder $tmpdir = FileDir::makeCorrectDir($data['destdir'] . '/.tmp/'); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating tmp-folder "' . $tmpdir . '"'); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mkdir -p ' . escapeshellarg($tmpdir)); FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir)); - $create_backup_tar_data = ""; + $create_export_tar_data = ""; // MySQL databases - if ($data['backup_dbs'] == 1) { + if ($data['dump_dbs'] == 1) { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating mysql-folder "' . FileDir::makeCorrectDir($tmpdir . '/mysql') . '"'); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql'))); FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql'))); @@ -152,7 +114,7 @@ class BackupCron extends FroxlorCron ]); $has_dbs = false; - $current_dbserver = null; + $current_dbserver = -1; while ($row = $sel_stmt->fetch()) { // Get sql_root data for the specific database-server the database resides on if ($current_dbserver != $row['dbserver']) { @@ -180,16 +142,18 @@ class BackupCron extends FroxlorCron } if ($has_dbs) { - $create_backup_tar_data .= './mysql '; + $create_export_tar_data .= './mysql '; } - unlink($mysqlcnf_file); + if (file_exists($mysqlcnf_file)) { + unlink($mysqlcnf_file); + } unset($sql_root); } // E-mail data - if ($data['backup_mail'] == 1) { + if ($data['dump_mail'] == 1) { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating mail-folder "' . FileDir::makeCorrectDir($tmpdir . '/mail') . '"'); FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mail'))); @@ -209,28 +173,41 @@ class BackupCron extends FroxlorCron if (!empty($tar_file_list)) { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfvz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/mail/' . $data['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list)); FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/mail/' . $data['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list)); - $create_backup_tar_data .= './mail '; + $create_export_tar_data .= './mail '; } } // Web data - if ($data['backup_web'] == 1) { + if ($data['dump_web'] == 1) { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating web-folder "' . FileDir::makeCorrectDir($tmpdir . '/web') . '"'); FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/web'))); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/web/' . $data['loginname'] . '-web.tar.gz')) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", FileDir::makeCorrectFile($tmpdir . '/*'))) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", substr(FileDir::makeCorrectDir($tmpdir), 0, -1))) . ' -C ' . escapeshellarg($customerdocroot) . ' .'); FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/web/' . $data['loginname'] . '-web.tar.gz')) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", FileDir::makeCorrectFile($tmpdir . '/*'))) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", substr(FileDir::makeCorrectFile($tmpdir), 0, -1))) . ' -C ' . escapeshellarg($customerdocroot) . ' .'); - $create_backup_tar_data .= './web '; + $create_export_tar_data .= './web '; } - if (!empty($create_backup_tar_data)) { - $backup_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-backup_' . date('YmdHi', time()) . '.tar.gz'); - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating backup-file "' . $backup_file . '"'); - // pack all archives in tmp-dir to one - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); - FileDir::safe_exec('tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); + if (!empty($create_export_tar_data)) { + // set owner to customer + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir)); + FileDir::safe_exec('chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir)); + // create tar-file + $export_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-export_' . date('YmdHi', time()) . '.tar.gz' . (!empty($data['pgp_public_key']) ? '.gpg' : '')); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating export-file "' . $export_file . '"'); + if (!empty($data['pgp_public_key'])) { + // pack all archives in tmp-dir to one archive and encrypt it with gpg + $recipient_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-recipients.gpg'); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating recipient-file "' . $recipient_file . '"'); + file_put_contents($recipient_file, $data['pgp_public_key']); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($export_file) . ' --trust-model always --batch --yes'); + FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($export_file) . ' --trust-model always --batch --yes', $return_value, ['|']); + } else { + // pack all archives in tmp-dir to one archive + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($export_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data)); + FileDir::safe_exec('tar cfz ' . escapeshellarg($export_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data)); + } // move to destination directory - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir'])); - FileDir::safe_exec('mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir'])); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($export_file) . ' ' . escapeshellarg($data['destdir'])); + FileDir::safe_exec('mv ' . escapeshellarg($export_file) . ' ' . escapeshellarg($data['destdir'])); // remove tmp-files $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> rm -rf ' . escapeshellarg($tmpdir)); FileDir::safe_exec('rm -rf ' . escapeshellarg($tmpdir)); diff --git a/lib/Froxlor/Cron/System/TasksCron.php b/lib/Froxlor/Cron/System/TasksCron.php index 337d0bf9..60face00 100644 --- a/lib/Froxlor/Cron/System/TasksCron.php +++ b/lib/Froxlor/Cron/System/TasksCron.php @@ -46,7 +46,7 @@ class TasksCron extends FroxlorCron * LOOK INTO TASKS TABLE TO SEE IF THERE ARE ANY UNDONE JOBS */ self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "TasksCron: Searching for tasks to do"); - // no type 99 (regenerate cron.d-file) and no type 20 (customer backup) + // no type 99 (regenerate cron.d-file) and no type 20 (customer data export) // order by type descending to re-create bind and then webserver at the end $result_tasks_stmt = Database::query(" SELECT `id`, `type`, `data` FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` <> '99' AND `type` <> '20' ORDER BY `type` DESC, `id` ASC diff --git a/lib/Froxlor/Cron/TaskId.php b/lib/Froxlor/Cron/TaskId.php index d80e6c11..9aa065c0 100644 --- a/lib/Froxlor/Cron/TaskId.php +++ b/lib/Froxlor/Cron/TaskId.php @@ -82,9 +82,9 @@ final class TaskId const DELETE_DOMAIN_SSL = 12; /** - * TYPE=20 COSTUMERBACKUP + * TYPE=20 CUSTUMER DATA DUMP */ - const CREATE_CUSTOMER_BACKUP = 20; + const CREATE_CUSTOMER_DATADUMP = 20; /** * TYPE=99 REGENERATE CRON diff --git a/lib/Froxlor/Cron/Traffic/TrafficCron.php b/lib/Froxlor/Cron/Traffic/TrafficCron.php index f6528802..30d1bd1b 100644 --- a/lib/Froxlor/Cron/Traffic/TrafficCron.php +++ b/lib/Froxlor/Cron/Traffic/TrafficCron.php @@ -30,6 +30,7 @@ namespace Froxlor\Cron\Traffic; * @author Froxlor team (2010-) */ +use Froxlor\Cron\Forkable; use Froxlor\Cron\FroxlorCron; use Froxlor\Database\Database; use Froxlor\FileDir; @@ -42,51 +43,15 @@ use PDO; class TrafficCron extends FroxlorCron { + use Forkable; public static function run() { - // Check Traffic-Lock - if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { - $TrafficLock = FileDir::makeCorrectFile("/var/run/froxlor_cron_traffic.lock"); - if (file_exists($TrafficLock) && is_numeric($TrafficPid = file_get_contents($TrafficLock))) { - if (function_exists('posix_kill')) { - $TrafficPidStatus = @posix_kill($TrafficPid, 0); - } else { - system("kill -CHLD " . $TrafficPid . " 1> /dev/null 2> /dev/null", $TrafficPidStatus); - $TrafficPidStatus = !$TrafficPidStatus; - } - if ($TrafficPidStatus) { - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Traffic Run already in progress'); - return 1; - } - } - // Create Traffic Log and Fork - // We close the database - connection before we fork, so we don't share resources with the child - Database::needRoot(false); // this forces the connection to be set to null - $TrafficPid = pcntl_fork(); - // Parent - if ($TrafficPid) { - file_put_contents($TrafficLock, $TrafficPid); - // unnecessary to recreate database connection here - return 0; - } elseif ($TrafficPid == 0) { - // Child - posix_setsid(); - // re-create db - Database::needRoot(false); - } else { - // Fork failed - return 1; - } - } elseif (!defined('CRON_NOFORK_FLAG')) { - if (extension_loaded('pcntl')) { - $msg = "PHP compiled with pcntl but pcntl_fork function is not available."; - } else { - $msg = "PHP compiled without pcntl."; - } - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, $msg . " Not forking traffic-cron, this may take a long time!"); - } + self::runFork([self::class, 'handle']); + } + public static function handle() + { /** * TRAFFIC AND DISKUSAGE MEASURE */ @@ -611,11 +576,6 @@ class TrafficCron extends FroxlorCron } Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `value` = UNIX_TIMESTAMP() WHERE `settinggroup` = 'system' AND `varname` = 'last_traffic_run'"); - - if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { - @unlink($TrafficLock); - die(); - } } /** diff --git a/lib/Froxlor/CurrentUser.php b/lib/Froxlor/CurrentUser.php index 211fd470..2971a1ca 100644 --- a/lib/Froxlor/CurrentUser.php +++ b/lib/Froxlor/CurrentUser.php @@ -25,10 +25,13 @@ namespace Froxlor; +use Exception; use Froxlor\Api\Commands\Customers; use Froxlor\Api\Commands\SubDomains; use Froxlor\Database\Database; use Froxlor\UI\Collection; +use Froxlor\UI\Response; +use RobThree\Auth\TwoFactorAuthException; /** * Class to manage the current user / session @@ -144,7 +147,7 @@ class CurrentUser $result_stmt = Database::prepare(" SELECT COUNT(`id`) as emaildomains FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `customerid`= :cid AND `isemaildomain` = '1' + WHERE `customerid`= :cid AND `isemaildomain` = '1' AND `deactivated` = '0' "); $result = Database::pexecute_first($result_stmt, [ "cid" => $_SESSION['userinfo']['customerid'] @@ -154,16 +157,84 @@ class CurrentUser if (Settings::IsInList('panel.customer_hide_options', 'domains')) { $addition = false; } else { - $parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'], - ['sql_search' => ['d.parentdomainid' => 0]])); + $parentDomainCollection = (new Collection( + SubDomains::class, + $_SESSION['userinfo'], + ['sql_search' => [ + 'd.parentdomainid' => 0, + 'd.deactivated' => 0, + 'd.id' => ['op' => '<>', 'value' => $_SESSION['userinfo']['standardsubdomain']] + ] + ] + )); $addition = $parentDomainCollection->count() != 0; } } elseif ($resource == 'domains') { $customerCollection = (new Collection(Customers::class, $_SESSION['userinfo'])); - $addition = $customerCollection != 0; + $addition = $customerCollection->count() != 0; } return ($_SESSION['userinfo'][$resource . '_used'] < $_SESSION['userinfo'][$resource] || $_SESSION['userinfo'][$resource] == '-1') && $addition; } + /** + * @throws TwoFactorAuthException + */ + public static function sendOtpEmail() + { + global $mail; + + if (self::getField('type_2fa') == 1) { + // generate code + $tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname')); + $code = $tfa->getCode($tfa->createSecret()); + // set code for user + $table = TABLE_PANEL_CUSTOMERS; + $uid = 'customerid'; + if (self::isAdmin()) { + $table = TABLE_PANEL_ADMINS; + $uid = 'adminid'; + } + $stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid"); + Database::pexecute($stmt, [ + "d2fa" => $code, + "uid" => self::getField($uid) + ]); + // build up & send email + $_mailerror = false; + $mailerr_msg = ""; + $replace_arr = [ + 'CODE' => $code + ]; + $mail_body = html_entity_decode(PhpHelper::replaceVariables(lng('mails.2fa.mailbody'), $replace_arr)); + + try { + $mail->Subject = lng('mails.2fa.subject'); + $mail->AltBody = $mail_body; + $mail->MsgHTML(str_replace("\n", "
", $mail_body)); + $mail->AddAddress(self::getField('email'), User::getCorrectUserSalutation(self::getData())); + $mail->Send(); + } catch (\PHPMailer\PHPMailer\Exception $e) { + $mailerr_msg = $e->errorMessage(); + $_mailerror = true; + } catch (Exception $e) { + $mailerr_msg = $e->getMessage(); + $_mailerror = true; + } + + if ($_mailerror) { + $rstlog = FroxlorLogger::getInstanceOf([ + 'loginname' => '2fa code-sending' + ]); + $rstlog->logAction(FroxlorLogger::ADM_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg); + Response::redirectTo('index.php', [ + 'showmessage' => '4', + 'customermail' => self::getField('email') + ]); + exit(); + } + + $mail->ClearAddresses(); + } + } } diff --git a/lib/Froxlor/Domain/Domain.php b/lib/Froxlor/Domain/Domain.php index 21aeead0..0f7a300b 100644 --- a/lib/Froxlor/Domain/Domain.php +++ b/lib/Froxlor/Domain/Domain.php @@ -235,51 +235,30 @@ class Domain } /** - * check whether a domain has subdomains added as full-domains - * #329 + * get ids of domains that are main domains but a subdomain of another main domain (for DNS) * - * @param int $id domain-id + * @param int $id main-domain to check * - * @return bool + * @return array * @throws \Exception */ - public static function domainHasMainSubDomains(int $id): bool + public static function getMainSubdomainIds(int $id): array { $result_stmt = Database::prepare(" - SELECT COUNT(`id`) as `mainsubs` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `ismainbutsubto` = :id"); - $result = Database::pexecute_first($result_stmt, [ - 'id' => $id - ]); - - if ($result && isset($result['mainsubs'])) { - return $result['mainsubs'] > 0; - } - return false; - } - - /** - * check whether a subof-domain exists - * #329 - * - * @param int $id subof-domain-id - * - * @return bool - * @throws \Exception - */ - public static function domainMainToSubExists(int $id): bool - { - $result_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id"); + SELECT id + FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE + isbinddomain = 1 AND + domain LIKE CONCAT('%.', ( SELECT d.domain FROM `" . TABLE_PANEL_DOMAINS . "` AS d WHERE d.id = :id )) + "); Database::pexecute($result_stmt, [ 'id' => $id ]); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - - if ($result && isset($result['id'])) { - return $result['id'] > 0; + $result = []; + while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result = $entry['id']; } - return false; + return $result; } /** diff --git a/lib/Froxlor/FileDir.php b/lib/Froxlor/FileDir.php index 2502a4a9..41250b1d 100644 --- a/lib/Froxlor/FileDir.php +++ b/lib/Froxlor/FileDir.php @@ -219,7 +219,7 @@ class FileDir } // execute the command and return output - $return = ''; + $return = []; // ------------------------------------------------------------------------------- if ($return_value == false) { diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php index b1e38a95..549908e4 100644 --- a/lib/Froxlor/Froxlor.php +++ b/lib/Froxlor/Froxlor.php @@ -31,10 +31,10 @@ final class Froxlor { // Main version variable - const VERSION = '2.0.24'; + const VERSION = '2.1.0-dev1'; // Database version (YYYYMMDDC where C is a daily counter) - const DBVERSION = '202304260'; + const DBVERSION = '202305240'; // Distribution branding-tag (used for Debian etc.) const BRANDING = ''; diff --git a/lib/Froxlor/Install/Install.php b/lib/Froxlor/Install/Install.php index ebf322cb..ebed9fdb 100644 --- a/lib/Froxlor/Install/Install.php +++ b/lib/Froxlor/Install/Install.php @@ -43,7 +43,7 @@ class Install public $formfield; public string $requiredVersion = '7.4.0'; public array $requiredExtensions = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'curl', 'gmp', 'json', 'gd']; - public array $suggestedExtensions = ['bcmath', 'zip']; + public array $suggestedExtensions = ['bcmath', 'zip', 'gnupg']; public array $suggestions = []; public array $criticals = []; public array $loadedExtensions; diff --git a/lib/Froxlor/Install/Install/Core.php b/lib/Froxlor/Install/Install/Core.php index 1a4eb835..839f856a 100644 --- a/lib/Froxlor/Install/Install/Core.php +++ b/lib/Froxlor/Install/Install/Core.php @@ -421,6 +421,7 @@ class Core $this->updateSetting($upd_stmt, $this->validatedData['activate_newsfeed'], 'admin', 'show_news_feed'); $this->updateSetting($upd_stmt, dirname(__FILE__, 5), 'system', 'letsencryptchallengepath'); + $this->updateSetting($upd_stmt, dirname(__FILE__, 5) . '/templates/misc/deactivated/', 'system', 'deactivateddocroot'); // insert the lastcronrun to be the installation date $this->updateSetting($upd_stmt, time(), 'system', 'lastcronrun'); diff --git a/lib/Froxlor/Settings.php b/lib/Froxlor/Settings.php index d24a1c8a..dad81814 100644 --- a/lib/Froxlor/Settings.php +++ b/lib/Froxlor/Settings.php @@ -129,7 +129,8 @@ class Settings { // set defaults self::$conf = [ - 'enable_webupdate' => false + 'enable_webupdate' => false, + 'disable_otp_security_check' => false, ]; $configfile = Froxlor::getInstallDir() . '/lib/config.inc.php'; diff --git a/lib/Froxlor/System/Cronjob.php b/lib/Froxlor/System/Cronjob.php index 84c7d337..a131052c 100644 --- a/lib/Froxlor/System/Cronjob.php +++ b/lib/Froxlor/System/Cronjob.php @@ -211,10 +211,10 @@ class Cronjob 'type' => TaskId::DELETE_DOMAIN_SSL, 'data' => $data ]); - } elseif ($type == TaskId::CREATE_CUSTOMER_BACKUP && isset($params[0]) && is_array($params[0])) { + } elseif ($type == TaskId::CREATE_CUSTOMER_DATADUMP && isset($params[0]) && is_array($params[0])) { $data = json_encode($params[0]); Database::pexecute($ins_stmt, [ - 'type' => TaskId::CREATE_CUSTOMER_BACKUP, + 'type' => TaskId::CREATE_CUSTOMER_DATADUMP, 'data' => $data ]); } @@ -310,42 +310,37 @@ class Cronjob } /** - * Cronjob function to end a cronjob in a critical condition - * but not without sending a notification mail to the admin + * Send notification to system admin via email * * @param string $message * @param string $subject * * @return void */ - public static function dieWithMail(string $message, string $subject = "[froxlor] Cronjob error") + public static function notifyMailToAdmin(string $message, string $subject = "[froxlor] Important notice") { - if (Settings::Get('system.send_cron_errors') == '1') { - $_mail = new Mailer(true); - $_mailerror = false; - $mailerr_msg = ""; - try { - $_mail->Subject = $subject; - $_mail->AltBody = $message; - $_mail->MsgHTML(nl2br($message)); - $_mail->AddAddress(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname')); - $_mail->Send(); - } catch (\PHPMailer\PHPMailer\Exception $e) { - $mailerr_msg = $e->errorMessage(); - $_mailerror = true; - } catch (Exception $e) { - $mailerr_msg = $e->getMessage(); - $_mailerror = true; - } - - $_mail->ClearAddresses(); - - if ($_mailerror) { - echo 'Error sending mail: ' . $mailerr_msg . "\n"; - } + $mail = new Mailer(true); + $mailerror = false; + $mailerr_msg = ""; + try { + $mail->Subject = $subject; + $mail->AltBody = $message; + $mail->MsgHTML(nl2br($message)); + $mail->AddAddress(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname')); + $mail->Send(); + } catch (\PHPMailer\PHPMailer\Exception $e) { + $mailerr_msg = $e->errorMessage(); + $mailerror = true; + } catch (Exception $e) { + $mailerr_msg = $e->getMessage(); + $mailerror = true; } - die($message); + $mail->ClearAddresses(); + + if ($mailerror) { + echo 'Error sending mail: ' . $mailerr_msg . "\n"; + } } /** diff --git a/lib/Froxlor/System/Markdown.php b/lib/Froxlor/System/Markdown.php new file mode 100644 index 00000000..a7a6e506 --- /dev/null +++ b/lib/Froxlor/System/Markdown.php @@ -0,0 +1,58 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +namespace Froxlor\System; + +use League\CommonMark\Exception\CommonMarkException; +use League\CommonMark\GithubFlavoredMarkdownConverter; + +class Markdown +{ + + private static $converter = null; + + public static function converter(): ?GithubFlavoredMarkdownConverter + { + if (is_null(self::$converter)) { + self::$converter = new GithubFlavoredMarkdownConverter([ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, + ]); + } + return self::$converter; + } + + public static function cleanCustomNotes(string $note = ""): string + { + if (!empty($note)) { + try { + $note = self::converter()->convert($note)->getContent(); + } catch (CommonMarkException $e) { + $note = ""; + } + } + return $note; + } +} diff --git a/lib/Froxlor/Traffic/Traffic.php b/lib/Froxlor/Traffic/Traffic.php index 2b14b654..d9fa4e99 100644 --- a/lib/Froxlor/Traffic/Traffic.php +++ b/lib/Froxlor/Traffic/Traffic.php @@ -25,10 +25,10 @@ namespace Froxlor\Traffic; -use Froxlor\Database\Database; use Froxlor\Api\Commands\Customers; -use Froxlor\UI\Collection; use Froxlor\Api\Commands\Traffic as TrafficAPI; +use Froxlor\Database\Database; +use Froxlor\UI\Collection; class Traffic { @@ -38,10 +38,10 @@ class Traffic * @return array * @throws \Exception */ - public static function getCustomerStats(array $userinfo, string $range = null): array + public static function getCustomerStats(array $userinfo, string $range = null, bool $overview = false): array { $trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo, - self::getParamsByRange($range, ['customer_traffic' => true,]))); + self::getParamsByRange($range, ['customer_traffic' => true]))); if ($userinfo['adminsession'] == 1) { $trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid'); } @@ -53,27 +53,36 @@ class Traffic $months = []; $days = []; foreach ($trafficCollection['data']['list'] as $item) { + $http = $item['http']; + $ftp = ($item['ftp_up'] + $item['ftp_down']); + $mail = $item['mail']; + $total = $http + $ftp + $mail; + // per user total - $users[$item['customerid']]['loginname'] = $item['customer']['loginname']; - $users[$item['customerid']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']); - $users[$item['customerid']]['http'] += $item['http']; - $users[$item['customerid']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']); - $users[$item['customerid']]['mail'] += $item['mail']; - // per year - $years[$item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']); - $years[$item['year']]['http'] += $item['http']; - $years[$item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']); - $years[$item['year']]['mail'] += $item['mail']; - // per month - $months[$item['month'] . '/' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']); - $months[$item['month'] . '/' . $item['year']]['http'] += $item['http']; - $months[$item['month'] . '/' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']); - $months[$item['month'] . '/' . $item['year']]['mail'] += $item['mail']; - // per day - $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']); - $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $item['http']; - $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']); - $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $item['mail']; + if ($userinfo['adminsession'] == 1) { + $users[$item['customerid']]['loginname'] = $item['customer']['loginname']; + } + $users[$item['customerid']]['total'] += $total; + $users[$item['customerid']]['http'] += $http; + $users[$item['customerid']]['ftp'] += $ftp; + $users[$item['customerid']]['mail'] += $mail; + if (!$overview) { + // per year + $years[$item['year']]['total'] += $total; + $years[$item['year']]['http'] += $http; + $years[$item['year']]['ftp'] += $ftp; + $years[$item['year']]['mail'] += $mail; + // per month + $months[$item['month'] . '/' . $item['year']]['total'] += $total; + $months[$item['month'] . '/' . $item['year']]['http'] += $http; + $months[$item['month'] . '/' . $item['year']]['ftp'] += $ftp; + $months[$item['month'] . '/' . $item['year']]['mail'] += $mail; + // per day + $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += $total; + $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $http; + $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += $ftp; + $days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $mail; + } } // calculate overview for given range from users @@ -85,10 +94,13 @@ class Traffic $metrics['mail'] += $user['mail']; } - // get all possible years for filter - $sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC"); - Database::pexecute($sel_stmt); - $years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC); + $years_avail = []; + if (!$overview) { + // get all possible years for filter + $sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC"); + Database::pexecute($sel_stmt); + $years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC); + } return [ 'metrics' => $metrics, diff --git a/lib/Froxlor/UI/Callbacks/Customer.php b/lib/Froxlor/UI/Callbacks/Customer.php index b1c1016f..aa2cb061 100644 --- a/lib/Froxlor/UI/Callbacks/Customer.php +++ b/lib/Froxlor/UI/Callbacks/Customer.php @@ -26,17 +26,19 @@ namespace Froxlor\UI\Callbacks; use Froxlor\Settings; +use Froxlor\System\Markdown; class Customer { - public static function isLocked(array $attributes) + public static function isLocked(array $attributes): bool { return $attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts') && $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime')); } - public static function hasNote(array $attributes) + public static function hasNote(array $attributes): bool { - return !empty($attributes['fields']['custom_notes']); + $cleanNote = Markdown::cleanCustomNotes($attributes['fields']['custom_notes'] ?? ""); + return !empty($cleanNote); } } diff --git a/lib/Froxlor/UI/Callbacks/Domain.php b/lib/Froxlor/UI/Callbacks/Domain.php index 0bac2390..5fb7729b 100644 --- a/lib/Froxlor/UI/Callbacks/Domain.php +++ b/lib/Froxlor/UI/Callbacks/Domain.php @@ -25,6 +25,7 @@ namespace Froxlor\UI\Callbacks; +use Froxlor\Database\Database; use Froxlor\Domain\Domain as DDomain; use Froxlor\FileDir; use Froxlor\Settings; @@ -51,6 +52,9 @@ class Domain public static function domainTarget(array $attributes) { if (empty($attributes['fields']['aliasdomain'])) { + if ($attributes['fields']['deactivated']) { + return lng('admin.deactivated'); + } // path or redirect if (preg_match('/^https?\:\/\//', $attributes['fields']['documentroot'])) { return [ @@ -76,11 +80,11 @@ class Domain { $result = ''; if ($attributes['fields']['parentdomainid'] != 0) { - $result = ''; + $result = ''; } $result .= '' . $attributes['data'] . ''; // check for statistics if parentdomainid==0 to show stats-link for customers - if ((int)UI::getCurrentUser()['adminsession'] == 0 && $attributes['fields']['parentdomainid'] == 0) { + if ((int)UI::getCurrentUser()['adminsession'] == 0 && $attributes['fields']['parentdomainid'] == 0 && $attributes['fields']['deactivated'] == 0) { $statsapp = Settings::Get('system.traffictool'); $result .= ' '; } @@ -95,12 +99,12 @@ class Domain public static function canEdit(array $attributes): bool { - return (bool)$attributes['fields']['caneditdomain']; + return (bool)($attributes['fields']['caneditdomain'] && !$attributes['fields']['deactivated']); } public static function canViewLogs(array $attributes): bool { - if ((int)$attributes['fields']['email_only'] == 0) { + if ((int)$attributes['fields']['email_only'] == 0 && !$attributes['fields']['deactivated']) { if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) { return true; } elseif ((int)UI::getCurrentUser()['adminsession'] == 1) { @@ -129,7 +133,8 @@ class Domain && UI::getCurrentUser()['dnsenabled'] == '1' && $attributes['fields']['caneditdomain'] == '1' && Settings::Get('system.bind_enable') == '1' - && Settings::Get('system.dnsenabled') == '1'; + && Settings::Get('system.dnsenabled') == '1' + && !$attributes['fields']['deactivated']; } public static function adminCanEditDNS(array $attributes): bool @@ -152,6 +157,7 @@ class Domain && (int)$attributes['fields']['caneditdomain'] == 1 && (int)$attributes['fields']['letsencrypt'] == 0 && (int)$attributes['fields']['email_only'] == 0 + && !$attributes['fields']['deactivated'] ) { return true; } @@ -184,13 +190,11 @@ class Domain // specified certificate for domain if ($attributes['fields']['domain_hascert'] == 1) { $result['icon'] .= ' text-success'; - } - // shared certificates (e.g. subdomain if domain where certificate is specified) + } // shared certificates (e.g. subdomain of domain where certificate is specified) elseif ($attributes['fields']['domain_hascert'] == 2) { $result['icon'] .= ' text-warning'; $result['title'] .= "\n" . lng('panel.ssleditor_infoshared'); - } - // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings) + } // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings) elseif ($attributes['fields']['domain_hascert'] == 0) { $result['icon'] .= ' text-danger'; $result['title'] .= "\n" . lng('panel.ssleditor_infoglobal'); @@ -212,4 +216,22 @@ class Domain } return lng('panel.empty'); } + + public static function getPhpConfigName(array $attributes): string + { + $sel_stmt = Database::prepare("SELECT `description` FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id"); + $phpconfig = Database::pexecute_first($sel_stmt, ['id' => $attributes['data']]); + if ((int)UI::getCurrentUser()['adminsession'] == 1) { + $linker = UI::getLinker(); + $result = '' . $phpconfig['description'] . ''; + } else { + $result = $phpconfig['description']; + } + return $result; + } } diff --git a/lib/Froxlor/UI/Callbacks/ProgressBar.php b/lib/Froxlor/UI/Callbacks/ProgressBar.php index 69d9e9cf..2b1e8a84 100644 --- a/lib/Froxlor/UI/Callbacks/ProgressBar.php +++ b/lib/Froxlor/UI/Callbacks/ProgressBar.php @@ -95,7 +95,7 @@ class ProgressBar $skip_customer_traffic = false; try { $attributes['fields']['deactivated'] = 0; - $result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth'); + $result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth', true); } catch (Exception $e) { if ($e->getCode() === 405) { $skip_customer_traffic = true; diff --git a/lib/Froxlor/UI/Callbacks/SSLCertificate.php b/lib/Froxlor/UI/Callbacks/SSLCertificate.php index 444bc34f..2b4be3c6 100644 --- a/lib/Froxlor/UI/Callbacks/SSLCertificate.php +++ b/lib/Froxlor/UI/Callbacks/SSLCertificate.php @@ -47,4 +47,9 @@ class SSLCertificate } return false; } + + public static function isNotLetsEncrypt(array $attributes): bool + { + return (int)$attributes['fields']['letsencrypt'] == 0; + } } diff --git a/lib/Froxlor/UI/Callbacks/Style.php b/lib/Froxlor/UI/Callbacks/Style.php index 7de6941b..315fbb47 100644 --- a/lib/Froxlor/UI/Callbacks/Style.php +++ b/lib/Froxlor/UI/Callbacks/Style.php @@ -34,6 +34,11 @@ class Style return $attributes['fields']['deactivated'] ? 'bg-danger' : ''; } + public static function loginDisabled(array $attributes): string + { + return $attributes['fields']['login_enabled'] == 'N' ? 'bg-danger' : ''; + } + public static function resultIntegrityBad(array $attributes): string { return $attributes['fields']['result'] ? '' : 'bg-warning'; @@ -60,17 +65,18 @@ class Style $today = time(); $termination_css = 'bg-warning'; if ($cdate < $today) { - $termination_css = 'bg-danger'; + $termination_css = 'bg-danger text-light'; } } - return $attributes['fields']['deactivated'] ? 'bg-info' : $termination_css; + $deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated']; + return $deactivated ? 'bg-info text-light' : $termination_css; } public static function resultCustomerLockedOrDeactivated(array $attributes): string { $row_css = ''; if ((int)$attributes['fields']['deactivated'] == 1) { - $row_css = 'bg-info'; + $row_css = 'bg-info text-light'; } elseif ( $attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts') && $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime')) diff --git a/lib/Froxlor/UI/Callbacks/Text.php b/lib/Froxlor/UI/Callbacks/Text.php index 3edfb72e..9e63e3c3 100644 --- a/lib/Froxlor/UI/Callbacks/Text.php +++ b/lib/Froxlor/UI/Callbacks/Text.php @@ -25,10 +25,14 @@ namespace Froxlor\UI\Callbacks; +use Froxlor\CurrentUser; +use Froxlor\Database\Database; use Froxlor\Froxlor; use Froxlor\PhpHelper; +use Froxlor\System\Markdown; use Froxlor\UI\Panel\UI; use Froxlor\User; +use PDO; class Text { @@ -40,6 +44,14 @@ class Text ]; } + public static function yesno(array $attributes): array + { + return [ + 'macro' => 'boolean', + 'data' => $attributes['data'] == 'Y' + ]; + } + public static function customerfullname(array $attributes): string { return User::getCorrectFullUserDetails($attributes['fields'], true); @@ -82,7 +94,7 @@ class Text 'entry' => $attributes['fields']['id'], 'id' => 'cnModal' . $attributes['fields']['id'], 'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']), - 'body' => nl2br($note) + 'body' => nl2br(Markdown::cleanCustomNotes($note)) ]; } @@ -105,4 +117,44 @@ class Text 'body' => $body ]; } + + public static function domainDuplicateModal(array $attributes): array + { + $linker = UI::getLinker(); + $result = $attributes['fields']; + + $customers = [ + 0 => lng('panel.please_choose') + ]; + $result_customers_stmt = Database::prepare(" + SELECT `customerid`, `loginname`, `name`, `firstname`, `company` + FROM `" . TABLE_PANEL_CUSTOMERS . "` " . (CurrentUser::getField('customers_see_all') ? '' : " WHERE `adminid` = :adminid ") . " + ORDER BY COALESCE(NULLIF(`name`,''), `company`) ASC + "); + $params = []; + if (CurrentUser::getField('customers_see_all') == '0') { + $params['adminid'] = CurrentUser::getField('adminid'); + } + Database::pexecute($result_customers_stmt, $params); + + while ($row_customer = $result_customers_stmt->fetch(PDO::FETCH_ASSOC)) { + $customers[$row_customer['customerid']] = User::getCorrectFullUserDetails($row_customer) . ' (' . $row_customer['loginname'] . ')'; + } + + $domdup_data = include Froxlor::getInstallDir() . '/lib/formfields/admin/domains/formfield.domains_duplicate.php'; + + $body = UI::twig()->render(UI::validateThemeTemplate('/user/inline-form.html.twig'), [ + 'formaction' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'duplicate']), + 'formdata' => $domdup_data['domain_duplicate'], + 'editid' => $attributes['fields']['id'], + 'nosubmit' => 0 + ]); + return [ + 'entry' => $attributes['fields']['id'], + 'id' => 'ddModal' . $attributes['fields']['id'], + 'title' => lng('admin.domain_duplicate_named', [$attributes['fields']['domain']]), + 'action' => 'duplicate', + 'body' => $body + ]; + } } diff --git a/lib/Froxlor/UI/Form.php b/lib/Froxlor/UI/Form.php index 6cf40ab8..e21fc727 100644 --- a/lib/Froxlor/UI/Form.php +++ b/lib/Froxlor/UI/Form.php @@ -25,6 +25,8 @@ namespace Froxlor\UI; +use Froxlor\CurrentUser; +use Froxlor\FroxlorTwoFactorAuth; use Froxlor\Settings; use Froxlor\Validate\Check; @@ -183,6 +185,21 @@ class Form } } + // OTP security validation for sensitive settings + if (!Settings::Config('disable_otp_security_check') && isset($fielddata['required_otp']) && $do_show) { + $otp_enabled_system = (bool)Settings::Get('2fa.enabled'); + $otp_enabled_user = (int)CurrentUser::getField('type_2fa') != 0; + $do_show = !$fielddata['required_otp'] || ($otp_enabled_system && $otp_enabled_user); + if (!$do_show) { + $fielddata['note'] = lng('serversettings.option_requires_otp'); + if (!$otp_enabled_system) { + $fielddata['note'] .= '
' . lng('2fa.2fa_not_activated'); + } elseif (!$otp_enabled_user) { + $fielddata['note'] .= '
' . lng('2fa.2fa_not_activated_for_user'); + } + } + } + if (!$do_show) { $fielddata['visible'] = false; } @@ -232,7 +249,7 @@ class Form if (((isset($fielddetails['visible']) && $fielddetails['visible']) || !isset($fielddetails['visible'])) && (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option'])))) { $newfieldvalue = self::getFormFieldData($fieldname, $fielddetails, $input); if ($newfieldvalue != $fielddetails['value']) { - if (($error = \Froxlor\Validate\Form::validateFormField($fieldname, $fielddetails, $newfieldvalue)) != true) { + if (($error = \Froxlor\Validate\Form::validateFormField($fieldname, $fielddetails, $newfieldvalue)) !== true) { Response::standardError($error, $fieldname); } else { $changed_fields[$fieldname] = $newfieldvalue; @@ -283,6 +300,38 @@ class Form } } } + if (!Settings::Config('disable_otp_security_check') && isset($fielddetails['required_otp']) && isset($changed_fields[$fieldname])) { + $otp_enabled_system = (bool)Settings::Get('2fa.enabled'); + $otp_enabled_user = (int)CurrentUser::getField('type_2fa') != 0; + $do_update = !$fielddetails['required_otp'] || ($otp_enabled_system && $otp_enabled_user); + if ($do_update) { + // setting that requires OTP verification + if (empty($input['otp_verification'])) { + // in case email 2fa is enabled, send it now + CurrentUser::sendOtpEmail(); + // build up form + if (is_array($url_params) && isset($url_params['filename'])) { + $filename = $url_params['filename']; + unset($url_params['filename']); + } else { + $filename = ''; + } + HTML::askOTP('please_enter_otp', $filename, array_merge($url_params, $submitted_fields)); + } else { + // validate given OTP code + $code = trim($input['otp_verification']); + $tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname')); + $result = $tfa->verifyCode(CurrentUser::getField('data_2fa'), $code, 3); + if (!$result) { + Response::standardError('otpnotvalidated'); + } + } + } else { + // do not update this setting + unset($changed_fields[$fieldname]); + } + } + } } } diff --git a/lib/Froxlor/UI/HTML.php b/lib/Froxlor/UI/HTML.php index acf635a3..181399b1 100644 --- a/lib/Froxlor/UI/HTML.php +++ b/lib/Froxlor/UI/HTML.php @@ -25,6 +25,8 @@ namespace Froxlor\UI; +use Froxlor\Settings; + class HTML { @@ -116,7 +118,7 @@ class HTML 'label' => $navlabel, 'icon' => $icon, 'items' => $navigation_links, - 'active' => $box_active + 'active' => ((int)Settings::Get('panel.menu_collapsed') == 0 ? 1 : $box_active) ]; } } @@ -221,4 +223,17 @@ class HTML ]); exit(); } + + public static function askOTP(string $text, string $targetfile, array $params = [], string $replacer = '', array $back_link = []) + { + $text = lng('question.' . $text, [htmlspecialchars($replacer)]); + + Panel\UI::view('form/otpquestion.html.twig', [ + 'action' => $targetfile, + 'url_params' => $params, + 'question' => $text, + 'back_link' => $back_link + ]); + exit(); + } } diff --git a/lib/Froxlor/UI/Listing.php b/lib/Froxlor/UI/Listing.php index 3d3be00a..c32f30f4 100644 --- a/lib/Froxlor/UI/Listing.php +++ b/lib/Froxlor/UI/Listing.php @@ -230,6 +230,7 @@ class Listing 'label' => $coldata['label'], 'checked' => in_array($column, $tabellisting['visible_columns']), 'searchable' => $coldata['searchable'] ?? true, + 'isdefaultsearchfield' => $coldata['isdefaultsearchfield'] ?? false, ]; } } diff --git a/lib/Froxlor/UI/Panel/FroxlorTwig.php b/lib/Froxlor/UI/Panel/FroxlorTwig.php index d2f49175..3cc32c12 100644 --- a/lib/Froxlor/UI/Panel/FroxlorTwig.php +++ b/lib/Froxlor/UI/Panel/FroxlorTwig.php @@ -29,6 +29,7 @@ namespace Froxlor\UI\Panel; use Froxlor\Idna\IdnaWrapper; use Froxlor\Settings; +use Froxlor\System\Markdown; use Parsedown; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -53,9 +54,9 @@ class FroxlorTwig extends AbstractExtension $this, 'idnDecodeFilter' ]), - new TwigFilter('parsedown', [ + new TwigFilter('markdown', [ $this, - 'callParsedown' + 'callMarkdown' ]) ]; } @@ -148,10 +149,9 @@ class FroxlorTwig extends AbstractExtension return UI::getLinker()->getLink($linkopts); } - public function callParsedown($string) + public function callMarkdown($string): string { - $pd = new Parsedown(); - return $pd->line($string); + return Markdown::cleanCustomNotes($string ?? ""); } /** diff --git a/lib/Froxlor/UI/Response.php b/lib/Froxlor/UI/Response.php index e8dd0279..a12b3683 100644 --- a/lib/Froxlor/UI/Response.php +++ b/lib/Froxlor/UI/Response.php @@ -32,7 +32,7 @@ class Response { /** - * Sends an header ( 'Location ...' ) to the browser. + * Sends a header ( 'Location ...' ) to the browser. * * @param string $destination * Destination @@ -74,18 +74,18 @@ class Response $linker->filename = $path . $destination; } header('Location: ' . $linker->getLink()); - exit(); + exit; } elseif ($get_variables == null) { $linker = new Linker($destination); header('Location: ' . $linker->getLink()); - exit(); + exit; } return false; } /** - * Prints one ore more errormessages on screen + * Prints one or more errormessages on screen * * @param array $errors * Errormessages @@ -93,8 +93,9 @@ class Response * A %s in the errormessage will be replaced by this string. * @param bool $throw_exception * - * @author Florian Lippert (2003-2009) + * @throws Exception * @author Ron Brand + * @author Florian Lippert (2003-2009) */ public static function standardError($errors = '', $replacer = '', $throw_exception = false) { @@ -115,7 +116,7 @@ class Response $error = ''; foreach ($errors as $single_error) { if (strpos($single_error, ".") === false) { - $single_error = 'error.'.$single_error; + $single_error = 'error.' . $single_error; } $single_error = lng($single_error, [htmlentities($replacer)]); if (empty($error)) { @@ -157,7 +158,7 @@ class Response } /** - * Prints one ore more errormessages on screen + * Prints one or more errormessages on screen * * @param array $success_message * Errormessages @@ -166,12 +167,13 @@ class Response * @param array $params * @param bool $throw_exception * + * @throws Exception * @author Florian Lippert (2003-2009) */ public static function standardSuccess($success_message = '', $replacer = '', $params = [], $throw_exception = false) { if (strpos($success_message, ".") === false) { - $success_message = 'success.'.$success_message; + $success_message = 'success.' . $success_message; } $success_message = lng($success_message, [htmlentities($replacer)]); diff --git a/lib/Froxlor/Validate/Check.php b/lib/Froxlor/Validate/Check.php index cb113c72..2d5229b1 100644 --- a/lib/Froxlor/Validate/Check.php +++ b/lib/Froxlor/Validate/Check.php @@ -314,4 +314,30 @@ class Check } return $returnvalue; } + + public static function checkPgpPublicKeySetting($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues) + { + // if the field is empty, we don't need to check anything + if ($newfieldvalue === '') { + return [self::FORMFIELDS_PLAUSIBILITY_CHECK_OK]; + } + + // check if gnupg extension is loaded + if (!extension_loaded('gnupg')) { + return [ + self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR, + 'gnupgextensionnotavailable' + ]; + } + // check if the pgp public key is a valid key + putenv('GNUPGHOME='.sys_get_temp_dir()); + if (gnupg_import(gnupg_init(), $newfieldvalue) === false) { + return [ + self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR, + 'invalidpgppublickey' + ]; + } + + return [self::FORMFIELDS_PLAUSIBILITY_CHECK_OK]; + } } diff --git a/lib/Froxlor/Validate/Form/Data.php b/lib/Froxlor/Validate/Form/Data.php index 3e376fb1..ac7cd0cd 100644 --- a/lib/Froxlor/Validate/Form/Data.php +++ b/lib/Froxlor/Validate/Form/Data.php @@ -319,7 +319,11 @@ class Data { $returnvalue = 'stringformaterror'; - if (preg_match('/^[^\0]*$/', $newfieldvalue)) { + if (isset($fielddata['string_regexp']) && $fielddata['string_regexp'] != '') { + if (preg_match($fielddata['string_regexp'], $newfieldvalue)) { + $returnvalue = true; + } + } else if (preg_match('/^[^\0]*$/', $newfieldvalue)) { $returnvalue = true; } diff --git a/lib/config.example.inc.php b/lib/config.example.inc.php index 7f34f735..cdec9462 100644 --- a/lib/config.example.inc.php +++ b/lib/config.example.inc.php @@ -11,4 +11,9 @@ return [ * updates the way the providers does it (e.g. automation, etc.) */ 'enable_webupdate' => false, + + /** + * @todo description + */ + 'disable_otp_security_check' => false, ]; diff --git a/lib/configfiles/bionic.xml b/lib/configfiles/bionic.xml index f4edb083..f031ec18 100644 --- a/lib/configfiles/bionic.xml +++ b/lib/configfiles/bionic.xml @@ -10,11 +10,13 @@ + + @@ -1527,7 +1529,7 @@ user = password = dbname = hosts = -query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' +query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' AND deactivated = 0 ]]> @@ -3960,7 +3962,6 @@ ServerName " FTP Server" ServerType standalone DeferWelcome off -MultilineRFC2228 on DefaultServer on ShowSymlinks on @@ -4297,7 +4298,6 @@ SQLNamedQuery get-quota-limit SELECT "ftp_users.username AS name, ftp_quotalimit SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used,bytes_out_used, bytes_xfer_used, files_in_used, files_out_used,files_xfer_used FROM ftp_quotatallies WHERE name = '%{0}' AND quota_type = '%{1}'" SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used= files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name= '%{6}' AND quota_type = '%{7}'" ftp_quotatallies SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6}, %{7}" ftp_quotatallies - ]]> @@ -4308,16 +4308,16 @@ SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6} TLSEngine on TLSLog /var/log/proftpd/tls.log -TLSProtocol TLSv1 TLSv1.1 TLSv1.2 +TLSProtocol TLSv1.2 TLSv1.3 TLSRSACertificateFile /etc/ssl/certs/proftpd.crt TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key -TLSOptions NoCertRequest NoSessionReuseRequired +TLSOptions NoSessionReuseRequired TLSVerifyClient off # Are clients required to use FTP over TLS when talking to this server? -#TLSRequired on +TLSRequired on # Allow SSL/TLS renegotiations when the client requests them, but # do not force the renegotiations. Some clients do not support @@ -4330,6 +4330,37 @@ TLSVerifyClient off ]]> + + +From 127.0.0.1 + + +MaxLoginAttempts 3 + + + BanEngine off + + + BanEngine on + +BanLog /var/log/proftpd/ban.log +BanTable /etc/proftpd/ban.tab +BanMessage "User %u was banned." +BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently" +BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00 +BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99 +BanControlsACLs all allow user root + + + +BanEngine off +DelayEngine off + + ]]> + + @@ -4717,7 +4748,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/bookworm.xml b/lib/configfiles/bookworm.xml index 9bbea2c6..1499ec17 100644 --- a/lib/configfiles/bookworm.xml +++ b/lib/configfiles/bookworm.xml @@ -10,11 +10,13 @@ + + @@ -1486,7 +1488,7 @@ user = password = dbname = hosts = -query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' +query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' AND deactivated = 0 ]]> @@ -2948,7 +2950,7 @@ SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6} TLSEngine on TLSLog /var/log/proftpd/tls.log -TLSProtocol TLSv1 TLSv1.1 TLSv1.2 +TLSProtocol TLSv1.2 TLSv1.3 TLSRSACertificateFile /etc/ssl/certs/proftpd.crt TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt @@ -2957,7 +2959,7 @@ TLSOptions NoSessionReuseRequired TLSVerifyClient off # Are clients required to use FTP over TLS when talking to this server? -#TLSRequired on +TLSRequired on # Allow SSL/TLS renegotiations when the client requests them, but # do not force the renegotiations. Some clients do not support @@ -2970,6 +2972,37 @@ TLSVerifyClient off ]]> + + +From 127.0.0.1 + + +MaxLoginAttempts 3 + + + BanEngine off + + + BanEngine on + +BanLog /var/log/proftpd/ban.log +BanTable /etc/proftpd/ban.tab +BanMessage "User %u was banned." +BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently" +BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00 +BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99 +BanControlsACLs all allow user root + + + +BanEngine off +DelayEngine off + + ]]> + + @@ -3357,7 +3390,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/bullseye.xml b/lib/configfiles/bullseye.xml index b9bd8053..0db23717 100644 --- a/lib/configfiles/bullseye.xml +++ b/lib/configfiles/bullseye.xml @@ -10,11 +10,13 @@ + + @@ -1486,7 +1488,7 @@ user = password = dbname = hosts = -query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' +query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' AND deactivated = 0 ]]> @@ -4170,7 +4172,6 @@ ServerName " FTP Server" ServerType standalone DeferWelcome off -MultilineRFC2228 on DefaultServer on ShowSymlinks on @@ -4509,7 +4510,6 @@ SQLNamedQuery get-quota-limit SELECT "ftp_users.username AS name, ftp_quotalimit SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used,bytes_out_used, bytes_xfer_used, files_in_used, files_out_used,files_xfer_used FROM ftp_quotatallies WHERE name = '%{0}' AND quota_type = '%{1}'" SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used= files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name= '%{6}' AND quota_type = '%{7}'" ftp_quotatallies SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6}, %{7}" ftp_quotatallies - ]]> @@ -4520,16 +4520,16 @@ SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6} TLSEngine on TLSLog /var/log/proftpd/tls.log -TLSProtocol TLSv1 TLSv1.1 TLSv1.2 +TLSProtocol TLSv1.2 TLSv1.3 TLSRSACertificateFile /etc/ssl/certs/proftpd.crt TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key -TLSOptions NoCertRequest NoSessionReuseRequired +TLSOptions NoSessionReuseRequired TLSVerifyClient off # Are clients required to use FTP over TLS when talking to this server? -#TLSRequired on +TLSRequired on # Allow SSL/TLS renegotiations when the client requests them, but # do not force the renegotiations. Some clients do not support @@ -4542,6 +4542,37 @@ TLSVerifyClient off ]]> + + +From 127.0.0.1 + + +MaxLoginAttempts 3 + + + BanEngine off + + + BanEngine on + +BanLog /var/log/proftpd/ban.log +BanTable /etc/proftpd/ban.tab +BanMessage "User %u was banned." +BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently" +BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00 +BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99 +BanControlsACLs all allow user root + + + +BanEngine off +DelayEngine off + + ]]> + + @@ -4929,7 +4960,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/buster.xml b/lib/configfiles/buster.xml index f1bdbe82..91a2f359 100644 --- a/lib/configfiles/buster.xml +++ b/lib/configfiles/buster.xml @@ -10,11 +10,13 @@ + + @@ -1486,7 +1488,7 @@ user = password = dbname = hosts = -query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' +query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' AND deactivated = 0 ]]> @@ -4163,7 +4165,6 @@ ServerName " FTP Server" ServerType standalone DeferWelcome off -MultilineRFC2228 on DefaultServer on ShowSymlinks on @@ -4500,7 +4501,6 @@ SQLNamedQuery get-quota-limit SELECT "ftp_users.username AS name, ftp_quotalimit SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used,bytes_out_used, bytes_xfer_used, files_in_used, files_out_used,files_xfer_used FROM ftp_quotatallies WHERE name = '%{0}' AND quota_type = '%{1}'" SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used= files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name= '%{6}' AND quota_type = '%{7}'" ftp_quotatallies SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6}, %{7}" ftp_quotatallies - ]]> @@ -4511,16 +4511,16 @@ SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6} TLSEngine on TLSLog /var/log/proftpd/tls.log -TLSProtocol TLSv1 TLSv1.1 TLSv1.2 +TLSProtocol TLSv1.2 TLSv1.3 TLSRSACertificateFile /etc/ssl/certs/proftpd.crt TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key -TLSOptions NoCertRequest NoSessionReuseRequired +TLSOptions NoSessionReuseRequired TLSVerifyClient off # Are clients required to use FTP over TLS when talking to this server? -#TLSRequired on +TLSRequired on # Allow SSL/TLS renegotiations when the client requests them, but # do not force the renegotiations. Some clients do not support @@ -4533,6 +4533,37 @@ TLSVerifyClient off ]]> + + +From 127.0.0.1 + + +MaxLoginAttempts 3 + + + BanEngine off + + + BanEngine on + +BanLog /var/log/proftpd/ban.log +BanTable /etc/proftpd/ban.tab +BanMessage "User %u was banned." +BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently" +BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00 +BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99 +BanControlsACLs all allow user root + + + +BanEngine off +DelayEngine off + + ]]> + + @@ -4920,7 +4951,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/focal.xml b/lib/configfiles/focal.xml index cce32d7d..2707b002 100644 --- a/lib/configfiles/focal.xml +++ b/lib/configfiles/focal.xml @@ -9,12 +9,14 @@ - + + + + @@ -1515,7 +1517,7 @@ user = password = dbname = hosts = -query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' +query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' AND deactivated = 0 ]]> @@ -3391,7 +3393,6 @@ ServerName " FTP Server" ServerType standalone DeferWelcome off -MultilineRFC2228 on DefaultServer on ShowSymlinks on @@ -3728,7 +3729,6 @@ SQLNamedQuery get-quota-limit SELECT "ftp_users.username AS name, ftp_quotalimit SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used,bytes_out_used, bytes_xfer_used, files_in_used, files_out_used,files_xfer_used FROM ftp_quotatallies WHERE name = '%{0}' AND quota_type = '%{1}'" SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used= files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name= '%{6}' AND quota_type = '%{7}'" ftp_quotatallies SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6}, %{7}" ftp_quotatallies - ]]> @@ -3739,16 +3739,16 @@ SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6} TLSEngine on TLSLog /var/log/proftpd/tls.log -TLSProtocol TLSv1 TLSv1.1 TLSv1.2 +TLSProtocol TLSv1.2 TLSv1.3 TLSRSACertificateFile /etc/ssl/certs/proftpd.crt TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key -TLSOptions NoCertRequest NoSessionReuseRequired +TLSOptions NoSessionReuseRequired TLSVerifyClient off # Are clients required to use FTP over TLS when talking to this server? -#TLSRequired on +TLSRequired on # Allow SSL/TLS renegotiations when the client requests them, but # do not force the renegotiations. Some clients do not support @@ -3761,6 +3761,37 @@ TLSVerifyClient off ]]> + + +From 127.0.0.1 + + +MaxLoginAttempts 3 + + + BanEngine off + + + BanEngine on + +BanLog /var/log/proftpd/ban.log +BanTable /etc/proftpd/ban.tab +BanMessage "User %u was banned." +BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently" +BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00 +BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99 +BanControlsACLs all allow user root + + + +BanEngine off +DelayEngine off + + ]]> + + @@ -4156,7 +4187,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml index a03d6a16..fbe8118f 100644 --- a/lib/configfiles/gentoo.xml +++ b/lib/configfiles/gentoo.xml @@ -1,6 +1,6 @@ - @@ -17,11 +17,13 @@ + + @@ -1471,7 +1473,7 @@ user = password = dbname = hosts = -query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' +query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' AND deactivated = 0 ]]> @@ -1668,7 +1670,6 @@ smtpd_tls_session_cache_timeout = 3600s #virtual_transport = virtual ## Generate maildirsize files or not #virtual_create_maildirsize = yes -## I use Courier IMAP compatibles files. #virtual_mailbox_extended = yes ## Limits only INBOX part (useful when ## using when you have IMAP users) @@ -1821,7 +1822,6 @@ virtual_mailbox_limit = 0 #virtual_transport = virtual ## Generate maildirsize files or not #virtual_create_maildirsize = yes -## I use Courier IMAP compatibles files. #virtual_mailbox_extended = yes ## Limits only INBOX part (useful when ## using when you have IMAP users) @@ -1846,134 +1846,6 @@ debugger_command = # Dovecot LDA dovecot unix - n n - - pipe flags=DRhu user=vmail:vmail argv=/usr/libexec/dovecot/deliver -d ${recipient} -]]> - - - //service[@type='smtp']/general/commands[@index=3] - - - - - //service[@type='smtp']/general/commands[@index=1] - - > /etc/portage/package.use/froxlor]]> - //service[@type='smtp']/general/installs[@index=1] - - //service[@type='smtp']/general/commands[@index=2] - - - - -# should be different from $mydomain eg. "mail.$mydomain" -myhostname = mail.$mydomain - -mydestination = $myhostname, - $mydomain, - localhost.$myhostname, - localhost.$mydomain, - localhost -mynetworks = 127.0.0.0/8 -inet_interfaces = all -append_dot_mydomain = no -biff = no - -# Postfix performance settings -default_destination_concurrency_limit = 20 -local_destination_concurrency_limit = 2 - -# SMTPD Settings -smtpd_banner = $myhostname ESMTP $mail_name ($mail_version) -smtpd_helo_required = yes -smtpd_recipient_restrictions = permit_mynetworks, - permit_sasl_authenticated, - reject_unauth_destination, - reject_unauth_pipelining, - reject_non_fqdn_recipient -smtpd_sender_restrictions = permit_mynetworks, - reject_sender_login_mismatch, - permit_sasl_authenticated, - reject_unknown_hostname, - reject_unknown_recipient_domain, - reject_unknown_sender_domain -smtpd_client_restrictions = permit_mynetworks, - permit_sasl_authenticated, - reject_unknown_hostname -smtpd_relay_restrictions = permit_mynetworks, - permit_sasl_authenticated, - defer_unauth_destination - -# Maximum size of Message in bytes (512MB) -message_size_limit = 536870912 - -## SASL Auth Settings -smtpd_sasl_auth_enable = yes -smtpd_sasl_local_domain = $myhostname -broken_sasl_auth_clients = yes - -# Virtual delivery settings -virtual_mailbox_base = / -virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf -virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql-virtual_mailbox_domains.cf -virtual_alias_maps = proxy:mysql:/etc/postfix/mysql-virtual_alias_maps.cf -smtpd_sender_login_maps = proxy:mysql:/etc/postfix/mysql-virtual_sender_permissions.cf -virtual_uid_maps = proxy:mysql:/etc/postfix/mysql-virtual_uid_maps.cf -virtual_gid_maps = proxy:mysql:/etc/postfix/mysql-virtual_gid_maps.cf - -# Local delivery settings -local_transport = local -alias_database = hash:/etc/mail/aliases -alias_maps = $alias_database - -# Default Mailbox size, is set to 0 which means unlimited! -mailbox_size_limit = 0 -virtual_mailbox_limit = 0 - -### TLS settings -### -## TLS for outgoing mails from the server to another server -#smtp_tls_security_level = may -#smtp_tls_note_starttls_offer = yes -## TLS for email client -#smtpd_tls_security_level = may -#smtpd_tls_cert_file = /etc/ssl/postfix/server.pem -#smtpd_tls_key_file = /etc/ssl/postfix/server.key -#smtpd_tls_CAfile = /etc/ssl/certs/ca-certificates.crt -#smtpd_tls_loglevel = 1 -#smtpd_tls_received_header = yes - -debugger_command = - PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin - ddd $daemon_directory/$process_name $process_id & sleep 5 -]]> - - - //service[@type='smtp']/general/files[@index=0] - - -sql_user: -sql_passwd: -sql_database: -sql_select: SELECT password_enc FROM mail_users WHERE username='%u@%r' OR email='%u@%r' ]]> @@ -2345,1047 +2217,6 @@ plugin { - - - - - - - - - -MYSQL_USERNAME -MYSQL_PASSWORD -MYSQL_PORT 0 -MYSQL_DATABASE -MYSQL_USER_TABLE mail_users -MYSQL_CRYPT_PWFIELD password_enc -MYSQL_UID_FIELD uid -MYSQL_GID_FIELD gid -MYSQL_LOGIN_FIELD username -MYSQL_HOME_FIELD homedir -MYSQL_MAILDIR_FIELD maildir -MYSQL_QUOTA_FIELD (quota*1024*1024) -MYSQL_AUXOPTIONS_FIELD CONCAT("allowimap=",imap,",allowpop3=",pop3) -]]> - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3419,7 +2250,6 @@ MaxInstances 50 # General settings DeferWelcome on -MultilineRFC2228 on ShowSymlinks on AllowOverwrite on AllowStoreRestart on @@ -3485,10 +2315,10 @@ SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6} TLSEngine on TLSLog /var/log/proftpd-tls.log -TLSProtocol TLSv1 TLSv1.1 TLSv1.2 +TLSProtocol TLSv1.2 TLSv1.3 #TLSTimeoutHandshake 120 # Really important for WinClients and some clients -TLSOptions NoCertRequest NoSessionReuseRequired +TLSOptions NoSessionReuseRequired TLSRSACertificateFile /etc/ssl/certs/proftpd.crt TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt @@ -3497,7 +2327,7 @@ TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key # Authenticate client that want to use FTP over TLS? TLSVerifyClient off # Uncomment the following line to force tls login -#TLSRequired on +TLSRequired on # LOG settings @@ -3515,6 +2345,32 @@ ExtendedLog /var/log/proftpd-access.log WRITE,READ write # make proftpd faster / do not perform ident and reverse dns lookup UseReverseDNS off + + +From 127.0.0.1 + + +MaxLoginAttempts 3 + + + BanEngine off + + + BanEngine on + +BanLog /var/log/proftpd-ban.log +BanTable /etc/proftpd/ban.tab +BanMessage "User %u was banned." +BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently" +BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00 +BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99 +BanControlsACLs all allow user root + + + +BanEngine off +DelayEngine off + ]]> @@ -3934,7 +2790,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/jammy.xml b/lib/configfiles/jammy.xml index cc324a71..ab44b42d 100644 --- a/lib/configfiles/jammy.xml +++ b/lib/configfiles/jammy.xml @@ -10,11 +10,13 @@ + + @@ -1515,7 +1517,7 @@ user = password = dbname = hosts = -query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' +query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' AND deactivated = 0 ]]> @@ -3383,7 +3385,6 @@ ServerName " FTP Server" ServerType standalone DeferWelcome off -MultilineRFC2228 on DefaultServer on ShowSymlinks on @@ -3720,7 +3721,6 @@ SQLNamedQuery get-quota-limit SELECT "ftp_users.username AS name, ftp_quotalimit SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used,bytes_out_used, bytes_xfer_used, files_in_used, files_out_used,files_xfer_used FROM ftp_quotatallies WHERE name = '%{0}' AND quota_type = '%{1}'" SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used= files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name= '%{6}' AND quota_type = '%{7}'" ftp_quotatallies SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6}, %{7}" ftp_quotatallies - ]]> @@ -3731,16 +3731,16 @@ SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6} TLSEngine on TLSLog /var/log/proftpd/tls.log -TLSProtocol TLSv1 TLSv1.1 TLSv1.2 +TLSProtocol TLSv1.2 TLSv1.3 TLSRSACertificateFile /etc/ssl/certs/proftpd.crt TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key -TLSOptions NoCertRequest NoSessionReuseRequired +TLSOptions NoSessionReuseRequired TLSVerifyClient off # Are clients required to use FTP over TLS when talking to this server? -#TLSRequired on +TLSRequired on # Allow SSL/TLS renegotiations when the client requests them, but # do not force the renegotiations. Some clients do not support @@ -3753,6 +3753,37 @@ TLSVerifyClient off ]]> + + +From 127.0.0.1 + + +MaxLoginAttempts 3 + + + BanEngine off + + + BanEngine on + +BanLog /var/log/proftpd/ban.log +BanTable /etc/proftpd/ban.tab +BanMessage "User %u was banned." +BanOnEvent ClientConnectRate 5/00:00:02 12:00:00 "Stop connecting frequently" +BanOnEvent MaxLoginAttempts 3/00:30:00 12:00:00 +BanOnEvent AnonRejectPasswords 1/01:00:00 99:99:99 +BanControlsACLs all allow user root + + + +BanEngine off +DelayEngine off + + ]]> + + @@ -4148,7 +4179,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/formfields/admin/admin/formfield.admin_add.php b/lib/formfields/admin/admin/formfield.admin_add.php index 6ade1bfc..71a428b3 100644 --- a/lib/formfields/admin/admin/formfield.admin_add.php +++ b/lib/formfields/admin/admin/formfield.admin_add.php @@ -120,6 +120,7 @@ return [ ], 'customers' => [ 'label' => lng('admin.customers'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -133,6 +134,7 @@ return [ ], 'domains' => [ 'label' => lng('admin.domains'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -146,6 +148,7 @@ return [ ], 'diskspace' => [ 'label' => lng('customer.diskspace') . ' (' . lng('customer.mib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 6, @@ -153,6 +156,7 @@ return [ ], 'traffic' => [ 'label' => lng('customer.traffic') . ' (' . lng('customer.gib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 4, @@ -160,6 +164,7 @@ return [ ], 'subdomains' => [ 'label' => lng('customer.subdomains'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -167,6 +172,7 @@ return [ ], 'emails' => [ 'label' => lng('customer.emails'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -174,6 +180,7 @@ return [ ], 'email_accounts' => [ 'label' => lng('customer.accounts'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -181,6 +188,7 @@ return [ ], 'email_forwarders' => [ 'label' => lng('customer.forwarders'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -188,6 +196,7 @@ return [ ], 'email_quota' => [ 'label' => lng('customer.email_quota') . ' (' . lng('customer.mib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -196,12 +205,14 @@ return [ ], 'ftps' => [ 'label' => lng('customer.ftps'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9 ], 'mysqls' => [ 'label' => lng('customer.mysqls'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, diff --git a/lib/formfields/admin/admin/formfield.admin_edit.php b/lib/formfields/admin/admin/formfield.admin_edit.php index d5cf9493..ef15245d 100644 --- a/lib/formfields/admin/admin/formfield.admin_edit.php +++ b/lib/formfields/admin/admin/formfield.admin_edit.php @@ -132,6 +132,7 @@ return [ ], 'customers' => [ 'label' => lng('admin.customers'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['customers']) ? '0' : $result['customers'], 'maxlength' => 9, @@ -145,6 +146,7 @@ return [ ], 'domains' => [ 'label' => lng('admin.domains'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['domains']) ? '0' : $result['domains'], 'maxlength' => 9, @@ -158,6 +160,7 @@ return [ ], 'diskspace' => [ 'label' => lng('customer.diskspace') . ' (' . lng('customer.mib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['diskspace']) ? '0' : $result['diskspace'], 'maxlength' => 6, @@ -165,6 +168,7 @@ return [ ], 'traffic' => [ 'label' => lng('customer.traffic') . ' (' . lng('customer.gib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['traffic']) ? '0' : $result['traffic'], 'maxlength' => 4, @@ -172,6 +176,7 @@ return [ ], 'subdomains' => [ 'label' => lng('customer.subdomains'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['subdomains']) ? '0' : $result['subdomains'], 'maxlength' => 9, @@ -179,6 +184,7 @@ return [ ], 'emails' => [ 'label' => lng('customer.emails'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['emails']) ? '0' : $result['emails'], 'maxlength' => 9, @@ -186,6 +192,7 @@ return [ ], 'email_accounts' => [ 'label' => lng('customer.accounts'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['email_accounts']) ? '0' : $result['email_accounts'], 'maxlength' => 9, @@ -193,6 +200,7 @@ return [ ], 'email_forwarders' => [ 'label' => lng('customer.forwarders'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['email_forwarders']) ? '0' : $result['email_forwarders'], 'maxlength' => 9, @@ -200,6 +208,7 @@ return [ ], 'email_quota' => [ 'label' => lng('customer.email_quota') . ' (' . lng('customer.mib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['email_quota']) ? '0' : $result['email_quota'], 'maxlength' => 9, @@ -208,12 +217,14 @@ return [ ], 'ftps' => [ 'label' => lng('customer.ftps'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['ftps']) ? '0' : $result['ftps'], 'maxlength' => 9 ], 'mysqls' => [ 'label' => lng('customer.mysqls'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['mysqls']) ? '0' : $result['mysqls'], 'maxlength' => 9, diff --git a/lib/formfields/admin/customer/formfield.customer_add.php b/lib/formfields/admin/customer/formfield.customer_add.php index 53bb2c76..77831b7a 100644 --- a/lib/formfields/admin/customer/formfield.customer_add.php +++ b/lib/formfields/admin/customer/formfield.customer_add.php @@ -89,7 +89,7 @@ return [ 'value' => '1', 'checked' => Settings::Get('api.enabled') == '1' && Settings::Get('api.customer_default'), 'visible' => Settings::Get('api.enabled') == '1' - ] + ], ] ], 'section_b' => [ @@ -187,6 +187,7 @@ return [ 'fields' => [ 'diskspace' => [ 'label' => lng('customer.diskspace') . ' (' . lng('customer.mib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 16, @@ -194,6 +195,7 @@ return [ ], 'traffic' => [ 'label' => lng('customer.traffic') . ' (' . lng('customer.gib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 14, @@ -201,6 +203,7 @@ return [ ], 'subdomains' => [ 'label' => lng('customer.subdomains'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -208,6 +211,7 @@ return [ ], 'emails' => [ 'label' => lng('customer.emails'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -215,6 +219,7 @@ return [ ], 'email_accounts' => [ 'label' => lng('customer.accounts'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -222,6 +227,7 @@ return [ ], 'email_forwarders' => [ 'label' => lng('customer.forwarders'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -229,6 +235,7 @@ return [ ], 'email_quota' => [ 'label' => lng('customer.email_quota') . ' (' . lng('customer.mib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -251,12 +258,14 @@ return [ ], 'ftps' => [ 'label' => lng('customer.ftps'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9 ], 'mysqls' => [ 'label' => lng('customer.mysqls'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => 0, 'maxlength' => 9, @@ -306,7 +315,7 @@ return [ 'type' => 'checkbox', 'value' => '1', 'checked' => true - ] + ], ] ] ] diff --git a/lib/formfields/admin/customer/formfield.customer_edit.php b/lib/formfields/admin/customer/formfield.customer_edit.php index 72d94408..a6ce7c57 100644 --- a/lib/formfields/admin/customer/formfield.customer_edit.php +++ b/lib/formfields/admin/customer/formfield.customer_edit.php @@ -87,7 +87,7 @@ return [ 'value' => '1', 'checked' => $result['api_allowed'], 'visible' => Settings::Get('api.enabled') == '1' - ] + ], ] ], 'section_b' => [ @@ -198,6 +198,7 @@ return [ 'fields' => [ 'diskspace' => [ 'label' => lng('customer.diskspace') . ' (' . lng('customer.mib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['diskspace']) ? '0' : $result['diskspace'], 'maxlength' => 16, @@ -205,6 +206,7 @@ return [ ], 'traffic' => [ 'label' => lng('customer.traffic') . ' (' . lng('customer.gib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['traffic']) ? '0' : $result['traffic'], 'maxlength' => 14, @@ -212,6 +214,7 @@ return [ ], 'subdomains' => [ 'label' => lng('customer.subdomains'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['subdomains']) ? '0' : $result['subdomains'], 'maxlength' => 9, @@ -219,6 +222,7 @@ return [ ], 'emails' => [ 'label' => lng('customer.emails'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['emails']) ? '0' : $result['emails'], 'maxlength' => 9, @@ -226,6 +230,7 @@ return [ ], 'email_accounts' => [ 'label' => lng('customer.accounts'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['email_accounts']) ? '0' : $result['email_accounts'], 'maxlength' => 9, @@ -233,6 +238,7 @@ return [ ], 'email_forwarders' => [ 'label' => lng('customer.forwarders'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['email_forwarders']) ? '0' : $result['email_forwarders'], 'maxlength' => 9, @@ -240,6 +246,7 @@ return [ ], 'email_quota' => [ 'label' => lng('customer.email_quota') . ' (' . lng('customer.mib') . ')', + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['email_quota']) ? '0' : $result['email_quota'], 'maxlength' => 9, @@ -262,6 +269,7 @@ return [ ], 'ftps' => [ 'label' => lng('customer.ftps'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['ftps']) ? '0' : $result['ftps'], 'maxlength' => 9, @@ -269,6 +277,7 @@ return [ ], 'mysqls' => [ 'label' => lng('customer.mysqls'), + 'desc' => lng('panel.use_checkbox_for_unlimited'), 'type' => 'textul', 'value' => empty($result['mysqls']) ? '0' : $result['mysqls'], 'maxlength' => 9, @@ -314,7 +323,7 @@ return [ 'type' => 'checkbox', 'value' => '1', 'checked' => $result['logviewenabled'] - ] + ], ] ], 'section_d' => [ diff --git a/lib/formfields/admin/domains/formfield.domains_add.php b/lib/formfields/admin/domains/formfield.domains_add.php index c48d0ce3..5400cc7d 100644 --- a/lib/formfields/admin/domains/formfield.domains_add.php +++ b/lib/formfields/admin/domains/formfield.domains_add.php @@ -60,12 +60,6 @@ return [ 'type' => 'select', 'select_var' => $domains ], - 'issubof' => [ - 'label' => lng('domains.issubof'), - 'desc' => lng('domains.issubofinfo'), - 'type' => 'select', - 'select_var' => $subtodomains - ], 'caneditdomain' => [ 'label' => lng('admin.domain_editable.title'), 'desc' => lng('admin.domain_editable.desc'), diff --git a/lib/formfields/admin/domains/formfield.domains_duplicate.php b/lib/formfields/admin/domains/formfield.domains_duplicate.php new file mode 100644 index 00000000..91386847 --- /dev/null +++ b/lib/formfields/admin/domains/formfield.domains_duplicate.php @@ -0,0 +1,58 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +return [ + 'domain_duplicate' => [ + 'title' => lng('admin.domain_duplicate'), + 'image' => 'fa-solid fa-globe', + 'self_overview' => ['section' => 'domains', 'page' => 'domains'], + 'id' => 'domain_add', + 'sections' => [ + 'section_a' => [ + 'title' => lng('domains.domainsettings'), + 'image' => 'icons/domain_add.png', + 'fields' => [ + 'domain' => [ + 'label' => 'Domain', + 'type' => 'text', + 'mandatory' => true + ], + 'customerid' => [ + 'label' => lng('admin.customer'), + 'type' => 'select', + 'select_var' => $customers, + 'selected' => $result['customerid'], + 'mandatory' => true + ], + ] + ] + ], + 'buttons' => [ + [ + 'label' => lng('admin.domain_duplicate') + ] + ] + ] +]; diff --git a/lib/formfields/admin/domains/formfield.domains_edit.php b/lib/formfields/admin/domains/formfield.domains_edit.php index c8f81bea..00b00cbd 100644 --- a/lib/formfields/admin/domains/formfield.domains_edit.php +++ b/lib/formfields/admin/domains/formfield.domains_edit.php @@ -65,13 +65,6 @@ return [ 'select_var' => $domains, 'selected' => $result['aliasdomain'] ], - 'issubof' => [ - 'label' => lng('domains.issubof'), - 'desc' => lng('domains.issubofinfo'), - 'type' => 'select', - 'select_var' => $subtodomains, - 'selected' => $result['ismainbutsubto'] - ], 'associated_info' => [ 'label' => lng('domains.associated_with_domain'), 'type' => 'label', @@ -104,7 +97,13 @@ return [ 'type' => 'date', 'value' => $result['termination_date'], 'size' => 10 - ] + ], + 'deactivated' => [ + 'label' => lng('admin.deactivated'), + 'type' => 'checkbox', + 'value' => '1', + 'checked' => $result['deactivated'] + ], ] ], 'section_e' => [ diff --git a/lib/formfields/admin/phpconfig/formfield.fpmconfig_add.php b/lib/formfields/admin/phpconfig/formfield.fpmconfig_add.php index 0f8d005d..a2267655 100644 --- a/lib/formfields/admin/phpconfig/formfield.fpmconfig_add.php +++ b/lib/formfields/admin/phpconfig/formfield.fpmconfig_add.php @@ -44,7 +44,8 @@ return [ 'type' => 'text', 'maxlength' => 255, 'value' => 'service php7.4-fpm restart', - 'mandatory' => true + 'mandatory' => true, + 'required_otp' => true ], 'config_dir' => [ 'label' => lng('serversettings.phpfpm_settings.configdir'), diff --git a/lib/formfields/admin/phpconfig/formfield.fpmconfig_edit.php b/lib/formfields/admin/phpconfig/formfield.fpmconfig_edit.php index 49f5daf7..d52437f0 100644 --- a/lib/formfields/admin/phpconfig/formfield.fpmconfig_edit.php +++ b/lib/formfields/admin/phpconfig/formfield.fpmconfig_edit.php @@ -45,7 +45,8 @@ return [ 'type' => 'text', 'maxlength' => 255, 'value' => $result['reload_cmd'], - 'mandatory' => true + 'mandatory' => true, + 'required_otp' => true ], 'config_dir' => [ 'label' => lng('serversettings.phpfpm_settings.configdir'), diff --git a/lib/formfields/admin/phpconfig/formfield.phpconfig_add.php b/lib/formfields/admin/phpconfig/formfield.phpconfig_add.php index 1decbbd7..157171f0 100644 --- a/lib/formfields/admin/phpconfig/formfield.phpconfig_add.php +++ b/lib/formfields/admin/phpconfig/formfield.phpconfig_add.php @@ -46,7 +46,8 @@ return [ 'label' => lng('admin.phpsettings.binary'), 'type' => 'text', 'maxlength' => 255, - 'value' => '/usr/bin/php-cgi' + 'value' => '/usr/bin/php-cgi', + 'required_otp' => true ], 'fpmconfig' => [ 'visible' => Settings::Get('phpfpm.enabled') == 1, @@ -61,7 +62,8 @@ return [ 'desc' => lng('admin.phpsettings.file_extensions_note'), 'type' => 'text', 'maxlength' => 255, - 'value' => 'php' + 'value' => 'php', + 'required_otp' => true ], 'mod_fcgid_starter' => [ 'visible' => Settings::Get('system.mod_fcgid') == 1, @@ -181,7 +183,8 @@ return [ 'cols' => 80, 'rows' => 20, 'value' => $result['phpsettings'], - 'mandatory' => true + 'mandatory' => true, + 'required_otp' => true ], 'allow_all_customers' => [ 'label' => lng('serversettings.phpfpm_settings.allow_all_customers.title'), diff --git a/lib/formfields/admin/phpconfig/formfield.phpconfig_edit.php b/lib/formfields/admin/phpconfig/formfield.phpconfig_edit.php index 11d59401..8216c27b 100644 --- a/lib/formfields/admin/phpconfig/formfield.phpconfig_edit.php +++ b/lib/formfields/admin/phpconfig/formfield.phpconfig_edit.php @@ -47,7 +47,8 @@ return [ 'label' => lng('admin.phpsettings.binary'), 'type' => 'text', 'maxlength' => 255, - 'value' => $result['binary'] + 'value' => $result['binary'], + 'required_otp' => true ], 'fpmconfig' => [ 'visible' => Settings::Get('phpfpm.enabled') == 1, @@ -62,7 +63,8 @@ return [ 'desc' => lng('admin.phpsettings.file_extensions_note'), 'type' => 'text', 'maxlength' => 255, - 'value' => $result['file_extensions'] + 'value' => $result['file_extensions'], + 'required_otp' => true ], 'mod_fcgid_starter' => [ 'visible' => Settings::Get('system.mod_fcgid') == 1, @@ -185,7 +187,8 @@ return [ 'cols' => 80, 'rows' => 20, 'value' => $result['phpsettings'], - 'mandatory' => true + 'mandatory' => true, + 'required_otp' => true ], 'allow_all_customers' => [ 'label' => lng('serversettings.phpfpm_settings.allow_all_customers.title'), diff --git a/lib/formfields/customer/domains/formfield.domains_add.php b/lib/formfields/customer/domains/formfield.domains_add.php index 029516de..610cf45a 100644 --- a/lib/formfields/customer/domains/formfield.domains_add.php +++ b/lib/formfields/customer/domains/formfield.domains_add.php @@ -89,7 +89,18 @@ return [ 'type' => 'select', 'select_var' => $phpconfigs, 'selected' => (int)Settings::Get('phpfpm.enabled') == 1 ? Settings::Get('phpfpm.defaultini') : Settings::Get('system.mod_fcgid_defaultini') - ] + ], + 'speciallogfile' => [ + 'label' => lng('admin.speciallogfile.title'), + 'desc' => lng('admin.speciallogfile.description'), + 'type' => 'select', + 'select_var' => [ + 0 => lng('panel.no'), + 1 => lng('panel.yes'), + 2 => lng('domain.inherited') + ], + 'selected' => 2 + ], ] ], 'section_bssl' => [ diff --git a/lib/formfields/customer/domains/formfield.domains_edit.php b/lib/formfields/customer/domains/formfield.domains_edit.php index 6d89369c..cd7d1553 100644 --- a/lib/formfields/customer/domains/formfield.domains_edit.php +++ b/lib/formfields/customer/domains/formfield.domains_edit.php @@ -104,7 +104,18 @@ return [ 'type' => 'select', 'select_var' => $phpconfigs, 'selected' => $result['phpsettingid'] - ] + ], + 'speciallogfile' => [ + 'label' => lng('admin.speciallogfile.title'), + 'desc' => lng('admin.speciallogfile.description'), + 'type' => 'checkbox', + 'value' => '1', + 'checked' => $result['speciallogfile'] + ], + 'speciallogverified' => [ + 'type' => 'hidden', + 'value' => '0' + ], ] ], 'section_bssl' => [ diff --git a/lib/formfields/customer/extras/formfield.backup.php b/lib/formfields/customer/extras/formfield.export.php similarity index 71% rename from lib/formfields/customer/extras/formfield.backup.php rename to lib/formfields/customer/extras/formfield.export.php index d6cdce7a..d9a5a96c 100644 --- a/lib/formfields/customer/extras/formfield.backup.php +++ b/lib/formfields/customer/extras/formfield.export.php @@ -18,43 +18,47 @@ use Froxlor\Settings; return [ - 'backup' => [ - 'title' => lng('extras.backup'), + 'export' => [ + 'title' => lng('extras.export'), 'image' => 'fa-solid fa-server', 'sections' => [ 'section_a' => [ - 'title' => lng('extras.backup'), - 'image' => 'icons/backup_big.png', + 'title' => lng('extras.export'), 'fields' => [ 'path' => [ - 'label' => lng('panel.backuppath.title'), - 'desc' => lng('panel.backuppath.description') . '
' . (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescription') : null), + 'label' => lng('panel.exportpath.title'), + 'desc' => lng('panel.exportpath.description') . '
' . (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescription') : null), 'type' => $pathSelect['type'], 'select_var' => $pathSelect['select_var'] ?? '', 'selected' => $pathSelect['value'], 'value' => $pathSelect['value'], 'note' => $pathSelect['note'] ?? '', ], + 'pgp_public_key' => [ + 'label' => lng('panel.export_pgp_public_key.title'), + 'desc' => lng('panel.export_pgp_public_key.description'), + 'type' => 'textarea', + ], 'path_protection_info' => [ 'label' => lng('extras.path_protection_label'), 'type' => 'infotext', 'value' => lng('extras.path_protection_info'), 'classes' => 'fw-bold text-danger' ], - 'backup_web' => [ - 'label' => lng('extras.backup_web'), + 'dump_web' => [ + 'label' => lng('extras.dump_web'), 'type' => 'checkbox', 'value' => '1', 'checked' => true ], - 'backup_mail' => [ - 'label' => lng('extras.backup_mail'), + 'dump_mail' => [ + 'label' => lng('extras.dump_mail'), 'type' => 'checkbox', 'value' => '1', 'checked' => true ], - 'backup_dbs' => [ - 'label' => lng('extras.backup_dbs'), + 'dump_dbs' => [ + 'label' => lng('extras.dump_dbs'), 'type' => 'checkbox', 'value' => '1', 'checked' => true diff --git a/lib/formfields/customer/ftp/formfield.ftp_add.php b/lib/formfields/customer/ftp/formfield.ftp_add.php index c2320cd2..2d7418a5 100644 --- a/lib/formfields/customer/ftp/formfield.ftp_add.php +++ b/lib/formfields/customer/ftp/formfield.ftp_add.php @@ -59,13 +59,16 @@ return [ 'label' => lng('login.password'), 'type' => 'password', 'autocomplete' => 'off', - 'mandatory' => true - ], - 'ftp_password_suggestion' => [ - 'label' => lng('customer.generated_pwd'), - 'type' => 'text', - 'visible' => (Settings::Get('panel.password_regex') == ''), - 'value' => Crypt::generatePassword() + 'mandatory' => true, + 'next_to' => [ + 'ftp_password_suggestion' => [ + 'next_to_prefix' => lng('customer.generated_pwd') . ':', + 'type' => 'text', + 'visible' => (Settings::Get('panel.password_regex') == ''), + 'value' => Crypt::generatePassword(), + 'readonly' => true + ] + ] ], 'sendinfomail' => [ 'label' => lng('customer.sendinfomail'), @@ -79,7 +82,13 @@ return [ 'type' => 'select', 'select_var' => $shells, 'selected' => '/bin/false' - ] + ], + 'login_enabled' => [ + 'label' => lng('panel.active'), + 'type' => 'checkbox', + 'value' => '1', + 'checked' => true + ], ] ] ] diff --git a/lib/formfields/customer/ftp/formfield.ftp_edit.php b/lib/formfields/customer/ftp/formfield.ftp_edit.php index 6375d568..b89d9240 100644 --- a/lib/formfields/customer/ftp/formfield.ftp_edit.php +++ b/lib/formfields/customer/ftp/formfield.ftp_edit.php @@ -51,13 +51,16 @@ return [ 'label' => lng('login.password'), 'desc' => lng('ftp.editpassdescription'), 'type' => 'password', - 'autocomplete' => 'off' - ], - 'ftp_password_suggestion' => [ - 'label' => lng('customer.generated_pwd'), - 'type' => 'text', - 'visible' => (Settings::Get('panel.password_regex') == ''), - 'value' => Crypt::generatePassword() + 'autocomplete' => 'off', + 'next_to' => [ + 'ftp_password_suggestion' => [ + 'next_to_prefix' => lng('customer.generated_pwd') . ':', + 'type' => 'text', + 'visible' => (Settings::Get('panel.password_regex') == ''), + 'value' => Crypt::generatePassword(), + 'readonly' => true + ] + ] ], 'shell' => [ 'visible' => Settings::Get('system.allow_customer_shell') == '1', @@ -65,7 +68,13 @@ return [ 'type' => 'select', 'select_var' => $shells, 'selected' => $result['shell'] ?? '/bin/false' - ] + ], + 'login_enabled' => [ + 'label' => lng('panel.active'), + 'type' => 'checkbox', + 'value' => '1', + 'checked' => $result['login_enabled'] == 'Y', + ], ] ] ] diff --git a/lib/formfields/formfield.dns_add.php b/lib/formfields/formfield.dns_add.php index b7c07da0..8e31f1b1 100644 --- a/lib/formfields/formfield.dns_add.php +++ b/lib/formfields/formfield.dns_add.php @@ -51,6 +51,7 @@ return [ 'RP' => 'RP', 'SRV' => 'SRV', 'SSHFP' => 'SSHFP', + 'TLSA' => 'TLSA', 'TXT' => 'TXT' ], 'selected' => $type diff --git a/lib/navigation/00.froxlor.main.php b/lib/navigation/00.froxlor.main.php index 9863ce54..aa2c8624 100644 --- a/lib/navigation/00.froxlor.main.php +++ b/lib/navigation/00.froxlor.main.php @@ -133,9 +133,9 @@ return [ 'show_element' => (Settings::Get('logger.enabled') == true) && (!Settings::IsInList('panel.customer_hide_options', 'extras.logger')) ], [ - 'url' => 'customer_extras.php?page=backup', - 'label' => lng('menue.extras.backup'), - 'show_element' => (Settings::Get('system.backupenabled') == true) && (!Settings::IsInList('panel.customer_hide_options', 'extras.backup')) + 'url' => 'customer_extras.php?page=export', + 'label' => lng('menue.extras.export'), + 'show_element' => (Settings::Get('system.exportenabled') == true) && (!Settings::IsInList('panel.customer_hide_options', 'extras.export')) ] ] ], @@ -186,7 +186,7 @@ return [ 'url' => 'admin_customers.php?page=customers', 'label' => lng('admin.customers'), 'required_resources' => 'customers', - 'add_shortlink' => CurrentUser::canAddResource('customers') ? 'admin_customers.php?page=customers&action=add' : null, + 'add_shortlink' => CurrentUser::isAdmin() && CurrentUser::canAddResource('customers') ? 'admin_customers.php?page=customers&action=add' : null, ], [ 'url' => 'admin_admins.php?page=admins', @@ -198,7 +198,7 @@ return [ 'url' => 'admin_domains.php?page=domains', 'label' => lng('admin.domains'), 'required_resources' => 'domains', - 'add_shortlink' => CurrentUser::canAddResource('domains') ? 'admin_domains.php?page=domains&action=add' : null, + 'add_shortlink' => CurrentUser::isAdmin() && CurrentUser::canAddResource('domains') ? 'admin_domains.php?page=domains&action=add' : null, ], [ 'url' => 'admin_domains.php?page=sslcertificates', @@ -244,7 +244,6 @@ return [ ], 'server' => [ 'label' => lng('admin.server'), - 'required_resources' => 'change_serversettings', 'icon' => 'fa-solid fa-server', 'elements' => [ [ @@ -265,7 +264,6 @@ return [ [ 'url' => 'admin_logger.php?page=log', 'label' => lng('menue.logger.logger'), - 'required_resources' => 'change_serversettings', 'show_element' => (Settings::Get('logger.enabled') == true) ], [ diff --git a/lib/tablelisting/admin/tablelisting.admins.php b/lib/tablelisting/admin/tablelisting.admins.php index aaa82126..637f3d4c 100644 --- a/lib/tablelisting/admin/tablelisting.admins.php +++ b/lib/tablelisting/admin/tablelisting.admins.php @@ -48,6 +48,7 @@ return [ 'field' => 'loginname', 'callback' => [Impersonate::class, 'admin'], 'sortable' => true, + 'isdefaultsearchfield' => true, ], 'name' => [ 'label' => lng('customer.name'), @@ -94,6 +95,11 @@ return [ 'class' => 'text-center', 'callback' => [Text::class, 'boolean'], ], + 'lastlogin_succ' => [ + 'label' => lng('admin.lastlogin_succ'), + 'field' => 'lastlogin_succ', + 'callback' => [Text::class, 'timestamp'], + ], 'theme' => [ 'label' => lng('panel.theme'), 'field' => 'theme', diff --git a/lib/tablelisting/admin/tablelisting.customers.php b/lib/tablelisting/admin/tablelisting.customers.php index bfe663ce..de3e8fb5 100644 --- a/lib/tablelisting/admin/tablelisting.customers.php +++ b/lib/tablelisting/admin/tablelisting.customers.php @@ -26,8 +26,8 @@ use Froxlor\UI\Callbacks\Customer; use Froxlor\UI\Callbacks\Impersonate; use Froxlor\UI\Callbacks\ProgressBar; -use Froxlor\UI\Callbacks\Text; use Froxlor\UI\Callbacks\Style; +use Froxlor\UI\Callbacks\Text; use Froxlor\UI\Listing; return [ diff --git a/lib/tablelisting/admin/tablelisting.domains.php b/lib/tablelisting/admin/tablelisting.domains.php index 11f8ec5e..a5f578ee 100644 --- a/lib/tablelisting/admin/tablelisting.domains.php +++ b/lib/tablelisting/admin/tablelisting.domains.php @@ -23,6 +23,7 @@ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 */ +use Froxlor\Settings; use Froxlor\UI\Callbacks\Domain; use Froxlor\UI\Callbacks\Impersonate; use Froxlor\UI\Callbacks\Style; @@ -48,6 +49,7 @@ return [ 'd.domain_ace' => [ 'label' => lng('domains.domainname'), 'field' => 'domain_ace', + 'isdefaultsearchfield' => true, ], 'ipsandports' => [ 'label' => lng('admin.ipsandports.ipsandports'), @@ -113,6 +115,13 @@ return [ 'field' => 'phpenabled', 'callback' => [Text::class, 'boolean'], ], + 'd.phpsettingid' => [ + 'label' => lng('admin.phpsettings.title'), + 'field' => 'phpsettingid', + 'searchable' => false, + 'callback' => [Domain::class, 'getPhpConfigName'], + 'visible' => (int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1 + ], 'd.openbasedir' => [ 'label' => lng('domains.openbasedirenabled'), 'field' => 'openbasedir', @@ -161,6 +170,11 @@ return [ 'id' => ':id' ], ], + 'duplicate' => [ + 'icon' => 'fa-solid fa-clone', + 'title' => lng('admin.domain_duplicate'), + 'modal' => [Text::class, 'domainDuplicateModal'], + ], 'logfiles' => [ 'icon' => 'fa-solid fa-file', 'title' => lng('panel.viewlogs'), diff --git a/lib/tablelisting/admin/tablelisting.fpmconfigs.php b/lib/tablelisting/admin/tablelisting.fpmconfigs.php index bae9c366..2d2ac0bc 100644 --- a/lib/tablelisting/admin/tablelisting.fpmconfigs.php +++ b/lib/tablelisting/admin/tablelisting.fpmconfigs.php @@ -41,6 +41,7 @@ return [ 'description' => [ 'label' => lng('admin.phpsettings.description'), 'field' => 'description', + 'isdefaultsearchfield' => true, ], 'configs' => [ 'label' => lng('admin.phpsettings.activephpconfigs'), diff --git a/lib/tablelisting/admin/tablelisting.ipsandports.php b/lib/tablelisting/admin/tablelisting.ipsandports.php index 47046c48..b5f920fd 100644 --- a/lib/tablelisting/admin/tablelisting.ipsandports.php +++ b/lib/tablelisting/admin/tablelisting.ipsandports.php @@ -37,6 +37,7 @@ return [ 'ip' => [ 'label' => lng('admin.ipsandports.ip'), 'field' => 'ip', + 'isdefaultsearchfield' => true, ], 'port' => [ 'label' => lng('admin.ipsandports.port'), diff --git a/lib/tablelisting/admin/tablelisting.mysqlserver.php b/lib/tablelisting/admin/tablelisting.mysqlserver.php index 53877395..01efcf40 100644 --- a/lib/tablelisting/admin/tablelisting.mysqlserver.php +++ b/lib/tablelisting/admin/tablelisting.mysqlserver.php @@ -40,6 +40,7 @@ return [ 'caption' => [ 'label' => lng('admin.mysqlserver.caption'), 'field' => 'caption', + 'isdefaultsearchfield' => true, ], 'host' => [ 'label' => lng('admin.mysqlserver.host'), diff --git a/lib/tablelisting/admin/tablelisting.phpconfigs.php b/lib/tablelisting/admin/tablelisting.phpconfigs.php index 207f90b4..a2f025b1 100644 --- a/lib/tablelisting/admin/tablelisting.phpconfigs.php +++ b/lib/tablelisting/admin/tablelisting.phpconfigs.php @@ -42,6 +42,7 @@ return [ 'c.description' => [ 'label' => lng('admin.phpsettings.description'), 'field' => 'description', + 'isdefaultsearchfield' => true, ], 'domains' => [ 'label' => lng('admin.phpsettings.activedomains'), diff --git a/lib/tablelisting/admin/tablelisting.plans.php b/lib/tablelisting/admin/tablelisting.plans.php index a4e08ed2..be4dd5ff 100644 --- a/lib/tablelisting/admin/tablelisting.plans.php +++ b/lib/tablelisting/admin/tablelisting.plans.php @@ -40,6 +40,7 @@ return [ 'p.name' => [ 'label' => lng('admin.plans.name'), 'field' => 'name', + 'isdefaultsearchfield' => true, ], 'p.description' => [ 'label' => lng('admin.plans.description'), diff --git a/lib/tablelisting/customer/tablelisting.backups.php b/lib/tablelisting/customer/tablelisting.export.php similarity index 69% rename from lib/tablelisting/customer/tablelisting.backups.php rename to lib/tablelisting/customer/tablelisting.export.php index 1ccf3d10..d485bb4c 100644 --- a/lib/tablelisting/customer/tablelisting.backups.php +++ b/lib/tablelisting/customer/tablelisting.export.php @@ -28,10 +28,10 @@ use Froxlor\UI\Callbacks\Text; use Froxlor\UI\Listing; return [ - 'backup_list' => [ - 'title' => lng('error.customerhasongoingbackupjob'), + 'export_list' => [ + 'title' => lng('error.customerhasongoingexportjob'), 'icon' => 'fa-solid fa-server', - 'self_overview' => ['section' => 'extras', 'page' => 'backup'], + 'self_overview' => ['section' => 'extras', 'page' => 'export'], 'default_sorting' => ['destdir' => 'asc'], 'columns' => [ 'destdir' => [ @@ -39,27 +39,33 @@ return [ 'field' => 'data.destdir', 'callback' => [Ftp::class, 'pathRelative'] ], - 'backup_web' => [ - 'label' => lng('extras.backup_web'), - 'field' => 'data.backup_web', + 'pgp_public_key' => [ + 'label' => lng('panel.pgp_public_key'), + 'field' => 'data.pgp_public_key', + 'callback' => [Text::class, 'boolean'] + ], + 'dump_web' => [ + 'label' => lng('extras.dump_web'), + 'field' => 'data.dump_web', 'callback' => [Text::class, 'boolean'], ], - 'backup_mail' => [ - 'label' => lng('extras.backup_mail'), - 'field' => 'data.backup_mail', + 'dump_mail' => [ + 'label' => lng('extras.dump_mail'), + 'field' => 'data.dump_mail', 'callback' => [Text::class, 'boolean'], ], - 'backup_dbs' => [ - 'label' => lng('extras.backup_dbs'), - 'field' => 'data.backup_dbs', + 'dump_dbs' => [ + 'label' => lng('extras.dump_dbs'), + 'field' => 'data.dump_dbs', 'callback' => [Text::class, 'boolean'], ] ], - 'visible_columns' => Listing::getVisibleColumnsForListing('backup_list', [ + 'visible_columns' => Listing::getVisibleColumnsForListing('export_list', [ 'destdir', - 'backup_web', - 'backup_mail', - 'backup_dbs' + 'pgp_public_key', + 'dump_web', + 'dump_mail', + 'dump_dbs' ]), 'actions' => [ 'delete' => [ @@ -68,7 +74,7 @@ return [ 'class' => 'btn-warning', 'href' => [ 'section' => 'extras', - 'page' => 'backup', + 'page' => 'export', 'action' => 'abort', 'id' => ':id' ], diff --git a/lib/tablelisting/customer/tablelisting.ftps.php b/lib/tablelisting/customer/tablelisting.ftps.php index 4eed75ea..3d58af12 100644 --- a/lib/tablelisting/customer/tablelisting.ftps.php +++ b/lib/tablelisting/customer/tablelisting.ftps.php @@ -25,6 +25,8 @@ use Froxlor\Settings; use Froxlor\UI\Callbacks\Ftp; +use Froxlor\UI\Callbacks\Style; +use Froxlor\UI\Callbacks\Text; use Froxlor\UI\Listing; return [ @@ -51,13 +53,19 @@ return [ 'label' => lng('panel.shell'), 'field' => 'shell', 'visible' => Settings::Get('system.allow_customer_shell') == '1' + ], + 'login_enabled' => [ + 'label' => lng('panel.active'), + 'field' => 'login_enabled', + 'callback' => [Text::class, 'yesno'], ] ], 'visible_columns' => Listing::getVisibleColumnsForListing('ftp_list', [ 'username', 'description', 'homedir', - 'shell' + 'shell', + 'login_enabled', ]), 'actions' => [ 'edit' => [ @@ -81,6 +89,9 @@ return [ 'id' => ':id' ], ] - ] + ], + 'format_callback' => [ + [Style::class, 'loginDisabled'] + ], ] ]; diff --git a/lib/tablelisting/tablelisting.sslcertificates.php b/lib/tablelisting/tablelisting.sslcertificates.php index d243f333..d5cf1255 100644 --- a/lib/tablelisting/tablelisting.sslcertificates.php +++ b/lib/tablelisting/tablelisting.sslcertificates.php @@ -89,6 +89,9 @@ return [ 'action' => 'delete', 'id' => ':id' ], + // Let's Encrypt certificates can be removed 'correctly' + // by disabling let's encrypt for the domain + 'visible' => [SSLCertificate::class, 'isNotLetsEncrypt'] ], ] ] diff --git a/lib/tables.inc.php b/lib/tables.inc.php index 43c52b08..26214333 100644 --- a/lib/tables.inc.php +++ b/lib/tables.inc.php @@ -56,3 +56,4 @@ const TABLE_PANEL_FPMDAEMONS = 'panel_fpmdaemons'; const TABLE_PANEL_PLANS = 'panel_plans'; const TABLE_API_KEYS = 'api_keys'; const TABLE_PANEL_USERCOLUMNS = 'panel_usercolumns'; +const TABLE_PANEL_LOGINLINKS = 'panel_loginlinks'; diff --git a/lng/cz.lng.php b/lng/cz.lng.php index dc9671db..970c7e14 100644 --- a/lng/cz.lng.php +++ b/lng/cz.lng.php @@ -456,10 +456,6 @@ return [ 'directory_browsing' => 'Procházení obsahu adresáře', 'pathoptions_edit' => 'Upravit možnosti cesty', 'execute_perl' => 'Spustit perl/CGI', - 'backup' => 'Vytvořit zálohu', - 'backup_web' => 'Zálohovat web-data', - 'backup_mail' => 'Zálohovat mail-data', - 'backup_dbs' => 'Zálohovat databáze', ], 'ftp' => [ 'description' => 'Zde můžeš tvořit a upravovat tvé FTP-účty.
Změny jsou uskutečněny okamžitě a účty mohou být použity ihned.', @@ -549,7 +545,6 @@ return [ 'extras' => 'Extra', 'directoryprotection' => 'Ochrana adresáře', 'pathoptions' => 'Možnosti cesty', - 'backup' => 'Záloha', ], 'traffic' => [ 'traffic' => 'Provoz', @@ -815,9 +810,6 @@ return [ 'caa_entry' => [ 'title' => 'Generovat CAA DNS záznamy', ], - 'backupenabled' => [ - 'title' => 'Povolit zálohy pro zákazníky', - ], 'mail_smtp_passwd' => 'SMTP heslo', ], 'success' => [ diff --git a/lng/de.lng.php b/lng/de.lng.php index c331ccd5..a5ac3368 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -46,6 +46,8 @@ return [ '2fa_overview_desc' => 'Hier kann für das Konto eine Zwei-Faktor-Authentisierung aktiviert werden.

Es kann entweder eine Authenticator-App (time-based one-time password / TOTP) genutzt werden oder ein Einmalpasswort, welches nach erfolgreichem Login an die hinterlegte E-Mail Adresse gesendet wird.', '2fa_email_desc' => 'Das Konto ist eingerichtet, um Einmalpasswörter per E-Mail zu erhalten. Zum Deaktivieren, klicke auf "2FA deaktivieren"', '2fa_ga_desc' => 'Das Konto ist eingerichtet, um zeitbasierte Einmalpasswörter via Authenticator-App zu erhalten. Um die gewünschte Authenticator-App einzurichten, scanne bitte den untenstehenden QR-Code. Zum Deaktivieren, klicke auf "2FA deaktivieren"', + '2fa_not_activated' => 'Zwei-Faktor Authentifizierung ist nicht aktiviert', + '2fa_not_activated_for_user' => 'Zwei-Faktor Authentifizierung ist für den aktuellen Benutzer nicht aktiviert', ], 'admin' => [ 'overview' => 'Übersicht', @@ -489,6 +491,8 @@ return [ 'adminguide' => 'Admin Guide', 'userguide' => 'User Guide', 'apiguide' => 'API Guide', + 'domain_duplicate' => 'Domain duplizieren', + 'domain_duplicate_named' => '%s duplizieren', ], 'apikeys' => [ 'no_api_keys' => 'Keine API Keys gefunden', @@ -525,7 +529,8 @@ return [ 'cron_usage_report' => 'Webspace- und Trafficreport', 'cron_mailboxsize' => 'Berechnung der Mailbox-Größen', 'cron_letsencrypt' => 'Aktualisierung der Let\'s Encrypt Zertifikate', - 'cron_backup' => 'Ausstehende Sicherungen erstellen', + 'cron_export' => 'Ausstehende Datenexporte erstellen', + 'cron_backup' => 'System- und Kunden-Sicherungen erstellen', ], 'cronjob' => [ 'cronjobsettings' => 'Cronjob-Einstellungen', @@ -642,6 +647,7 @@ return [ ], 'domain' => [ 'openbasedirpath' => 'OpenBasedir-Pfad', + 'inherited' => 'Gleich wie Elterndomain', 'docroot' => 'Oben angegebener Pfad', 'homedir' => 'Heimverzeichnis', 'docparent' => 'Elternverzeichnis des oben angegebenen Pfads', @@ -665,9 +671,6 @@ return [ 'aliasdomains' => 'Aliasdomains', 'redirectifpathisurl' => 'Redirect-Code (Standard: leer)', 'redirectifpathisurlinfo' => 'Der Redirect-Code kann gewählt werden, wenn der eingegebene Pfad eine URL ist.
HINWEIS: Änderungen werden nur wirksam wenn der Pfad eine URL ist.', - 'issubof' => 'Diese Domain ist eine Subdomain von der Domain', - 'issubofinfo' => 'Diese Einstellung muss gesetzt werden, wenn Sie eine Subdomain einer Hauptdomain als Hauptdomain anlegen (z. B. soll "www.domain.tld" hinzugefügt werden, somit muss hier "domain.tld" ausgewählt werden).', - 'nosubtomaindomain' => 'Keine Subdomain einer Hauptdomain', 'ipandport_multi' => [ 'title' => 'IP-Adresse(n)', 'description' => 'Definieren Sie eine oder mehrere IP-Adresse(n) für diese Domain.

Hinweis: Die IP-Adressen können nicht geändert werden, sollte die Domain als Alias-Domain für eine andere Domain konfiguriert worden sein.
', @@ -700,6 +703,7 @@ return [ 'openbasedirenabled' => 'Openbasedir Einschränkung', 'hsts' => 'HSTS aktiviert', 'aliasdomainid' => 'ID der Alias-Domain', + 'nodomainsassignedbyadmin' => 'Diesem Account wurde noch keine (aktive) Domain zugewiesen. Bitte kontaktiere deinen Administrator, wenn du der Meinung bist, das ist nicht korrekt.', ], 'emails' => [ 'description' => 'Hier können Sie Ihre E-Mail-Adressen einrichten.
Ein Konto ist wie Ihr Briefkasten vor der Haustür. Wenn jemand eine E-Mail an Sie schreibt, wird diese in dieses Konto gelegt.

Die Zugangsdaten lauten wie folgt: (Die Angaben in kursiver Schrift sind durch die jeweiligen Einträge zu ersetzen)

Hostname: Domainname
Benutzername: Kontoname / E-Mail-Adresse
Passwort: das gewählte Passwort', @@ -770,11 +774,12 @@ return [ 'domainisaliasorothercustomer' => 'Die ausgewählte Aliasdomain ist entweder selbst eine Aliasdomain, hat nicht die gleiche IP/Port-Kombination oder gehört einem anderen Kunden.', 'emailexistalready' => 'Die E-Mail-Adresse "%s" existiert bereits.', 'maindomainnonexist' => 'Die Hauptdomain "%s" existiert nicht.', + 'maindomaindeactivated' => 'Die Hauptdomain "%s" ist deaktiviert.', 'destinationnonexist' => 'Bitte geben Sie Ihre Weiterleitungsadresse im Feld \'Nach\' ein.', 'destinationalreadyexistasmail' => 'Die Weiterleitung zu "%s" existiert bereits als aktive E-Mail-Adresse.', 'destinationalreadyexist' => 'Es existiert bereits eine Weiterleitung nach "%s".', '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', + 'dumpfoldercannotbedocroot' => 'Der Ordner für Daten-Export darf nicht das Heimatverzeichnis sein, wählen Sie einen Ordner unterhalb des Heimatverzeichnisses, z.B. /dumps', '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.', @@ -877,6 +882,9 @@ return [ 'moveofcustomerfailed' => 'Das Verschieben des Kunden ist fehlgeschlagen. Alle übrigen Änderungen wurden durchgeführt und gespeichert.

Fehlermeldung: %s', 'domain_import_error' => 'Der folgende Fehler trat beim Importieren der Domains auf: %s', 'fcgidandphpfpmnogoodtogether' => 'FCGID und PHP-FPM können nicht gleichzeitig aktiviert werden.', + 'no_apcuinfo' => 'Keine APCu Cache Informationen verfügbar. APCu scheint nicht installiert zu sein.', + 'no_opcacheinfo' => 'Keine OPCache Informationen verfügbar. OPCache scheint nicht installiert zu sein.', + 'inactive_opcacheinfo' => 'OPCache ist installiert, aber nicht aktiviert.', 'nowildcardwithletsencrypt' => 'Let\'s Encrypt kann mittels ACME Wildcard-Domains nur via DNS validieren, sorry. Bitte den ServerAlias auf WWW setzen oder deaktivieren', 'customized_version' => 'Es scheint als wäre die Froxlor Installation angepasst worden. Kein Support, sorry.', 'autoupdate_0' => 'Unbekannter Fehler', @@ -891,8 +899,8 @@ return [ 'autoupdate_10' => 'Minimum unterstützte Version von PHP ist 7.4.0', 'autoupdate_11' => 'Webupdate ist deaktiviert', 'mailaccistobedeleted' => 'Ein vorheriges Konto mit dem gleichen Namen (%s) wird aktuell noch gelöscht und kann daher derzeit nicht angelegt werden', - 'customerhasongoingbackupjob' => 'Es gibt noch einen austehenden Backup-Job. Bitte haben Sie etwas Geduld.', - 'backupfunctionnotenabled' => 'Die Sicherungs-Funktion is nicht aktiviert', + 'customerhasongoingexportjob' => 'Es gibt noch einen austehenden Daten-Export. Bitte haben Sie etwas Geduld.', + 'exportfunctionnotenabled' => 'Die Datenexport-Funktion is nicht aktiviert', 'dns_domain_nodns' => 'DNS ist für diese Domain nicht aktiviert', 'dns_content_empty' => 'Keinen Inhalt angegeben', 'dns_content_invalid' => 'DNS Eintrag ungültig', @@ -914,6 +922,7 @@ return [ 'domain_nopunycode' => 'Die Eingabe von Punycode (IDNA) ist nicht notwendig. Die Domain wird automatisch konvertiert.', 'dns_record_toolong' => 'Records/Labels können maximal 63 Zeichen lang sein', 'noipportgiven' => 'Keine IP/Port angegeben', + 'nosslippportgiven' => 'Wenn SSL aktiviert ist, muss eine SSL IP/Port angegeben werden', 'jsonextensionnotfound' => 'Diese Funktion benötigt die PHP json-Erweiterung.', 'cannotdeletesuperadmin' => 'Der erste Administrator kann nicht gelöscht werden.', 'no_wwwcnamae_ifwwwalias' => 'Es kann kein CNAME Eintrag für "www" angelegt werden, da die Domain einen www-Alias aktiviert hat. Ändere diese Einstellung auf "Kein Alias" oder "Wildcard Alias"', @@ -927,6 +936,10 @@ return [ 'invalidcronjobintervalvalue' => 'Cronjob Intervall muss einer der folgenden Werte sein: %s', 'phpgdextensionnotavailable' => 'Die PHP GD Extension ist nicht verfügbar. Bild-Daten können nicht validiert werden.', '2fa_wrongcode' => 'Der angegebene Code ist nicht korrekt', + 'gnupgextensionnotavailable' => 'Die PHP GnuPG Extension ist nicht verfügbar. PGP Schlüssel können nicht validiert werden.', + 'invalidpgppublickey' => 'Der angegebene PGP Public Key ist ungültig', + 'invalid_validtime' => 'Wert der valid_time in Sekunden muss zwischen 10 und 120 liegen.', + 'customerphpenabledbutnoconfig' => 'Kunde hat PHP aktiviert aber keine PHP-Konfiguration wurde gewählt.', ], 'extras' => [ 'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.
Die Änderungen sind erst nach einer kurzen Zeit wirksam.', @@ -946,10 +959,10 @@ return [ 'execute_perl' => 'Perl/CGI ausführen', 'htpasswdauthname' => 'Grund der Authentifizierung (AuthName)', 'directoryprotection_edit' => 'Verzeichnisschutz bearbeiten', - 'backup' => 'Sicherung erstellen', - 'backup_web' => 'Web-Daten sichern', - 'backup_mail' => 'E-Mail Daten sichern', - 'backup_dbs' => 'Datenbanken sichern', + 'export' => 'Datenexport erstellen', + 'dump_web' => 'Web-Daten hinzufügen', + 'dump_mail' => 'E-Mail Daten hinzufügen', + 'dump_dbs' => 'Datenbanken hinzufügen', 'path_protection_label' => 'Wichtig', 'path_protection_info' => 'Wir raten dringend dazu den angegebenen Pfad zu schützen, siehe "Extras" -> "Verzeichnisschutz"', ], @@ -1098,7 +1111,7 @@ Vielen Dank, Ihr Administrator', 'extras' => 'Extras', 'directoryprotection' => 'Verzeichnisschutz', 'pathoptions' => 'Pfadoptionen', - 'backup' => 'Sicherung', + 'export' => 'Datenexport', ], 'traffic' => [ 'traffic' => 'Traffic', @@ -1194,10 +1207,15 @@ Vielen Dank, Ihr Administrator', 'ftpdesc' => 'FTP-Beschreibung', 'letsencrypt' => 'Benutzt Let\'s encrypt', 'set' => 'Setzen', - 'backuppath' => [ - 'title' => 'Pfad zur Ablage der Backups', - 'description' => 'In diesem Ordner werden die Backups abgelegt. Wenn das Sichern von Web-Daten aktiviert ist, werden alle Dateien aus dem Heimatverzeichnis gesichert, exklusive des hier angegebenen Backup-Ordners.', + 'exportpath' => [ + 'title' => 'Pfad zur Ablage des Exports', + 'description' => 'In diesem Ordner werden die Export-Archive abgelegt. Wenn Web-Daten exportiert werden, werden alle Dateien aus dem Heimatverzeichnis gesichert, exklusive des hier angegebenen Ordners.', ], + 'export_pgp_public_key' => [ + 'title' => 'Öffentlicher PGP-Schlüssel', + 'description' => 'Der öffentliche PGP-Schlüssel, mit dem die Exporte verschlüsselt werden sollen. Wenn kein Schlüssel angegeben ist, werden die Exporte nicht verschlüsselt.', + ], + 'pgp_public_key' => 'Öffentlicher PGP-Schlüssel', 'none_value' => 'Keine', 'viewlogs' => 'Logdateien einsehen', 'not_configured' => 'Das System wurde noch nicht konfiguriert. Klicke auf den Button um die Installation zu starten.', @@ -1226,6 +1244,8 @@ Vielen Dank, Ihr Administrator', 'description' => 'Wähle das zu durchsuchende Feld aus' ], 'upload_import' => 'Hochladen und importieren', + 'profile' => 'Mein Profil', + 'use_checkbox_for_unlimited' => 'Der Wert "0" deaktiviert die Resource. Die Checkbox rechts erlaubt "unlimitierte" Nutzung.', ], 'phpfpm' => [ 'vhost_httpuser' => 'Lokaler Benutzer für PHP-FPM (Froxlor-Vhost)', @@ -1264,7 +1284,7 @@ Vielen Dank, Ihr Administrator', 'email_reallydelete_forwarder' => 'Wollen Sie die Weiterleitung "%s" wirklich löschen?', 'extras_reallydelete' => 'Wollen Sie den Verzeichnisschutz für "%s" wirklich löschen?', 'extras_reallydelete_pathoptions' => 'Wollen Sie die Optionen für den Pfad "%s" wirklich löschen?', - 'extras_reallydelete_backup' => 'Wollen Sie die geplante Sicherung wirklich löschen?', + 'extras_reallydelete_export' => 'Wollen Sie den geplanten Daten-Export wirklich löschen?', 'ftp_reallydelete' => 'Wollen Sie das FTP-Benutzerkonto "%s" wirklich löschen?', 'mysql_reallydelete' => 'Wollen Sie die Datenbank "%s" wirklich löschen?
ACHTUNG! Alle Daten gehen unwiderruflich verloren!', 'admin_configs_reallyrebuild' => 'Wollen Sie wirklich alle Konfigurationsdateien neu erstellen lassen?', @@ -1280,7 +1300,6 @@ Vielen Dank, Ihr Administrator', 'admin_quotas_reallyenforce' => 'Sind Sie sicher, dass Sie allen Benutzern das Default-Quota zuweisen wollen? Dies kann nicht rückgängig gemacht werden!', 'phpsetting_reallydelete' => 'Wollen Sie diese PHP-Einstellungen wirklich löschen? Alle Domains die diese Einstellungen bis jetzt verwendet haben, werden dann auf die Standardeinstellungen umgestellt.', 'fpmsetting_reallydelete' => 'Wollen Sie diese PHP-FPM Einstellungen wirklich löschen? Alle PHP Konfigurationen die diese Einstellungen bis jetzt verwendet haben, werden dann auf die Standardeinstellungen umgestellt.', - 'remove_subbutmain_domains' => 'Auch Domains entfernen, welche als volle Domains hinzugefügt wurden, aber Subdomains von dieser Domain sind?', 'customer_reallyunlock' => 'Wollen Sie den Kunden "%s" wirklich entsperren?', 'admin_integritycheck_reallyfix' => 'Möchten Sie wirklich versuchen sämtliche Datenbank-Integritätsprobleme automatisch zu beheben?', 'plan_reallydelete' => 'Wollen Sie den Hostingplan %s wirklich löschen?', @@ -1917,9 +1936,9 @@ Vielen Dank, Ihr Administrator', 'title' => 'Zusätzliche CAA DNS Einträge', 'description' => 'DNS Certification Authority Authorization (CAA) verwendet das Domain Name System, um dem Besitzer einer Domain die Möglichkeit zu bieten, gewisse Zertifizierungsstellen (CAs) dazu zu berechtigen,
ein Zertifikat für die betroffene Domain auszustellen. CAA Records sollen verhindern, dass Zertifikate fälschlicherweise für eine Domain ausgestellt werden.

Der Inhalt dieses Feldes wird direkt in die DNS Zone übernommen (eine Zeile pro CAA Record). Wenn Let\'s Encrypt für eine Domain aktiviert wurde und die obige Option aktiviert wurde, wird immer automatisch dieser Eintrag angefügt und muss nicht selber angegeben werden:
0 issue "letsencrypt.org" (Wenn wildcard aktiviert ist, wird statdessen issuewild benutzt).
Um Incident Reporting per Mail zu aktivieren, muss eine iodef Zeile angefügt werden. Ein Beispiel für einen Report an me@example.com wäre:
0 iodef "mailto:me@example.com"

ACHTUNG: Der Code wird nicht auf Fehler geprüft. Etwaige Fehler werden also auch übernommen. Die CAA finalen Einträge könnten daher falsch sein!', ], - 'backupenabled' => [ - 'title' => 'Backup für Kunden aktivieren', - 'description' => 'Wenn dies aktiviert ist, kann der Kunde Sicherungen planen (cron-backup) welche ein Archiv in sein Heimatverzeichnis ablegt (Unterordner vom Kunden wählbar)', + 'exportenabled' => [ + 'title' => 'Daten-Export für Kunden aktivieren', + 'description' => 'Wenn dies aktiviert ist, kann der Kunde Daten-Exporte planen (cron-export) welche ein Archiv in sein Heimatverzeichnis ablegen (Unterordner vom Kunden wählbar)', ], 'dnseditorenable' => [ 'title' => 'DNS Editor aktivieren', @@ -2011,12 +2030,8 @@ Vielen Dank, Ihr Administrator', 'description' => 'Der Inhalt dieses Feldes wird direkt in den IP/Port-vHost-Container übernommen. Die folgenden Variablen können verwendet werden:
{DOMAIN}, {DOCROOT}, {CUSTOMER}, {IP}, {PORT}, {SCHEME}, {FPMSOCKET} (wenn zutreffend)

ACHTUNG: Der Code wird nicht auf Fehler geprüft. Etwaige Fehler werden also auch übernommen. Der Webserver könnte nicht mehr starten!', ], 'includedefault_sslvhostconf' => 'Nicht-SSL vHost-Einstellungen in SSL-vHost inkludieren', - 'apply_specialsettings_default' => [ - 'title' => 'Standardwert für "Übernehme Einstellungen für alle Subdomains (*.beispiel.de)\' Einstellung beim Bearbeiten einer Domain', - ], - 'apply_phpconfigs_default' => [ - 'title' => 'Standardwert für "PHP-Config für alle Subdomains übernehmen:\' Einstellung beim Bearbeiten einer Domain', - ], + 'apply_specialsettings_default' => 'Standardwert für "Übernehme Einstellungen für alle Subdomains (*.beispiel.de)" Einstellung beim Bearbeiten einer Domain', + 'apply_phpconfigs_default' => 'Standardwert für "PHP-Config für alle Subdomains übernehmen:" Einstellung beim Bearbeiten einer Domain', 'awstats' => [ 'logformat' => [ 'title' => 'LogFormat Einstellung', @@ -2081,6 +2096,11 @@ Vielen Dank, Ihr Administrator', 'title' => 'Rate-Limit-Intervall', 'description' => 'Zeit in Sekunden für die maximale Anzahl von HTTP-Anfragen, Standard ist "60".', ], + 'option_requires_otp' => 'Das Ändern dieser Einstellung erfordert OTP Validierung', + 'panel_menu_collapsed' => [ + 'title' => 'Menüabschnitte einklappen', + 'description' => 'Bei Deaktivierung werden die Menübereiche auf der linken Seite immer aufgeklappt angezeigt.', + ], ], 'spf' => [ 'use_spf' => 'Aktiviere SPF für Domains?', @@ -2093,8 +2113,8 @@ Vielen Dank, Ihr Administrator', 'settingssaved' => 'Die Einstellungen wurden erfolgreich gespeichert.', 'rebuildingconfigs' => 'Task für Neuerstellung der Konfigurationen wurde erfolgreich eingetragen', 'domain_import_successfully' => 'Erfolgreich %s Domains importiert.', - 'backupscheduled' => 'Ihre Sicherung wurde erfolgreich geplant. Bitte warten Sie nun, bis diese abgearbeitet wurde.', - 'backupaborted' => 'Die geplante Sicherung wurde abgebrochen', + 'exportscheduled' => 'Ihr Daten-Export wurde erfolgreich geplant. Bitte warten Sie nun, bis dieser bearbeitet wurde.', + 'exportaborted' => 'Der geplante Daten-Export wurde abgebrochen', 'dns_record_added' => 'Eintrag erfolgreich hinzugefügt', 'dns_record_deleted' => 'Eintrag erfolgreich entfernt', 'testmailsent' => 'Test E-Mail erfolgreich gesendet', @@ -2113,7 +2133,7 @@ Vielen Dank, Ihr Administrator', 'DELETE_EMAIL_DATA' => 'E-Mail-Dateien des Kunden löschen', 'DELETE_FTP_DATA' => 'Kunden FTP-Konto Dateien löschen', 'REBUILD_CRON' => 'Neuerstellung der cron.d-Datei', - 'CREATE_CUSTOMER_BACKUP' => 'Datensicherung für Kunde %s', + 'CREATE_CUSTOMER_DATADUMP' => 'Daten-Export für Kunde %s', 'DELETE_DOMAIN_PDNS' => 'Lösche Domain %s von PowerDNS Datenbank', 'DELETE_DOMAIN_SSL' => 'Lösche SSL Dateien von Domain %s', ], @@ -2193,7 +2213,7 @@ Vielen Dank, Ihr Administrator', 'usersettings' => [ 'custom_notes' => [ 'title' => 'Eigene Notizen', - 'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.', + 'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.
Markdown ist unterstützt, HTML wird entfernt.', 'show' => 'Zeige die Notizen auf dem Dashboard des Benutzers', ], 'api_allowed' => [ diff --git a/lng/en.lng.php b/lng/en.lng.php index 1d798bca..07ae6773 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -47,6 +47,8 @@ return [ '2fa_overview_desc' => 'Here you can activate a two-factor authentication for your account.

You can either use an authenticator-app (time-based one-time password / TOTP) or let froxlor send you an email to your account-address after each successful login with a one-time password.', '2fa_email_desc' => 'Your account is set up to use one-time passwords via e-mail. To deactivate, click on "Deactivate 2FA"', '2fa_ga_desc' => 'Your account is set up to use time-based one-time passwords via authenticator-app. Please scan the QR code below with your desired authenticator app to generate the codes. To deactivate, click on "Deactivate 2FA"', + '2fa_not_activated' => 'Two-factor authentication is not enabled', + '2fa_not_activated_for_user' => 'Two-factor authentication is not enabled for the current user', ], 'admin' => [ 'overview' => 'Overview', @@ -501,6 +503,11 @@ return [ 'adminguide' => 'Admin guide', 'userguide' => 'User guide', 'apiguide' => 'API guide', + 'domain_duplicate' => 'Duplicate domain', + 'domain_duplicate_named' => 'Duplicate %s', + 'backups' => [ + 'backups' => 'Backups', + ], ], 'apcuinfo' => [ 'clearcache' => 'Clear APCu cache', @@ -569,7 +576,8 @@ return [ 'cron_usage_report' => 'Web- and traffic-reports', 'cron_mailboxsize' => 'Mailbox-size calculation', 'cron_letsencrypt' => 'Let\'s Encrypt certificate updates', - 'cron_backup' => 'Process backup jobs', + 'cron_export' => 'Process data-export jobs', + 'cron_backup' => 'Process system- and customer backup jobs', ], 'cronjob' => [ 'cronjobsettings' => 'Cronjob settings', @@ -702,11 +710,13 @@ return [ 'RP' => 'Responsible Person record
Structure: mailbox[replace @ with a dot] txt-record-name
Example: team.froxlor.org. froxlor.org.', 'SRV' => 'Service location record, used for newer protocols instead of creating protocol-specific records such as MX.
Structure: priority weight port target
Example: 0 5 5060 sipserver.example.com.
Note: For priority, use field above', 'SSHFP' => 'The SSHFP resource record is used to publish secure shell (SSH) key fingerprints in the DNS.
Structure: algorithm type fingerprint
Algorithms: 0: reserved, 1: RSA, 2: DSA, 3: ECDSA, 4: Ed25519, 6: Ed448
Types: 0: reserved, 1: SHA-1, 2: SHA-256
Example: 2 1 123456789abcdef67890123456789abcdef67890', + 'TLSA' => 'TLSA (TLS Authentication) record is used to publish fingerprint of a TLS/SSL certificate. It is commonly used for DANE.
TLSA records can only be trusted if DNSSEC is enabled on your domain.
Structure: usage selector type fingerprint
Certificate usage: 0: PKIX-T, 1: PKIX-EE, 2: DANE-TA, 3: DANE-EE
Selector: 0: Use full certificate, 1: Use subject public key
Matching type: 0: Full: No Hash, 1: SHA-256 Hash, 2:SHA-512 Hash
Example: 3 1 1 123456789abcdef67890123456789abcdef123456789abcdef123456789abcde', 'TXT' => 'Free definable, descriptive text.' ] ], 'domain' => [ 'openbasedirpath' => 'OpenBasedir-path', + 'inherited' => 'Same as parent-domain', 'docroot' => 'Path from field above', 'homedir' => 'Home directory', 'docparent' => 'Parent-directory of path from field above', @@ -732,9 +742,6 @@ return [ 'aliasdomains' => 'Alias domains', 'redirectifpathisurl' => 'Redirect code (default: empty)', 'redirectifpathisurlinfo' => 'You only need to select one of these if you entered an URL as path
NOTE: Changes are only applied if the given path is an URL.', - 'issubof' => 'This domain is a subdomain of another domain', - 'issubofinfo' => 'You have to set this to the correct domain if you want to add a subdomain as full-domain (e.g. you want to add "www.domain.tld", you have to select "domain.tld" here)', - 'nosubtomaindomain' => 'No subdomain of a full domain', 'ipandport_multi' => [ 'title' => 'IP address(es)', 'description' => 'Specify one or more IP address for the domain.

NOTE: IP addresses cannot be changed when the domain is configured as alias-domain of another domain.
', @@ -767,6 +774,7 @@ return [ 'openbasedirenabled' => 'Openbasedir restiction', 'hsts' => 'HSTS enabled', 'aliasdomainid' => 'ID of alias domain', + 'nodomainsassignedbyadmin' => 'Your account has currently no (active) domains assigned to it. Please contact your administrator if you think this is wrong.', ], 'emails' => [ 'description' => 'Here you can create and change your email addresses.
An account is like your letterbox in front of your house. If someone sends you an email, it will be dropped into the account.

To download your emails use the following settings in your mailprogram: (The data in italics has to be changed to the equivalents you typed in!)
Hostname: domainname
Username: account name / e-mail address
password: the password you\'ve chosen', @@ -837,11 +845,12 @@ return [ 'domainisaliasorothercustomer' => 'The selected alias domain is either itself an alias domain, has a different ip/port combination or belongs to another customer.', 'emailexistalready' => 'The email-address %s already exists.', 'maindomainnonexist' => 'The main-domain %s does not exist.', + 'maindomaindeactivated' => 'The main-domain %s is deactivated.', 'destinationnonexist' => 'Please create your forwarder in the field \'Destination\'.', 'destinationalreadyexistasmail' => 'The forwarder to %s already exists as active email-address.', 'destinationalreadyexist' => 'You have already defined a forwarder to "%s"', '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', + 'dumpfoldercannotbedocroot' => 'The folder for data-dumps cannot be your homedir, please chose a folder within your homedir, e.g. /dumps', '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.', @@ -946,7 +955,8 @@ return [ 'domain_import_error' => 'Following error occurred while importing domains: %s', 'fcgidandphpfpmnogoodtogether' => 'FCGID and PHP-FPM cannot be activated at the same time', 'no_apcuinfo' => 'No cache info available. APCu does not appear to be running.', - 'no_opcacheinfo' => 'No cache info available. OPCache does not appear to be running.', + 'no_opcacheinfo' => 'No OPCache info available. OPCache does not appear to be loaded.', + 'inactive_opcacheinfo' => 'OPCache seems to be installed but not activated.', 'nowildcardwithletsencrypt' => 'Let\'s Encrypt cannot handle wildcard-domains using ACME in froxlor (requires dns-challenge), sorry. Please set the ServerAlias to WWW or disable it completely', 'customized_version' => 'It looks like your Froxlor installation has been modified, no support sorry.', 'autoupdate_0' => 'Unknown error', @@ -961,8 +971,8 @@ return [ 'autoupdate_10' => 'Minimum supported version of PHP is 7.4.0', 'autoupdate_11' => 'Webupdate is disabled', 'mailaccistobedeleted' => 'Another account with the same name (%s) is currently being deleted and can therefore not be added at this moment.', - 'customerhasongoingbackupjob' => 'There is already a backup job waiting to be processed, please be patient.', - 'backupfunctionnotenabled' => 'The backup function is not enabled', + 'customerhasongoingexportjob' => 'There is already a data export job waiting to be processed, please be patient.', + 'exportfunctionnotenabled' => 'The export function is not enabled', 'dns_domain_nodns' => 'DNS is not enabled for this domain', 'dns_content_empty' => 'No content given', 'dns_content_invalid' => 'DNS content invalid', @@ -984,6 +994,7 @@ return [ 'domain_nopunycode' => 'You must not specify punycode (IDNA). The domain will automatically be converted', 'dns_record_toolong' => 'Records/labels can only be up to 63 characters', 'noipportgiven' => 'No IP/port given', + 'nosslippportgiven' => 'When enabling SSL you need to select a SSL IP/port', 'jsonextensionnotfound' => 'This feature requires the php json-extension.', 'cannotdeletesuperadmin' => 'The first admin cannot be deleted.', 'no_wwwcnamae_ifwwwalias' => 'Cannot set CNAME record for "www" as domain is set to generate a www-alias. Please change settings to either "No alias" or "Wildcard alias"', @@ -997,6 +1008,10 @@ return [ 'invalidcronjobintervalvalue' => 'Cronjob interval must be one of: %s', 'phpgdextensionnotavailable' => 'The PHP GD extension is not available. Unable to validate image-data', '2fa_wrongcode' => 'The code entered is not valid', + 'gnupgextensionnotavailable' => 'The PHP GnuPG extension is not available. Unable to validate PGP Public Key', + 'invalidpgppublickey' => 'The PGP Public Key is not valid', + 'invalid_validtime' => 'Valid time in seconds can only be between 10 and 120', + 'customerphpenabledbutnoconfig' => 'Customer has PHP activated but no PHP-configuration was selected.', ], 'extras' => [ 'description' => 'Here you can add some extras, for example directory protection.
The system will need some time to apply the new settings after every change.', @@ -1016,10 +1031,10 @@ return [ 'execute_perl' => 'Execute perl/CGI', 'htpasswdauthname' => 'Authentication reason (AuthName)', 'directoryprotection_edit' => 'Edit directory protection', - 'backup' => 'Create backup', - 'backup_web' => 'Backup web-data', - 'backup_mail' => 'Backup mail-data', - 'backup_dbs' => 'Backup databases', + 'export' => 'Create data dump', + 'dump_web' => 'Include web-data', + 'dump_mail' => 'Include mail-data', + 'dump_dbs' => 'Include databases', 'path_protection_label' => 'Important', 'path_protection_info' => 'We strongly recommend protecting the given path, see "Extras" -> "Directory protection"', ], @@ -1168,7 +1183,7 @@ Yours sincerely, your administrator', 'extras' => 'Extras', 'directoryprotection' => 'Directory protection', 'pathoptions' => 'Path options', - 'backup' => 'Backup', + 'export' => 'Data export', ], 'traffic' => [ 'traffic' => 'Traffic', @@ -1307,10 +1322,15 @@ Yours sincerely, your administrator', 'letsencrypt' => 'Using Let\'s encrypt', 'set' => 'Apply', 'shell' => 'Shell', - 'backuppath' => [ - 'title' => 'Destination path for the backup', - 'description' => 'This is the path where the backups will be stored. If backup of web-data is selected, all files from the homedir are stored excluding the backup-folder specified here.', + 'exportpath' => [ + 'title' => 'Destination path for the exported data', + 'description' => 'This is the path where the export-archive will be stored. If web-data is being included, all files from the homedir are stored excluding the folder specified here.', ], + 'export_pgp_public_key' => [ + 'title' => 'Public PGP key for encryption', + 'description' => 'This is the public PGP key which will be used to encrypt the export. If you leave this field empty, the export will not be encrypted.', + ], + 'pgp_public_key' => 'Public PGP key', 'none_value' => 'None', 'viewlogs' => 'View logfiles', 'not_configured' => 'System not configured yet. Click here to go to configurations.', @@ -1339,6 +1359,8 @@ Yours sincerely, your administrator', 'description' => 'Select the field you want to search in' ], 'upload_import' => 'Upload and import', + 'profile' => 'My profile', + 'use_checkbox_for_unlimited' => 'The value "0" deactivates this resource. The checkbox on the right allows "unlimited" usage.', ], 'phpfpm' => [ 'vhost_httpuser' => 'Local user to use for PHP-FPM (Froxlor vHost)', @@ -1377,7 +1399,7 @@ Yours sincerely, your administrator', 'email_reallydelete_forwarder' => 'Do you really want to delete the forwarder %s?', 'extras_reallydelete' => 'Do you really want to delete the directory protection for %s?', 'extras_reallydelete_pathoptions' => 'Do you really want to delete the path options for %s?', - 'extras_reallydelete_backup' => 'Do you really want to abort the planned backup job?', + 'extras_reallydelete_export' => 'Do you really want to abort the planned export job?', 'ftp_reallydelete' => 'Do you really want to delete the FTP-account %s?', 'mysql_reallydelete' => 'Do you really want to delete the database %s? This cannot be undone!', 'admin_configs_reallyrebuild' => 'Do you really want to rebuild all config files?', @@ -1393,7 +1415,6 @@ Yours sincerely, your administrator', 'admin_quotas_reallyenforce' => 'Do you really want to enforce the default quota to all Users? This cannot be reverted!', 'phpsetting_reallydelete' => 'Do you really want to delete these settings? All domains which use these settings currently will be changed to the default config.', 'fpmsetting_reallydelete' => 'Do you really want to delete these php-fpm settings? All php configurations which use these settings currently will be changed to the default config.', - 'remove_subbutmain_domains' => 'Also remove domains which are added as full domains but which are subdomains of this domain?', 'customer_reallyunlock' => 'Do you really want to unlock customer %s?', 'admin_integritycheck_reallyfix' => 'Do you really want to try fixing all database integrity problems automatically?', 'plan_reallydelete' => 'Do you really want to delete the hosting plan %s?', @@ -2037,9 +2058,9 @@ Yours sincerely, your administrator', 'title' => 'Additional CAA DNS records', 'description' => 'DNS Certification Authority Authorization (CAA) is an Internet security policy mechanism which allows domain name holders to indicate to certificate authorities
whether they are authorized to issue digital certificates for a particular domain name. It does this by means of a new "CAA" Domain Name System (DNS) resource record.

The content of this field will be included into the DNS zone directly (each line results in a CAA record).
If Let\'s Encrypt is enabled for this domain, this entry will always be added automatically and does not need to be added manually:
0 issue "letsencrypt.org" (If domain is a wildcard domain, issuewild will be used instead).
To enable Incident Reporting, you can add an iodef record. An example for sending such report to me@example.com would be:
0 iodef "mailto:me@example.com"

Attention: The code won\'t be checked for any errors. If it contains errors, your CAA records might not work!', ], - 'backupenabled' => [ - 'title' => 'Enable backup for customers', - 'description' => 'If activated, the customer will be able to schedule backup jobs (cron-backup) which generates an archive within his docroot (subdirectory chosable by customer)', + 'exportenabled' => [ + 'title' => 'Enable data export for customers', + 'description' => 'If activated, the customer will be able to schedule data export jobs (cron-export) which generates an archive within his docroot (subdirectory chosable by customer)', ], 'dnseditorenable' => [ 'title' => 'Enable DNS editor', @@ -2131,12 +2152,8 @@ Yours sincerely, your administrator', 'description' => 'The content of this field will be included into this ip/port vHost container directly. You can use the following variables:
{DOMAIN}, {DOCROOT}, {CUSTOMER}, {IP}, {PORT}, {SCHEME}, {FPMSOCKET} (if applicable)
Attention: The code won\'t be checked for any errors. If it contains errors, webserver might not start again!', ], 'includedefault_sslvhostconf' => 'Include non-SSL vHost-settings in SSL-vHost', - 'apply_specialsettings_default' => [ - 'title' => 'Default value for "Apply specialsettings to all subdomains (*.example.com)\' setting when editing a domain', - ], - 'apply_phpconfigs_default' => [ - 'title' => 'Default value for "Apply php-config to all subdomains:\' setting when editing a domain', - ], + 'apply_specialsettings_default' => 'Default value for "Apply specialsettings to all subdomains (*.example.com)" setting when editing a domain', + 'apply_phpconfigs_default' => 'Default value for "Apply php-config to all subdomains" setting when editing a domain', 'awstats' => [ 'logformat' => [ 'title' => 'LogFormat setting', @@ -2195,7 +2212,7 @@ Yours sincerely, your administrator', 'toolselect' => 'Traffic analyzer', 'webalizer' => 'Webalizer', 'awstats' => 'AWStats', - 'goaccess' => 'goacccess' + 'goaccess' => 'goaccess' ], 'requires_reconfiguration' => 'Changing this settings might require a reconfiguration of the following services:
%s', 'req_limit_per_interval' => [ @@ -2206,6 +2223,11 @@ Yours sincerely, your administrator', 'title' => 'Rate-limit interval', 'description' => 'Specify the time in seconds for the number of HTTP requests, default is "60"', ], + 'option_requires_otp' => 'This setting requires an OTP validation', + 'panel_menu_collapsed' => [ + 'title' => 'Collapse menu-sections', + 'description' => 'If deactivated, the left-side menu sections will always be expanded.', + ], ], 'spf' => [ 'use_spf' => 'Activate SPF for domains?', @@ -2224,8 +2246,8 @@ Yours sincerely, your administrator', 'settingssaved' => 'The settings have been successfully saved.', 'rebuildingconfigs' => 'Successfully inserted tasks for rebuild configfiles', 'domain_import_successfully' => 'Successfully imported %s domains.', - 'backupscheduled' => 'Your backup job has been scheduled. Please wait for it to be processed', - 'backupaborted' => 'Your scheduled backup has been cancelled', + 'exportscheduled' => 'Your export job has been scheduled. Please wait for it to be processed', + 'exportaborted' => 'Your scheduled export has been cancelled', 'dns_record_added' => 'Record added successfully', 'dns_record_deleted' => 'Record deleted successfully', 'testmailsent' => 'Test mail sent successfully', @@ -2244,7 +2266,7 @@ Yours sincerely, your administrator', 'DELETE_EMAIL_DATA' => 'Delete customer e-mail data.', 'DELETE_FTP_DATA' => 'Delete customer ftp-account data.', 'REBUILD_CRON' => 'Rebuilding the cron.d-file', - 'CREATE_CUSTOMER_BACKUP' => 'Backup job for customer %s', + 'CREATE_CUSTOMER_DATADUMP' => 'Data export job for customer %s', 'DELETE_DOMAIN_PDNS' => 'Delete domain %s from PowerDNS database', 'DELETE_DOMAIN_SSL' => 'Delete ssl files of domain %s', ], @@ -2325,7 +2347,7 @@ Yours sincerely, your administrator', 'usersettings' => [ 'custom_notes' => [ 'title' => 'Custom notes', - 'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.', + 'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.
Markdown is supported, HTML will be removed.', 'show' => 'Show your notes on the dashboard of the user', ], 'api_allowed' => [ diff --git a/lng/es.lng.php b/lng/es.lng.php index 28eb7454..eb464ccd 100644 --- a/lng/es.lng.php +++ b/lng/es.lng.php @@ -731,9 +731,6 @@ return [ 'aliasdomains' => 'Alias dominios', 'redirectifpathisurl' => 'Código de redirección (por defecto: vacío)', 'redirectifpathisurlinfo' => 'Sólo tiene que seleccionar una de estas opciones si ha introducido una URL como ruta
NOTA: Los cambios sólo se aplican si la ruta indicada es una URL.', - 'issubof' => 'Este dominio es un subdominio de otro dominio', - 'issubofinfo' => 'Si desea añadir un subdominio como dominio completo, deberá establecerlo en el dominio correcto (por ejemplo, si desea añadir "www.domain.tld", deberá seleccionar "dominio.tld").', - 'nosubtomaindomain' => 'No es subdominio de un dominio completo', 'ipandport_multi' => [ 'title' => 'Direcciones IP', 'description' => 'Especifique una o más direcciones IP para el dominio.

NOTA: Las direcciones IP no pueden cambiarse cuando el dominio está configurado como alias-dominio de otro dominio.
' @@ -840,7 +837,6 @@ return [ 'destinationalreadyexistasmail' => 'El remitente a %s ya existe como dirección de correo electrónico activa.', 'destinationalreadyexist' => 'Ya ha definido un reenviador para "%s".', 'destinationiswrong' => 'La %s la redirección contiene caracteres no válidos o está incompleta.', - 'backupfoldercannotbedocroot' => 'La carpeta para las copias de seguridad no puede ser su carpeta de inicio, elija una carpeta dentro de su carpeta de inicio, por ejemplo, /backups.', 'templatelanguagecombodefined' => 'La combinación idioma/plantilla seleccionada ya ha sido definida.', 'templatelanguageinvalid' => 'El idioma seleccionado no existe.', 'ipstillhasdomains' => 'La combinación IP/Puerto que desea eliminar todavía tiene dominios asignados, por favor reasígnelos a otras combinaciones IP/Puerto antes de eliminar esta combinación IP/Puerto.', @@ -960,8 +956,6 @@ return [ 'autoupdate_10' => 'La versión mínima soportada de PHP es 7.4.0', 'autoupdate_11' => 'Webupdate está desactivado', 'mailaccistobedeleted' => 'Otra cuenta con el mismo nombre (%s) está siendo eliminada y por lo tanto no puede ser añadida en este momento.', - 'customerhasongoingbackupjob' => 'Ya hay un trabajo de copia de seguridad esperando a ser procesado, por favor sea paciente.', - 'backupfunctionnotenabled' => 'La función de copia de seguridad no está habilitada', 'dns_domain_nodns' => 'DNS no está habilitado para este dominio', 'dns_content_empty' => 'No hay contenido', 'dns_content_invalid' => 'El contenido DNS no es válido', @@ -1013,10 +1007,6 @@ return [ 'execute_perl' => 'Ejecutar perl/CGI', 'htpasswdauthname' => 'Razón de autenticación (AuthName)', 'directoryprotection_edit' => 'Editar protección de directorio', - 'backup' => 'Crear copia de seguridad', - 'backup_web' => 'Copia de seguridad de datos web', - 'backup_mail' => 'Copia de seguridad de los datos de correo', - 'backup_dbs' => 'Copia de seguridad de bases de datos', 'path_protection_label' => 'Importante', 'path_protection_info' => 'Le recomendamos encarecidamente que proteja la ruta indicada, consulte "Extras" -> "Protección de directorios".' ], @@ -1165,7 +1155,7 @@ Atentamente, su administrador' 'extras' => 'Extras', 'directoryprotection' => 'Protección de directorios', 'pathoptions' => 'Opciones de ruta', - 'backup' => 'Copia de seguridad' + 'export' => 'Exportación de datos' ], 'traffic' => [ 'traffic' => 'Tráfico', @@ -1374,7 +1364,6 @@ Atentamente, su administrador' 'email_reallydelete_forwarder' => '¿Realmente quieres borrar el forwarder %s?', 'extras_reallydelete' => '¿Realmente quieres borrar la protección de directorio de %s?', 'extras_reallydelete_pathoptions' => '¿Realmente quieres borrar las opciones de ruta de %s?', - 'extras_reallydelete_backup' => '¿Realmente quieres abortar el trabajo de copia de seguridad planificado?', 'ftp_reallydelete' => '¿Realmente quieres borrar la cuenta FTP %s?', 'mysql_reallydelete' => '¿Realmente quieres borrar la base de datos %s? Esto no se puede deshacer.', 'admin_configs_reallyrebuild' => '¿Realmente quieres reconstruir todos los archivos de configuración?', @@ -1390,7 +1379,6 @@ Atentamente, su administrador' 'admin_quotas_reallyenforce' => '¿Realmente desea aplicar la cuota por defecto a todos los usuarios? Esto no se puede revertir.', 'phpsetting_reallydelete' => '¿Realmente desea eliminar esta configuración? Todos los dominios que usen esta configuración serán cambiados a la configuración por defecto.', 'fpmsetting_reallydelete' => '¿Realmente desea eliminar esta configuración de php-fpm? Todas las configuraciones de php que utilicen estos ajustes se cambiarán a la configuración por defecto.', - 'remove_subbutmain_domains' => '¿Quitar también los dominios que se añaden como dominios completos pero que son subdominios de este dominio?', 'customer_reallyunlock' => '¿Realmente quieres desbloquear al cliente %s?', 'admin_integritycheck_reallyfix' => '¿Realmente quieres intentar arreglar todos los problemas de integridad de la base de datos automáticamente?', 'plan_reallydelete' => '¿De verdad quieres eliminar el plan de alojamiento %s?', @@ -2034,10 +2022,6 @@ Atentamente, su administrador' 'title' => 'Registros DNS CAA adicionales', 'description' => 'DNS Certification Authority Authorization (CAA) es un mecanismo de política de seguridad en Internet que permite a los titulares de nombres de dominio indicar a las autoridades de certificación
si están autorizadas a emitir certificados digitales para un nombre de dominio concreto. Lo hace mediante un nuevo registro de recursos del Sistema de Nombres de Dominio (DNS) "CAA".

El contenido de este campo se incluirá en la zona DNS directamente (cada línea da lugar a un registro CAA).
Si Let\'s Encrypt está habilitado para este dominio, esta entrada siempre se añadirá automáticamente y no es necesario añadirla manualmente:
0issue "letsencrypt.org" (Si el dominio es un dominio comodín, se utilizará issuewild en su lugar).
Para habilitar el informe de incidentes, puede añadir un registro iodef. Un ejemplo para enviar dicho informe a me@example.com sería:
0iodef "mailto:me@example.com"

Atención: No se comprobará si el código contiene errores. Si contiene errores, ¡es posible que sus registros CAA no funcionen!' ], - 'backupenabled' => [ - 'title' => 'Activar copia de seguridad para clientes', - 'description' => 'Si se activa, el cliente podrá programar trabajos de copia de seguridad (cron-backup) que generan un archivo dentro de su docroot (subdirectorio a elección del cliente)' - ], 'dnseditorenable' => [ 'title' => 'Habilitar editor DNS', 'description' => 'Permite a los administradores y a los clientes gestionar las entradas DNS del dominio' @@ -2192,7 +2176,7 @@ Atentamente, su administrador' 'toolselect' => 'Analizador de tráfico', 'webalizer' => 'Webalizer', 'awstats' => 'AWStats', - 'goaccess' => 'goacccess' + 'goaccess' => 'goaccess' ], 'requires_reconfiguration' => 'El cambio de esta configuración podría requerir una reconfiguración de los siguientes servicios:
%s' ], @@ -2213,8 +2197,6 @@ Atentamente, su administrador' 'settingssaved' => 'La configuración se ha guardado correctamente.', 'rebuildingconfigs' => 'Tareas insertadas con éxito para reconstruir archivos de configuración', 'domain_import_successfully' => 'Se han importado correctamente los dominios %s.', - 'backupscheduled' => 'Se ha programado su tarea de copia de seguridad. Espere a que se procese.', - 'backupaborted' => 'Su copia de seguridad programada ha sido cancelada', 'dns_record_added' => 'Registro añadido correctamente', 'dns_record_deleted' => 'Registro eliminado correctamente', 'testmailsent' => 'Correo de prueba enviado correctamente', @@ -2233,7 +2215,6 @@ Atentamente, su administrador' 'DELETE_EMAIL_DATA' => 'Borrar datos de e-mail del cliente.', 'DELETE_FTP_DATA' => 'Borrar los datos de la cuenta ftp del cliente.', 'REBUILD_CRON' => 'Reconstruir el archivo cron.d', - 'CREATE_CUSTOMER_BACKUP' => 'Trabajo de copia de seguridad para el cliente %s', 'DELETE_DOMAIN_PDNS' => 'Borrar dominio %s de la base de datos PowerDNS', 'DELETE_DOMAIN_SSL' => 'Borrar archivos ssl de dominio %s' ], diff --git a/lng/it.lng.php b/lng/it.lng.php index af5bff91..58610b82 100644 --- a/lng/it.lng.php +++ b/lng/it.lng.php @@ -732,9 +732,6 @@ return [ 'aliasdomains' => 'Alias domini', 'redirectifpathisurl' => 'Codice di redirezione (Predefinito: vuoto)', 'redirectifpathisurlinfo' => 'È necessario selezionare uno di questi se hai inserito un URL come percorso', - 'issubof' => 'Questo dominio è un sottodominio di un altro dominio', - 'issubofinfo' => 'Devi impostare correttamente questo dominio se si desidera aggiungere un sottodominio come dominio completo (es. si vuole aggiungere "www.domain.tld", devi selezionare qui "domain.tld")', - 'nosubtomaindomain' => 'No sottodominio di un dominio completo', 'ipandport_multi' => [ 'title' => 'Indirizzi IP', 'description' => 'Specifica uno o più indirizzi IP per il dominio.

NOTA: L\'indirizzo IP non può essere modificato quando il dominio è configurato come alias-domain di un altro dominio.
', @@ -1196,7 +1193,6 @@ Cordiali Saluti, Team Froxlor', 'admin_quotas_reallywipe' => 'Sei sicuro di voler cancellare tutti i limiti dalla tabella mail_users? Questa operazione non può essere annullata!', 'admin_quotas_reallyenforce' => 'Sei sicuro di voler impostare il limite predefinito a tutti gli utenti? Questa operazione non può essere annullata!', 'phpsetting_reallydelete' => 'Do you really want to delete these settings? All domains which use these settings currently will be changed to the default config.', - 'remove_subbutmain_domains' => 'Rimuover anche i domini che sono stati aggiunti come domini completi, ma quali sono i sottodomini di questo dominio?', 'customer_reallyunlock' => 'Sei sicuro di voler sbloccare il cliente %s?', 'admin_customer_alsoremovemail' => 'Eliminare completamente i dati della posta elettronica dal filesystem??', 'admin_customer_alsoremoveftphomedir' => 'Rimuovere anche la cartella homedir dell\'utente FTP?', diff --git a/lng/nl.lng.php b/lng/nl.lng.php index ad47606f..02548024 100644 --- a/lng/nl.lng.php +++ b/lng/nl.lng.php @@ -400,9 +400,6 @@ return [ 'aliasdomains' => 'Alternatieve domeinnamen', 'redirectifpathisurl' => 'Doorverwijzingscode (standaard: leegt)', 'redirectifpathisurlinfo' => 'U dient deze alleen op te geven indien u een URL als pad hebt opgegeven', - 'issubof' => 'Dit domein is een subdomein van een ander domein', - 'issubofinfo' => 'U dient het correcte domein op te geven indien u een subdomein als volledig domein wilt (bijvoorbeeld als u "www.domain.tld" wilt gebruiken, dan geeft u hier "domain.tld")', - 'nosubtomaindomain' => 'Geen subdomein van volledig domein', ], 'emails' => [ 'description' => 'Hier kunt u e-mail adressen maken en wijzigen.
Een account is net als een brievenbus voor uw huis. Als iemand u mail stuurt wordt dit op uw account bezorgd.

Om uw emails te downloaden moet u het volgende instellen in uw mailprogramma: (De schuingedrukte gegevens moeten gewijzigd worden in wat u ingegeven heeft!)
Servernaam: Domeinnaam
Gebruikersnaam: Account naam / E-mailadres
Wachtwoord: het door u ingegeven wachtwoord', @@ -741,7 +738,6 @@ Met vriendelijke groet, uw beheerder', 'admin_quotas_reallywipe' => 'Weet u zeker dat u alle quota wilt verwijderen? Dit is niet terug te draaien!', 'admin_quotas_reallyenforce' => 'Weet u zeker dat u quota wilt afdwingen? Dit is niet terug te draaien!', 'phpsetting_reallydelete' => 'Weet u zeker dat u deze instellingen wilt verwijderen? Alle domeinen die deze configuratie gebruiken zullen terugvallen op de standaardinstellingen.', - 'remove_subbutmain_domains' => 'Verwijder ook domeinen die als volledige domeinen zijn opgegeven maar een subdomein zijn van dit domein?', 'customer_reallyunlock' => 'Weet u zeker dat u klant %s? wilt ontgrendelen', ], 'serversettings' => [ diff --git a/templates/Froxlor/form/otpquestion.html.twig b/templates/Froxlor/form/otpquestion.html.twig new file mode 100644 index 00000000..06e5bc9a --- /dev/null +++ b/templates/Froxlor/form/otpquestion.html.twig @@ -0,0 +1,31 @@ +{% extends "Froxlor/userarea.html.twig" %} + +{% block content %} + +
+ + + +
+ +{% endblock %} diff --git a/templates/Froxlor/src/js/components/domains.js b/templates/Froxlor/src/js/components/domains.js index fe5611fc..f7bfb4d2 100644 --- a/templates/Froxlor/src/js/components/domains.js +++ b/templates/Froxlor/src/js/components/domains.js @@ -1,4 +1,4 @@ -$(function() { +$(function () { // disable unusable php-configuration by customer settings $('#customerid').on('change', function () { @@ -36,7 +36,7 @@ $(function() { $('#speciallogfile').removeClass('is-invalid'); $('#speciallogverified').val(0); $.ajax({ - url: "admin_domains.php?page=overview&action=jqSpeciallogfileNote", + url: window.location.pathname.substring(1) + "?page=overview&action=jqSpeciallogfileNote", type: "POST", data: { id: $('input[name=id]').val(), newval: +$('#speciallogfile').is(':checked') @@ -84,4 +84,24 @@ $(function() { $('#section_d').show(); } }) + + /** + * ssl enabled domain - hide unnecessary/unused sections + */ + if ($('#id') && !$('#sslenabled').is(':checked')) { + $('#section_bssl>.formfields>.row').not(":first").addClass("d-none"); + } + + /** + * toggle show/hide of sections in case of ssl enabled flag + */ + $('#sslenabled').on('click', function () { + if ($(this).is(':checked')) { + // show sections + $('#section_bssl>.formfields>.row').removeClass("d-none"); + } else { + // hide unnecessary sections + $('#section_bssl>.formfields>.row').not(":first").addClass("d-none"); + } + }) }); diff --git a/templates/Froxlor/src/js/components/search.js b/templates/Froxlor/src/js/components/search.js old mode 100644 new mode 100755 index e551b4ac..be898a41 --- a/templates/Froxlor/src/js/components/search.js +++ b/templates/Froxlor/src/js/components/search.js @@ -42,7 +42,7 @@ $(function() { Object.keys(data).forEach(key => { dropdown.append('
  • ' + key + '
  • '); data[key].forEach(item => { - dropdown.append('
  • ' + item.title + '
  • '); + dropdown.append('
  • ' + item.title + '
  • '); }); }); }, diff --git a/templates/Froxlor/table/table.html.twig b/templates/Froxlor/table/table.html.twig index 939b9b00..32d9dcb7 100644 --- a/templates/Froxlor/table/table.html.twig +++ b/templates/Froxlor/table/table.html.twig @@ -111,7 +111,7 @@ diff --git a/templates/Froxlor/user/change_language.html.twig b/templates/Froxlor/user/change_language.html.twig deleted file mode 100644 index c035f1f9..00000000 --- a/templates/Froxlor/user/change_language.html.twig +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "Froxlor/userarea.html.twig" %} - -{% block content %} -
    -
    -
    -
    -
    -
    {{ lng('menue.main.changelanguage') }}
    - -
    - - -
    -
    - -
    - - - - -
    -
    -
    -
    -
    -{% endblock %} diff --git a/templates/Froxlor/user/change_password.html.twig b/templates/Froxlor/user/change_password.html.twig deleted file mode 100644 index 49d49761..00000000 --- a/templates/Froxlor/user/change_password.html.twig +++ /dev/null @@ -1,57 +0,0 @@ -{% extends "Froxlor/userarea.html.twig" %} - -{% block content %} -
    -
    -
    -
    -
    -
    {{ lng('menue.main.changepassword') }}
    - -
    - - -
    -
    - - -
    -
    - - -
    - - {% if userinfo.adminsession == 0 %} - -
    - -
    - - -
    -
    - -
    - -
    - - -
    -
    - - {% endif %} -
    - -
    - - - - -
    -
    -
    -
    -
    -{% endblock %} diff --git a/templates/Froxlor/user/change_theme.html.twig b/templates/Froxlor/user/change_theme.html.twig deleted file mode 100644 index 12ecae79..00000000 --- a/templates/Froxlor/user/change_theme.html.twig +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "Froxlor/userarea.html.twig" %} - -{% block content %} -
    -
    -
    -
    -
    -
    {{ lng('menue.main.changetheme') }}
    - -
    - - -
    -
    - -
    - - - - -
    -
    -
    -
    -
    -{% endblock %} diff --git a/templates/Froxlor/user/form-datatable.html.twig b/templates/Froxlor/user/form-datatable.html.twig index e04ebd2d..9343c9b9 100644 --- a/templates/Froxlor/user/form-datatable.html.twig +++ b/templates/Froxlor/user/form-datatable.html.twig @@ -4,7 +4,7 @@ {% if tabledata.table.tr is not empty and tabledata.table.tr is iterable %}
    {% set type = 'warning' %} - {% set alert_msg = lng('error.customerhasongoingbackupjob') %} + {% set alert_msg = lng('error.customerhasongoingexportjob') %} {% include 'Froxlor/misc/alertbox.html.twig' %}
    {% import "Froxlor/table/table.html.twig" as table %} diff --git a/templates/Froxlor/user/form.html.twig b/templates/Froxlor/user/form.html.twig index 96019b8d..fce9afe0 100644 --- a/templates/Froxlor/user/form.html.twig +++ b/templates/Froxlor/user/form.html.twig @@ -31,18 +31,14 @@
    {% if actions_links is iterable %} {% for link in actions_links %} - - {{ link.label }} - + {% if link.visible is not defined or (link.visible is defined and link.visible == true) %} + + + {% if link.label is defined and link.label is not empty %}{{ link.label }}{% endif %} + + {% endif %} {% endfor %} {% endif %} - {# TODO: eventually not used anymore because of using a documentation link - {% if entity_info is defined and entity_info is not empty %} - - {% endif %} - #}
    {% endif %} diff --git a/templates/Froxlor/user/index.html.twig b/templates/Froxlor/user/index.html.twig index 0933c4b8..7d40f263 100644 --- a/templates/Froxlor/user/index.html.twig +++ b/templates/Froxlor/user/index.html.twig @@ -41,6 +41,9 @@ {{ dashboard.ditem('customer.emails', userinfo.emails, userinfo.emails_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) }} + {% if get_setting('system.mail_quota_enabled') and userinfo.email_quota != '0' %} + {{ dashboard.ditem('customer.email_quota', userinfo.email_quota_bytes, userinfo.email_quota_bytes_used, null, true) }} + {% endif %} {{ dashboard.ditem('customer.ftps', userinfo.ftps, userinfo.ftps_used) }} {% endif %} @@ -133,12 +136,12 @@ - {% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %} + {% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
    • - {{ userinfo.custom_notes|nl2br|raw }} + {{ userinfo.custom_notes|markdown|raw }}
    @@ -260,10 +263,10 @@
    {% endif %} - {% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %} + {% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
  • - {{ userinfo.custom_notes|nl2br|raw }} + {{ userinfo.custom_notes|markdown|raw }}
  • {% endif %} diff --git a/templates/Froxlor/user/inline-form.html.twig b/templates/Froxlor/user/inline-form.html.twig index 0df2fc4d..7c92faa1 100644 --- a/templates/Froxlor/user/inline-form.html.twig +++ b/templates/Froxlor/user/inline-form.html.twig @@ -1,2 +1,2 @@ {% import "Froxlor/form/form.html.twig" as form %} -{{ form.form(formdata, formaction|default('#'), formdata.title, editid|default(''), true, idprefix|default('')) }} +{{ form.form(formdata, formaction|default('#'), formdata.title, editid|default(''), nosubmit|default(true), idprefix|default('')) }} diff --git a/templates/Froxlor/user/logfiles.html.twig b/templates/Froxlor/user/logfiles.html.twig index f8be277d..226876a6 100644 --- a/templates/Froxlor/user/logfiles.html.twig +++ b/templates/Froxlor/user/logfiles.html.twig @@ -15,9 +15,9 @@
    {% if actions_links is iterable %} {% for link in actions_links %} - + - {{ link.label }} + {% if link.label is defined and link.label is not empty %}{{ link.label }}{% endif %} {% endfor %} {% endif %} diff --git a/templates/Froxlor/user/profile.html.twig b/templates/Froxlor/user/profile.html.twig new file mode 100644 index 00000000..842120eb --- /dev/null +++ b/templates/Froxlor/user/profile.html.twig @@ -0,0 +1,142 @@ +{% extends "Froxlor/userarea.html.twig" %} + +{% block content %} + + +
    +
    + {# change password #} +
    +
    +
    +
    {{ lng('menue.main.changepassword') }}
    + +
    + + +
    +
    + + +
    +
    + + +
    + + {% if userinfo.adminsession == 0 %} + +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + + {% endif %} +
    + +
    + + + + +
    +
    +
    +
    + {% if (get_setting('panel.allow_theme_change_admin') == '1' and userinfo.adminsession == 1) or (get_setting('panel.allow_theme_change_customer') == '1' and userinfo.adminsession == 0) %} +
    + {# change theme #} +
    +
    +
    +
    {{ lng('menue.main.changetheme') }}
    + +
    + + +
    +
    + +
    + + + + +
    +
    +
    +
    + {% endif %} +
    + {# change language #} +
    +
    +
    +
    {{ lng('menue.main.changelanguage') }}
    + +
    + + +
    +
    + +
    + + + + +
    +
    +
    +
    + +
    +{% endblock %} diff --git a/templates/Froxlor/user/table.html.twig b/templates/Froxlor/user/table.html.twig index 5ca36e4f..4568f5ff 100644 --- a/templates/Froxlor/user/table.html.twig +++ b/templates/Froxlor/user/table.html.twig @@ -33,19 +33,14 @@
    {% if actions_links is iterable %} {% for link in actions_links %} - - - {{ link.label }} - + {% if link.visible is not defined or (link.visible is defined and link.visible == true) %} + + + {% if link.label is defined and link.label is not empty %}{{ link.label }}{% endif %} + + {% endif %} {% endfor %} {% endif %} - {# TODO: eventually not used anymore because of using a documentation link - {% if entity_info is defined and entity_info is not empty %} - - {% endif %} - #}
    {% endif %} {% endblock %} diff --git a/templates/Froxlor/userarea.html.twig b/templates/Froxlor/userarea.html.twig index 815df808..ac0bc9b1 100644 --- a/templates/Froxlor/userarea.html.twig +++ b/templates/Froxlor/userarea.html.twig @@ -51,25 +51,13 @@