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/admin_autoupdate.php b/admin_autoupdate.php index c06331f1..dcaedae8 100644 --- a/admin_autoupdate.php +++ b/admin_autoupdate.php @@ -28,7 +28,7 @@ require __DIR__ . '/lib/init.php'; use Froxlor\Froxlor; use Froxlor\FroxlorLogger; -use Froxlor\Http\HttpClient; +use Froxlor\FileDir; use Froxlor\Install\AutoUpdate; use Froxlor\Settings; use Froxlor\UI\Panel\UI; @@ -132,7 +132,7 @@ elseif ($page == 'getdownload') { elseif ($page == 'extract') { if (isset($_POST['send']) && $_POST['send'] == 'send') { $toExtract = isset($_POST['archive']) ? $_POST['archive'] : null; - $localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract; + $localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract); $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Extracting " . $localArchive . " to " . Froxlor::getInstallDir()); $result = AutoUpdate::extractZip($localArchive); if ($result > 0) { @@ -146,7 +146,7 @@ elseif ($page == 'extract') { Response::redirectTo('admin_updates.php'); } else { $toExtract = isset($_GET['archive']) ? $_GET['archive'] : null; - $localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract; + $localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract); } if (!file_exists($localArchive)) { diff --git a/admin_domains.php b/admin_domains.php index 59755968..408e64bc 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -114,15 +114,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 +248,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 +268,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 +450,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 +516,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 +636,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' => $_POST['domain'] ?? "" + ]); + } else { + Response::redirectTo($filename, [ + 'page' => 'overview' + ]); + } } } elseif ($page == 'domainssleditor') { require_once __DIR__ . '/ssl_editor.php'; diff --git a/customer_domains.php b/customer_domains.php index 933edd13..2fba8e38 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -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'); @@ -73,10 +73,17 @@ if ($page == 'overview' || $page == 'domains') { ]; } - UI::view('user/table.html.twig', [ + $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 +137,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 +147,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 diff --git a/customer_email.php b/customer_email.php index a9944734..ca311caa 100644 --- a/customer_email.php +++ b/customer_email.php @@ -84,7 +84,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) { diff --git a/index.php b/index.php index d59146e3..2bdd947c 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,10 +38,10 @@ 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; -use Froxlor\Language; if ($action == '') { $action = 'login'; @@ -161,7 +162,7 @@ if ($action == '2fa_entercode') { ]); exit(); } elseif ($action == 'login') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { + if (!empty($_POST)) { $loginname = Validate::validate($_POST['loginname'], 'loginname'); $password = Validate::validate($_POST['password'], 'password'); @@ -431,13 +432,13 @@ if ($action == '2fa_entercode') { } $lastqrystr = ""; if (isset($_REQUEST['qrystr']) && $_REQUEST['qrystr'] != "") { - $lastqrystr = htmlspecialchars($_REQUEST['qrystr'], ENT_QUOTES); + $lastqrystr = urlencode($_REQUEST['qrystr']); } + $_SESSION['lastscript'] = $lastscript; + $_SESSION['lastqrystr'] = $lastqrystr; UI::view('login/login.html.twig', [ 'pagetitle' => 'Login', - 'lastscript' => $lastscript, - 'lastqrystr' => $lastqrystr, 'upd_in_progress' => $update_in_progress, 'message' => $message, 'successmsg' => $successmessage @@ -449,7 +450,7 @@ if ($action == 'forgotpwd') { $adminchecked = false; $message = ''; - if (isset($_POST['send']) && $_POST['send'] == 'send') { + if (!empty($_POST)) { $loginname = Validate::validate($_POST['loginname'], 'loginname'); $email = Validate::validateEmail($_POST['loginemail']); $result_stmt = Database::prepare("SELECT `adminid`, `customerid`, `customernumber`, `firstname`, `name`, `company`, `email`, `loginname`, `def_language`, `deactivated` FROM `" . TABLE_PANEL_CUSTOMERS . "` @@ -633,7 +634,7 @@ if ($action == 'forgotpwd') { UI::view('login/fpwd.html.twig', [ 'pagetitle' => lng('login.presend'), - 'action' => $action, + 'formaction' => 'index.php?action='.$action, 'message' => $message, ]); } @@ -656,7 +657,7 @@ if ($action == 'resetpwd') { $check = substr($activationcode, 40, 10); if (substr(md5($third . $timestamp), 0, 10) == $check && $timestamp >= time() - 86400) { - if (isset($_POST['send']) && $_POST['send'] == 'send') { + if (!empty($_POST)) { $stmt = Database::prepare("SELECT `userid`, `admin` FROM `" . TABLE_PANEL_ACTIVATION . "` WHERE `activationcode` = :activationcode"); $result = Database::pexecute_first($stmt, [ @@ -730,6 +731,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'] != '') { @@ -746,29 +799,34 @@ function finishLogin($userinfo) } $qryparams = []; - if (isset($_POST['qrystr']) && $_POST['qrystr'] != "") { - parse_str(urldecode($_POST['qrystr']), $qryparams); + if (isset($_SESSION['lastqrystr']) && !empty($_SESSION['lastqrystr'])) { + parse_str(urldecode($_SESSION['lastqrystr']), $qryparams); + unset($_SESSION['lastqrystr']); } if ($userinfo['adminsession'] == '1') { if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) { Response::redirectTo('admin_updates.php?page=overview'); } else { - if (isset($_POST['script']) && $_POST['script'] != "") { - if (preg_match("/customer\_/", $_POST['script']) === 1) { + if (isset($_SESSION['lastscript']) && !empty($_SESSION['lastscript'])) { + $lastscript = $_SESSION['lastscript']; + unset($_SESSION['lastscript']); + if (preg_match("/customer\_/", $lastscript) === 1) { Response::redirectTo('admin_customers.php', [ "page" => "customers" ]); } else { - Response::redirectTo($_POST['script'], $qryparams); + Response::redirectTo($lastscript, $qryparams); } } else { Response::redirectTo('admin_index.php', $qryparams); } } } else { - if (isset($_POST['script']) && $_POST['script'] != "") { - Response::redirectTo($_POST['script'], $qryparams); + if (isset($_SESSION['lastscript']) && !empty($_SESSION['lastscript'])) { + $lastscript = $_SESSION['lastscript']; + unset($_SESSION['lastscript']); + Response::redirectTo($lastscript, $qryparams); } else { Response::redirectTo('customer_index.php', $qryparams); } diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index 62c626f2..0e06cbe3 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', ''), @@ -697,7 +696,7 @@ opcache.validate_timestamps'), ('system', 'distribution', ''), ('system', 'update_channel', 'stable'), ('system', 'updatecheck_data', ''), - ('system', 'update_notify_last', '2.0.19'), + ('system', 'update_notify_last', '2.0.20'), ('system', 'traffictool', 'goaccess'), ('system', 'req_limit_per_interval', 60), ('system', 'req_limit_interval', 60), @@ -744,8 +743,8 @@ opcache.validate_timestamps'), ('panel', 'logo_overridetheme', '0'), ('panel', 'logo_overridecustom', '0'), ('panel', 'settings_mode', '0'), - ('panel', 'version', '2.0.19'), - ('panel', 'db_version', '202304260'); + ('panel', 'version', '2.0.20'), + ('panel', 'db_version', '202305240'); DROP TABLE IF EXISTS `panel_tasks`; @@ -1052,4 +1051,13 @@ 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_2.x.inc.php b/install/updates/froxlor/update_2.x.inc.php index 228fdac1..83906fb0 100644 --- a/install/updates/froxlor/update_2.x.inc.php +++ b/install/updates/froxlor/update_2.x.inc.php @@ -492,3 +492,37 @@ if (Froxlor::isFroxlorVersion('2.0.18')) { Update::showUpdateStep("Updating from 2.0.18 to 2.0.19", false); Froxlor::updateToVersion('2.0.19'); } + +if (Froxlor::isFroxlorVersion('2.0.19')) { + Update::showUpdateStep("Updating from 2.0.19 to 2.0.20", false); + Froxlor::updateToVersion('2.0.20'); +} + +if (Froxlor::isDatabaseVersion('202304260')) { + 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("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'); + } + + Froxlor::updateToDbVersion('202305240'); +} diff --git a/lib/Froxlor/Api/ApiCommand.php b/lib/Froxlor/Api/ApiCommand.php index 210d28ac..327a1b4d 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) { diff --git a/lib/Froxlor/Api/Commands/Admins.php b/lib/Froxlor/Api/Commands/Admins.php index ae7d12c3..10175ddf 100644 --- a/lib/Froxlor/Api/Commands/Admins.php +++ b/lib/Froxlor/Api/Commands/Admins.php @@ -95,7 +95,7 @@ class Admins extends ApiCommand implements ResourceEntity public function listing() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list admins"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list admins"); $query_fields = []; $result_stmt = Database::prepare(" SELECT * @@ -407,7 +407,7 @@ class Admins extends ApiCommand implements ResourceEntity ]; $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get admin '" . $result['loginname'] . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get admin '" . $result['loginname'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'"); @@ -705,7 +705,7 @@ class Admins extends ApiCommand implements ResourceEntity WHERE `adminid` = :adminid "); Database::pexecute($upd_stmt, $upd_data, true, true); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] edited admin '" . $result['loginname'] . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] edited admin '" . $result['loginname'] . "'"); // get all admin-data for return-array $result = $this->apiCall('Admins.get', [ diff --git a/lib/Froxlor/Api/Commands/Certificates.php b/lib/Froxlor/Api/Commands/Certificates.php index 3ad44633..df0e7619 100644 --- a/lib/Froxlor/Api/Commands/Certificates.php +++ b/lib/Froxlor/Api/Commands/Certificates.php @@ -97,7 +97,7 @@ class Certificates extends ApiCommand implements ResourceEntity } if (!$has_cert) { $this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, true); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added ssl-certificate for '" . $domain['domain'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ssl-certificate for '" . $domain['domain'] . "'"); $result = $this->apiCall('Certificates.get', [ 'id' => $domain['id'] ]); @@ -248,7 +248,7 @@ class Certificates extends ApiCommand implements ResourceEntity $ssl_ca_file = $this->getParam('ssl_ca_file', true, ''); $ssl_cert_chainfile = $this->getParam('ssl_cert_chainfile', true, ''); $this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, false); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ssl-certificate for '" . $domain['domain'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ssl-certificate for '" . $domain['domain'] . "'"); $result = $this->apiCall('Certificates.get', [ 'id' => $domain['id'] ]); @@ -470,7 +470,7 @@ class Certificates extends ApiCommand implements ResourceEntity if ($chk['letsencrypt'] == '1') { Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $chk['domain']); } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] removed ssl-certificate for '" . $chk['domain'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] removed ssl-certificate for '" . $chk['domain'] . "'"); return $this->response($result); } throw new Exception("Unable to determine SSL certificate. Maybe no access?", 406); diff --git a/lib/Froxlor/Api/Commands/Cronjobs.php b/lib/Froxlor/Api/Commands/Cronjobs.php index d9678763..73d0c933 100644 --- a/lib/Froxlor/Api/Commands/Cronjobs.php +++ b/lib/Froxlor/Api/Commands/Cronjobs.php @@ -147,7 +147,7 @@ class Cronjobs extends ApiCommand implements ResourceEntity // insert task to re-generate the cron.d-file Cronjob::inserttask(TaskId::REBUILD_CRON); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] cronjob with description '" . $result['module'] . '/' . $result['cronfile'] . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] cronjob with description '" . $result['module'] . '/' . $result['cronfile'] . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); $result = $this->apiCall('Cronjobs.get', [ 'id' => $id ]); @@ -177,7 +177,7 @@ class Cronjobs extends ApiCommand implements ResourceEntity public function listing() { if ($this->isAdmin()) { - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list cronjobs"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list cronjobs"); $query_fields = []; $result_stmt = Database::prepare(" SELECT `c`.* FROM `" . TABLE_PANEL_CRONRUNS . "` `c` " . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()); diff --git a/lib/Froxlor/Api/Commands/CustomerBackups.php b/lib/Froxlor/Api/Commands/CustomerBackups.php index 412bc0b6..83360684 100644 --- a/lib/Froxlor/Api/Commands/CustomerBackups.php +++ b/lib/Froxlor/Api/Commands/CustomerBackups.php @@ -212,7 +212,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity $result[] = $entry; } } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list customer-backups"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer-backups"); return $this->response([ 'count' => count($result), 'list' => $result diff --git a/lib/Froxlor/Api/Commands/Customers.php b/lib/Froxlor/Api/Commands/Customers.php index e4c23e6c..b6ac3c89 100644 --- a/lib/Froxlor/Api/Commands/Customers.php +++ b/lib/Froxlor/Api/Commands/Customers.php @@ -895,7 +895,7 @@ class Customers extends ApiCommand implements ResourceEntity $result['dbspace_used'] = 0; } } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get customer '" . $result['loginname'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get customer '" . $result['loginname'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'"); @@ -1327,7 +1327,7 @@ class Customers extends ApiCommand implements ResourceEntity 'vu' => $valid_until ], true, true); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'"); Cronjob::inserttask(TaskId::REBUILD_VHOST); } @@ -1538,7 +1538,7 @@ class Customers extends ApiCommand implements ResourceEntity Database::query($admin_update_query); } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited user '" . $result['loginname'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited user '" . $result['loginname'] . "'"); /* * move customer to another admin/reseller; #1166 @@ -1911,7 +1911,7 @@ class Customers extends ApiCommand implements ResourceEntity // now, recalculate the resource-usage for the old and the new admin User::updateCounters(false); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'"); $result = $this->apiCall('Customers.get', [ 'id' => $c_result['customerid'] diff --git a/lib/Froxlor/Api/Commands/DirOptions.php b/lib/Froxlor/Api/Commands/DirOptions.php index 96a89265..f55ef889 100644 --- a/lib/Froxlor/Api/Commands/DirOptions.php +++ b/lib/Froxlor/Api/Commands/DirOptions.php @@ -144,7 +144,7 @@ class DirOptions extends ApiCommand implements ResourceEntity ]; Database::pexecute($stmt, $params, true, true); $id = Database::lastInsertId(); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added directory-option for '" . $userpath . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added directory-option for '" . $userpath . "'"); Cronjob::inserttask(TaskId::REBUILD_VHOST); $result = $this->apiCall('DirOptions.get', [ @@ -247,7 +247,7 @@ class DirOptions extends ApiCommand implements ResourceEntity $params['id'] = $id; $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get directory options for '" . $result['path'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get directory options for '" . $result['path'] . "'"); return $this->response($result); } $key = "id #" . $id; @@ -331,7 +331,7 @@ class DirOptions extends ApiCommand implements ResourceEntity "id" => $id ]; Database::pexecute($stmt, $params, true, true); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited directory options for '" . str_replace($customer['documentroot'], '/', $result['path']) . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited directory options for '" . str_replace($customer['documentroot'], '/', $result['path']) . "'"); } $result = $this->apiCall('DirOptions.get', [ @@ -379,7 +379,7 @@ class DirOptions extends ApiCommand implements ResourceEntity while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $result[] = $row; } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list directory-options"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list directory-options"); return $this->response([ 'count' => count($result), 'list' => $result @@ -478,7 +478,7 @@ class DirOptions extends ApiCommand implements ResourceEntity "customerid" => $customer_data['customerid'], "id" => $id ], true, true); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted directory-option for '" . str_replace($customer_data['documentroot'], '/', $result['path']) . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted directory-option for '" . str_replace($customer_data['documentroot'], '/', $result['path']) . "'"); Cronjob::inserttask(TaskId::REBUILD_VHOST); return $this->response($result); } diff --git a/lib/Froxlor/Api/Commands/DirProtections.php b/lib/Froxlor/Api/Commands/DirProtections.php index 690adf8b..b21bdd6e 100644 --- a/lib/Froxlor/Api/Commands/DirProtections.php +++ b/lib/Froxlor/Api/Commands/DirProtections.php @@ -129,7 +129,7 @@ class DirProtections extends ApiCommand implements ResourceEntity ]; Database::pexecute($stmt, $params, true, true); $id = Database::lastInsertId(); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added directory-protection for '" . $username . " (" . $path . ")'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added directory-protection for '" . $username . " (" . $path . ")'"); Cronjob::inserttask(TaskId::REBUILD_VHOST); $result = $this->apiCall('DirProtections.get', [ @@ -196,7 +196,7 @@ class DirProtections extends ApiCommand implements ResourceEntity $params['idun'] = ($id <= 0 ? $username : $id); $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get directory protection for '" . $result['path'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get directory protection for '" . $result['path'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "username '" . $username . "'"); @@ -279,7 +279,7 @@ class DirProtections extends ApiCommand implements ResourceEntity Cronjob::inserttask(TaskId::REBUILD_VHOST); } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated directory-protection '" . $result['username'] . " (" . $result['path'] . ")'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated directory-protection '" . $result['username'] . " (" . $result['path'] . ")'"); $result = $this->apiCall('DirProtections.get', [ 'id' => $result['id'] ]); @@ -325,7 +325,7 @@ class DirProtections extends ApiCommand implements ResourceEntity while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $result[] = $row; } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list directory-protections"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list directory-protections"); return $this->response([ 'count' => count($result), 'list' => $result @@ -413,7 +413,7 @@ class DirProtections extends ApiCommand implements ResourceEntity "id" => $id ]); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'"); Cronjob::inserttask(TaskId::REBUILD_VHOST); return $this->response($result); } diff --git a/lib/Froxlor/Api/Commands/DomainZones.php b/lib/Froxlor/Api/Commands/DomainZones.php index e8e2163e..3975fd4a 100644 --- a/lib/Froxlor/Api/Commands/DomainZones.php +++ b/lib/Froxlor/Api/Commands/DomainZones.php @@ -413,7 +413,7 @@ class DomainZones extends ApiCommand implements ResourceEntity $zone = Dns::createDomainZone($id); $zonefile = (string)$zone; - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get dns-zone for '" . $result['domain'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get dns-zone for '" . $result['domain'] . "'"); return $this->response(explode("\n", $zonefile)); } diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php index ea318e97..e916c072 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); @@ -665,10 +661,6 @@ class Domains extends ApiCommand implements ResourceEntity $serveraliasoption = '0'; } - if ($issubof <= 0) { - $issubof = '0'; - } - $idna_convert = new IdnaWrapper(); if ($domain == '') { Response::standardError([ @@ -723,7 +715,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 +768,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, @@ -898,7 +888,7 @@ class Domains extends ApiCommand implements ResourceEntity $result['ipsandports'] = $this->getIpsForDomain($result['id']); } $result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get domain '" . $result['domain'] . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get domain '" . $result['domain'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'"); @@ -1069,9 +1059,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 +1076,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 +1088,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 +1117,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 +1125,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 +1137,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 +1180,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']); @@ -1246,6 +1234,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(" @@ -1640,10 +1629,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 +1651,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'] @@ -1801,7 +1785,7 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($upd_stmt, [ 'id' => $id ], true, true); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] removed specialsettings on all subdomains of domain #" . $id); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] removed specialsettings on all subdomains of domain #" . $id); } $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; @@ -1837,7 +1821,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 +1835,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 +1869,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 +1882,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 +1929,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 +1962,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 +2083,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 +2098,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 +2105,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 +2130,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); @@ -2221,7 +2209,7 @@ class Domains extends ApiCommand implements ResourceEntity // remove domain from acme.sh / lets encrypt if used Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $result['domain']); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] deleted domain/subdomains (#" . $result['id'] . ")"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] deleted domain/subdomains (#" . $result['id'] . ")"); User::updateCounters(); Cronjob::inserttask(TaskId::REBUILD_VHOST); // Using nameserver, insert a task which rebuilds the server config @@ -2230,4 +2218,114 @@ 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']); + + $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 f341c880..e560fd0d 100644 --- a/lib/Froxlor/Api/Commands/EmailAccounts.php +++ b/lib/Froxlor/Api/Commands/EmailAccounts.php @@ -95,9 +95,18 @@ 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(); + $emailaddr = $idna_convert->encode($emailaddr); + } // get email address $result = $this->apiCall('Emails.get', [ @@ -306,7 +315,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity } } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email account for '" . $result['email_full'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email account for '" . $result['email_full'] . "'"); $result = $this->apiCall('Emails.get', [ 'emailaddr' => $result['email_full'] ]); @@ -357,6 +366,11 @@ class EmailAccounts extends ApiCommand implements ResourceEntity $ea_optional = $id > 0; $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + if (!empty($emailaddr)) { + $idna_convert = new IdnaWrapper(); + $emailaddr = $idna_convert->encode($emailaddr); + } + // validation $result = $this->apiCall('Emails.get', [ 'id' => $id, @@ -450,7 +464,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity Admins::increaseUsage($customer['adminid'], 'email_quota_used', '', ($quota - $result['quota'])); } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated email account '" . $result['email_full'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated email account '" . $result['email_full'] . "'"); $result = $this->apiCall('Emails.get', [ 'emailaddr' => $result['email_full'] ]); @@ -556,7 +570,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity Customers::decreaseUsage($customer['customerid'], 'email_accounts_used'); Customers::decreaseUsage($customer['customerid'], 'email_quota_used', '', $quota); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email account for '" . $result['email_full'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted email account for '" . $result['email_full'] . "'"); return $this->response($result); } } diff --git a/lib/Froxlor/Api/Commands/EmailDomains.php b/lib/Froxlor/Api/Commands/EmailDomains.php index 0e7f0b37..8fadf3a0 100644 --- a/lib/Froxlor/Api/Commands/EmailDomains.php +++ b/lib/Froxlor/Api/Commands/EmailDomains.php @@ -89,7 +89,7 @@ class EmailDomains extends ApiCommand implements ResourceEntity while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $result[] = $row; } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list email-domains"); return $this->response([ 'count' => count($result), diff --git a/lib/Froxlor/Api/Commands/EmailForwarders.php b/lib/Froxlor/Api/Commands/EmailForwarders.php index 520fd05c..79c178b0 100644 --- a/lib/Froxlor/Api/Commands/EmailForwarders.php +++ b/lib/Froxlor/Api/Commands/EmailForwarders.php @@ -77,6 +77,11 @@ class EmailForwarders extends ApiCommand implements ResourceEntity $idna_convert = new IdnaWrapper(); $destination = $idna_convert->encode($destination); + if (!empty($emailaddr)) { + $idna_convert = new IdnaWrapper(); + $emailaddr = $idna_convert->encode($emailaddr); + } + $result = $this->apiCall('Emails.get', [ 'id' => $id, 'emailaddr' => $emailaddr @@ -116,7 +121,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity // update customer usage Customers::increaseUsage($customer['customerid'], 'email_forwarders_used'); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email forwarder for '" . $result['email_full'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email forwarder for '" . $result['email_full'] . "'"); $result = $this->apiCall('Emails.get', [ 'emailaddr' => $result['email_full'] @@ -293,7 +298,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity // update customer usage Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used'); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email forwarder for '" . $result['email_full'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted email forwarder for '" . $result['email_full'] . "'"); $result = $this->apiCall('Emails.get', [ 'emailaddr' => $result['email_full'] diff --git a/lib/Froxlor/Api/Commands/Emails.php b/lib/Froxlor/Api/Commands/Emails.php index 3ef990c6..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; @@ -159,7 +162,7 @@ class Emails extends ApiCommand implements ResourceEntity // update customer usage Customers::increaseUsage($customer['customerid'], 'emails_used'); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added email address '" . $email_full . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added email address '" . $email_full . "'"); $result = $this->apiCall('Emails.get', [ 'emailaddr' => $email_full @@ -199,7 +202,7 @@ class Emails extends ApiCommand implements ResourceEntity ); $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get email address '" . $result['email_full'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get email address '" . $result['email_full'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "emailaddr '" . $emailaddr . "'"); @@ -294,7 +297,7 @@ class Emails extends ApiCommand implements ResourceEntity "id" => $id ]; Database::pexecute($stmt, $params, true, true); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'"); $result = $this->apiCall('Emails.get', [ 'emailaddr' => $result['email_full'] @@ -340,7 +343,7 @@ class Emails extends ApiCommand implements ResourceEntity while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $result[] = $row; } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list email-addresses"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list email-addresses"); return $this->response([ 'count' => count($result), 'list' => $result @@ -445,7 +448,7 @@ class Emails extends ApiCommand implements ResourceEntity ], true, true); Customers::decreaseUsage($customer['customerid'], 'emails_used'); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] deleted email address '" . $result['email_full'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] deleted email address '" . $result['email_full'] . "'"); return $this->response($result); } } diff --git a/lib/Froxlor/Api/Commands/FpmDaemons.php b/lib/Froxlor/Api/Commands/FpmDaemons.php index a0577ad4..26d4d493 100644 --- a/lib/Froxlor/Api/Commands/FpmDaemons.php +++ b/lib/Froxlor/Api/Commands/FpmDaemons.php @@ -64,7 +64,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity public function listing() { if ($this->isAdmin()) { - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list fpm-daemons"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list fpm-daemons"); $query_fields = []; $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "`" . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()); @@ -258,7 +258,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity $id = Database::lastInsertId(); Cronjob::inserttask(TaskId::REBUILD_VHOST); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] fpm-daemon with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); $result = $this->apiCall('FpmDaemons.get', [ 'id' => $id ]); @@ -384,7 +384,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity Database::pexecute($upd_stmt, $upd_data, true, true); Cronjob::inserttask(TaskId::REBUILD_VHOST); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] fpm-daemon with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); $result = $this->apiCall('FpmDaemons.get', [ 'id' => $id ]); @@ -433,7 +433,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity ], true, true); Cronjob::inserttask(TaskId::REBUILD_VHOST); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] fpm-daemon setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] fpm-daemon setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'"); return $this->response($result); } throw new Exception("Not allowed to execute given command.", 403); diff --git a/lib/Froxlor/Api/Commands/Froxlor.php b/lib/Froxlor/Api/Commands/Froxlor.php index 690b5880..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; @@ -72,7 +73,7 @@ class Froxlor extends ApiCommand if (empty($uc_data) || empty($response) || $uc_data['ts'] + self::UPDATE_CHECK_INTERVAL < time() || $uc_data['channel'] != Settings::Get('system.update_channel') || $force_ucheck) { // log our actions - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] checking for updates"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] checking for updates"); // check for new version $aucheck = AutoUpdate::checkVersion(); @@ -142,7 +143,7 @@ class Froxlor extends ApiCommand { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { $json_str = $this->getParam('json_str'); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "User " . $this->getUserDetail('loginname') . " imported settings"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "User " . $this->getUserDetail('loginname') . " imported settings"); try { SImExporter::import($json_str); Cronjob::inserttask(TaskId::REBUILD_VHOST); @@ -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 76826087..41dc8cb9 100644 --- a/lib/Froxlor/Api/Commands/Ftps.php +++ b/lib/Froxlor/Api/Commands/Ftps.php @@ -257,7 +257,7 @@ class Ftps extends ApiCommand implements ResourceEntity Customers::increaseUsage($customer['customerid'], 'ftp_lastaccountnumber'); } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] added ftp-account '" . $username . " (" . $path . ")'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ftp-account '" . $username . " (" . $path . ")'"); Cronjob::inserttask(TaskId::CREATE_FTP); if ($sendinfomail == 1) { @@ -302,7 +302,7 @@ class Ftps extends ApiCommand implements ResourceEntity $this->mailer()->clearAddresses(); } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] added ftp-user '" . $username . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added ftp-user '" . $username . "'"); $result = $this->apiCall('Ftps.get', [ 'username' => $username @@ -367,7 +367,7 @@ class Ftps extends ApiCommand implements ResourceEntity $params['idun'] = ($id <= 0 ? $username : $id); $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get ftp-user '" . $result['username'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get ftp-user '" . $result['username'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "username '" . $username . "'"); @@ -453,7 +453,7 @@ class Ftps extends ApiCommand implements ResourceEntity "id" => $id, "password" => $cryptPassword ], true, true); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ftp-account password for '" . $result['username'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ftp-account password for '" . $result['username'] . "'"); } // path update? @@ -471,7 +471,7 @@ class Ftps extends ApiCommand implements ResourceEntity "customerid" => $customer['customerid'], "id" => $id ], true, true); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] updated ftp-account homdir for '" . $result['username'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated ftp-account homdir for '" . $result['username'] . "'"); } } // it's the task for "new ftp" but that will @@ -533,7 +533,7 @@ class Ftps extends ApiCommand implements ResourceEntity while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $result[] = $row; } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list ftp-users"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list ftp-users"); return $this->response([ 'count' => count($result), 'list' => $result diff --git a/lib/Froxlor/Api/Commands/HostingPlans.php b/lib/Froxlor/Api/Commands/HostingPlans.php index 7fde4652..971307b5 100644 --- a/lib/Froxlor/Api/Commands/HostingPlans.php +++ b/lib/Froxlor/Api/Commands/HostingPlans.php @@ -61,7 +61,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity public function listing() { if ($this->isAdmin()) { - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list hosting-plans"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list hosting-plans"); $query_fields = []; $result_stmt = Database::prepare(" SELECT p.*, a.loginname as adminname @@ -227,7 +227,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity 'valuearr' => json_encode($value_arr) ]; Database::pexecute($ins_stmt, $ins_data, true, true); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] added hosting-plan '" . $name . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] added hosting-plan '" . $name . "'"); $result = $this->apiCall('HostingPlans.get', [ 'planname' => $name ]); @@ -264,7 +264,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity } $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get hosting-plan '" . $result['name'] . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get hosting-plan '" . $result['name'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "planname '" . $planname . "'"); @@ -414,7 +414,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity 'id' => $id ]; Database::pexecute($upd_stmt, $update_data, true, true); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] updated hosting-plan '" . $result['name'] . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] updated hosting-plan '" . $result['name'] . "'"); return $this->response($update_data); } throw new Exception("Not allowed to execute given command.", 403); diff --git a/lib/Froxlor/Api/Commands/IpsAndPorts.php b/lib/Froxlor/Api/Commands/IpsAndPorts.php index d3dfe6be..0aac2182 100644 --- a/lib/Froxlor/Api/Commands/IpsAndPorts.php +++ b/lib/Froxlor/Api/Commands/IpsAndPorts.php @@ -65,7 +65,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity public function listing() { if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || !empty($this->getUserDetail('ip')))) { - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list ips and ports"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list ips and ports"); $ip_where = ""; $append_where = false; if (!empty($this->getUserDetail('ip')) && $this->getUserDetail('ip') != -1) { @@ -335,7 +335,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity 'id' => $id ], true, true); if ($result) { - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] get ip " . $result['ip'] . " " . $result['port']); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get ip " . $result['ip'] . " " . $result['port']); return $this->response($result); } throw new Exception("IP/port with id #" . $id . " could not be found", 404); diff --git a/lib/Froxlor/Api/Commands/MysqlServer.php b/lib/Froxlor/Api/Commands/MysqlServer.php index c1fbbea2..a3f6a830 100644 --- a/lib/Froxlor/Api/Commands/MysqlServer.php +++ b/lib/Froxlor/Api/Commands/MysqlServer.php @@ -26,14 +26,15 @@ namespace Froxlor\Api\Commands; use Exception; -use PDO; -use PDOException; -use Froxlor\Froxlor; -use Froxlor\PhpHelper; use Froxlor\Api\ApiCommand; use Froxlor\Api\ResourceEntity; use Froxlor\Database\Database; +use Froxlor\Froxlor; +use Froxlor\FroxlorLogger; +use Froxlor\PhpHelper; use Froxlor\Validate\Validate; +use PDO; +use PDOException; class MysqlServer extends ApiCommand implements ResourceEntity { @@ -73,8 +74,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity * optional, test connection with given credentials, default is true (yes) * * @access admin - * @throws Exception * @return string json-encoded array + * @throws Exception */ public function add() { @@ -112,7 +113,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity ); if (!empty($mysql_ca)) { $options[PDO::MYSQL_ATTR_SSL_CA] = $mysql_ca; - $options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (bool) $mysql_verifycert; + $options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (bool)$mysql_verifycert; } $dsn = "mysql:host=" . $mysql_host . ";port=" . $mysql_port . ";"; @@ -167,6 +168,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity $this->addDatabaseFromCustomerAllowedList($newdbserver); } + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] added new database server '" . $description . "' (" . $mysql_host . ")"); + return $this->response(['dbserver' => $newdbserver]); } @@ -179,16 +182,16 @@ class MysqlServer extends ApiCommand implements ResourceEntity * optional the number of the mysql server (either id or dbserver must be set) * * @access admin - * @throws Exception * @return string json-encoded array + * @throws Exception */ public function delete() { $this->validateAccess(); - $id = (int) $this->getParam('id', true, -1); + $id = (int)$this->getParam('id', true, -1); $dn_optional = $id >= 0; - $dbserver = (int) $this->getParam('dbserver', $dn_optional, -1); + $dbserver = (int)$this->getParam('dbserver', $dn_optional, -1); $dbserver = $id >= 0 ? $id : $dbserver; if ($dbserver == 0) { @@ -212,8 +215,12 @@ class MysqlServer extends ApiCommand implements ResourceEntity // when removing, remove from list of allowed_mysqlservers from any customers $this->removeDatabaseFromCustomerAllowedList($dbserver); + $description = $sql_root[$dbserver]['caption'] ?? "unknown"; + $mysql_host = $sql_root[$dbserver]['host'] ?? "unknown"; unset($sql_root[$dbserver]); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] removed database server '" . $description . "' (" . $mysql_host . ")"); + $this->generateNewUserData($sql, $sql_root); return $this->response(['true']); } @@ -287,14 +294,14 @@ class MysqlServer extends ApiCommand implements ResourceEntity * optional the number of the mysql server (either id or dbserver must be set) * * @access admin, customer - * @throws Exception * @return string json-encoded array + * @throws Exception */ public function get() { - $id = (int) $this->getParam('id', true, -1); + $id = (int)$this->getParam('id', true, -1); $dn_optional = $id >= 0; - $dbserver = (int) $this->getParam('dbserver', $dn_optional, -1); + $dbserver = (int)$this->getParam('dbserver', $dn_optional, -1); $dbserver = $id >= 0 ? $id : $dbserver; $sql_root = []; @@ -317,6 +324,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity unset($sql_root[$dbserver]['password']); $sql_root[$dbserver]['id'] = $dbserver; + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get database-server '" . $sql_root[$dbserver]['caption'] . "'"); return $this->response($sql_root[$dbserver]); } @@ -347,16 +355,16 @@ class MysqlServer extends ApiCommand implements ResourceEntity * optional, test connection with given credentials, default is true (yes) * * @access admin - * @throws Exception * @return string json-encoded array + * @throws Exception */ public function update() { $this->validateAccess(); - $id = (int) $this->getParam('id', true, -1); + $id = (int)$this->getParam('id', true, -1); $dn_optional = $id >= 0; - $dbserver = (int) $this->getParam('dbserver', $dn_optional, -1); + $dbserver = (int)$this->getParam('dbserver', $dn_optional, -1); $dbserver = $id >= 0 ? $id : $dbserver; $sql_root = []; @@ -417,7 +425,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity ); if (!empty($mysql_ca)) { $options[PDO::MYSQL_ATTR_SSL_CA] = $mysql_ca; - $options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (bool) $mysql_verifycert; + $options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (bool)$mysql_verifycert; } $dsn = "mysql:host=" . $mysql_host . ";port=" . $mysql_port . ";"; @@ -448,6 +456,8 @@ class MysqlServer extends ApiCommand implements ResourceEntity $this->addDatabaseFromCustomerAllowedList($dbserver); } + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] edited database server '" . $description . "' (" . $mysql_host . ")"); + return $this->response(['true']); } @@ -472,7 +482,7 @@ class MysqlServer extends ApiCommand implements ResourceEntity WHERE `dbserver` = :dbserver "); $result = Database::pexecute_first($result_stmt, ['dbserver' => $dbserver], true, true); - return (int) $result['num_dbs']; + return (int)$result['num_dbs']; } else { $dbserver = $this->getParam('mysql_server'); $customer_ids = $this->getAllowedCustomerIds(); diff --git a/lib/Froxlor/Api/Commands/Mysqls.php b/lib/Froxlor/Api/Commands/Mysqls.php index c5d78501..8ef6c1e7 100644 --- a/lib/Froxlor/Api/Commands/Mysqls.php +++ b/lib/Froxlor/Api/Commands/Mysqls.php @@ -199,7 +199,7 @@ class Mysqls extends ApiCommand implements ResourceEntity $this->mailer()->clearAddresses(); } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] added mysql-database '" . $username . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added mysql-database '" . $username . "'"); $result = $this->apiCall('Mysqls.get', [ 'dbname' => $username, @@ -299,7 +299,7 @@ class Mysqls extends ApiCommand implements ResourceEntity $mbdata = $mbdata_stmt->fetch(PDO::FETCH_ASSOC); Database::needRoot(false); $result['size'] = $mbdata['MB'] ?? 0; - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get database '" . $result['databasename'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get database '" . $result['databasename'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "dbname '" . $dbname . "'"); @@ -388,7 +388,7 @@ class Mysqls extends ApiCommand implements ResourceEntity ]; Database::pexecute($stmt, $params, true, true); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_WARNING, "[API] updated mysql-database '" . $result['databasename'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] updated mysql-database '" . $result['databasename'] . "'"); $result = $this->apiCall('Mysqls.get', [ 'dbname' => $result['databasename'] ]); diff --git a/lib/Froxlor/Api/Commands/PhpSettings.php b/lib/Froxlor/Api/Commands/PhpSettings.php index cb5c396b..588c04a6 100644 --- a/lib/Froxlor/Api/Commands/PhpSettings.php +++ b/lib/Froxlor/Api/Commands/PhpSettings.php @@ -67,7 +67,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity public function listing() { if ($this->isAdmin()) { - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list php-configs"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list php-configs"); $with_subdomains = $this->getBoolParam('with_subdomains', true, false); $query_fields = []; @@ -392,7 +392,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity $ins_data['id'] = Database::lastInsertId(); Cronjob::inserttask(TaskId::REBUILD_VHOST); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] php setting with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); $result = $this->apiCall('PhpSettings.get', [ 'id' => $ins_data['id'] @@ -629,7 +629,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity Database::pexecute($upd_stmt, $upd_data, true, true); Cronjob::inserttask(TaskId::REBUILD_VHOST); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] php setting with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); $result = $this->apiCall('PhpSettings.get', [ 'id' => $id @@ -686,7 +686,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity ], true, true); Cronjob::inserttask(TaskId::REBUILD_VHOST); - $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] php setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'"); + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] php setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'"); return $this->response($result); } throw new Exception("Not allowed to execute given command.", 403); diff --git a/lib/Froxlor/Api/Commands/SubDomains.php b/lib/Froxlor/Api/Commands/SubDomains.php index 433871f0..77cf0377 100644 --- a/lib/Froxlor/Api/Commands/SubDomains.php +++ b/lib/Froxlor/Api/Commands/SubDomains.php @@ -229,6 +229,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 @@ -486,7 +489,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $result['ipsandports'] = $this->getIpsForDomain($result['id']); } $result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] get subdomain '" . $result['domain'] . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] get subdomain '" . $result['domain'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'"); @@ -856,7 +859,7 @@ class SubDomains extends ApiCommand implements ResourceEntity Cronjob::inserttask(TaskId::REBUILD_VHOST); Cronjob::inserttask(TaskId::REBUILD_DNS); $idna_convert = new IdnaWrapper(); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'"); } $result = $this->apiCall('SubDomains.get', [ 'id' => $id @@ -865,7 +868,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 +913,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 +930,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 +944,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 +959,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); diff --git a/lib/Froxlor/Api/Commands/SysLog.php b/lib/Froxlor/Api/Commands/SysLog.php index c3686a54..9236d577 100644 --- a/lib/Froxlor/Api/Commands/SysLog.php +++ b/lib/Froxlor/Api/Commands/SysLog.php @@ -92,7 +92,7 @@ class SysLog extends ApiCommand implements ResourceEntity while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $result[] = $row; } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list log-entries"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list log-entries"); return $this->response([ 'count' => count($result), 'list' => $result diff --git a/lib/Froxlor/Api/Commands/Traffic.php b/lib/Froxlor/Api/Commands/Traffic.php index dc657133..1f8692b9 100644 --- a/lib/Froxlor/Api/Commands/Traffic.php +++ b/lib/Froxlor/Api/Commands/Traffic.php @@ -166,7 +166,7 @@ class Traffic extends ApiCommand implements ResourceEntity $row['mail'] *= 1024; $result[] = $row; } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] list traffic"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list traffic"); return $this->response([ 'count' => count($result), 'list' => $result 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/MasterCron.php b/lib/Froxlor/Cli/MasterCron.php index 72d43f70..ec85d230 100644 --- a/lib/Froxlor/Cli/MasterCron.php +++ b/lib/Froxlor/Cli/MasterCron.php @@ -166,6 +166,9 @@ final class MasterCron extends CliCommand FroxlorLogger::getInstanceOf()->setCronLog(0); } + // clean up possible old login-links + Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()"); + return $result; } 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/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/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 51230334..55b02a96 100644 --- a/lib/Froxlor/Cron/Http/Nginx.php +++ b/lib/Froxlor/Cron/Http/Nginx.php @@ -225,7 +225,7 @@ class Nginx extends HttpConfigBase $this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n"; // protect bin/ - $this->nginx_data[$vhost_filename] .= "\t" . 'location ~ ' . rtrim($relpath, "/") . '/(bin|cache|logs|tests|vendor) {' . "\n"; + $this->nginx_data[$vhost_filename] .= "\t" . 'location ~ ^' . rtrim($relpath, "/") . '/(bin|cache|logs|tests|vendor) {' . "\n"; $this->nginx_data[$vhost_filename] .= "\t" . ' deny all;' . "\n"; $this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n"; } @@ -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/CurrentUser.php b/lib/Froxlor/CurrentUser.php index 22a62082..45168176 100644 --- a/lib/Froxlor/CurrentUser.php +++ b/lib/Froxlor/CurrentUser.php @@ -25,10 +25,10 @@ namespace Froxlor; -use Froxlor\Database\Database; -use Froxlor\UI\Collection; use Froxlor\Api\Commands\Customers; use Froxlor\Api\Commands\SubDomains; +use Froxlor\Database\Database; +use Froxlor\UI\Collection; /** * Class to manage the current user / session @@ -144,22 +144,29 @@ 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'] ]); $addition = $result['emaildomains'] != 0; } elseif ($resource == 'subdomains') { - $parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'], - ['sql_search' => ['d.parentdomainid' => 0]])); - $addition = $parentDomainCollection != 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; } - } 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/Froxlor.php b/lib/Froxlor/Froxlor.php index 6f05abf1..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.19'; + 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/Core.php b/lib/Froxlor/Install/Install/Core.php index 3ba10023..5eb04f9a 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/UI/Callbacks/Domain.php b/lib/Froxlor/UI/Callbacks/Domain.php index 0bac2390..d56c83d6 100644 --- a/lib/Froxlor/UI/Callbacks/Domain.php +++ b/lib/Froxlor/UI/Callbacks/Domain.php @@ -51,6 +51,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 [ @@ -80,7 +83,7 @@ class Domain } $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 +98,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 +132,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 +156,7 @@ class Domain && (int)$attributes['fields']['caneditdomain'] == 1 && (int)$attributes['fields']['letsencrypt'] == 0 && (int)$attributes['fields']['email_only'] == 0 + && !$attributes['fields']['deactivated'] ) { return true; } diff --git a/lib/Froxlor/UI/Callbacks/Style.php b/lib/Froxlor/UI/Callbacks/Style.php index 7de6941b..1019151c 100644 --- a/lib/Froxlor/UI/Callbacks/Style.php +++ b/lib/Froxlor/UI/Callbacks/Style.php @@ -60,17 +60,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..2ec9735e 100644 --- a/lib/Froxlor/UI/Callbacks/Text.php +++ b/lib/Froxlor/UI/Callbacks/Text.php @@ -25,10 +25,13 @@ namespace Froxlor\UI\Callbacks; +use Froxlor\CurrentUser; +use Froxlor\Database\Database; use Froxlor\Froxlor; use Froxlor\PhpHelper; use Froxlor\UI\Panel\UI; use Froxlor\User; +use PDO; class Text { @@ -105,4 +108,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/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/Validate/Check.php b/lib/Froxlor/Validate/Check.php index 5841444f..cb113c72 100644 --- a/lib/Froxlor/Validate/Check.php +++ b/lib/Froxlor/Validate/Check.php @@ -52,16 +52,16 @@ class Check ]; $check_array = [ - 'system_mod_fcgid_enabled' => [ - 'other_post_field' => 'system_phpfpm_enabled', + 'system_mod_fcgid' => [ + 'other_post_field' => 'phpfpm_enabled', 'other_enabled' => 'phpfpm.enabled', 'other_enabled_lng' => 'phpfpmstillenabled', 'deactivate' => [ 'phpfpm.enabled_ownvhost' => 0 ] ], - 'system_phpfpm_enabled' => [ - 'other_post_field' => 'system_mod_fcgid_enabled', + 'phpfpm_enabled' => [ + 'other_post_field' => 'system_mod_fcgid', 'other_enabled' => 'system.mod_fcgid', 'other_enabled_lng' => 'fcgidstillenabled', 'deactivate' => [ diff --git a/lib/configfiles/bionic.xml b/lib/configfiles/bionic.xml index 22dd1233..76ccc55c 100644 --- a/lib/configfiles/bionic.xml +++ b/lib/configfiles/bionic.xml @@ -10,11 +10,13 @@ + + diff --git a/lib/configfiles/bookworm.xml b/lib/configfiles/bookworm.xml index f82d87d4..d0d89f4b 100644 --- a/lib/configfiles/bookworm.xml +++ b/lib/configfiles/bookworm.xml @@ -10,11 +10,13 @@ + + diff --git a/lib/configfiles/bullseye.xml b/lib/configfiles/bullseye.xml index 2e8fe249..1b7d352d 100644 --- a/lib/configfiles/bullseye.xml +++ b/lib/configfiles/bullseye.xml @@ -10,11 +10,13 @@ + + diff --git a/lib/configfiles/buster.xml b/lib/configfiles/buster.xml index 17c988e8..82d2f4b2 100644 --- a/lib/configfiles/buster.xml +++ b/lib/configfiles/buster.xml @@ -10,11 +10,13 @@ + + diff --git a/lib/configfiles/focal.xml b/lib/configfiles/focal.xml index 862a80b6..96563815 100644 --- a/lib/configfiles/focal.xml +++ b/lib/configfiles/focal.xml @@ -9,12 +9,14 @@ - + + + + diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml index aecee819..697c8924 100644 --- a/lib/configfiles/gentoo.xml +++ b/lib/configfiles/gentoo.xml @@ -17,11 +17,13 @@ + + diff --git a/lib/configfiles/jammy.xml b/lib/configfiles/jammy.xml index c7f7172e..1ffcf68b 100644 --- a/lib/configfiles/jammy.xml +++ b/lib/configfiles/jammy.xml @@ -10,11 +10,13 @@ + + diff --git a/lib/formfields/admin/admin/formfield.admin_edit.php b/lib/formfields/admin/admin/formfield.admin_edit.php index 3d641103..d5cf9493 100644 --- a/lib/formfields/admin/admin/formfield.admin_edit.php +++ b/lib/formfields/admin/admin/formfield.admin_edit.php @@ -133,7 +133,7 @@ return [ 'customers' => [ 'label' => lng('admin.customers'), 'type' => 'textul', - 'value' => $result['customers'], + 'value' => empty($result['customers']) ? '0' : $result['customers'], 'maxlength' => 9, 'mandatory' => true ], @@ -146,7 +146,7 @@ return [ 'domains' => [ 'label' => lng('admin.domains'), 'type' => 'textul', - 'value' => $result['domains'], + 'value' => empty($result['domains']) ? '0' : $result['domains'], 'maxlength' => 9, 'mandatory' => true ], @@ -159,49 +159,49 @@ return [ 'diskspace' => [ 'label' => lng('customer.diskspace') . ' (' . lng('customer.mib') . ')', 'type' => 'textul', - 'value' => $result['diskspace'], + 'value' => empty($result['diskspace']) ? '0' : $result['diskspace'], 'maxlength' => 6, 'mandatory' => true ], 'traffic' => [ 'label' => lng('customer.traffic') . ' (' . lng('customer.gib') . ')', 'type' => 'textul', - 'value' => $result['traffic'], + 'value' => empty($result['traffic']) ? '0' : $result['traffic'], 'maxlength' => 4, 'mandatory' => true ], 'subdomains' => [ 'label' => lng('customer.subdomains'), 'type' => 'textul', - 'value' => $result['subdomains'], + 'value' => empty($result['subdomains']) ? '0' : $result['subdomains'], 'maxlength' => 9, 'mandatory' => true ], 'emails' => [ 'label' => lng('customer.emails'), 'type' => 'textul', - 'value' => $result['emails'], + 'value' => empty($result['emails']) ? '0' : $result['emails'], 'maxlength' => 9, 'mandatory' => true ], 'email_accounts' => [ 'label' => lng('customer.accounts'), 'type' => 'textul', - 'value' => $result['email_accounts'], + 'value' => empty($result['email_accounts']) ? '0' : $result['email_accounts'], 'maxlength' => 9, 'mandatory' => true ], 'email_forwarders' => [ 'label' => lng('customer.forwarders'), 'type' => 'textul', - 'value' => $result['email_forwarders'], + 'value' => empty($result['email_forwarders']) ? '0' : $result['email_forwarders'], 'maxlength' => 9, 'mandatory' => true ], 'email_quota' => [ 'label' => lng('customer.email_quota') . ' (' . lng('customer.mib') . ')', 'type' => 'textul', - 'value' => $result['email_quota'], + 'value' => empty($result['email_quota']) ? '0' : $result['email_quota'], 'maxlength' => 9, 'visible' => Settings::Get('system.mail_quota_enabled') == '1', 'mandatory' => true @@ -209,13 +209,13 @@ return [ 'ftps' => [ 'label' => lng('customer.ftps'), 'type' => 'textul', - 'value' => $result['ftps'], + 'value' => empty($result['ftps']) ? '0' : $result['ftps'], 'maxlength' => 9 ], 'mysqls' => [ 'label' => lng('customer.mysqls'), 'type' => 'textul', - 'value' => $result['mysqls'], + 'value' => empty($result['mysqls']) ? '0' : $result['mysqls'], 'maxlength' => 9, 'mandatory' => true ] 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_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/customer/email/formfield.emails_accountchangepasswd.php b/lib/formfields/customer/email/formfield.emails_accountchangepasswd.php index 02cd8f5f..f18d69b7 100644 --- a/lib/formfields/customer/email/formfield.emails_accountchangepasswd.php +++ b/lib/formfields/customer/email/formfield.emails_accountchangepasswd.php @@ -43,13 +43,16 @@ return [ 'email_password' => [ 'label' => lng('login.password'), 'type' => 'password', - 'autocomplete' => 'off' - ], - 'email_password_suggestion' => [ - 'label' => lng('customer.generated_pwd'), - 'type' => 'text', - 'visible' => (Settings::Get('panel.password_regex') == ''), - 'value' => Crypt::generatePassword() + 'autocomplete' => 'off', + 'next_to' => [ + 'email_password_suggestion' => [ + 'next_to_prefix' => lng('customer.generated_pwd') . ':', + 'type' => 'text', + 'visible' => (Settings::Get('panel.password_regex') == ''), + 'value' => Crypt::generatePassword(), + 'readonly' => true + ] + ] ] ] ] diff --git a/lib/formfields/customer/email/formfield.emails_addaccount.php b/lib/formfields/customer/email/formfield.emails_addaccount.php index 01c871dd..ea0be6d6 100644 --- a/lib/formfields/customer/email/formfield.emails_addaccount.php +++ b/lib/formfields/customer/email/formfield.emails_addaccount.php @@ -46,7 +46,7 @@ return [ 'autocomplete' => 'off', 'mandatory' => true, 'next_to' => [ - 'admin_password_suggestion' => [ + 'email_password_suggestion' => [ 'next_to_prefix' => lng('customer.generated_pwd') . ':', 'type' => 'text', 'visible' => (Settings::Get('panel.password_regex') == ''), diff --git a/lib/navigation/00.froxlor.main.php b/lib/navigation/00.froxlor.main.php index b01e5f43..7f19ab3e 100644 --- a/lib/navigation/00.froxlor.main.php +++ b/lib/navigation/00.froxlor.main.php @@ -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', diff --git a/lib/tablelisting/admin/tablelisting.admins.php b/lib/tablelisting/admin/tablelisting.admins.php index aaa82126..d9abf86d 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'), diff --git a/lib/tablelisting/admin/tablelisting.domains.php b/lib/tablelisting/admin/tablelisting.domains.php index 11f8ec5e..3c484e11 100644 --- a/lib/tablelisting/admin/tablelisting.domains.php +++ b/lib/tablelisting/admin/tablelisting.domains.php @@ -48,6 +48,7 @@ return [ 'd.domain_ace' => [ 'label' => lng('domains.domainname'), 'field' => 'domain_ace', + 'isdefaultsearchfield' => true, ], 'ipsandports' => [ 'label' => lng('admin.ipsandports.ipsandports'), @@ -161,6 +162,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/tables.inc.php b/lib/tables.inc.php index 7476a9ab..f385091d 100644 --- a/lib/tables.inc.php +++ b/lib/tables.inc.php @@ -57,3 +57,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/de.lng.php b/lng/de.lng.php index 7ce6d207..c02a5e2b 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -489,6 +489,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', @@ -665,9 +667,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 +699,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,6 +770,7 @@ 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".', @@ -929,6 +930,7 @@ return [ '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.', ], 'extras' => [ 'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.
Die Änderungen sind erst nach einer kurzen Zeit wirksam.', @@ -1287,7 +1289,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?', @@ -2018,12 +2019,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', diff --git a/lng/en.lng.php b/lng/en.lng.php index 617af153..c2a60b53 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -500,6 +500,8 @@ return [ 'adminguide' => 'Admin guide', 'userguide' => 'User guide', 'apiguide' => 'API guide', + 'domain_duplicate' => 'Duplicate domain', + 'domain_duplicate_named' => 'Duplicate %s', 'backups' => [ 'backups' => 'Backups', ], @@ -734,9 +736,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.
', @@ -769,6 +768,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', @@ -839,6 +839,7 @@ 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"', @@ -1001,6 +1002,7 @@ return [ '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', ], '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.', @@ -1402,7 +1404,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?', @@ -2140,12 +2141,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', diff --git a/lng/es.lng.php b/lng/es.lng.php index 28eb7454..f5d5974d 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.
' @@ -1390,7 +1387,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?', 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/login/fpwd.html.twig b/templates/Froxlor/login/fpwd.html.twig index e20b3ee0..6832c5ab 100644 --- a/templates/Froxlor/login/fpwd.html.twig +++ b/templates/Froxlor/login/fpwd.html.twig @@ -3,7 +3,7 @@ {% block content %}
-
+ Froxlor Server Management Panel
@@ -38,8 +38,6 @@
- -
diff --git a/templates/Froxlor/login/login.html.twig b/templates/Froxlor/login/login.html.twig index f78bc8e9..afcaf4df 100644 --- a/templates/Froxlor/login/login.html.twig +++ b/templates/Froxlor/login/login.html.twig @@ -39,9 +39,6 @@
- - -
diff --git a/templates/Froxlor/login/rpwd.html.twig b/templates/Froxlor/login/rpwd.html.twig index ebe4fdec..a24653dc 100644 --- a/templates/Froxlor/login/rpwd.html.twig +++ b/templates/Froxlor/login/rpwd.html.twig @@ -30,8 +30,6 @@
- -
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/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/table.html.twig b/templates/Froxlor/user/table.html.twig index 5ca36e4f..fac9e0d6 100644 --- a/templates/Froxlor/user/table.html.twig +++ b/templates/Froxlor/user/table.html.twig @@ -35,7 +35,7 @@ {% 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/misc/deactivated/index.html b/templates/misc/deactivated/index.html new file mode 100644 index 00000000..a07df96f --- /dev/null +++ b/templates/misc/deactivated/index.html @@ -0,0 +1,56 @@ + + + + + + + froxlor - Deactivated page + + + +
+

Domain deactivated

+

+ This domain is managed using the froxlor Server Management Panel. + If you see this page, this domain has been deactivated by an administrator. +

+
    +
  • + + + + Please ask your provider/hoster if you think this is not correct +
  • +
+
+ + + + diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index 716f18e6..48a771f2 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -385,6 +385,26 @@ class DomainsTest extends TestCase * * @depends testAdminDomainsMove */ + public function testAdminDomainsDuplicate() + { + global $admin_userdata; + $data = [ + 'domainname' => 'test.local', + 'domain' => 'test.duplicate.local', + 'description' => 'duplicated domain' + ]; + $json_result = Domains::getLocal($admin_userdata, $data)->duplicate(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('/var/customers/webs/test3/test.duplicate.local/', $result['documentroot']); + $this->assertEquals(1, $result['email_only']); + $this->assertEquals('test.duplicate.local', $result['domain']); + $this->assertEquals('duplicated domain', $result['description']); + } + + /** + * + * @depends testAdminDomainsDuplicate + */ public function testAdminDomainsDelete() { global $admin_userdata;