From 5731f5ffffeecb95ce60ed3103e13e869d61e9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maurice=20Preu=C3=9F=20=28envoyr=29?= Date: Sun, 21 May 2023 20:51:18 +0200 Subject: [PATCH 01/87] add pgp public key encryption for backup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurice Preuß (envoyr) --- lib/Froxlor/Api/Commands/CustomerBackups.php | 18 +++++++++ lib/Froxlor/Cron/System/BackupCron.php | 38 +++++++++++++------ lib/Froxlor/Install/Install.php | 2 +- lib/configfiles/bionic.xml | 2 +- lib/configfiles/bookworm.xml | 2 +- lib/configfiles/bullseye.xml | 2 +- lib/configfiles/buster.xml | 2 +- lib/configfiles/focal.xml | 2 +- lib/configfiles/gentoo.xml | 2 +- lib/configfiles/jammy.xml | 2 +- .../customer/extras/formfield.backup.php | 5 +++ .../customer/tablelisting.backups.php | 6 +++ lng/de.lng.php | 7 ++++ lng/en.lng.php | 7 ++++ 14 files changed, 78 insertions(+), 19 deletions(-) diff --git a/lib/Froxlor/Api/Commands/CustomerBackups.php b/lib/Froxlor/Api/Commands/CustomerBackups.php index efbbaa66..412bc0b6 100644 --- a/lib/Froxlor/Api/Commands/CustomerBackups.php +++ b/lib/Froxlor/Api/Commands/CustomerBackups.php @@ -49,6 +49,8 @@ class CustomerBackups extends ApiCommand implements ResourceEntity * * @param string $path * path to store the backup to + * @param string $pgp_public_key + * optional pgp public key to encrypt the backup, default is empty * @param bool $backup_dbs * optional whether to backup databases, default is 0 (false) * @param bool $backup_mail @@ -72,6 +74,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity $path = $this->getParam('path'); // parameter + $pgp_public_key = $this->getParam('pgp_public_key', true, ''); $backup_dbs = $this->getBoolParam('backup_dbs', true, 0); $backup_mail = $this->getBoolParam('backup_mail', true, 0); $backup_web = $this->getBoolParam('backup_web', true, 0); @@ -89,6 +92,19 @@ class CustomerBackups extends ApiCommand implements ResourceEntity Response::standardError('backupfoldercannotbedocroot', '', true); } + // pgp public key validation + if (!empty($pgp_public_key)) { + // check if gnupg extension is loaded + if (!extension_loaded('gnupg')) { + Response::standardError('gnupgextensionnotavailable', '', true); + } + // check if the pgp public key is a valid key + putenv('GNUPGHOME='.sys_get_temp_dir()); + if (gnupg_import(gnupg_init(), $pgp_public_key) === false) { + Response::standardError('invalidpgppublickey', '', true); + } + } + if ($backup_dbs != '1') { $backup_dbs = '0'; } @@ -107,10 +123,12 @@ class CustomerBackups extends ApiCommand implements ResourceEntity 'gid' => $customer['guid'], 'loginname' => $customer['loginname'], 'destdir' => $path, + 'pgp_public_key' => $pgp_public_key, 'backup_dbs' => $backup_dbs, 'backup_mail' => $backup_mail, 'backup_web' => $backup_web ]; + // schedule backup job Cronjob::inserttask(TaskId::CREATE_CUSTOMER_BACKUP, $task_data); diff --git a/lib/Froxlor/Cron/System/BackupCron.php b/lib/Froxlor/Cron/System/BackupCron.php index 5b818b56..eb67522c 100644 --- a/lib/Froxlor/Cron/System/BackupCron.php +++ b/lib/Froxlor/Cron/System/BackupCron.php @@ -36,9 +36,9 @@ class BackupCron extends FroxlorCron public static function run() { - // Check Traffic-Lock - if (function_exists('pcntl_fork')) { - $BackupLock = FileDir::makeCorrectFile(dirname(self::getLockfile()) . "/froxlor_cron_backup.lock"); + // Check Backup-Lock + if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { + $BackupLock = FileDir::makeCorrectFile("/var/run/froxlor_cron_backup.lock"); if (file_exists($BackupLock) && is_numeric($BackupPid = file_get_contents($BackupLock))) { if (function_exists('posix_kill')) { $BackupPidStatus = @posix_kill($BackupPid, 0); @@ -69,7 +69,7 @@ class BackupCron extends FroxlorCron // Fork failed return 1; } - } else { + } elseif (!defined('CRON_NOFORK_FLAG')) { if (extension_loaded('pcntl')) { $msg = "PHP compiled with pcntl but pcntl_fork function is not available."; } else { @@ -114,7 +114,8 @@ class BackupCron extends FroxlorCron ]); } - if (function_exists('pcntl_fork')) { + + if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { @unlink($BackupLock); die(); } @@ -152,7 +153,7 @@ class BackupCron extends FroxlorCron ]); $has_dbs = false; - $current_dbserver = null; + $current_dbserver = -1; while ($row = $sel_stmt->fetch()) { // Get sql_root data for the specific database-server the database resides on if ($current_dbserver != $row['dbserver']) { @@ -183,7 +184,9 @@ class BackupCron extends FroxlorCron $create_backup_tar_data .= './mysql '; } - unlink($mysqlcnf_file); + if (file_exists($mysqlcnf_file)) { + unlink($mysqlcnf_file); + } unset($sql_root); } @@ -223,11 +226,24 @@ class BackupCron extends FroxlorCron } if (!empty($create_backup_tar_data)) { - $backup_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-backup_' . date('YmdHi', time()) . '.tar.gz'); + // set owner to customer + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir)); + FileDir::safe_exec('chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir)); + // create tar-file + $backup_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-backup_' . date('YmdHi', time()) . '.tar.gz' . (!empty($data['pgp_public_key']) ? '.gpg' : '')); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating backup-file "' . $backup_file . '"'); - // pack all archives in tmp-dir to one - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); - FileDir::safe_exec('tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); + if (!empty($data['pgp_public_key'])){ + // pack all archives in tmp-dir to one archive and encrypt it with gpg + $recipient_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-recipients.gpg'); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating recipient-file "' . $recipient_file . '"'); + file_put_contents($recipient_file, $data['pgp_public_key']); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file '. escapeshellarg($recipient_file) .' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes'); + FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file '. escapeshellarg($recipient_file) .' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes', $return_value, ['|']); + } else { + // pack all archives in tmp-dir to one archive + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); + FileDir::safe_exec('tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); + } // move to destination directory $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir'])); FileDir::safe_exec('mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir'])); diff --git a/lib/Froxlor/Install/Install.php b/lib/Froxlor/Install/Install.php index ebf322cb..ebed9fdb 100644 --- a/lib/Froxlor/Install/Install.php +++ b/lib/Froxlor/Install/Install.php @@ -43,7 +43,7 @@ class Install public $formfield; public string $requiredVersion = '7.4.0'; public array $requiredExtensions = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'curl', 'gmp', 'json', 'gd']; - public array $suggestedExtensions = ['bcmath', 'zip']; + public array $suggestedExtensions = ['bcmath', 'zip', 'gnupg']; public array $suggestions = []; public array $criticals = []; public array $loadedExtensions; diff --git a/lib/configfiles/bionic.xml b/lib/configfiles/bionic.xml index f4edb083..22dd1233 100644 --- a/lib/configfiles/bionic.xml +++ b/lib/configfiles/bionic.xml @@ -4717,7 +4717,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/bookworm.xml b/lib/configfiles/bookworm.xml index 1cf35604..f82d87d4 100644 --- a/lib/configfiles/bookworm.xml +++ b/lib/configfiles/bookworm.xml @@ -3359,7 +3359,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/bullseye.xml b/lib/configfiles/bullseye.xml index b9bd8053..2e8fe249 100644 --- a/lib/configfiles/bullseye.xml +++ b/lib/configfiles/bullseye.xml @@ -4929,7 +4929,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/buster.xml b/lib/configfiles/buster.xml index f1bdbe82..17c988e8 100644 --- a/lib/configfiles/buster.xml +++ b/lib/configfiles/buster.xml @@ -4920,7 +4920,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/focal.xml b/lib/configfiles/focal.xml index cce32d7d..862a80b6 100644 --- a/lib/configfiles/focal.xml +++ b/lib/configfiles/focal.xml @@ -4156,7 +4156,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml index a03d6a16..aecee819 100644 --- a/lib/configfiles/gentoo.xml +++ b/lib/configfiles/gentoo.xml @@ -3934,7 +3934,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/configfiles/jammy.xml b/lib/configfiles/jammy.xml index cc324a71..c7f7172e 100644 --- a/lib/configfiles/jammy.xml +++ b/lib/configfiles/jammy.xml @@ -4148,7 +4148,7 @@ aliases: files - + bin/froxlor-cli /usr/local/bin/froxlor-cli]]> bin/froxlor-cli froxlor:cron --run-task 99]]> diff --git a/lib/formfields/customer/extras/formfield.backup.php b/lib/formfields/customer/extras/formfield.backup.php index d6cdce7a..b8fe8898 100644 --- a/lib/formfields/customer/extras/formfield.backup.php +++ b/lib/formfields/customer/extras/formfield.backup.php @@ -35,6 +35,11 @@ return [ 'value' => $pathSelect['value'], 'note' => $pathSelect['note'] ?? '', ], + 'pgp_public_key' => [ + 'label' => lng('panel.backup_pgp_public_key.title'), + 'desc' => lng('panel.backup_pgp_public_key.description'), + 'type' => 'textarea', + ], 'path_protection_info' => [ 'label' => lng('extras.path_protection_label'), 'type' => 'infotext', diff --git a/lib/tablelisting/customer/tablelisting.backups.php b/lib/tablelisting/customer/tablelisting.backups.php index 1ccf3d10..8b5f2b24 100644 --- a/lib/tablelisting/customer/tablelisting.backups.php +++ b/lib/tablelisting/customer/tablelisting.backups.php @@ -39,6 +39,11 @@ return [ 'field' => 'data.destdir', 'callback' => [Ftp::class, 'pathRelative'] ], + 'pgp_public_key' => [ + 'label' => lng('panel.pgp_public_key'), + 'field' => 'data.pgp_public_key', + 'callback' => [Text::class, 'boolean'] + ], 'backup_web' => [ 'label' => lng('extras.backup_web'), 'field' => 'data.backup_web', @@ -57,6 +62,7 @@ return [ ], 'visible_columns' => Listing::getVisibleColumnsForListing('backup_list', [ 'destdir', + 'pgp_public_key', 'backup_web', 'backup_mail', 'backup_dbs' diff --git a/lng/de.lng.php b/lng/de.lng.php index c331ccd5..7ce6d207 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -927,6 +927,8 @@ return [ 'invalidcronjobintervalvalue' => 'Cronjob Intervall muss einer der folgenden Werte sein: %s', 'phpgdextensionnotavailable' => 'Die PHP GD Extension ist nicht verfügbar. Bild-Daten können nicht validiert werden.', '2fa_wrongcode' => 'Der angegebene Code ist nicht korrekt', + 'gnupgextensionnotavailable' => 'Die PHP GnuPG Extension ist nicht verfügbar. PGP Schlüssel können nicht validiert werden.', + 'invalidpgppublickey' => 'Der angegebene PGP Public Key ist ungültig', ], 'extras' => [ 'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.
Die Änderungen sind erst nach einer kurzen Zeit wirksam.', @@ -1198,6 +1200,11 @@ Vielen Dank, Ihr Administrator', 'title' => 'Pfad zur Ablage der Backups', 'description' => 'In diesem Ordner werden die Backups abgelegt. Wenn das Sichern von Web-Daten aktiviert ist, werden alle Dateien aus dem Heimatverzeichnis gesichert, exklusive des hier angegebenen Backup-Ordners.', ], + 'backup_pgp_public_key' => [ + 'title' => 'Öffentlicher PGP-Schlüssel', + 'description' => 'Der öffentliche PGP-Schlüssel, mit dem die Backups verschlüsselt werden sollen. Wenn kein Schlüssel angegeben ist, werden die Backups nicht verschlüsselt.', + ], + 'pgp_public_key' => 'Öffentlicher PGP-Schlüssel', 'none_value' => 'Keine', 'viewlogs' => 'Logdateien einsehen', 'not_configured' => 'Das System wurde noch nicht konfiguriert. Klicke auf den Button um die Installation zu starten.', diff --git a/lng/en.lng.php b/lng/en.lng.php index e4645ca3..0a05daf4 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -996,6 +996,8 @@ return [ 'invalidcronjobintervalvalue' => 'Cronjob interval must be one of: %s', 'phpgdextensionnotavailable' => 'The PHP GD extension is not available. Unable to validate image-data', '2fa_wrongcode' => 'The code entered is not valid', + 'gnupgextensionnotavailable' => 'The PHP GnuPG extension is not available. Unable to validate PGP Public Key', + 'invalidpgppublickey' => 'The PGP Public Key is not valid', ], '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.', @@ -1310,6 +1312,11 @@ Yours sincerely, your administrator', 'title' => 'Destination path for the backup', 'description' => 'This is the path where the backups will be stored. If backup of web-data is selected, all files from the homedir are stored excluding the backup-folder specified here.', ], + 'backup_pgp_public_key' => [ + 'title' => 'Public PGP key for encryption', + 'description' => 'This is the public PGP key which will be used to encrypt the backup. If you leave this field empty, the backup will not be encrypted.', + ], + 'pgp_public_key' => 'Public PGP key', 'none_value' => 'None', 'viewlogs' => 'View logfiles', 'not_configured' => 'System not configured yet. Click here to go to configurations.', From c1f03c1683c7a43f0bb1797dbc3e88c240f3bdf9 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 23 May 2023 14:36:45 +0200 Subject: [PATCH 02/87] remove 'main but subdomain' as we now automatically create the correct order of vhost configs and dns zones Signed-off-by: Michael Kaufmann --- admin_domains.php | 48 ++-------------- install/froxlor.sql.php | 1 - install/updates/froxlor/update_2.x.inc.php | 8 +++ lib/Froxlor/Api/Commands/Domains.php | 55 ++----------------- lib/Froxlor/Cron/Dns/Bind.php | 6 +- lib/Froxlor/Cron/Dns/DnsBase.php | 24 ++++---- lib/Froxlor/Cron/Dns/PowerDNS.php | 6 +- lib/Froxlor/Cron/Http/Apache.php | 23 -------- lib/Froxlor/Cron/Http/HttpConfigBase.php | 15 +++++ lib/Froxlor/Cron/Http/Lighttpd.php | 21 +------ lib/Froxlor/Cron/Http/Nginx.php | 20 ------- lib/Froxlor/Domain/Domain.php | 49 +++++------------ .../admin/domains/formfield.domains_add.php | 6 -- .../admin/domains/formfield.domains_edit.php | 7 --- lng/de.lng.php | 4 -- lng/en.lng.php | 4 -- lng/es.lng.php | 4 -- lng/it.lng.php | 4 -- lng/nl.lng.php | 4 -- 19 files changed, 70 insertions(+), 239 deletions(-) diff --git a/admin_domains.php b/admin_domains.php index 59755968..c295387c 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'), diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index 62c626f2..8b680cfe 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', diff --git a/install/updates/froxlor/update_2.x.inc.php b/install/updates/froxlor/update_2.x.inc.php index 228fdac1..5c035172 100644 --- a/install/updates/froxlor/update_2.x.inc.php +++ b/install/updates/froxlor/update_2.x.inc.php @@ -492,3 +492,11 @@ 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::isDatabaseVersion('202304260')) { + Update::showUpdateStep("Cleaning domains table"); + Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;"); + Update::lastStepStatus(0); + + Froxlor::updateToDbVersion('202305231'); +} diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php index ea318e97..ab81efd5 100644 --- a/lib/Froxlor/Api/Commands/Domains.php +++ b/lib/Froxlor/Api/Commands/Domains.php @@ -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 @@ -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, @@ -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 @@ -1191,7 +1178,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']); @@ -1640,10 +1626,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 +1648,6 @@ class Domains extends ApiCommand implements ResourceEntity || $writeaccesslog != $result['writeaccesslog'] || $writeerrorlog != $result['writeerrorlog'] || $aliasdomain != $result['aliasdomain'] - || $issubof != $result['ismainbutsubto'] || $email_only != $result['email_only'] || ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1') || $letsencrypt != $result['letsencrypt'] @@ -1837,7 +1818,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; @@ -1885,7 +1865,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, @@ -2073,9 +2052,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 +2067,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 +2074,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 +2099,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); 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..3d2ad85e 100644 --- a/lib/Froxlor/Cron/Http/Nginx.php +++ b/lib/Froxlor/Cron/Http/Nginx.php @@ -467,26 +467,6 @@ class Nginx extends HttpConfigBase } } - protected function getVhostFilename($domain, $ssl_vhost = false) - { - if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) { - $vhost_no = '35'; - } elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) { - $vhost_no = '30'; - } else { - // number of dots in a domain specifies it's position (and depth of subdomain) starting at 29 going downwards on higher depth - $vhost_no = (string)(30 - substr_count($domain['domain'], ".") + 1); - } - - if ($ssl_vhost === true) { - $vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_ssl_vhost_' . $domain['domain'] . '.conf'); - } else { - $vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_normal_vhost_' . $domain['domain'] . '.conf'); - } - - return $vhost_filename; - } - protected function getVhostContent($domain, $ssl_vhost = false) { if ($ssl_vhost === true && $domain['ssl'] != '1' && $domain['ssl_redirect'] != '1') { diff --git a/lib/Froxlor/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/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..b6a9c308 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', diff --git a/lng/de.lng.php b/lng/de.lng.php index 7ce6d207..b899a717 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -665,9 +665,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.
', @@ -1287,7 +1284,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?', diff --git a/lng/en.lng.php b/lng/en.lng.php index 0a05daf4..d4cb30fa 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -731,9 +731,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.
', @@ -1399,7 +1396,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?', 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' => [ From 2e6b939ec6d4ed90d8657a8915c87da845efd4e4 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 23 May 2023 15:21:25 +0200 Subject: [PATCH 03/87] set dbversion Signed-off-by: Michael Kaufmann --- install/froxlor.sql.php | 2 +- install/updates/froxlor/update_2.x.inc.php | 2 +- lib/Froxlor/Froxlor.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index 8b680cfe..a4cb7c5a 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -744,7 +744,7 @@ opcache.validate_timestamps'), ('panel', 'logo_overridecustom', '0'), ('panel', 'settings_mode', '0'), ('panel', 'version', '2.0.19'), - ('panel', 'db_version', '202304260'); + ('panel', 'db_version', '202305230'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/updates/froxlor/update_2.x.inc.php b/install/updates/froxlor/update_2.x.inc.php index 5c035172..50f0f3cb 100644 --- a/install/updates/froxlor/update_2.x.inc.php +++ b/install/updates/froxlor/update_2.x.inc.php @@ -498,5 +498,5 @@ if (Froxlor::isDatabaseVersion('202304260')) { Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;"); Update::lastStepStatus(0); - Froxlor::updateToDbVersion('202305231'); + Froxlor::updateToDbVersion('202305230'); } diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php index 6f05abf1..df17100a 100644 --- a/lib/Froxlor/Froxlor.php +++ b/lib/Froxlor/Froxlor.php @@ -34,7 +34,7 @@ final class Froxlor const VERSION = '2.0.19'; // Database version (YYYYMMDDC where C is a daily counter) - const DBVERSION = '202304260'; + const DBVERSION = '202305230'; // Distribution branding-tag (used for Debian etc.) const BRANDING = ''; From 09b3c1c45a14be9e7394511118366b4908aaf363 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 24 May 2023 09:05:50 +0200 Subject: [PATCH 04/87] implement Domains.duplicate() API call, refs #807 Signed-off-by: Michael Kaufmann --- lib/Froxlor/Api/Commands/Domains.php | 134 ++++++++++++++++++++++++--- tests/Domains/DomainsTest.php | 20 ++++ 2 files changed, 142 insertions(+), 12 deletions(-) diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php index ab81efd5..1b4bf6f2 100644 --- a/lib/Froxlor/Api/Commands/Domains.php +++ b/lib/Froxlor/Api/Commands/Domains.php @@ -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) @@ -207,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 @@ -216,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) @@ -241,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 @@ -249,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 @@ -260,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 @@ -1076,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 @@ -1088,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) @@ -1117,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 @@ -1125,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 @@ -2187,4 +2187,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/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; From 233bf27afed66a34675c180301879beaa5870c8f Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 24 May 2023 16:02:07 +0200 Subject: [PATCH 05/87] add Froxlor.generateLoginLink() API call to allow generation of one-time-login links for customers, thx to INWX for supporting and sponsoring this feature Signed-off-by: Michael Kaufmann --- index.php | 55 +++++++++++++++- install/froxlor.sql.php | 11 +++- install/updates/froxlor/update_2.x.inc.php | 14 +++- lib/Froxlor/Api/Commands/Froxlor.php | 74 ++++++++++++++++++++++ lib/Froxlor/Api/FroxlorRPC.php | 4 +- lib/Froxlor/Cli/MasterCron.php | 3 + lib/Froxlor/Froxlor.php | 2 +- lib/tables.inc.php | 1 + lng/de.lng.php | 1 + lng/en.lng.php | 1 + 10 files changed, 160 insertions(+), 6 deletions(-) diff --git a/index.php b/index.php index d59146e3..de0b38f4 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'; @@ -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'] != '') { diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index a4cb7c5a..57901d03 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -744,7 +744,7 @@ opcache.validate_timestamps'), ('panel', 'logo_overridecustom', '0'), ('panel', 'settings_mode', '0'), ('panel', 'version', '2.0.19'), - ('panel', 'db_version', '202305230'); + ('panel', 'db_version', '202305240'); DROP TABLE IF EXISTS `panel_tasks`; @@ -1051,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 50f0f3cb..30eafde4 100644 --- a/install/updates/froxlor/update_2.x.inc.php +++ b/install/updates/froxlor/update_2.x.inc.php @@ -498,5 +498,17 @@ if (Froxlor::isDatabaseVersion('202304260')) { Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;"); Update::lastStepStatus(0); - Froxlor::updateToDbVersion('202305230'); + 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); + + Froxlor::updateToDbVersion('202305240'); } diff --git a/lib/Froxlor/Api/Commands/Froxlor.php b/lib/Froxlor/Api/Commands/Froxlor.php index 690b5880..d7cb4920 100644 --- a/lib/Froxlor/Api/Commands/Froxlor.php +++ b/lib/Froxlor/Api/Commands/Froxlor.php @@ -37,6 +37,7 @@ use Froxlor\Settings; use Froxlor\SImExporter; use Froxlor\System\Cronjob; use Froxlor\System\Crypt; +use Froxlor\Validate\Validate; use PDO; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; @@ -269,6 +270,79 @@ class Froxlor extends ApiCommand return $this->response(Crypt::generatePassword($length)); } + /** + * return a one-time login link URL for a given user + * + * @param int $customerid optional, required if $loginname is not specified, user to create link for + * @param string $loginname optional, required if $customerid is not specified, user to create link for + * @param int $valid_time optional, value in seconds how long the link will be valid, default is 10 seconds, valid values are numbers from 10 to 120 + * @param string $allowed_from optional, comma separated list of ip addresses or networks to allow login from via this link + * + * @access admin + * @return string json-encoded array [base => domain, uri => relative link] + * @throws Exception + */ + public function generateLoginLink() + { + if ($this->isAdmin()) { + $customer = $this->getCustomerData(); + + // cannot create link for deactivated users + if ((int)$customer['deactivated'] == 1) { + throw new Exception("Cannot generate link for deactivated user", 406); + } + + $valid_time = (int)$this->getParam('valid_time', true, 10); + $allowed_from = $this->getParam('allowed_from', true, ''); + + $valid_time = Validate::validate($valid_time, 'valid time', '/^(1[0-1][0-9]|120|[1-9][0-9])$/', 'invalid_validtime', [10], true); + + // validate allowed_from + if (!empty($allowed_from)) { + $ip_list = array_map('trim', explode(",", $allowed_from)); + $_check_list = $ip_list; + foreach ($_check_list as $idx => $ip) { + if (Validate::validate_ip2($ip, true, 'invalidip', true, true, true) == false) { + throw new Exception('Invalid ip address', 406); + } + // check for cidr + if (strpos($ip, '/') !== false) { + $ipparts = explode("/", $ip); + // shorten IP + $ip = inet_ntop(inet_pton($ipparts[0])); + // re-add cidr + $ip .= '/' . $ipparts[1]; + } else { + // shorten IP + $ip = inet_ntop(inet_pton($ip)); + } + $ip_list[$idx] = $ip; + } + $allowed_from = implode(",", array_unique($ip_list)); + } + + $hash = hash('sha256', openssl_random_pseudo_bytes(64 * 64)); + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_LOGINLINKS . "` + SET `hash` = :hash, `loginname` = :loginname, `valid_until` = :validuntil, `allowed_from` = :allowedfrom + ON DUPLICATE KEY UPDATE `hash` = :hash, `valid_until` = :validuntil, `allowed_from` = :allowedfrom + "); + Database::pexecute($ins_stmt, [ + 'hash' => $hash, + 'loginname' => $customer['loginname'], + 'validuntil' => time() + $valid_time, + 'allowedfrom' => $allowed_from + ]); + + return $this->response([ + 'base' => 'https://' . Settings::Get('system.hostname') . '/' . (Settings::Get('system.froxlordirectlyviahostname') != 1 ? basename(\Froxlor\Froxlor::getInstallDir()) . '/' : ''), + 'uri' => 'index.php?action=ll&ln=' . $customer['loginname'] . '&h=' . $hash + ]); + } + throw new Exception("Not allowed to execute given command.", 403); + } + /** * can be used to remotely run the integritiy checks froxlor implements * diff --git a/lib/Froxlor/Api/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/Froxlor.php b/lib/Froxlor/Froxlor.php index df17100a..39f4aab5 100644 --- a/lib/Froxlor/Froxlor.php +++ b/lib/Froxlor/Froxlor.php @@ -34,7 +34,7 @@ final class Froxlor const VERSION = '2.0.19'; // Database version (YYYYMMDDC where C is a daily counter) - const DBVERSION = '202305230'; + const DBVERSION = '202305240'; // Distribution branding-tag (used for Debian etc.) const BRANDING = ''; diff --git a/lib/tables.inc.php b/lib/tables.inc.php index 43c52b08..26214333 100644 --- a/lib/tables.inc.php +++ b/lib/tables.inc.php @@ -56,3 +56,4 @@ const TABLE_PANEL_FPMDAEMONS = 'panel_fpmdaemons'; const TABLE_PANEL_PLANS = 'panel_plans'; const TABLE_API_KEYS = 'api_keys'; const TABLE_PANEL_USERCOLUMNS = 'panel_usercolumns'; +const TABLE_PANEL_LOGINLINKS = 'panel_loginlinks'; diff --git a/lng/de.lng.php b/lng/de.lng.php index b899a717..6cf9fcf9 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -926,6 +926,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.', diff --git a/lng/en.lng.php b/lng/en.lng.php index d4cb30fa..9169ec37 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -995,6 +995,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.', From 8deaf6a0139309f25fc1e6ef6ab7eb37662b083f Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 25 May 2023 12:33:43 +0200 Subject: [PATCH 06/87] frontend implementation of Domains.duplicate() Signed-off-by: Michael Kaufmann --- admin_domains.php | 17 ++++++++ lib/Froxlor/UI/Callbacks/Text.php | 43 +++++++++++++++++++ .../admin/tablelisting.domains.php | 5 +++ lng/de.lng.php | 2 + lng/en.lng.php | 2 + templates/Froxlor/user/inline-form.html.twig | 2 +- 6 files changed, 70 insertions(+), 1 deletion(-) diff --git a/admin_domains.php b/admin_domains.php index c295387c..408e64bc 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -636,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/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/tablelisting/admin/tablelisting.domains.php b/lib/tablelisting/admin/tablelisting.domains.php index 11f8ec5e..17aa75bc 100644 --- a/lib/tablelisting/admin/tablelisting.domains.php +++ b/lib/tablelisting/admin/tablelisting.domains.php @@ -161,6 +161,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/lng/de.lng.php b/lng/de.lng.php index b899a717..e6c25761 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', diff --git a/lng/en.lng.php b/lng/en.lng.php index d4cb30fa..97f60ce4 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', ], 'apcuinfo' => [ 'clearcache' => 'Clear APCu cache', 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('')) }} From 84599011cf2f3836c570eb904b889e0aaf95b701 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Fri, 26 May 2023 12:53:27 +0200 Subject: [PATCH 07/87] Allow editing/viewing of standard subdomain for customer, fixes #1121 Signed-off-by: Michael Kaufmann --- lib/Froxlor/Api/Commands/SubDomains.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/Froxlor/Api/Commands/SubDomains.php b/lib/Froxlor/Api/Commands/SubDomains.php index 433871f0..3f95a2a1 100644 --- a/lib/Froxlor/Api/Commands/SubDomains.php +++ b/lib/Froxlor/Api/Commands/SubDomains.php @@ -865,7 +865,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 +910,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 +927,6 @@ class SubDomains extends ApiCommand implements ResourceEntity $customer_ids = [ $this->getUserDetail('customerid') ]; - $customer_stdsubs = [ - $this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain') - ]; $select_fields = [ '`d`.`id`', @@ -963,7 +955,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); From 426f20447325a729e0ce897d8ecc8e6532358f1e Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Fri, 26 May 2023 13:15:01 +0200 Subject: [PATCH 08/87] specify default search-field for on-page listing-search/filter Signed-off-by: Michael Kaufmann --- lib/Froxlor/UI/Listing.php | 1 + lib/tablelisting/admin/tablelisting.admins.php | 1 + lib/tablelisting/admin/tablelisting.domains.php | 1 + lib/tablelisting/admin/tablelisting.fpmconfigs.php | 1 + lib/tablelisting/admin/tablelisting.ipsandports.php | 1 + lib/tablelisting/admin/tablelisting.mysqlserver.php | 1 + lib/tablelisting/admin/tablelisting.phpconfigs.php | 1 + lib/tablelisting/admin/tablelisting.plans.php | 1 + templates/Froxlor/table/table.html.twig | 2 +- 9 files changed, 9 insertions(+), 1 deletion(-) 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/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 17aa75bc..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'), 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/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 @@ From 0b685d569f66d1b631683e6673c11ee4e6090e90 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Fri, 26 May 2023 13:58:41 +0200 Subject: [PATCH 09/87] start to integrate domain-deactivated flag in UI Signed-off-by: Michael Kaufmann --- lib/Froxlor/Api/Commands/Domains.php | 18 +++++++++++++++--- lib/Froxlor/Api/Commands/SubDomains.php | 3 ++- lib/Froxlor/UI/Callbacks/Domain.php | 13 +++++++++---- lib/Froxlor/UI/Callbacks/Style.php | 3 ++- .../admin/domains/formfield.domains_edit.php | 8 +++++++- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php index 1b4bf6f2..475f458e 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`) @@ -1137,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 @@ -1232,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(" @@ -1832,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(" @@ -1878,11 +1882,17 @@ 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']) { + // @TODO + } + $_update_data['customerid'] = $customerid; $_update_data['adminid'] = $adminid; $_update_data['phpenabled'] = $phpenabled; @@ -1900,6 +1910,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 = ''; @@ -1932,7 +1943,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, + `deativated` = :deactivated " . $update_phpconfig . $upd_specialsettings . $updatechildren . $update_sslredirect . " WHERE `parentdomainid` = :parentdomainid "); diff --git a/lib/Froxlor/Api/Commands/SubDomains.php b/lib/Froxlor/Api/Commands/SubDomains.php index 3f95a2a1..be29ed3e 100644 --- a/lib/Froxlor/Api/Commands/SubDomains.php +++ b/lib/Froxlor/Api/Commands/SubDomains.php @@ -941,7 +941,8 @@ class SubDomains extends ApiCommand implements ResourceEntity '`d`.`parentdomainid`', '`d`.`letsencrypt`', '`d`.`registration_date`', - '`d`.`termination_date`' + '`d`.`termination_date`', + '`d`.`deactivated`' ]; } $query_fields = []; 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..6f19bee8 100644 --- a/lib/Froxlor/UI/Callbacks/Style.php +++ b/lib/Froxlor/UI/Callbacks/Style.php @@ -63,7 +63,8 @@ class Style $termination_css = 'bg-danger'; } } - return $attributes['fields']['deactivated'] ? 'bg-info' : $termination_css; + $deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated']; + return $deactivated ? 'bg-info' : $termination_css; } public static function resultCustomerLockedOrDeactivated(array $attributes): string diff --git a/lib/formfields/admin/domains/formfield.domains_edit.php b/lib/formfields/admin/domains/formfield.domains_edit.php index b6a9c308..00b00cbd 100644 --- a/lib/formfields/admin/domains/formfield.domains_edit.php +++ b/lib/formfields/admin/domains/formfield.domains_edit.php @@ -97,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' => [ From 63d81201de899fde18404fc3d2de243d967df5d9 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Fri, 26 May 2023 14:02:09 +0200 Subject: [PATCH 10/87] fix typo Signed-off-by: Michael Kaufmann --- lib/Froxlor/Api/Commands/Domains.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php index 475f458e..22639b7d 100644 --- a/lib/Froxlor/Api/Commands/Domains.php +++ b/lib/Froxlor/Api/Commands/Domains.php @@ -1944,7 +1944,7 @@ class Domains extends ApiCommand implements ResourceEntity `tlsv13_cipher_list` = :tlsv13_cipher_list, `ssl_honorcipherorder` = :honorcipherorder, `ssl_sessiontickets` = :sessiontickets, - `deativated` = :deactivated + `deactivated` = :deactivated " . $update_phpconfig . $upd_specialsettings . $updatechildren . $update_sslredirect . " WHERE `parentdomainid` = :parentdomainid "); From ca5f36d91278f4ece572331722a6a87dd873dbd4 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Fri, 26 May 2023 21:24:08 +0200 Subject: [PATCH 11/87] corrected language index in system-settings, fixes #1145 Signed-off-by: Michael Kaufmann --- lng/de.lng.php | 8 ++------ lng/en.lng.php | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lng/de.lng.php b/lng/de.lng.php index ff39aa19..616f92aa 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -2017,12 +2017,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 f92730a0..45925fc2 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -2136,12 +2136,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', From e1e7bc7b42b40d07887b92cbfe9d2d5c920e9bdd Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Mon, 5 Jun 2023 09:01:41 +0200 Subject: [PATCH 12/87] set fastcgi_ipcdir according to webserver after installation (regardless of using phpfpm) Signed-off-by: Michael Kaufmann --- lib/configfiles/bionic.xml | 2 ++ lib/configfiles/bookworm.xml | 2 ++ lib/configfiles/bullseye.xml | 2 ++ lib/configfiles/buster.xml | 2 ++ lib/configfiles/focal.xml | 4 +++- lib/configfiles/gentoo.xml | 2 ++ lib/configfiles/jammy.xml | 2 ++ 7 files changed, 15 insertions(+), 1 deletion(-) 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 @@ + + From 981d819fd727318326e38a019292f65ce1c63c2d Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 6 Jun 2023 09:05:49 +0200 Subject: [PATCH 13/87] display notice if customer has no domains assigned yet to add subdomains; corrected subdomains-usage-check to exclude std-subdomain Signed-off-by: Michael Kaufmann --- customer_domains.php | 21 ++++++++++++++++++--- customer_email.php | 2 +- lib/Froxlor/Api/ApiCommand.php | 3 ++- lib/Froxlor/Cron/Http/Php/Fpm.php | 2 +- lib/Froxlor/CurrentUser.php | 20 +++++++++++++------- lng/de.lng.php | 1 + lng/en.lng.php | 1 + templates/Froxlor/user/table.html.twig | 2 +- 8 files changed, 38 insertions(+), 14 deletions(-) diff --git a/customer_domains.php b/customer_domains.php index 933edd13..45348e2f 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 { @@ -139,6 +146,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/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/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..6c814b47 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 @@ -151,15 +151,21 @@ class CurrentUser ]); $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.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/lng/de.lng.php b/lng/de.lng.php index 616f92aa..0c6e3353 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -699,6 +699,7 @@ return [ 'openbasedirenabled' => 'Openbasedir Einschränkung', 'hsts' => 'HSTS aktiviert', 'aliasdomainid' => 'ID der Alias-Domain', + 'nodomainsassignedbyadmin' => 'Diesem Account wurde noch keine 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', diff --git a/lng/en.lng.php b/lng/en.lng.php index 45925fc2..51f8f784 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -765,6 +765,7 @@ return [ 'openbasedirenabled' => 'Openbasedir restiction', 'hsts' => 'HSTS enabled', 'aliasdomainid' => 'ID of alias domain', + 'nodomainsassignedbyadmin' => 'Your account has currently no 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', 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 %} From 20755bceadd8b20db613037f87c12748487ec971 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 6 Jun 2023 09:35:12 +0200 Subject: [PATCH 14/87] set version in 2.1.x branch to 2.1.0-alpha1 Signed-off-by: Michael Kaufmann --- lib/Froxlor/Froxlor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php index 935b1092..0c2fa9ee 100644 --- a/lib/Froxlor/Froxlor.php +++ b/lib/Froxlor/Froxlor.php @@ -31,7 +31,7 @@ final class Froxlor { // Main version variable - const VERSION = '2.0.20'; + const VERSION = '2.1.0-alpha1'; // Database version (YYYYMMDDC where C is a daily counter) const DBVERSION = '202305240'; From 9ed45ea7f8c7fef33a832211dfd0e2d0d04413b4 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 6 Jun 2023 09:46:31 +0200 Subject: [PATCH 15/87] make alpha to dev, version check does not know about 'alpha' Signed-off-by: Michael Kaufmann --- lib/Froxlor/Froxlor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php index 0c2fa9ee..549908e4 100644 --- a/lib/Froxlor/Froxlor.php +++ b/lib/Froxlor/Froxlor.php @@ -31,7 +31,7 @@ final class Froxlor { // Main version variable - const VERSION = '2.1.0-alpha1'; + const VERSION = '2.1.0-dev1'; // Database version (YYYYMMDDC where C is a daily counter) const DBVERSION = '202305240'; From 196ef9378a6c9f450f0e33563ff73c029f6c29f1 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 6 Jun 2023 10:10:32 +0200 Subject: [PATCH 16/87] deactivate/reactivate email-accounts for deactivated/reactivated domain; set deactivated-docroot by default to not have deactivated domains point to froxlor login but rather show a message Signed-off-by: Michael Kaufmann --- .gitignore | 1 + install/froxlor.sql.php | 2 +- install/updates/froxlor/update_2.x.inc.php | 9 ++++ lib/Froxlor/Api/Commands/Domains.php | 21 +++++++- lib/Froxlor/Install/Install/Core.php | 1 + templates/misc/deactivated/index.html | 56 ++++++++++++++++++++++ 6 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 templates/misc/deactivated/index.html 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/install/froxlor.sql.php b/install/froxlor.sql.php index 82f49de8..0e06cbe3 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -554,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', ''), diff --git a/install/updates/froxlor/update_2.x.inc.php b/install/updates/froxlor/update_2.x.inc.php index bd443a0d..83906fb0 100644 --- a/install/updates/froxlor/update_2.x.inc.php +++ b/install/updates/froxlor/update_2.x.inc.php @@ -515,5 +515,14 @@ if (Froxlor::isDatabaseVersion('202304260')) { 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/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php index 7fb7991c..d16030e1 100644 --- a/lib/Froxlor/Api/Commands/Domains.php +++ b/lib/Froxlor/Api/Commands/Domains.php @@ -1890,7 +1890,26 @@ class Domains extends ApiCommand implements ResourceEntity // activate/deactivate domain-based services if ($deactivated != $result['deactivated']) { - // @TODO + // deactivate email accounts + $yesno = ($deactivated ? 'N' : 'Y'); + $pop3 = ($deactivated ? '0' : (int)$result['pop3']); + $imap = ($deactivated ? '0' : (int)$result['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; 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/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 +
  • +
+
+ + + + From 6e37b55ac619d6dbe71f9ddfdc62c55afaf4c117 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 6 Jun 2023 10:23:47 +0200 Subject: [PATCH 17/87] more integration of domain deactivated flag Signed-off-by: Michael Kaufmann --- customer_domains.php | 1 + lib/Froxlor/Api/Commands/Emails.php | 5 ++++- lib/Froxlor/Api/Commands/SubDomains.php | 3 +++ lib/Froxlor/CurrentUser.php | 3 ++- lng/de.lng.php | 3 ++- lng/en.lng.php | 3 ++- 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/customer_domains.php b/customer_domains.php index 45348e2f..2fba8e38 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -137,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'] diff --git a/lib/Froxlor/Api/Commands/Emails.php b/lib/Froxlor/Api/Commands/Emails.php index 221efa9b..85c19a49 100644 --- a/lib/Froxlor/Api/Commands/Emails.php +++ b/lib/Froxlor/Api/Commands/Emails.php @@ -88,9 +88,12 @@ class Emails extends ApiCommand implements ResourceEntity $domain_check = $this->apiCall('SubDomains.get', [ 'domainname' => $domain ], true); - if ($domain_check['isemaildomain'] == 0) { + if ((int)$domain_check['isemaildomain'] == 0) { Response::standardError('maindomainnonexist', $domain, true); } + if ((int)$domain_check['deactivated'] == 1) { + Response::standardError('maindomaindeactivated', $domain, true); + } if (Settings::Get('catchall.catchall_enabled') != '1') { $iscatchall = 0; diff --git a/lib/Froxlor/Api/Commands/SubDomains.php b/lib/Froxlor/Api/Commands/SubDomains.php index fdc6e9c8..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 diff --git a/lib/Froxlor/CurrentUser.php b/lib/Froxlor/CurrentUser.php index 6c814b47..45168176 100644 --- a/lib/Froxlor/CurrentUser.php +++ b/lib/Froxlor/CurrentUser.php @@ -144,7 +144,7 @@ class CurrentUser $result_stmt = Database::prepare(" SELECT COUNT(`id`) as emaildomains FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `customerid`= :cid AND `isemaildomain` = '1' + WHERE `customerid`= :cid AND `isemaildomain` = '1' AND `deactivated` = '0' "); $result = Database::pexecute_first($result_stmt, [ "cid" => $_SESSION['userinfo']['customerid'] @@ -156,6 +156,7 @@ class CurrentUser $_SESSION['userinfo'], ['sql_search' => [ 'd.parentdomainid' => 0, + 'd.deactivated' => 0, 'd.id' => ['op' => '<>', 'value' => $_SESSION['userinfo']['standardsubdomain']] ] ] diff --git a/lng/de.lng.php b/lng/de.lng.php index 0c6e3353..c02a5e2b 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -699,7 +699,7 @@ return [ 'openbasedirenabled' => 'Openbasedir Einschränkung', 'hsts' => 'HSTS aktiviert', 'aliasdomainid' => 'ID der Alias-Domain', - 'nodomainsassignedbyadmin' => 'Diesem Account wurde noch keine Domain zugewiesen. Bitte kontaktiere deinen Administrator, wenn du der Meinung bist, das ist nicht korrekt.', + '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".', diff --git a/lng/en.lng.php b/lng/en.lng.php index 51f8f784..234a8ca6 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -765,7 +765,7 @@ return [ 'openbasedirenabled' => 'Openbasedir restiction', 'hsts' => 'HSTS enabled', 'aliasdomainid' => 'ID of alias domain', - 'nodomainsassignedbyadmin' => 'Your account has currently no domains assigned to it. Please contact your administrator if you think this is wrong.', + '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', @@ -836,6 +836,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"', From 03257f04cba9c12f787ee7816044da3aa7708556 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 6 Jun 2023 14:10:07 +0200 Subject: [PATCH 18/87] more integration of domain deactivated flag Signed-off-by: Michael Kaufmann --- lib/Froxlor/Api/Commands/Domains.php | 4 ++-- lib/Froxlor/Api/Commands/EmailAccounts.php | 4 ++++ lib/Froxlor/UI/Callbacks/Style.php | 6 +++--- lib/navigation/00.froxlor.main.php | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/Froxlor/Api/Commands/Domains.php b/lib/Froxlor/Api/Commands/Domains.php index d16030e1..e916c072 100644 --- a/lib/Froxlor/Api/Commands/Domains.php +++ b/lib/Froxlor/Api/Commands/Domains.php @@ -1892,8 +1892,8 @@ class Domains extends ApiCommand implements ResourceEntity if ($deactivated != $result['deactivated']) { // deactivate email accounts $yesno = ($deactivated ? 'N' : 'Y'); - $pop3 = ($deactivated ? '0' : (int)$result['pop3']); - $imap = ($deactivated ? '0' : (int)$result['imap']); + $pop3 = ($deactivated ? '0' : (int)$customer['pop3']); + $imap = ($deactivated ? '0' : (int)$customer['imap']); $upd_stmt = Database::prepare(" UPDATE `" . TABLE_MAIL_USERS . "` diff --git a/lib/Froxlor/Api/Commands/EmailAccounts.php b/lib/Froxlor/Api/Commands/EmailAccounts.php index 605a6daa..e560fd0d 100644 --- a/lib/Froxlor/Api/Commands/EmailAccounts.php +++ b/lib/Froxlor/Api/Commands/EmailAccounts.php @@ -95,9 +95,13 @@ class EmailAccounts extends ApiCommand implements ResourceEntity $customer = $this->getCustomerData('email_accounts'); // check for imap||pop3 == 1, see #1298 + // d00p, 6.5.2023 @revert this - if a customer has resources which allow email accounts + // it implicitly allowed SMTP, e.g. sending of emails which also requires an account to exist + /* if ($customer['imap'] != '1' && $customer['pop3'] != '1') { Response::standardError('notallowedtouseaccounts', '', true); } + */ if (!empty($emailaddr)) { $idna_convert = new IdnaWrapper(); diff --git a/lib/Froxlor/UI/Callbacks/Style.php b/lib/Froxlor/UI/Callbacks/Style.php index 6f19bee8..1019151c 100644 --- a/lib/Froxlor/UI/Callbacks/Style.php +++ b/lib/Froxlor/UI/Callbacks/Style.php @@ -60,18 +60,18 @@ class Style $today = time(); $termination_css = 'bg-warning'; if ($cdate < $today) { - $termination_css = 'bg-danger'; + $termination_css = 'bg-danger text-light'; } } $deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated']; - return $deactivated ? 'bg-info' : $termination_css; + 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/navigation/00.froxlor.main.php b/lib/navigation/00.froxlor.main.php index d65819fb..45568057 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', From 0c3ac31231a8696cc206a9df8e43ffd5f331fdf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maurice=20Preu=C3=9F=20=28envoyr=29?= Date: Tue, 6 Jun 2023 14:38:08 +0200 Subject: [PATCH 19/87] initial backup feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurice Preuß (envoyr) --- actions/admin/settings/230.backup.php | 124 +++++++++++ admin_backups.php | 75 +++++++ lib/Froxlor/Api/Commands/Backups.php | 201 ++++++++++++++++++ .../admin/backups/formfield.backups_add.php | 33 +++ .../admin/backups/formfield.backups_edit.php | 33 +++ .../backups/formfield.backups_restore.php | 33 +++ lib/formfields/admin/backups/index.html | 0 lib/navigation/00.froxlor.main.php | 6 + .../admin/tablelisting.backups.php | 122 +++++++++++ lib/tables.inc.php | 1 + lng/en.lng.php | 8 + 11 files changed, 636 insertions(+) create mode 100644 actions/admin/settings/230.backup.php create mode 100644 admin_backups.php create mode 100644 lib/Froxlor/Api/Commands/Backups.php create mode 100644 lib/formfields/admin/backups/formfield.backups_add.php create mode 100644 lib/formfields/admin/backups/formfield.backups_edit.php create mode 100644 lib/formfields/admin/backups/formfield.backups_restore.php create mode 100644 lib/formfields/admin/backups/index.html create mode 100644 lib/tablelisting/admin/tablelisting.backups.php diff --git a/actions/admin/settings/230.backup.php b/actions/admin/settings/230.backup.php new file mode 100644 index 00000000..87a211c7 --- /dev/null +++ b/actions/admin/settings/230.backup.php @@ -0,0 +1,124 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +return [ + 'groups' => [ + 'backup' => [ + 'title' => lng('backup'), + 'icon' => 'fa-solid fa-sliders', + 'advanced_mode' => true, + 'fields' => [ + 'system_backup_enabled' => [ + 'label' => lng('serversettings.backup_enabled'), + 'settinggroup' => 'system', + 'varname' => 'diskquota_enabled', + 'type' => 'checkbox', + 'default' => false, + 'save_method' => 'storeSettingField', + 'overview_option' => true + ], + 'system_backup_type' => [ + 'label' => lng('serversettings.backup_type'), + 'settinggroup' => 'system', + 'varname' => 'backup_type', + 'type' => 'select', + 'default' => 'S3', + 'select_var' => [ + 'Local' => lng('serversettings.local'), + 'SFTP' => lng('serversettings.sftp'), + 'FTPS' => lng('serversettings.ftps'), + 'S3' => lng('serversettings.s3'), + ], + 'save_method' => 'storeSettingField', + 'overview_option' => true, + ], + 'system_backup_region' => [ + 'label' => lng('serversettings.backup_region'), + 'settinggroup' => 'system', + 'varname' => 'backup_region', + 'type' => 'text', + 'default' => 'eu-central-1', + 'save_method' => 'storeSettingField', + ], + 'system_backup_bucket' => [ + 'label' => lng('serversettings.backup_bucket'), + 'settinggroup' => 'system', + 'varname' => 'backup_bucket', + 'type' => 'text', + 'default' => '', + 'save_method' => 'storeSettingField', + ], + 'system_backup_destination_path' => [ + 'label' => lng('serversettings.backup_destination_path'), + 'settinggroup' => 'system', + 'varname' => 'backup_destination_path', + 'type' => 'text', + 'default' => 'backups', + 'save_method' => 'storeSettingField', + ], + 'system_backup_hostname' => [ + 'label' => lng('serversettings.backup_hostname'), + 'settinggroup' => 'system', + 'varname' => 'backup_hostname', + 'type' => 'text', + 'default' => '', + 'save_method' => 'storeSettingField', + ], + 'system_backup_username' => [ + 'label' => lng('serversettings.backup_username'), + 'settinggroup' => 'system', + 'varname' => 'backup_username', + 'type' => 'text', + 'default' => '', + 'save_method' => 'storeSettingField', + ], + 'system_backup_password' => [ + 'label' => lng('serversettings.backup_password'), + 'settinggroup' => 'system', + 'varname' => 'backup_password', + 'type' => 'password', + 'default' => '', + 'save_method' => 'storeSettingField', + ], + 'system_backup_pgp_public_key' => [ + 'label' => lng('serversettings.backup_pgp_public_key'), + 'settinggroup' => 'system', + 'varname' => 'backup_pgp_public_key', + 'type' => 'textarea', + 'default' => '', + 'save_method' => 'storeSettingField', + ], + 'system_backup_retention' => [ + 'label' => lng('serversettings.backup_retention'), + 'settinggroup' => 'system', + 'varname' => 'backup_retention', + 'type' => 'number', + 'default' => 3, + 'save_method' => 'storeSettingField', + ], + ] + ] + ] +]; diff --git a/admin_backups.php b/admin_backups.php new file mode 100644 index 00000000..d34933f0 --- /dev/null +++ b/admin_backups.php @@ -0,0 +1,75 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +const AREA = 'admin'; +require __DIR__ . '/lib/init.php'; + +use Froxlor\Api\Commands\Backups; +use Froxlor\FroxlorLogger; +use Froxlor\UI\Collection; +use Froxlor\UI\Listing; +use Froxlor\UI\Panel\UI; +use Froxlor\UI\Request; +use Froxlor\UI\Response; + +$id = (int)Request::any('id'); + +if (($page == 'admins' || $page == 'overview') && $userinfo['change_serversettings'] == '1') { + if ($action == '') { + $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "viewed admin_backups"); + + try { + $admin_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backups.php'; + $collection = (new Collection(Backups::class, $userinfo)) + ->withPagination($admin_list_data['backups_list']['columns'], $admin_list_data['backups_list']['default_sorting']); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + + UI::view('user/table.html.twig', [ + 'listing' => Listing::format($collection, $admin_list_data, 'backups_list'), + 'actions_links' => [ + [ + 'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'add']), + 'label' => lng('admin.backups_add') + ], + [ + 'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'restore']), + 'label' => lng('admin.backups_restore'), + 'icon' => 'fa-solid fa-file-import', + 'class' => 'btn-outline-secondary' + ] + ] + ]); + } elseif ($action == 'delete' && $id != 0) { + + } elseif ($action == 'add') { + + } elseif ($action == 'edit' && $id != 0) { + + } elseif ($action == 'restore') { + + } +} diff --git a/lib/Froxlor/Api/Commands/Backups.php b/lib/Froxlor/Api/Commands/Backups.php new file mode 100644 index 00000000..638b4912 --- /dev/null +++ b/lib/Froxlor/Api/Commands/Backups.php @@ -0,0 +1,201 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +namespace Froxlor\Api\Commands; + +use Exception; +use Froxlor\Api\ApiCommand; +use Froxlor\Api\ResourceEntity; +use Froxlor\Database\Database; +use Froxlor\FroxlorLogger; +use Froxlor\Idna\IdnaWrapper; +use Froxlor\Settings; +use Froxlor\System\Crypt; +use Froxlor\UI\Response; +use Froxlor\User; +use Froxlor\Validate\Validate; +use PDO; + +/** + * @since 2.1.0 + */ +class Backups extends ApiCommand implements ResourceEntity +{ + + /** + * increase resource-usage + * + * @param int $adminid + * @param string $resource + * @param string $extra + * optional, default empty + * @param int $increase_by + * optional, default 1 + */ + public static function increaseUsage($adminid = 0, $resource = null, $extra = '', $increase_by = 1) + { + self::updateResourceUsage(TABLE_PANEL_BACKUPS, 'adminid', $adminid, '+', $resource, $extra, $increase_by); + } + + /** + * decrease resource-usage + * + * @param int $adminid + * @param string $resource + * @param string $extra + * optional, default empty + * @param int $decrease_by + * optional, default 1 + */ + public static function decreaseUsage($adminid = 0, $resource = null, $extra = '', $decrease_by = 1) + { + self::updateResourceUsage(TABLE_PANEL_BACKUPS, 'adminid', $adminid, '-', $resource, $extra, $decrease_by); + } + + /** + * lists all admin entries + * + * @param array $sql_search + * optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =), + * LIKE is used if left empty and 'value' => searchvalue + * @param int $sql_limit + * optional specify number of results to be returned + * @param int $sql_offset + * optional specify offset for resultset + * @param array $sql_orderby + * optional array with index = fieldname and value = ASC|DESC to order the resultset by one or more + * fields + * + * @access admin + * @return string json-encoded array count|list + * @throws Exception + */ + public function listing() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list backups"); + $query_fields = []; + $result_stmt = Database::prepare(" + SELECT `b`.*, `a`.`loginname` as `adminname` + FROM `" . TABLE_PANEL_BACKUPS . "` `b` + LEFT JOIN `" . TABLE_PANEL_ADMINS . "` `a` USING(`adminid`) + "); + Database::pexecute($result_stmt, $query_fields, true, true); + $result = []; + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + return $this->response([ + 'count' => count($result), + 'list' => $result + ]); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + /** + * returns the total number of backups for the given admin + * + * @access admin + * @return string json-encoded response message + * @throws Exception + */ + public function listingCount() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + $result_stmt = Database::prepare(" + SELECT COUNT(*) as num_backups + FROM `" . TABLE_PANEL_BACKUPS . "` + "); + $result = Database::pexecute_first($result_stmt, null, true, true); + if ($result) { + return $this->response($result['num_backups']); + } + $this->response(0); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + /** + * create a new admin user + * + * @param string $name + * + * @access admin + * @return string json-encoded array + * @throws Exception + */ + public function add() + { + throw new Exception("Not allowed to execute given command.", 403); + } + + /** + * return an admin entry by either id or loginname + * + * @param int $id + * optional, the admin-id + * @param string $loginname + * optional, the loginname + * + * @access admin + * @return string json-encoded array + * @throws Exception + */ + public function get() + { + throw new Exception("Not allowed to execute given command.", 403); + } + + /** + * update an admin user by given id or loginname + * + * @param int $id + * required, the admin-id + * + * @access admin + * @return string json-encoded array + * @throws Exception + */ + public function update() + { + throw new Exception("Not allowed to execute given command.", 403); + } + + /** + * delete a admin entry by either id or loginname + * + * @param int $id + * required, the admin-id + * + * @access admin + * @return string json-encoded array + * @throws Exception + */ + public function delete() + { + throw new Exception("Not allowed to execute given command.", 403); + } +} diff --git a/lib/formfields/admin/backups/formfield.backups_add.php b/lib/formfields/admin/backups/formfield.backups_add.php new file mode 100644 index 00000000..2b2546ab --- /dev/null +++ b/lib/formfields/admin/backups/formfield.backups_add.php @@ -0,0 +1,33 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +return [ + 'backups_add' => [ + 'title' => lng('backups.backups_add'), + 'image' => 'fa-solid fa-file-archive', + 'self_overview' => ['section' => 'backups', 'page' => 'backups'], + 'sections' => [] + ], +]; diff --git a/lib/formfields/admin/backups/formfield.backups_edit.php b/lib/formfields/admin/backups/formfield.backups_edit.php new file mode 100644 index 00000000..dea4d84f --- /dev/null +++ b/lib/formfields/admin/backups/formfield.backups_edit.php @@ -0,0 +1,33 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +return [ + 'backups_edit' => [ + 'title' => lng('backups.backups_edit'), + 'image' => 'fa-solid fa-file-archive', + 'self_overview' => ['section' => 'backups', 'page' => 'admins'], + 'sections' => [] + ], +]; diff --git a/lib/formfields/admin/backups/formfield.backups_restore.php b/lib/formfields/admin/backups/formfield.backups_restore.php new file mode 100644 index 00000000..f146bdec --- /dev/null +++ b/lib/formfields/admin/backups/formfield.backups_restore.php @@ -0,0 +1,33 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +return [ + 'backups_restore' => [ + 'title' => lng('backups.backups_restore'), + 'image' => 'fa-solid fa-file-archive', + 'self_overview' => ['section' => 'backups', 'page' => 'backups'], + 'sections' => [] + ], +]; diff --git a/lib/formfields/admin/backups/index.html b/lib/formfields/admin/backups/index.html new file mode 100644 index 00000000..e69de29b diff --git a/lib/navigation/00.froxlor.main.php b/lib/navigation/00.froxlor.main.php index d65819fb..b01e5f43 100644 --- a/lib/navigation/00.froxlor.main.php +++ b/lib/navigation/00.froxlor.main.php @@ -223,6 +223,12 @@ return [ 'required_resources' => 'customers', 'add_shortlink' => 'admin_plans.php?page=overview&action=add' ], + [ + 'url' => 'admin_backups.php?page=overview', + 'label' => lng('admin.backups.backups'), + 'required_resources' => 'change_serversettings', + 'add_shortlink' => 'admin_backups.php?page=overview&action=add' + ], [ 'url' => 'admin_settings.php?page=updatecounters', 'label' => lng('admin.updatecounters'), diff --git a/lib/tablelisting/admin/tablelisting.backups.php b/lib/tablelisting/admin/tablelisting.backups.php new file mode 100644 index 00000000..b5b2b20e --- /dev/null +++ b/lib/tablelisting/admin/tablelisting.backups.php @@ -0,0 +1,122 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +use Froxlor\UI\Callbacks\Admin; +use Froxlor\UI\Callbacks\Customer; +use Froxlor\UI\Callbacks\Impersonate; +use Froxlor\UI\Callbacks\ProgressBar; +use Froxlor\UI\Callbacks\Style; +use Froxlor\UI\Callbacks\Text; +use Froxlor\UI\Listing; + +return [ + 'backups_list' => [ + 'title' => lng('admin.backups.backups'), + 'icon' => 'fa-solid fa-file-archive', + 'self_overview' => ['section' => 'admins', 'page' => 'admins'], + 'default_sorting' => ['loginname' => 'asc'], + 'columns' => [ + 'id' => [ + 'label' => 'ID', + 'field' => 'id', + 'sortable' => true, + ], + 'customerid' => [ + 'label' => lng('customerid'), + 'field' => 'customerid', + 'sortable' => true, + ], + 'loginname' => [ + 'label' => lng('login.username'), + 'field' => 'loginname', + 'callback' => [Impersonate::class, 'customer'], + 'sortable' => true, + ], + 'adminid' => [ + 'label' => lng('adminid'), + 'field' => 'adminid', + 'sortable' => true, + ], + 'adminname' => [ + 'label' => lng('admin.admin'), + 'field' => 'adminname', + 'callback' => [Impersonate::class, 'admin'], + 'sortable' => true, + ], + 'size' => [ + 'label' => lng('backup.size'), + 'field' => 'size', + 'sortable' => true, + ], + 'created_at' => [ + 'label' => lng('backup.created_at'), + 'field' => 'created_at', + 'sortable' => true, + ], + ], + 'visible_columns' => Listing::getVisibleColumnsForListing('admin_list', [ + 'id', + 'adminname', + 'loginname', + 'size', + 'created_at', + ]), + 'actions' => [ + 'show' => [ + 'icon' => 'fa-solid fa-eye', + 'title' => lng('usersettings.custom_notes.title'), + 'modal' => [Text::class, 'customerNoteDetailModal'], + 'visible' => [Customer::class, 'hasNote'] + ], + 'edit' => [ + 'icon' => 'fa-solid fa-edit', + 'title' => lng('panel.edit'), + 'href' => [ + 'section' => 'backups', + 'page' => 'backups', + 'action' => 'edit', + 'id' => ':id' + ], + ], + 'delete' => [ + 'icon' => 'fa-solid fa-trash', + 'title' => lng('panel.delete'), + 'class' => 'btn-danger', + 'href' => [ + 'section' => 'backups', + 'page' => 'backups', + 'action' => 'delete', + 'id' => ':id' + ], + 'visible' => [Admin::class, 'isNotMe'] + ], + ], + 'format_callback' => [ + [Style::class, 'deactivated'], + [Style::class, 'diskspaceWarning'], + [Style::class, 'trafficWarning'] + ] + ] +]; diff --git a/lib/tables.inc.php b/lib/tables.inc.php index 43c52b08..7476a9ab 100644 --- a/lib/tables.inc.php +++ b/lib/tables.inc.php @@ -32,6 +32,7 @@ const TABLE_MAIL_USERS = 'mail_users'; const TABLE_MAIL_VIRTUAL = 'mail_virtual'; const TABLE_PANEL_ACTIVATION = 'panel_activation'; const TABLE_PANEL_ADMINS = 'panel_admins'; +const TABLE_PANEL_BACKUPS = 'panel_backups'; const TABLE_PANEL_CUSTOMERS = 'panel_customers'; const TABLE_PANEL_DATABASES = 'panel_databases'; const TABLE_PANEL_DOMAINS = 'panel_domains'; diff --git a/lng/en.lng.php b/lng/en.lng.php index 0a05daf4..617af153 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -500,6 +500,9 @@ return [ 'adminguide' => 'Admin guide', 'userguide' => 'User guide', 'apiguide' => 'API guide', + 'backups' => [ + 'backups' => 'Backups', + ], ], 'apcuinfo' => [ 'clearcache' => 'Clear APCu cache', @@ -2420,4 +2423,9 @@ Yours sincerely, your administrator', 'config_note' => 'In order for froxlor to be able to communicate properly with the backend, you have to configure it.', 'config_now' => 'Configure now' ], + 'backup' => [ + 'backup' => 'Backup', + 'size' => 'Size', + 'created_at' => 'Created at', + ], ]; From 94051dc9eb14a9dd3e8779e869347b9ca0d7d6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maurice=20Preu=C3=9F=20=28envoyr=29?= Date: Tue, 6 Jun 2023 17:30:12 +0200 Subject: [PATCH 20/87] add backup settings and update cron fork MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurice Preuß (envoyr) --- actions/admin/settings/110.accounts.php | 2 +- actions/admin/settings/230.backup.php | 53 +++++++++------- install/froxlor.sql.php | 13 +++- install/updates/froxlor/update_2.x.inc.php | 47 ++++++++++++++ lib/Froxlor/Cli/MasterCron.php | 17 +++--- lib/Froxlor/Cron/Backup/BackupCron.php | 61 +++++++++++++++++++ lib/Froxlor/Cron/Forkable.php | 58 ++++++++++++++++++ .../System/{BackupCron.php => ExportCron.php} | 57 +++-------------- lib/Froxlor/Cron/Traffic/TrafficCron.php | 52 ++-------------- lib/Froxlor/Validate/Check.php | 26 ++++++++ 10 files changed, 259 insertions(+), 127 deletions(-) create mode 100644 lib/Froxlor/Cron/Backup/BackupCron.php create mode 100644 lib/Froxlor/Cron/Forkable.php rename lib/Froxlor/Cron/System/{BackupCron.php => ExportCron.php} (86%) diff --git a/actions/admin/settings/110.accounts.php b/actions/admin/settings/110.accounts.php index 7a0b92ab..3ba87597 100644 --- a/actions/admin/settings/110.accounts.php +++ b/actions/admin/settings/110.accounts.php @@ -236,7 +236,7 @@ return [ 'varname' => 'backupenabled', 'type' => 'checkbox', 'default' => false, - 'cronmodule' => 'froxlor/backup', + 'cronmodule' => 'froxlor/export', 'save_method' => 'storeSettingField' ], 'system_createstdsubdom_default' => [ diff --git a/actions/admin/settings/230.backup.php b/actions/admin/settings/230.backup.php index 87a211c7..01ba96ed 100644 --- a/actions/admin/settings/230.backup.php +++ b/actions/admin/settings/230.backup.php @@ -32,19 +32,20 @@ return [ 'fields' => [ 'system_backup_enabled' => [ 'label' => lng('serversettings.backup_enabled'), - 'settinggroup' => 'system', - 'varname' => 'diskquota_enabled', + 'settinggroup' => 'backup', + 'varname' => 'enabled', 'type' => 'checkbox', 'default' => false, 'save_method' => 'storeSettingField', - 'overview_option' => true + 'overview_option' => true, + 'cronmodule' => 'froxlor/backup' ], 'system_backup_type' => [ 'label' => lng('serversettings.backup_type'), - 'settinggroup' => 'system', - 'varname' => 'backup_type', + 'settinggroup' => 'backup', + 'varname' => 'type', 'type' => 'select', - 'default' => 'S3', + 'default' => 'Local', 'select_var' => [ 'Local' => lng('serversettings.local'), 'SFTP' => lng('serversettings.sftp'), @@ -56,66 +57,72 @@ return [ ], 'system_backup_region' => [ 'label' => lng('serversettings.backup_region'), - 'settinggroup' => 'system', - 'varname' => 'backup_region', + 'settinggroup' => 'backup', + 'varname' => 'region', 'type' => 'text', 'default' => 'eu-central-1', 'save_method' => 'storeSettingField', ], 'system_backup_bucket' => [ 'label' => lng('serversettings.backup_bucket'), - 'settinggroup' => 'system', - 'varname' => 'backup_bucket', + 'settinggroup' => 'backup', + 'varname' => 'bucket', 'type' => 'text', 'default' => '', 'save_method' => 'storeSettingField', ], 'system_backup_destination_path' => [ 'label' => lng('serversettings.backup_destination_path'), - 'settinggroup' => 'system', - 'varname' => 'backup_destination_path', + 'settinggroup' => 'backup', + 'varname' => 'destination_path', 'type' => 'text', - 'default' => 'backups', + 'string_type' => 'confdir', + 'default' => '/srv/backups/', 'save_method' => 'storeSettingField', ], 'system_backup_hostname' => [ 'label' => lng('serversettings.backup_hostname'), - 'settinggroup' => 'system', - 'varname' => 'backup_hostname', + 'settinggroup' => 'backup', + 'varname' => 'hostname', 'type' => 'text', 'default' => '', 'save_method' => 'storeSettingField', ], 'system_backup_username' => [ 'label' => lng('serversettings.backup_username'), - 'settinggroup' => 'system', - 'varname' => 'backup_username', + 'settinggroup' => 'backup', + 'varname' => 'username', 'type' => 'text', 'default' => '', 'save_method' => 'storeSettingField', ], 'system_backup_password' => [ 'label' => lng('serversettings.backup_password'), - 'settinggroup' => 'system', - 'varname' => 'backup_password', + 'settinggroup' => 'backup', + 'varname' => 'password', 'type' => 'password', 'default' => '', 'save_method' => 'storeSettingField', ], 'system_backup_pgp_public_key' => [ 'label' => lng('serversettings.backup_pgp_public_key'), - 'settinggroup' => 'system', - 'varname' => 'backup_pgp_public_key', + 'settinggroup' => 'backup', + 'varname' => 'pgp_public_key', 'type' => 'textarea', 'default' => '', 'save_method' => 'storeSettingField', + 'plausibility_check_method' => [ + '\\Froxlor\\Validate\\Check', + 'checkPgpPublicKeySetting' + ], ], 'system_backup_retention' => [ 'label' => lng('serversettings.backup_retention'), - 'settinggroup' => 'system', - 'varname' => 'backup_retention', + 'settinggroup' => 'backup', + 'varname' => 'retention', 'type' => 'number', 'default' => 3, + 'min' => 0, 'save_method' => 'storeSettingField', ], ] diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index 0e06cbe3..5c627a93 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -700,6 +700,16 @@ opcache.validate_timestamps'), ('system', 'traffictool', 'goaccess'), ('system', 'req_limit_per_interval', 60), ('system', 'req_limit_interval', 60), + ('backup', 'enabled', 0), + ('backup', 'type', 'Local'), + ('backup', 'region', ''), + ('backup', 'bucket', ''), + ('backup', 'destination_path', '/srv/backups/'), + ('backup', 'hostname', ''), + ('backup', 'username', ''), + ('backup', 'password', ''), + ('backup', 'pgp_public_key', ''), + ('backup', 'retention', '3'), ('api', 'enabled', '0'), ('api', 'customer_default', '1'), ('2fa', 'enabled', '1'), @@ -913,7 +923,8 @@ INSERT INTO `cronjobs_run` (`id`, `module`, `cronfile`, `cronclass`, `interval`, (3, 'froxlor/reports', 'usage_report', '\\Froxlor\\Cron\\Traffic\\ReportsCron', '1 DAY', '1', 'cron_usage_report'), (4, 'froxlor/core', 'mailboxsize', '\\Froxlor\\Cron\\System\\MailboxsizeCron', '6 HOUR', '1', 'cron_mailboxsize'), (5, 'froxlor/letsencrypt', 'letsencrypt', '\\Froxlor\\Cron\\Http\\LetsEncrypt\\AcmeSh', '5 MINUTE', '0', 'cron_letsencrypt'), - (6, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\System\\BackupCron', '1 DAY', '0', 'cron_backup'); + (6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 DAY', '0', 'cron_export'); + (7, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\Backup\\BackupCron', '1 DAY', '0', 'cron_backup'); DROP TABLE IF EXISTS `ftp_quotalimits`; diff --git a/install/updates/froxlor/update_2.x.inc.php b/install/updates/froxlor/update_2.x.inc.php index 83906fb0..aa4eb8d4 100644 --- a/install/updates/froxlor/update_2.x.inc.php +++ b/install/updates/froxlor/update_2.x.inc.php @@ -524,5 +524,52 @@ if (Froxlor::isDatabaseVersion('202304260')) { Update::lastStepStatus(1, 'Customized setting, not changing'); } + Update::showUpdateStep("Creating new tables and fields for backups"); + Database::query("DROP TABLE IF EXISTS `panel_backups`;"); + $sql = "CREATE TABLE `panel_backups` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `adminid` int(11) NOT NULL, + `customerid` int(11) NOT NULL, + `loginname` varchar(255) NOT NULL, + `size` bigint(20) NOT NULL, + `created_at` int(15) NOT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + Database::query($sql); + Update::lastStepStatus(0); + + Update::showUpdateStep("Adding new backup settings"); + Settings::AddNew('backup.enabled', 0); + Settings::AddNew('backup.type', 'Local'); + Settings::AddNew('backup.region', ''); + Settings::AddNew('backup.bucket', ''); + Settings::AddNew('backup.destination_path', '/srv/backups/'); + Settings::AddNew('backup.hostname', ''); + Settings::AddNew('backup.username', ''); + Settings::AddNew('backup.password', ''); + Settings::AddNew('backup.pgp_public_key', ''); + Settings::AddNew('backup.retention', 3); + Update::lastStepStatus(0); + + Update::showUpdateStep("Adjusting cronjobs"); + Database::query(" + UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET + `module`= 'froxlor/export', + `cronfile` = 'export', + `cronclass` = '\\Froxlor\\Cron\\System\\ExportCron', + `desc_lng_key` = 'cron_export' + WHERE `module` = 'froxlor/backup' + "); + Database::query(" + INSERT INTO `" . TABLE_PANEL_CRONRUNS . "` SET + `module`= 'froxlor/backup', + `cronfile` = 'backup', + `cronclass` = '\\Froxlor\\Cron\\Backup\\BackupCron', + `interval` = '1 DAY', + `isactive` = '0', + `desc_lng_key` = 'cron_backup' + "); + Update::lastStepStatus(0); + Froxlor::updateToDbVersion('202305240'); } diff --git a/lib/Froxlor/Cli/MasterCron.php b/lib/Froxlor/Cli/MasterCron.php index ec85d230..dd361d50 100644 --- a/lib/Froxlor/Cli/MasterCron.php +++ b/lib/Froxlor/Cli/MasterCron.php @@ -52,7 +52,7 @@ final class MasterCron extends CliCommand $this->setDescription('Regulary perform tasks created by froxlor'); $this->addArgument('job', InputArgument::IS_ARRAY, 'Job(s) to run'); $this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 10 = re-set quotas, 99 = re-create cron.d-file]') - ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces re-generating of config-files (webserver, nameserver, etc.)') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces given job or, if none given, forces re-generating of config-files (webserver, nameserver, etc.)') ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Output debug information about what is going on to STDOUT.') ->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).'); } @@ -71,12 +71,13 @@ final class MasterCron extends CliCommand // handle force option if ($input->getOption('force')) { - // rebuild all config files - Cronjob::inserttask(TaskId::REBUILD_VHOST); - Cronjob::inserttask(TaskId::REBUILD_DNS); - Cronjob::inserttask(TaskId::CREATE_QUOTA); - Cronjob::inserttask(TaskId::REBUILD_CRON); - array_push($jobs, 'tasks'); + if (empty($jobs) || in_array('tasks', $jobs)) { + Cronjob::inserttask(TaskId::REBUILD_VHOST); + Cronjob::inserttask(TaskId::REBUILD_DNS); + Cronjob::inserttask(TaskId::CREATE_QUOTA); + Cronjob::inserttask(TaskId::REBUILD_CRON); + array_push($jobs, 'tasks'); + } define('CRON_IS_FORCED', 1); } // handle debug option @@ -91,7 +92,7 @@ final class MasterCron extends CliCommand if ($input->getOption('run-task')) { $tasks_to_run = $input->getOption('run-task'); foreach ($tasks_to_run as $ttr) { - if (in_array($ttr, [1, 4, 10, 99])) { + if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) { Cronjob::inserttask($ttr); array_push($jobs, 'tasks'); } else { diff --git a/lib/Froxlor/Cron/Backup/BackupCron.php b/lib/Froxlor/Cron/Backup/BackupCron.php new file mode 100644 index 00000000..248cbe34 --- /dev/null +++ b/lib/Froxlor/Cron/Backup/BackupCron.php @@ -0,0 +1,61 @@ + + * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 + */ + +namespace Froxlor\Cron\Backup; + +use Froxlor\Cron\Forkable; +use Froxlor\Cron\FroxlorCron; + +class BackupCron extends FroxlorCron +{ + use Forkable; + + public static function run() + { + $users = ['web1', 'web2', 'web3', 'web4', 'web5', 'web6', 'web7', 'web8', 'web9', 'web10']; + + self::runFork([self::class, 'handle'], [ + [ + 'user' => '1', + 'data' => 'value1', + ], + [ + 'user' => '2', + 'data' => 'value2', + ] + ]); + } + + private static function handle($user, $data) + { + echo "BackupCron: started - creating customer backup for user $user\n"; + + echo $data . "\n"; + + sleep(rand(1, 3)); + + echo "BackupCron: finished - creating customer backup for user $user\n"; + } +} diff --git a/lib/Froxlor/Cron/Forkable.php b/lib/Froxlor/Cron/Forkable.php new file mode 100644 index 00000000..884dc6a9 --- /dev/null +++ b/lib/Froxlor/Cron/Forkable.php @@ -0,0 +1,58 @@ += $concurrentChildren) { + foreach($childrenPids as $key => $pid) { + $res = pcntl_waitpid($pid, $status, WNOHANG); + + // If the process has already exited + if($res == -1 || $res > 0) + unset($childrenPids[$key]); + } + + sleep(1); + } + } + } + while(pcntl_waitpid(0, $status) != -1); + } else { + if (!defined('CRON_NOFORK_FLAG')) { + if (extension_loaded('pcntl')) { + $msg = "PHP compiled with pcntl but pcntl_fork function is not available."; + } else { + $msg = "PHP compiled without pcntl."; + } + FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, $msg . " Not forking " . self::class . ", this may take a long time!"); + } + foreach ($attributes as $closureAttributes) { + $closure(...$closureAttributes); + } + } + } +} diff --git a/lib/Froxlor/Cron/System/BackupCron.php b/lib/Froxlor/Cron/System/ExportCron.php similarity index 86% rename from lib/Froxlor/Cron/System/BackupCron.php rename to lib/Froxlor/Cron/System/ExportCron.php index eb67522c..1f2df748 100644 --- a/lib/Froxlor/Cron/System/BackupCron.php +++ b/lib/Froxlor/Cron/System/ExportCron.php @@ -25,59 +25,25 @@ namespace Froxlor\Cron\System; +use Exception; +use Froxlor\Cron\Forkable; use Froxlor\Cron\FroxlorCron; use Froxlor\Database\Database; use Froxlor\FileDir; use Froxlor\FroxlorLogger; use Froxlor\Settings; -class BackupCron extends FroxlorCron +class ExportCron extends FroxlorCron { + use Forkable; public static function run() { - // Check Backup-Lock - if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { - $BackupLock = FileDir::makeCorrectFile("/var/run/froxlor_cron_backup.lock"); - if (file_exists($BackupLock) && is_numeric($BackupPid = file_get_contents($BackupLock))) { - if (function_exists('posix_kill')) { - $BackupPidStatus = @posix_kill($BackupPid, 0); - } else { - system("kill -CHLD " . $BackupPid . " 1> /dev/null 2> /dev/null", $BackupPidStatus); - $BackupPidStatus = !$BackupPidStatus; - } - if ($BackupPidStatus) { - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Backup run already in progress'); - return 1; - } - } - // Create Backup Log and Fork - // We close the database - connection before we fork, so we don't share resources with the child - Database::needRoot(false); // this forces the connection to be set to null - $BackupPid = pcntl_fork(); - // Parent - if ($BackupPid) { - file_put_contents($BackupLock, $BackupPid); - // unnecessary to recreate database connection here - return 0; - } elseif ($BackupPid == 0) { - // Child - posix_setsid(); - // re-create db - Database::needRoot(false); - } else { - // Fork failed - return 1; - } - } elseif (!defined('CRON_NOFORK_FLAG')) { - if (extension_loaded('pcntl')) { - $msg = "PHP compiled with pcntl but pcntl_fork function is not available."; - } else { - $msg = "PHP compiled without pcntl."; - } - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, $msg . " Not forking backup-cron, this may take a long time!"); - } + self::runFork([self::class, 'handle']); + } + public static function handle() + { FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'BackupCron: started - creating customer backup'); $result_tasks_stmt = Database::query(" @@ -113,12 +79,6 @@ class BackupCron extends FroxlorCron 'id' => $row['id'] ]); } - - - if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { - @unlink($BackupLock); - die(); - } } /** @@ -128,6 +88,7 @@ class BackupCron extends FroxlorCron * * @return void * + * @throws Exception */ private static function createCustomerBackup($data = null, $customerdocroot = null, &$cronlog = null) { diff --git a/lib/Froxlor/Cron/Traffic/TrafficCron.php b/lib/Froxlor/Cron/Traffic/TrafficCron.php index f6528802..30d1bd1b 100644 --- a/lib/Froxlor/Cron/Traffic/TrafficCron.php +++ b/lib/Froxlor/Cron/Traffic/TrafficCron.php @@ -30,6 +30,7 @@ namespace Froxlor\Cron\Traffic; * @author Froxlor team (2010-) */ +use Froxlor\Cron\Forkable; use Froxlor\Cron\FroxlorCron; use Froxlor\Database\Database; use Froxlor\FileDir; @@ -42,51 +43,15 @@ use PDO; class TrafficCron extends FroxlorCron { + use Forkable; public static function run() { - // Check Traffic-Lock - if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { - $TrafficLock = FileDir::makeCorrectFile("/var/run/froxlor_cron_traffic.lock"); - if (file_exists($TrafficLock) && is_numeric($TrafficPid = file_get_contents($TrafficLock))) { - if (function_exists('posix_kill')) { - $TrafficPidStatus = @posix_kill($TrafficPid, 0); - } else { - system("kill -CHLD " . $TrafficPid . " 1> /dev/null 2> /dev/null", $TrafficPidStatus); - $TrafficPidStatus = !$TrafficPidStatus; - } - if ($TrafficPidStatus) { - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Traffic Run already in progress'); - return 1; - } - } - // Create Traffic Log and Fork - // We close the database - connection before we fork, so we don't share resources with the child - Database::needRoot(false); // this forces the connection to be set to null - $TrafficPid = pcntl_fork(); - // Parent - if ($TrafficPid) { - file_put_contents($TrafficLock, $TrafficPid); - // unnecessary to recreate database connection here - return 0; - } elseif ($TrafficPid == 0) { - // Child - posix_setsid(); - // re-create db - Database::needRoot(false); - } else { - // Fork failed - return 1; - } - } elseif (!defined('CRON_NOFORK_FLAG')) { - if (extension_loaded('pcntl')) { - $msg = "PHP compiled with pcntl but pcntl_fork function is not available."; - } else { - $msg = "PHP compiled without pcntl."; - } - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, $msg . " Not forking traffic-cron, this may take a long time!"); - } + self::runFork([self::class, 'handle']); + } + public static function handle() + { /** * TRAFFIC AND DISKUSAGE MEASURE */ @@ -611,11 +576,6 @@ class TrafficCron extends FroxlorCron } Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `value` = UNIX_TIMESTAMP() WHERE `settinggroup` = 'system' AND `varname` = 'last_traffic_run'"); - - if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) { - @unlink($TrafficLock); - die(); - } } /** diff --git a/lib/Froxlor/Validate/Check.php b/lib/Froxlor/Validate/Check.php index cb113c72..2d5229b1 100644 --- a/lib/Froxlor/Validate/Check.php +++ b/lib/Froxlor/Validate/Check.php @@ -314,4 +314,30 @@ class Check } return $returnvalue; } + + public static function checkPgpPublicKeySetting($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues) + { + // if the field is empty, we don't need to check anything + if ($newfieldvalue === '') { + return [self::FORMFIELDS_PLAUSIBILITY_CHECK_OK]; + } + + // check if gnupg extension is loaded + if (!extension_loaded('gnupg')) { + return [ + self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR, + 'gnupgextensionnotavailable' + ]; + } + // check if the pgp public key is a valid key + putenv('GNUPGHOME='.sys_get_temp_dir()); + if (gnupg_import(gnupg_init(), $newfieldvalue) === false) { + return [ + self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR, + 'invalidpgppublickey' + ]; + } + + return [self::FORMFIELDS_PLAUSIBILITY_CHECK_OK]; + } } From 69c58d21be0b12150da8876ee35e2e48c5ad5866 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 6 Jun 2023 17:45:14 +0200 Subject: [PATCH 21/87] correctly fork export cron action with new Forkable-trait Signed-off-by: Michael Kaufmann --- lib/Froxlor/Cron/System/ExportCron.php | 64 +++++++++++++------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/lib/Froxlor/Cron/System/ExportCron.php b/lib/Froxlor/Cron/System/ExportCron.php index 1f2df748..b05952b7 100644 --- a/lib/Froxlor/Cron/System/ExportCron.php +++ b/lib/Froxlor/Cron/System/ExportCron.php @@ -38,47 +38,45 @@ class ExportCron extends FroxlorCron use Forkable; public static function run() - { - self::runFork([self::class, 'handle']); - } - - public static function handle() { FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'BackupCron: started - creating customer backup'); $result_tasks_stmt = Database::query(" SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20' ORDER BY `id` ASC "); - - $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :id"); - - $cronlog = FroxlorLogger::getInstanceOf(); $all_jobs = $result_tasks_stmt->fetchAll(); - foreach ($all_jobs as $row) { - if ($row['data'] != '') { - $row['data'] = json_decode($row['data'], true); - } - if (is_array($row['data'])) { - if (isset($row['data']['customerid']) && isset($row['data']['loginname']) && isset($row['data']['destdir'])) { - $row['data']['destdir'] = FileDir::makeCorrectDir($row['data']['destdir']); - $customerdocroot = FileDir::makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $row['data']['loginname'] . '/'); + self::runFork([self::class, 'handle'], $all_jobs); + } - // create folder if not exists - if (!file_exists($row['data']['destdir']) && $row['data']['destdir'] != '/' && $row['data']['destdir'] != Settings::Get('system.documentroot_prefix') && $row['data']['destdir'] != $customerdocroot) { - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating backup-destination path for customer: ' . escapeshellarg($row['data']['destdir'])); - FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir'])); - } + public static function handle(array $row) + { + $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :id"); + $cronlog = FroxlorLogger::getInstanceOf(); - self::createCustomerBackup($row['data'], $customerdocroot, $cronlog); - } - } - - // remove entry - Database::pexecute($del_stmt, [ - 'id' => $row['id'] - ]); + if ($row['data'] != '') { + $row['data'] = json_decode($row['data'], true); } + + if (is_array($row['data'])) { + if (isset($row['data']['customerid']) && isset($row['data']['loginname']) && isset($row['data']['destdir'])) { + $row['data']['destdir'] = FileDir::makeCorrectDir($row['data']['destdir']); + $customerdocroot = FileDir::makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $row['data']['loginname'] . '/'); + + // create folder if not exists + if (!file_exists($row['data']['destdir']) && $row['data']['destdir'] != '/' && $row['data']['destdir'] != Settings::Get('system.documentroot_prefix') && $row['data']['destdir'] != $customerdocroot) { + FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating backup-destination path for customer: ' . escapeshellarg($row['data']['destdir'])); + FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir'])); + } + + self::createCustomerBackup($row['data'], $customerdocroot, $cronlog); + } + } + + // remove entry + Database::pexecute($del_stmt, [ + 'id' => $row['id'] + ]); } /** @@ -193,13 +191,13 @@ class ExportCron extends FroxlorCron // create tar-file $backup_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-backup_' . date('YmdHi', time()) . '.tar.gz' . (!empty($data['pgp_public_key']) ? '.gpg' : '')); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating backup-file "' . $backup_file . '"'); - if (!empty($data['pgp_public_key'])){ + if (!empty($data['pgp_public_key'])) { // pack all archives in tmp-dir to one archive and encrypt it with gpg $recipient_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-recipients.gpg'); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating recipient-file "' . $recipient_file . '"'); file_put_contents($recipient_file, $data['pgp_public_key']); - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file '. escapeshellarg($recipient_file) .' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes'); - FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file '. escapeshellarg($recipient_file) .' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes', $return_value, ['|']); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes'); + FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes', $return_value, ['|']); } else { // pack all archives in tmp-dir to one archive $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); From 4bfed71ac9b106c73f2383f81d06e6e179c16b14 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 6 Jun 2023 17:47:59 +0200 Subject: [PATCH 22/87] fix install sql Signed-off-by: Michael Kaufmann --- install/froxlor.sql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index 5c627a93..6df8754c 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -923,7 +923,7 @@ INSERT INTO `cronjobs_run` (`id`, `module`, `cronfile`, `cronclass`, `interval`, (3, 'froxlor/reports', 'usage_report', '\\Froxlor\\Cron\\Traffic\\ReportsCron', '1 DAY', '1', 'cron_usage_report'), (4, 'froxlor/core', 'mailboxsize', '\\Froxlor\\Cron\\System\\MailboxsizeCron', '6 HOUR', '1', 'cron_mailboxsize'), (5, 'froxlor/letsencrypt', 'letsencrypt', '\\Froxlor\\Cron\\Http\\LetsEncrypt\\AcmeSh', '5 MINUTE', '0', 'cron_letsencrypt'), - (6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 DAY', '0', 'cron_export'); + (6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 DAY', '0', 'cron_export'), (7, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\Backup\\BackupCron', '1 DAY', '0', 'cron_backup'); From 518160292157d7ab55088f5b7f0e5fe09a09d63d Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 6 Jun 2023 17:51:07 +0200 Subject: [PATCH 23/87] fix cronjobs test Signed-off-by: Michael Kaufmann --- tests/Cronjobs/CronjobsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Cronjobs/CronjobsTest.php b/tests/Cronjobs/CronjobsTest.php index 633e537c..d7159554 100644 --- a/tests/Cronjobs/CronjobsTest.php +++ b/tests/Cronjobs/CronjobsTest.php @@ -24,7 +24,7 @@ class CronjobsTest extends TestCase $json_result = Cronjobs::getLocal($admin_userdata)->listingCount(); $result = json_decode($json_result, true)['data']; - $this->assertEquals(6, $result); + $this->assertEquals(7, $result); } public function testCustomerCronjobsListNotAllowed() From e958cfed84ce558469b8e0981603f26d46eda03d Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 7 Jun 2023 11:37:00 +0200 Subject: [PATCH 24/87] big renaming of CustomerBackup to DataDump / export for the real backup-feature to shine :P Signed-off-by: Michael Kaufmann --- actions/admin/settings/100.panel.php | 2 +- actions/admin/settings/110.accounts.php | 6 +- customer_extras.php | 34 +++--- install/froxlor.sql.php | 13 +- install/updates/froxlor/update_2.x.inc.php | 7 ++ .../{CustomerBackups.php => DataDump.php} | 112 +++++++++--------- lib/Froxlor/Cron/Forkable.php | 17 ++- lib/Froxlor/Cron/System/ExportCron.php | 44 +++---- lib/Froxlor/Cron/System/TasksCron.php | 2 +- lib/Froxlor/Cron/TaskId.php | 4 +- lib/Froxlor/System/Cronjob.php | 4 +- ...mfield.backup.php => formfield.export.php} | 27 ++--- lib/navigation/00.froxlor.main.php | 6 +- ...ng.backups.php => tablelisting.export.php} | 32 ++--- lng/cz.lng.php | 8 -- lng/de.lng.php | 43 +++---- lng/en.lng.php | 43 +++---- lng/es.lng.php | 16 +-- ...stomerBackupsTest.php => DataDumpTest.php} | 96 +++++++-------- tests/Cron/TaskIdTest.php | 2 +- 20 files changed, 257 insertions(+), 261 deletions(-) rename lib/Froxlor/Api/Commands/{CustomerBackups.php => DataDump.php} (68%) rename lib/formfields/customer/extras/{formfield.backup.php => formfield.export.php} (73%) rename lib/tablelisting/customer/{tablelisting.backups.php => tablelisting.export.php} (77%) rename tests/Backup/{CustomerBackupsTest.php => DataDumpTest.php} (52%) diff --git a/actions/admin/settings/100.panel.php b/actions/admin/settings/100.panel.php index b1ac1522..18a64def 100644 --- a/actions/admin/settings/100.panel.php +++ b/actions/admin/settings/100.panel.php @@ -265,7 +265,7 @@ return [ 'extras.directoryprotection' => lng('menue.extras.extras') . " / " . lng('menue.extras.directoryprotection'), 'extras.pathoptions' => lng('menue.extras.extras') . " / " . lng('menue.extras.pathoptions'), 'extras.logger' => lng('menue.extras.extras') . " / " . lng('menue.logger.logger'), - 'extras.backup' => lng('menue.extras.extras') . " / " . lng('menue.extras.backup'), + 'extras.export' => lng('menue.extras.extras') . " / " . lng('menue.extras.export'), 'traffic' => lng('menue.traffic.traffic'), 'traffic.http' => lng('menue.traffic.traffic') . " / HTTP", 'traffic.ftp' => lng('menue.traffic.traffic') . " / FTP", diff --git a/actions/admin/settings/110.accounts.php b/actions/admin/settings/110.accounts.php index 3ba87597..cff32356 100644 --- a/actions/admin/settings/110.accounts.php +++ b/actions/admin/settings/110.accounts.php @@ -230,10 +230,10 @@ return [ 'onlyif' => 1 ] ], - 'system_backupenabled' => [ - 'label' => lng('serversettings.backupenabled'), + 'system_exportenabled' => [ + 'label' => lng('serversettings.exportenabled'), 'settinggroup' => 'system', - 'varname' => 'backupenabled', + 'varname' => 'exportenabled', 'type' => 'checkbox', 'default' => false, 'cronmodule' => 'froxlor/export', diff --git a/customer_extras.php b/customer_extras.php index 05ee93d7..f5a850f2 100644 --- a/customer_extras.php +++ b/customer_extras.php @@ -26,7 +26,7 @@ const AREA = 'customer'; require __DIR__ . '/lib/init.php'; -use Froxlor\Api\Commands\CustomerBackups as CustomerBackups; +use Froxlor\Api\Commands\DataDump as DataDump; use Froxlor\Api\Commands\DirOptions as DirOptions; use Froxlor\Api\Commands\DirProtections as DirProtections; use Froxlor\Customer\Customer; @@ -282,18 +282,18 @@ if ($page == 'overview' || $page == 'htpasswds') { } } } -} elseif ($page == 'backup') { +} elseif ($page == 'export') { // redirect if this customer sub-page is hidden via settings - if (Settings::IsInList('panel.customer_hide_options', 'extras.backup')) { + if (Settings::IsInList('panel.customer_hide_options', 'extras.export')) { Response::redirectTo('customer_index.php'); } - if (Settings::Get('system.backupenabled') == 1) { + if (Settings::Get('system.exportenabled') == 1) { if ($action == 'abort') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "customer_extras::backup - aborted scheduled backupjob"); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "customer_extras::export - aborted scheduled data export job"); try { - CustomerBackups::getLocal($userinfo, $_POST)->delete(); + DataDump::getLocal($userinfo, $_POST)->delete(); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } @@ -302,43 +302,43 @@ if ($page == 'overview' || $page == 'htpasswds') { 'action' => '' ]); } else { - HTML::askYesNo('extras_reallydelete_backup', $filename, [ - 'backup_job_entry' => $id, + HTML::askYesNo('extras_reallydelete_export', $filename, [ + 'job_entry' => $id, 'section' => 'extras', 'page' => $page, 'action' => $action ]); } } elseif ($action == '') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_extras::backup"); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_extras::export"); // check whether there is a backup-job for this customer try { - $backup_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.backups.php'; - $collection = (new Collection(CustomerBackups::class, $userinfo)); + $export_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.export.php'; + $collection = (new Collection(DataDump::class, $userinfo)); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } if (isset($_POST['send']) && $_POST['send'] == 'send') { try { - CustomerBackups::getLocal($userinfo, $_POST)->add(); + DataDump::getLocal($userinfo, $_POST)->add(); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } - Response::standardSuccess('backupscheduled'); + Response::standardSuccess('exportscheduled'); } else { $pathSelect = FileDir::makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']); - $backup_data = include_once dirname(__FILE__) . '/lib/formfields/customer/extras/formfield.backup.php'; + $export_data = include_once dirname(__FILE__) . '/lib/formfields/customer/extras/formfield.export.php'; UI::view('user/form-datatable.html.twig', [ 'formaction' => $linker->getLink(['section' => 'extras']), - 'formdata' => $backup_data['backup'], - 'tabledata' => Listing::format($collection, $backup_list_data, 'backup_list'), + 'formdata' => $export_data['export'], + 'tabledata' => Listing::format($collection, $export_list_data, 'export_list'), ]); } } } else { - Response::standardError('backupfunctionnotenabled'); + Response::standardError('exportfunctionnotenabled'); } } diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index 6df8754c..cd6e0bdb 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -646,7 +646,7 @@ opcache.validate_timestamps'), ('system', 'letsencryptreuseold', 0), ('system', 'leenabled', '0'), ('system', 'leapiversion', '2'), - ('system', 'backupenabled', '0'), + ('system', 'exportenabled', '0'), ('system', 'dnsenabled', '0'), ('system', 'dns_server', 'Bind'), ('system', 'apacheglobaldiropt', ''), @@ -1071,4 +1071,15 @@ CREATE TABLE `panel_loginlinks` ( `allowed_from` text NOT NULL, UNIQUE KEY `loginname` (`loginname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; + +DROP TABLE IF EXISTS `panel_backups`; +CREATE TABLE `panel_backups` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `adminid` int(11) NOT NULL, + `customerid` int(11) NOT NULL, + `loginname` varchar(255) NOT NULL, + `size` bigint(20) NOT NULL, + `created_at` int(15) NOT NULL, + PRIMARY KEY (`id`) +) 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 aa4eb8d4..fd0d1217 100644 --- a/install/updates/froxlor/update_2.x.inc.php +++ b/install/updates/froxlor/update_2.x.inc.php @@ -571,5 +571,12 @@ if (Froxlor::isDatabaseVersion('202304260')) { "); Update::lastStepStatus(0); + Update::showUpdateStep("Adjusting system for data-export function"); + Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `varname` = 'exportenabled' WHERE `settinggroup`= 'system' AND `varname`= 'backupenabled"); + Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `value` = REPLACE(`value`, 'extras.backup', 'extras.export') WHERE `settinggroup` = 'panel' AND `varname` = 'customer_hide_options'"); + Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'backup_list'"); + Database::query("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'"); + Update::lastStepStatus(0); + Froxlor::updateToDbVersion('202305240'); } diff --git a/lib/Froxlor/Api/Commands/CustomerBackups.php b/lib/Froxlor/Api/Commands/DataDump.php similarity index 68% rename from lib/Froxlor/Api/Commands/CustomerBackups.php rename to lib/Froxlor/Api/Commands/DataDump.php index 83360684..00579733 100644 --- a/lib/Froxlor/Api/Commands/CustomerBackups.php +++ b/lib/Froxlor/Api/Commands/DataDump.php @@ -41,22 +41,22 @@ use PDO; /** * @since 0.10.0 */ -class CustomerBackups extends ApiCommand implements ResourceEntity +class DataDump extends ApiCommand implements ResourceEntity { /** - * add a new customer backup job + * add a new data dump job * * @param string $path - * path to store the backup to + * path to store the dumped data to * @param string $pgp_public_key - * optional pgp public key to encrypt the backup, default is empty - * @param bool $backup_dbs - * optional whether to backup databases, default is 0 (false) - * @param bool $backup_mail - * optional whether to backup mail-data, default is 0 (false) - * @param bool $backup_web - * optional whether to backup web-data, default is 0 (false) + * optional pgp public key to encrypt the archive, default is empty + * @param bool $dump_dbs + * optional whether to include databases, default is 0 (false) + * @param bool $dump_mail + * optional whether to include mail-data, default is 0 (false) + * @param bool $dump_web + * optional whether to incoude web-data, default is 0 (false) * @param int $customerid * optional, required when called as admin (if $loginname is not specified) * @param string $loginname @@ -75,9 +75,9 @@ class CustomerBackups extends ApiCommand implements ResourceEntity // parameter $pgp_public_key = $this->getParam('pgp_public_key', true, ''); - $backup_dbs = $this->getBoolParam('backup_dbs', true, 0); - $backup_mail = $this->getBoolParam('backup_mail', true, 0); - $backup_web = $this->getBoolParam('backup_web', true, 0); + $dump_dbs = $this->getBoolParam('dump_dbs', true, 0); + $dump_mail = $this->getBoolParam('dump_mail', true, 0); + $dump_web = $this->getBoolParam('dump_web', true, 0); // get customer data $customer = $this->getCustomerData(); @@ -89,7 +89,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity // path cannot be the customers docroot if ($path == FileDir::makeCorrectDir($customer['documentroot'])) { - Response::standardError('backupfoldercannotbedocroot', '', true); + Response::standardError('dumpfoldercannotbedocroot', '', true); } // pgp public key validation @@ -105,16 +105,16 @@ class CustomerBackups extends ApiCommand implements ResourceEntity } } - if ($backup_dbs != '1') { - $backup_dbs = '0'; + if ($dump_dbs != '1') { + $dump_dbs = '0'; } - if ($backup_mail != '1') { - $backup_mail = '0'; + if ($dump_mail != '1') { + $dump_mail = '0'; } - if ($backup_web != '1') { - $backup_web = '0'; + if ($dump_web != '1') { + $dump_web = '0'; } $task_data = [ @@ -124,62 +124,62 @@ class CustomerBackups extends ApiCommand implements ResourceEntity 'loginname' => $customer['loginname'], 'destdir' => $path, 'pgp_public_key' => $pgp_public_key, - 'backup_dbs' => $backup_dbs, - 'backup_mail' => $backup_mail, - 'backup_web' => $backup_web + 'dump_dbs' => $dump_dbs, + 'dump_mail' => $dump_mail, + 'dump_web' => $dump_web ]; - // schedule backup job - Cronjob::inserttask(TaskId::CREATE_CUSTOMER_BACKUP, $task_data); + // schedule export job + Cronjob::inserttask(TaskId::CREATE_CUSTOMER_DATADUMP, $task_data); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer-backup job for '" . $customer['loginname'] . "'. Target directory: " . $userpath); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer data-dump job for '" . $customer['loginname'] . "'. Target directory: " . $userpath); return $this->response($task_data); } /** - * check whether backup is enabled systemwide and if accessible for customer (hide_options) + * check whether data dump is enabled systemwide and if accessible for customer (hide_options) * * @throws Exception */ private function validateAccess() { - if (Settings::Get('system.backupenabled') != 1) { + if (Settings::Get('system.exportenabled') != 1) { throw new Exception("You cannot access this resource", 405); } if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { throw new Exception("You cannot access this resource", 405); } - if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.backup')) { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.export')) { throw new Exception("You cannot access this resource", 405); } } /** - * You cannot get a planned backup. - * Try CustomerBackups.listing() + * You cannot get a planned data export. + * Try DataDump.listing() */ public function get() { - throw new Exception('You cannot get a planned backup. Try CustomerBackups.listing()', 303); + throw new Exception('You cannot get a planned data export. Try DataDump.listing()', 303); } /** - * You cannot update a planned backup. + * You cannot update a planned data export. * You need to delete it and re-add it. */ public function update() { - throw new Exception('You cannot update a planned backup. You need to delete it and re-add it.', 303); + throw new Exception('You cannot update a planned data export. You need to delete it and re-add it.', 303); } /** - * list all planned backup-jobs, if called from an admin, list all planned backup-jobs of all customers you are + * list all planned data export jobs, if called from an admin, list all planned data export jobs of all customers you are * allowed to view, or specify id or loginname for one specific customer * * @param int $customerid - * optional, admin-only, select backup-jobs of a specific customer by id + * optional, admin-only, select data export jobs of a specific customer by id * @param string $loginname - * optional, admin-only, select backup-jobs of a specific customer by loginname + * optional, admin-only, select data export jobs of a specific customer by loginname * @param array $sql_search * optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =), * LIKE is used if left empty and 'value' => searchvalue @@ -199,9 +199,9 @@ class CustomerBackups extends ApiCommand implements ResourceEntity { $this->validateAccess(); - $customer_ids = $this->getAllowedCustomerIds('extras.backup'); + $customer_ids = $this->getAllowedCustomerIds('extras.export'); - // check whether there is a backup-job for this customer + // check whether there is a data export job for this customer $query_fields = []; $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit()); Database::pexecute($sel_stmt, $query_fields, true, true); @@ -212,7 +212,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity $result[] = $entry; } } - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer-backups"); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer data dump jobs"); return $this->response([ 'count' => count($result), 'list' => $result @@ -220,12 +220,12 @@ class CustomerBackups extends ApiCommand implements ResourceEntity } /** - * returns the total number of planned backups + * returns the total number of planned data exports * * @param int $customerid - * optional, admin-only, select backup-jobs of a specific customer by id + * optional, admin-only, select data export jobs of a specific customer by id * @param string $loginname - * optional, admin-only, select backup-jobs of a specific customer by loginname + * optional, admin-only, select data export jobs of a specific customer by loginname * * @access admin, customer * @return string json-encoded response message @@ -235,9 +235,9 @@ class CustomerBackups extends ApiCommand implements ResourceEntity { $this->validateAccess(); - $customer_ids = $this->getAllowedCustomerIds('extras.backup'); + $customer_ids = $this->getAllowedCustomerIds('extras.export'); - // check whether there is a backup-job for this customer + // check whether there is a data export job for this customer $result_count = 0; $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'"); Database::pexecute($sel_stmt, null, true, true); @@ -251,10 +251,10 @@ class CustomerBackups extends ApiCommand implements ResourceEntity } /** - * delete a planned backup-jobs by id, if called from an admin you need to specify the customerid/loginname + * delete a planned data export jobs by id, if called from an admin you need to specify the customerid/loginname * - * @param int $backup_job_entry - * id of backup job + * @param int $job_entry + * id of data export job * @param int $customerid * optional, required when called as admin (if $loginname is not specified) * @param string $loginname @@ -266,26 +266,26 @@ class CustomerBackups extends ApiCommand implements ResourceEntity */ public function delete() { - // get planned backups - $result = $this->apiCall('CustomerBackups.listing', $this->getParamList()); + // get planned exports + $result = $this->apiCall('DataDump.listing', $this->getParamList()); - $entry = $this->getParam('backup_job_entry'); - $customer_ids = $this->getAllowedCustomerIds('extras.backup'); + $entry = $this->getParam('job_entry'); + $customer_ids = $this->getAllowedCustomerIds('extras.export'); if ($result['count'] > 0 && $entry > 0) { // prepare statement $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :tid"); // check for the correct job - foreach ($result['list'] as $backupjob) { - if ($backupjob['id'] == $entry && in_array($backupjob['data']['customerid'], $customer_ids)) { + foreach ($result['list'] as $exportjob) { + if ($exportjob['id'] == $entry && in_array($exportjob['data']['customerid'], $customer_ids)) { Database::pexecute($del_stmt, [ 'tid' => $entry ], true, true); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted planned customer-backup #" . $entry); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted planned customer data export job #" . $entry); return $this->response(true); } } } - throw new Exception('Backup job with id #' . $entry . ' could not be found', 404); + throw new Exception('Data export job with id #' . $entry . ' could not be found', 404); } } diff --git a/lib/Froxlor/Cron/Forkable.php b/lib/Froxlor/Cron/Forkable.php index 884dc6a9..dc928f8c 100644 --- a/lib/Froxlor/Cron/Forkable.php +++ b/lib/Froxlor/Cron/Forkable.php @@ -20,27 +20,26 @@ trait Forkable if ($pid == -1) { exit("Error forking...\n"); - } else if ($pid == 0) { + } elseif ($pid == 0) { // re-create db Database::needRoot(false); - $closure(...$closureAttributes); + $closure($closureAttributes); exit(); } else { $childrenPids[] = $pid; - while(count($childrenPids) >= $concurrentChildren) { - foreach($childrenPids as $key => $pid) { + while (count($childrenPids) >= $concurrentChildren) { + foreach ($childrenPids as $key => $pid) { $res = pcntl_waitpid($pid, $status, WNOHANG); - // If the process has already exited - if($res == -1 || $res > 0) + if ($res == -1 || $res > 0) { unset($childrenPids[$key]); + } } - sleep(1); } } } - while(pcntl_waitpid(0, $status) != -1); + while (pcntl_waitpid(0, $status) != -1); } else { if (!defined('CRON_NOFORK_FLAG')) { if (extension_loaded('pcntl')) { @@ -51,7 +50,7 @@ trait Forkable FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, $msg . " Not forking " . self::class . ", this may take a long time!"); } foreach ($attributes as $closureAttributes) { - $closure(...$closureAttributes); + $closure($closureAttributes); } } } diff --git a/lib/Froxlor/Cron/System/ExportCron.php b/lib/Froxlor/Cron/System/ExportCron.php index b05952b7..8575013a 100644 --- a/lib/Froxlor/Cron/System/ExportCron.php +++ b/lib/Froxlor/Cron/System/ExportCron.php @@ -39,7 +39,7 @@ class ExportCron extends FroxlorCron public static function run() { - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'BackupCron: started - creating customer backup'); + FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'ExportCron: started - creating customer data export'); $result_tasks_stmt = Database::query(" SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20' ORDER BY `id` ASC @@ -65,11 +65,11 @@ class ExportCron extends FroxlorCron // create folder if not exists if (!file_exists($row['data']['destdir']) && $row['data']['destdir'] != '/' && $row['data']['destdir'] != Settings::Get('system.documentroot_prefix') && $row['data']['destdir'] != $customerdocroot) { - FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating backup-destination path for customer: ' . escapeshellarg($row['data']['destdir'])); + FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating data export destination path for customer: ' . escapeshellarg($row['data']['destdir'])); FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir'])); } - self::createCustomerBackup($row['data'], $customerdocroot, $cronlog); + self::createCustomerExport($row['data'], $customerdocroot, $cronlog); } } @@ -80,7 +80,7 @@ class ExportCron extends FroxlorCron } /** - * depending on the give choice, the customers web-data, email-data and databases are being backup'ed + * depending on the give choice, the customers web-data, email-data and databases are being exported * * @param array $data * @@ -88,19 +88,19 @@ class ExportCron extends FroxlorCron * * @throws Exception */ - private static function createCustomerBackup($data = null, $customerdocroot = null, &$cronlog = null) + private static function createCustomerExport($data = null, $customerdocroot = null, &$cronlog = null) { - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating Backup for user "' . $data['loginname'] . '"'); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating data export for user "' . $data['loginname'] . '"'); // create tmp folder $tmpdir = FileDir::makeCorrectDir($data['destdir'] . '/.tmp/'); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating tmp-folder "' . $tmpdir . '"'); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mkdir -p ' . escapeshellarg($tmpdir)); FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir)); - $create_backup_tar_data = ""; + $create_export_tar_data = ""; // MySQL databases - if ($data['backup_dbs'] == 1) { + if ($data['dump_dbs'] == 1) { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating mysql-folder "' . FileDir::makeCorrectDir($tmpdir . '/mysql') . '"'); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql'))); FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql'))); @@ -140,7 +140,7 @@ class ExportCron extends FroxlorCron } if ($has_dbs) { - $create_backup_tar_data .= './mysql '; + $create_export_tar_data .= './mysql '; } if (file_exists($mysqlcnf_file)) { @@ -151,7 +151,7 @@ class ExportCron extends FroxlorCron } // E-mail data - if ($data['backup_mail'] == 1) { + if ($data['dump_mail'] == 1) { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating mail-folder "' . FileDir::makeCorrectDir($tmpdir . '/mail') . '"'); FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mail'))); @@ -171,41 +171,41 @@ class ExportCron extends FroxlorCron if (!empty($tar_file_list)) { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfvz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/mail/' . $data['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list)); FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/mail/' . $data['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list)); - $create_backup_tar_data .= './mail '; + $create_export_tar_data .= './mail '; } } // Web data - if ($data['backup_web'] == 1) { + if ($data['dump_web'] == 1) { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating web-folder "' . FileDir::makeCorrectDir($tmpdir . '/web') . '"'); FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/web'))); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/web/' . $data['loginname'] . '-web.tar.gz')) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", FileDir::makeCorrectFile($tmpdir . '/*'))) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", substr(FileDir::makeCorrectDir($tmpdir), 0, -1))) . ' -C ' . escapeshellarg($customerdocroot) . ' .'); FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/web/' . $data['loginname'] . '-web.tar.gz')) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", FileDir::makeCorrectFile($tmpdir . '/*'))) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", substr(FileDir::makeCorrectFile($tmpdir), 0, -1))) . ' -C ' . escapeshellarg($customerdocroot) . ' .'); - $create_backup_tar_data .= './web '; + $create_export_tar_data .= './web '; } - if (!empty($create_backup_tar_data)) { + if (!empty($create_export_tar_data)) { // set owner to customer $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir)); FileDir::safe_exec('chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir)); // create tar-file - $backup_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-backup_' . date('YmdHi', time()) . '.tar.gz' . (!empty($data['pgp_public_key']) ? '.gpg' : '')); - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating backup-file "' . $backup_file . '"'); + $export_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-export_' . date('YmdHi', time()) . '.tar.gz' . (!empty($data['pgp_public_key']) ? '.gpg' : '')); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating export-file "' . $export_file . '"'); if (!empty($data['pgp_public_key'])) { // pack all archives in tmp-dir to one archive and encrypt it with gpg $recipient_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-recipients.gpg'); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating recipient-file "' . $recipient_file . '"'); file_put_contents($recipient_file, $data['pgp_public_key']); - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes'); - FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($backup_file) . ' --trust-model always --batch --yes', $return_value, ['|']); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($export_file) . ' --trust-model always --batch --yes'); + FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($export_file) . ' --trust-model always --batch --yes', $return_value, ['|']); } else { // pack all archives in tmp-dir to one archive - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); - FileDir::safe_exec('tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data)); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($export_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data)); + FileDir::safe_exec('tar cfz ' . escapeshellarg($export_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data)); } // move to destination directory - $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir'])); - FileDir::safe_exec('mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir'])); + $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($export_file) . ' ' . escapeshellarg($data['destdir'])); + FileDir::safe_exec('mv ' . escapeshellarg($export_file) . ' ' . escapeshellarg($data['destdir'])); // remove tmp-files $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> rm -rf ' . escapeshellarg($tmpdir)); FileDir::safe_exec('rm -rf ' . escapeshellarg($tmpdir)); diff --git a/lib/Froxlor/Cron/System/TasksCron.php b/lib/Froxlor/Cron/System/TasksCron.php index 337d0bf9..60face00 100644 --- a/lib/Froxlor/Cron/System/TasksCron.php +++ b/lib/Froxlor/Cron/System/TasksCron.php @@ -46,7 +46,7 @@ class TasksCron extends FroxlorCron * LOOK INTO TASKS TABLE TO SEE IF THERE ARE ANY UNDONE JOBS */ self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "TasksCron: Searching for tasks to do"); - // no type 99 (regenerate cron.d-file) and no type 20 (customer backup) + // no type 99 (regenerate cron.d-file) and no type 20 (customer data export) // order by type descending to re-create bind and then webserver at the end $result_tasks_stmt = Database::query(" SELECT `id`, `type`, `data` FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` <> '99' AND `type` <> '20' ORDER BY `type` DESC, `id` ASC diff --git a/lib/Froxlor/Cron/TaskId.php b/lib/Froxlor/Cron/TaskId.php index d80e6c11..9aa065c0 100644 --- a/lib/Froxlor/Cron/TaskId.php +++ b/lib/Froxlor/Cron/TaskId.php @@ -82,9 +82,9 @@ final class TaskId const DELETE_DOMAIN_SSL = 12; /** - * TYPE=20 COSTUMERBACKUP + * TYPE=20 CUSTUMER DATA DUMP */ - const CREATE_CUSTOMER_BACKUP = 20; + const CREATE_CUSTOMER_DATADUMP = 20; /** * TYPE=99 REGENERATE CRON diff --git a/lib/Froxlor/System/Cronjob.php b/lib/Froxlor/System/Cronjob.php index 84c7d337..4a765049 100644 --- a/lib/Froxlor/System/Cronjob.php +++ b/lib/Froxlor/System/Cronjob.php @@ -211,10 +211,10 @@ class Cronjob 'type' => TaskId::DELETE_DOMAIN_SSL, 'data' => $data ]); - } elseif ($type == TaskId::CREATE_CUSTOMER_BACKUP && isset($params[0]) && is_array($params[0])) { + } elseif ($type == TaskId::CREATE_CUSTOMER_DATADUMP && isset($params[0]) && is_array($params[0])) { $data = json_encode($params[0]); Database::pexecute($ins_stmt, [ - 'type' => TaskId::CREATE_CUSTOMER_BACKUP, + 'type' => TaskId::CREATE_CUSTOMER_DATADUMP, 'data' => $data ]); } diff --git a/lib/formfields/customer/extras/formfield.backup.php b/lib/formfields/customer/extras/formfield.export.php similarity index 73% rename from lib/formfields/customer/extras/formfield.backup.php rename to lib/formfields/customer/extras/formfield.export.php index b8fe8898..d9a5a96c 100644 --- a/lib/formfields/customer/extras/formfield.backup.php +++ b/lib/formfields/customer/extras/formfield.export.php @@ -18,17 +18,16 @@ use Froxlor\Settings; return [ - 'backup' => [ - 'title' => lng('extras.backup'), + 'export' => [ + 'title' => lng('extras.export'), 'image' => 'fa-solid fa-server', 'sections' => [ 'section_a' => [ - 'title' => lng('extras.backup'), - 'image' => 'icons/backup_big.png', + 'title' => lng('extras.export'), 'fields' => [ 'path' => [ - 'label' => lng('panel.backuppath.title'), - 'desc' => lng('panel.backuppath.description') . '
' . (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescription') : null), + 'label' => lng('panel.exportpath.title'), + 'desc' => lng('panel.exportpath.description') . '
' . (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescription') : null), 'type' => $pathSelect['type'], 'select_var' => $pathSelect['select_var'] ?? '', 'selected' => $pathSelect['value'], @@ -36,8 +35,8 @@ return [ 'note' => $pathSelect['note'] ?? '', ], 'pgp_public_key' => [ - 'label' => lng('panel.backup_pgp_public_key.title'), - 'desc' => lng('panel.backup_pgp_public_key.description'), + 'label' => lng('panel.export_pgp_public_key.title'), + 'desc' => lng('panel.export_pgp_public_key.description'), 'type' => 'textarea', ], 'path_protection_info' => [ @@ -46,20 +45,20 @@ return [ 'value' => lng('extras.path_protection_info'), 'classes' => 'fw-bold text-danger' ], - 'backup_web' => [ - 'label' => lng('extras.backup_web'), + 'dump_web' => [ + 'label' => lng('extras.dump_web'), 'type' => 'checkbox', 'value' => '1', 'checked' => true ], - 'backup_mail' => [ - 'label' => lng('extras.backup_mail'), + 'dump_mail' => [ + 'label' => lng('extras.dump_mail'), 'type' => 'checkbox', 'value' => '1', 'checked' => true ], - 'backup_dbs' => [ - 'label' => lng('extras.backup_dbs'), + 'dump_dbs' => [ + 'label' => lng('extras.dump_dbs'), 'type' => 'checkbox', 'value' => '1', 'checked' => true diff --git a/lib/navigation/00.froxlor.main.php b/lib/navigation/00.froxlor.main.php index 7f19ab3e..012308e0 100644 --- a/lib/navigation/00.froxlor.main.php +++ b/lib/navigation/00.froxlor.main.php @@ -133,9 +133,9 @@ return [ 'show_element' => (Settings::Get('logger.enabled') == true) && (!Settings::IsInList('panel.customer_hide_options', 'extras.logger')) ], [ - 'url' => 'customer_extras.php?page=backup', - 'label' => lng('menue.extras.backup'), - 'show_element' => (Settings::Get('system.backupenabled') == true) && (!Settings::IsInList('panel.customer_hide_options', 'extras.backup')) + 'url' => 'customer_extras.php?page=export', + 'label' => lng('menue.extras.export'), + 'show_element' => (Settings::Get('system.exportenabled') == true) && (!Settings::IsInList('panel.customer_hide_options', 'extras.export')) ] ] ], diff --git a/lib/tablelisting/customer/tablelisting.backups.php b/lib/tablelisting/customer/tablelisting.export.php similarity index 77% rename from lib/tablelisting/customer/tablelisting.backups.php rename to lib/tablelisting/customer/tablelisting.export.php index 8b5f2b24..fe37c571 100644 --- a/lib/tablelisting/customer/tablelisting.backups.php +++ b/lib/tablelisting/customer/tablelisting.export.php @@ -28,10 +28,10 @@ use Froxlor\UI\Callbacks\Text; use Froxlor\UI\Listing; return [ - 'backup_list' => [ - 'title' => lng('error.customerhasongoingbackupjob'), + 'export_list' => [ + 'title' => lng('error.customerhasongoingexportjob'), 'icon' => 'fa-solid fa-server', - 'self_overview' => ['section' => 'extras', 'page' => 'backup'], + 'self_overview' => ['section' => 'extras', 'page' => 'export'], 'default_sorting' => ['destdir' => 'asc'], 'columns' => [ 'destdir' => [ @@ -44,28 +44,28 @@ return [ 'field' => 'data.pgp_public_key', 'callback' => [Text::class, 'boolean'] ], - 'backup_web' => [ - 'label' => lng('extras.backup_web'), - 'field' => 'data.backup_web', + 'dump_web' => [ + 'label' => lng('extras.dump_web'), + 'field' => 'data.dump_web', 'callback' => [Text::class, 'boolean'], ], - 'backup_mail' => [ - 'label' => lng('extras.backup_mail'), - 'field' => 'data.backup_mail', + 'dump_mail' => [ + 'label' => lng('extras.dump_mail'), + 'field' => 'data.dump_mail', 'callback' => [Text::class, 'boolean'], ], - 'backup_dbs' => [ - 'label' => lng('extras.backup_dbs'), - 'field' => 'data.backup_dbs', + 'dump_dbs' => [ + 'label' => lng('extras.dump_dbs'), + 'field' => 'data.dump_dbs', 'callback' => [Text::class, 'boolean'], ] ], - 'visible_columns' => Listing::getVisibleColumnsForListing('backup_list', [ + 'visible_columns' => Listing::getVisibleColumnsForListing('export_list', [ 'destdir', 'pgp_public_key', - 'backup_web', - 'backup_mail', - 'backup_dbs' + 'dump_web', + 'dump_mail', + 'dump_dbs' ]), 'actions' => [ 'delete' => [ diff --git a/lng/cz.lng.php b/lng/cz.lng.php index dc9671db..970c7e14 100644 --- a/lng/cz.lng.php +++ b/lng/cz.lng.php @@ -456,10 +456,6 @@ return [ 'directory_browsing' => 'Procházení obsahu adresáře', 'pathoptions_edit' => 'Upravit možnosti cesty', 'execute_perl' => 'Spustit perl/CGI', - 'backup' => 'Vytvořit zálohu', - 'backup_web' => 'Zálohovat web-data', - 'backup_mail' => 'Zálohovat mail-data', - 'backup_dbs' => 'Zálohovat databáze', ], 'ftp' => [ 'description' => 'Zde můžeš tvořit a upravovat tvé FTP-účty.
Změny jsou uskutečněny okamžitě a účty mohou být použity ihned.', @@ -549,7 +545,6 @@ return [ 'extras' => 'Extra', 'directoryprotection' => 'Ochrana adresáře', 'pathoptions' => 'Možnosti cesty', - 'backup' => 'Záloha', ], 'traffic' => [ 'traffic' => 'Provoz', @@ -815,9 +810,6 @@ return [ 'caa_entry' => [ 'title' => 'Generovat CAA DNS záznamy', ], - 'backupenabled' => [ - 'title' => 'Povolit zálohy pro zákazníky', - ], 'mail_smtp_passwd' => 'SMTP heslo', ], 'success' => [ diff --git a/lng/de.lng.php b/lng/de.lng.php index c02a5e2b..46b64892 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -527,7 +527,8 @@ return [ 'cron_usage_report' => 'Webspace- und Trafficreport', 'cron_mailboxsize' => 'Berechnung der Mailbox-Größen', 'cron_letsencrypt' => 'Aktualisierung der Let\'s Encrypt Zertifikate', - 'cron_backup' => 'Ausstehende Sicherungen erstellen', + 'cron_export' => 'Ausstehende Datenexporte erstellen', + 'cron_backup' => 'System- und Kunden-Sicherungen erstellen', ], 'cronjob' => [ 'cronjobsettings' => 'Cronjob-Einstellungen', @@ -775,7 +776,7 @@ return [ 'destinationalreadyexistasmail' => 'Die Weiterleitung zu "%s" existiert bereits als aktive E-Mail-Adresse.', 'destinationalreadyexist' => 'Es existiert bereits eine Weiterleitung nach "%s".', 'destinationiswrong' => 'Die Weiterleitungsadresse "%s" enthält ungültige Zeichen oder ist nicht vollständig.', - 'backupfoldercannotbedocroot' => 'Der Ordner für Backups darf nicht das Heimatverzeichnis sein, wählen Sie einen Ordner unterhalb des Heimatverzeichnisses, z.B. /backups', + 'dumpfoldercannotbedocroot' => 'Der Ordner für Daten-Export darf nicht das Heimatverzeichnis sein, wählen Sie einen Ordner unterhalb des Heimatverzeichnisses, z.B. /dumps', 'templatelanguagecombodefined' => 'Die gewählte Kombination aus Sprache und Vorlage ist bereits definiert.', 'templatelanguageinvalid' => 'Die gewählte Sprache existiert nicht', 'ipstillhasdomains' => 'Die IP/Port-Kombination, die Sie löschen wollen, ist noch bei einer oder mehreren Domains eingetragen. Bitte ändern Sie die Domains vorher auf eine andere IP/Port-Kombination, um diese löschen zu können.', @@ -892,8 +893,8 @@ return [ 'autoupdate_10' => 'Minimum unterstützte Version von PHP ist 7.4.0', 'autoupdate_11' => 'Webupdate ist deaktiviert', 'mailaccistobedeleted' => 'Ein vorheriges Konto mit dem gleichen Namen (%s) wird aktuell noch gelöscht und kann daher derzeit nicht angelegt werden', - 'customerhasongoingbackupjob' => 'Es gibt noch einen austehenden Backup-Job. Bitte haben Sie etwas Geduld.', - 'backupfunctionnotenabled' => 'Die Sicherungs-Funktion is nicht aktiviert', + 'customerhasongoingexportjob' => 'Es gibt noch einen austehenden Daten-Export. Bitte haben Sie etwas Geduld.', + 'exportfunctionnotenabled' => 'Die Datenexport-Funktion is nicht aktiviert', 'dns_domain_nodns' => 'DNS ist für diese Domain nicht aktiviert', 'dns_content_empty' => 'Keinen Inhalt angegeben', 'dns_content_invalid' => 'DNS Eintrag ungültig', @@ -950,10 +951,10 @@ return [ 'execute_perl' => 'Perl/CGI ausführen', 'htpasswdauthname' => 'Grund der Authentifizierung (AuthName)', 'directoryprotection_edit' => 'Verzeichnisschutz bearbeiten', - 'backup' => 'Sicherung erstellen', - 'backup_web' => 'Web-Daten sichern', - 'backup_mail' => 'E-Mail Daten sichern', - 'backup_dbs' => 'Datenbanken sichern', + 'export' => 'Datenexport erstellen', + 'dump_web' => 'Web-Daten hinzufügen', + 'dump_mail' => 'E-Mail Daten hinzufügen', + 'dump_dbs' => 'Datenbanken hinzufügen', 'path_protection_label' => 'Wichtig', 'path_protection_info' => 'Wir raten dringend dazu den angegebenen Pfad zu schützen, siehe "Extras" -> "Verzeichnisschutz"', ], @@ -1102,7 +1103,7 @@ Vielen Dank, Ihr Administrator', 'extras' => 'Extras', 'directoryprotection' => 'Verzeichnisschutz', 'pathoptions' => 'Pfadoptionen', - 'backup' => 'Sicherung', + 'export' => 'Datenexport', ], 'traffic' => [ 'traffic' => 'Traffic', @@ -1198,13 +1199,13 @@ Vielen Dank, Ihr Administrator', 'ftpdesc' => 'FTP-Beschreibung', 'letsencrypt' => 'Benutzt Let\'s encrypt', 'set' => 'Setzen', - 'backuppath' => [ - 'title' => 'Pfad zur Ablage der Backups', - 'description' => 'In diesem Ordner werden die Backups abgelegt. Wenn das Sichern von Web-Daten aktiviert ist, werden alle Dateien aus dem Heimatverzeichnis gesichert, exklusive des hier angegebenen Backup-Ordners.', + 'exportpath' => [ + 'title' => 'Pfad zur Ablage des Exports', + 'description' => 'In diesem Ordner werden die Export-Archive abgelegt. Wenn Web-Daten exportiert werden, werden alle Dateien aus dem Heimatverzeichnis gesichert, exklusive des hier angegebenen Ordners.', ], - 'backup_pgp_public_key' => [ + 'export_pgp_public_key' => [ 'title' => 'Öffentlicher PGP-Schlüssel', - 'description' => 'Der öffentliche PGP-Schlüssel, mit dem die Backups verschlüsselt werden sollen. Wenn kein Schlüssel angegeben ist, werden die Backups nicht verschlüsselt.', + 'description' => 'Der öffentliche PGP-Schlüssel, mit dem die Exporte verschlüsselt werden sollen. Wenn kein Schlüssel angegeben ist, werden die Exporte nicht verschlüsselt.', ], 'pgp_public_key' => 'Öffentlicher PGP-Schlüssel', 'none_value' => 'Keine', @@ -1273,7 +1274,7 @@ Vielen Dank, Ihr Administrator', 'email_reallydelete_forwarder' => 'Wollen Sie die Weiterleitung "%s" wirklich löschen?', 'extras_reallydelete' => 'Wollen Sie den Verzeichnisschutz für "%s" wirklich löschen?', 'extras_reallydelete_pathoptions' => 'Wollen Sie die Optionen für den Pfad "%s" wirklich löschen?', - 'extras_reallydelete_backup' => 'Wollen Sie die geplante Sicherung wirklich löschen?', + 'extras_reallydelete_export' => 'Wollen Sie den geplanten Daten-Export wirklich löschen?', 'ftp_reallydelete' => 'Wollen Sie das FTP-Benutzerkonto "%s" wirklich löschen?', 'mysql_reallydelete' => 'Wollen Sie die Datenbank "%s" wirklich löschen?
ACHTUNG! Alle Daten gehen unwiderruflich verloren!', 'admin_configs_reallyrebuild' => 'Wollen Sie wirklich alle Konfigurationsdateien neu erstellen lassen?', @@ -1925,9 +1926,9 @@ Vielen Dank, Ihr Administrator', 'title' => 'Zusätzliche CAA DNS Einträge', 'description' => 'DNS Certification Authority Authorization (CAA) verwendet das Domain Name System, um dem Besitzer einer Domain die Möglichkeit zu bieten, gewisse Zertifizierungsstellen (CAs) dazu zu berechtigen,
ein Zertifikat für die betroffene Domain auszustellen. CAA Records sollen verhindern, dass Zertifikate fälschlicherweise für eine Domain ausgestellt werden.

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

ACHTUNG: Der Code wird nicht auf Fehler geprüft. Etwaige Fehler werden also auch übernommen. Die CAA finalen Einträge könnten daher falsch sein!', ], - 'backupenabled' => [ - 'title' => 'Backup für Kunden aktivieren', - 'description' => 'Wenn dies aktiviert ist, kann der Kunde Sicherungen planen (cron-backup) welche ein Archiv in sein Heimatverzeichnis ablegt (Unterordner vom Kunden wählbar)', + 'exportenabled' => [ + 'title' => 'Daten-Export für Kunden aktivieren', + 'description' => 'Wenn dies aktiviert ist, kann der Kunde Daten-Exporte planen (cron-export) welche ein Archiv in sein Heimatverzeichnis ablegen (Unterordner vom Kunden wählbar)', ], 'dnseditorenable' => [ 'title' => 'DNS Editor aktivieren', @@ -2097,8 +2098,8 @@ Vielen Dank, Ihr Administrator', 'settingssaved' => 'Die Einstellungen wurden erfolgreich gespeichert.', 'rebuildingconfigs' => 'Task für Neuerstellung der Konfigurationen wurde erfolgreich eingetragen', 'domain_import_successfully' => 'Erfolgreich %s Domains importiert.', - 'backupscheduled' => 'Ihre Sicherung wurde erfolgreich geplant. Bitte warten Sie nun, bis diese abgearbeitet wurde.', - 'backupaborted' => 'Die geplante Sicherung wurde abgebrochen', + 'exportscheduled' => 'Ihr Daten-Export wurde erfolgreich geplant. Bitte warten Sie nun, bis dieser bearbeitet wurde.', + 'exportaborted' => 'Der geplante Daten-Export wurde abgebrochen', 'dns_record_added' => 'Eintrag erfolgreich hinzugefügt', 'dns_record_deleted' => 'Eintrag erfolgreich entfernt', 'testmailsent' => 'Test E-Mail erfolgreich gesendet', @@ -2117,7 +2118,7 @@ Vielen Dank, Ihr Administrator', 'DELETE_EMAIL_DATA' => 'E-Mail-Dateien des Kunden löschen', 'DELETE_FTP_DATA' => 'Kunden FTP-Konto Dateien löschen', 'REBUILD_CRON' => 'Neuerstellung der cron.d-Datei', - 'CREATE_CUSTOMER_BACKUP' => 'Datensicherung für Kunde %s', + 'CREATE_CUSTOMER_DATADUMP' => 'Daten-Export für Kunde %s', 'DELETE_DOMAIN_PDNS' => 'Lösche Domain %s von PowerDNS Datenbank', 'DELETE_DOMAIN_SSL' => 'Lösche SSL Dateien von Domain %s', ], diff --git a/lng/en.lng.php b/lng/en.lng.php index c2a60b53..2a20e567 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -573,7 +573,8 @@ return [ 'cron_usage_report' => 'Web- and traffic-reports', 'cron_mailboxsize' => 'Mailbox-size calculation', 'cron_letsencrypt' => 'Let\'s Encrypt certificate updates', - 'cron_backup' => 'Process backup jobs', + 'cron_export' => 'Process data-export jobs', + 'cron_backup' => 'Process system- and customer backup jobs', ], 'cronjob' => [ 'cronjobsettings' => 'Cronjob settings', @@ -844,7 +845,7 @@ return [ 'destinationalreadyexistasmail' => 'The forwarder to %s already exists as active email-address.', 'destinationalreadyexist' => 'You have already defined a forwarder to "%s"', 'destinationiswrong' => 'The forwarder %s contains invalid character(s) or is incomplete.', - 'backupfoldercannotbedocroot' => 'The folder for backups cannot be your homedir, please chose a folder within your homedir, e.g. /backups', + 'dumpfoldercannotbedocroot' => 'The folder for data-dumps cannot be your homedir, please chose a folder within your homedir, e.g. /dumps', 'templatelanguagecombodefined' => 'The selected language/template combination has already been defined.', 'templatelanguageinvalid' => 'The selected language does not exist', 'ipstillhasdomains' => 'The IP/Port combination you want to delete still has domains assigned to it, please reassign those to other IP/Port combinations before deleting this IP/Port combination.', @@ -964,8 +965,8 @@ return [ 'autoupdate_10' => 'Minimum supported version of PHP is 7.4.0', 'autoupdate_11' => 'Webupdate is disabled', 'mailaccistobedeleted' => 'Another account with the same name (%s) is currently being deleted and can therefore not be added at this moment.', - 'customerhasongoingbackupjob' => 'There is already a backup job waiting to be processed, please be patient.', - 'backupfunctionnotenabled' => 'The backup function is not enabled', + 'customerhasongoingexportjob' => 'There is already a data export job waiting to be processed, please be patient.', + 'exportfunctionnotenabled' => 'The export function is not enabled', 'dns_domain_nodns' => 'DNS is not enabled for this domain', 'dns_content_empty' => 'No content given', 'dns_content_invalid' => 'DNS content invalid', @@ -1022,10 +1023,10 @@ return [ 'execute_perl' => 'Execute perl/CGI', 'htpasswdauthname' => 'Authentication reason (AuthName)', 'directoryprotection_edit' => 'Edit directory protection', - 'backup' => 'Create backup', - 'backup_web' => 'Backup web-data', - 'backup_mail' => 'Backup mail-data', - 'backup_dbs' => 'Backup databases', + 'export' => 'Create data dump', + 'dump_web' => 'Include web-data', + 'dump_mail' => 'Include mail-data', + 'dump_dbs' => 'Include databases', 'path_protection_label' => 'Important', 'path_protection_info' => 'We strongly recommend protecting the given path, see "Extras" -> "Directory protection"', ], @@ -1174,7 +1175,7 @@ Yours sincerely, your administrator', 'extras' => 'Extras', 'directoryprotection' => 'Directory protection', 'pathoptions' => 'Path options', - 'backup' => 'Backup', + 'export' => 'Data export', ], 'traffic' => [ 'traffic' => 'Traffic', @@ -1313,13 +1314,13 @@ Yours sincerely, your administrator', 'letsencrypt' => 'Using Let\'s encrypt', 'set' => 'Apply', 'shell' => 'Shell', - 'backuppath' => [ - 'title' => 'Destination path for the backup', - 'description' => 'This is the path where the backups will be stored. If backup of web-data is selected, all files from the homedir are stored excluding the backup-folder specified here.', + 'exportpath' => [ + 'title' => 'Destination path for the exported data', + 'description' => 'This is the path where the export-archive will be stored. If web-data is being included, all files from the homedir are stored excluding the folder specified here.', ], - 'backup_pgp_public_key' => [ + 'export_pgp_public_key' => [ 'title' => 'Public PGP key for encryption', - 'description' => 'This is the public PGP key which will be used to encrypt the backup. If you leave this field empty, the backup will not be encrypted.', + 'description' => 'This is the public PGP key which will be used to encrypt the export. If you leave this field empty, the export will not be encrypted.', ], 'pgp_public_key' => 'Public PGP key', 'none_value' => 'None', @@ -1388,7 +1389,7 @@ Yours sincerely, your administrator', 'email_reallydelete_forwarder' => 'Do you really want to delete the forwarder %s?', 'extras_reallydelete' => 'Do you really want to delete the directory protection for %s?', 'extras_reallydelete_pathoptions' => 'Do you really want to delete the path options for %s?', - 'extras_reallydelete_backup' => 'Do you really want to abort the planned backup job?', + 'extras_reallydelete_export' => 'Do you really want to abort the planned export job?', 'ftp_reallydelete' => 'Do you really want to delete the FTP-account %s?', 'mysql_reallydelete' => 'Do you really want to delete the database %s? This cannot be undone!', 'admin_configs_reallyrebuild' => 'Do you really want to rebuild all config files?', @@ -2047,9 +2048,9 @@ Yours sincerely, your administrator', 'title' => 'Additional CAA DNS records', 'description' => 'DNS Certification Authority Authorization (CAA) is an Internet security policy mechanism which allows domain name holders to indicate to certificate authorities
whether they are authorized to issue digital certificates for a particular domain name. It does this by means of a new "CAA" Domain Name System (DNS) resource record.

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

Attention: The code won\'t be checked for any errors. If it contains errors, your CAA records might not work!', ], - 'backupenabled' => [ - 'title' => 'Enable backup for customers', - 'description' => 'If activated, the customer will be able to schedule backup jobs (cron-backup) which generates an archive within his docroot (subdirectory chosable by customer)', + 'exportenabled' => [ + 'title' => 'Enable data export for customers', + 'description' => 'If activated, the customer will be able to schedule data export jobs (cron-export) which generates an archive within his docroot (subdirectory chosable by customer)', ], 'dnseditorenable' => [ 'title' => 'Enable DNS editor', @@ -2230,8 +2231,8 @@ Yours sincerely, your administrator', 'settingssaved' => 'The settings have been successfully saved.', 'rebuildingconfigs' => 'Successfully inserted tasks for rebuild configfiles', 'domain_import_successfully' => 'Successfully imported %s domains.', - 'backupscheduled' => 'Your backup job has been scheduled. Please wait for it to be processed', - 'backupaborted' => 'Your scheduled backup has been cancelled', + 'exportscheduled' => 'Your export job has been scheduled. Please wait for it to be processed', + 'exportaborted' => 'Your scheduled export has been cancelled', 'dns_record_added' => 'Record added successfully', 'dns_record_deleted' => 'Record deleted successfully', 'testmailsent' => 'Test mail sent successfully', @@ -2250,7 +2251,7 @@ Yours sincerely, your administrator', 'DELETE_EMAIL_DATA' => 'Delete customer e-mail data.', 'DELETE_FTP_DATA' => 'Delete customer ftp-account data.', 'REBUILD_CRON' => 'Rebuilding the cron.d-file', - 'CREATE_CUSTOMER_BACKUP' => 'Backup job for customer %s', + 'CREATE_CUSTOMER_DATADUMP' => 'Data export job for customer %s', 'DELETE_DOMAIN_PDNS' => 'Delete domain %s from PowerDNS database', 'DELETE_DOMAIN_SSL' => 'Delete ssl files of domain %s', ], diff --git a/lng/es.lng.php b/lng/es.lng.php index f5d5974d..29973803 100644 --- a/lng/es.lng.php +++ b/lng/es.lng.php @@ -837,7 +837,6 @@ return [ 'destinationalreadyexistasmail' => 'El remitente a %s ya existe como dirección de correo electrónico activa.', 'destinationalreadyexist' => 'Ya ha definido un reenviador para "%s".', 'destinationiswrong' => 'La %s la redirección contiene caracteres no válidos o está incompleta.', - 'backupfoldercannotbedocroot' => 'La carpeta para las copias de seguridad no puede ser su carpeta de inicio, elija una carpeta dentro de su carpeta de inicio, por ejemplo, /backups.', 'templatelanguagecombodefined' => 'La combinación idioma/plantilla seleccionada ya ha sido definida.', 'templatelanguageinvalid' => 'El idioma seleccionado no existe.', 'ipstillhasdomains' => 'La combinación IP/Puerto que desea eliminar todavía tiene dominios asignados, por favor reasígnelos a otras combinaciones IP/Puerto antes de eliminar esta combinación IP/Puerto.', @@ -958,7 +957,6 @@ return [ 'autoupdate_11' => 'Webupdate está desactivado', 'mailaccistobedeleted' => 'Otra cuenta con el mismo nombre (%s) está siendo eliminada y por lo tanto no puede ser añadida en este momento.', 'customerhasongoingbackupjob' => 'Ya hay un trabajo de copia de seguridad esperando a ser procesado, por favor sea paciente.', - 'backupfunctionnotenabled' => 'La función de copia de seguridad no está habilitada', 'dns_domain_nodns' => 'DNS no está habilitado para este dominio', 'dns_content_empty' => 'No hay contenido', 'dns_content_invalid' => 'El contenido DNS no es válido', @@ -1010,10 +1008,6 @@ return [ 'execute_perl' => 'Ejecutar perl/CGI', 'htpasswdauthname' => 'Razón de autenticación (AuthName)', 'directoryprotection_edit' => 'Editar protección de directorio', - 'backup' => 'Crear copia de seguridad', - 'backup_web' => 'Copia de seguridad de datos web', - 'backup_mail' => 'Copia de seguridad de los datos de correo', - 'backup_dbs' => 'Copia de seguridad de bases de datos', 'path_protection_label' => 'Importante', 'path_protection_info' => 'Le recomendamos encarecidamente que proteja la ruta indicada, consulte "Extras" -> "Protección de directorios".' ], @@ -1162,7 +1156,7 @@ Atentamente, su administrador' 'extras' => 'Extras', 'directoryprotection' => 'Protección de directorios', 'pathoptions' => 'Opciones de ruta', - 'backup' => 'Copia de seguridad' + 'export' => 'Exportación de datos' ], 'traffic' => [ 'traffic' => 'Tráfico', @@ -1371,7 +1365,6 @@ Atentamente, su administrador' 'email_reallydelete_forwarder' => '¿Realmente quieres borrar el forwarder %s?', 'extras_reallydelete' => '¿Realmente quieres borrar la protección de directorio de %s?', 'extras_reallydelete_pathoptions' => '¿Realmente quieres borrar las opciones de ruta de %s?', - 'extras_reallydelete_backup' => '¿Realmente quieres abortar el trabajo de copia de seguridad planificado?', 'ftp_reallydelete' => '¿Realmente quieres borrar la cuenta FTP %s?', 'mysql_reallydelete' => '¿Realmente quieres borrar la base de datos %s? Esto no se puede deshacer.', 'admin_configs_reallyrebuild' => '¿Realmente quieres reconstruir todos los archivos de configuración?', @@ -2030,10 +2023,6 @@ Atentamente, su administrador' 'title' => 'Registros DNS CAA adicionales', 'description' => 'DNS Certification Authority Authorization (CAA) es un mecanismo de política de seguridad en Internet que permite a los titulares de nombres de dominio indicar a las autoridades de certificación
si están autorizadas a emitir certificados digitales para un nombre de dominio concreto. Lo hace mediante un nuevo registro de recursos del Sistema de Nombres de Dominio (DNS) "CAA".

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

Atención: No se comprobará si el código contiene errores. Si contiene errores, ¡es posible que sus registros CAA no funcionen!' ], - 'backupenabled' => [ - 'title' => 'Activar copia de seguridad para clientes', - 'description' => 'Si se activa, el cliente podrá programar trabajos de copia de seguridad (cron-backup) que generan un archivo dentro de su docroot (subdirectorio a elección del cliente)' - ], 'dnseditorenable' => [ 'title' => 'Habilitar editor DNS', 'description' => 'Permite a los administradores y a los clientes gestionar las entradas DNS del dominio' @@ -2209,8 +2198,6 @@ Atentamente, su administrador' 'settingssaved' => 'La configuración se ha guardado correctamente.', 'rebuildingconfigs' => 'Tareas insertadas con éxito para reconstruir archivos de configuración', 'domain_import_successfully' => 'Se han importado correctamente los dominios %s.', - 'backupscheduled' => 'Se ha programado su tarea de copia de seguridad. Espere a que se procese.', - 'backupaborted' => 'Su copia de seguridad programada ha sido cancelada', 'dns_record_added' => 'Registro añadido correctamente', 'dns_record_deleted' => 'Registro eliminado correctamente', 'testmailsent' => 'Correo de prueba enviado correctamente', @@ -2229,7 +2216,6 @@ Atentamente, su administrador' 'DELETE_EMAIL_DATA' => 'Borrar datos de e-mail del cliente.', 'DELETE_FTP_DATA' => 'Borrar los datos de la cuenta ftp del cliente.', 'REBUILD_CRON' => 'Reconstruir el archivo cron.d', - 'CREATE_CUSTOMER_BACKUP' => 'Trabajo de copia de seguridad para el cliente %s', 'DELETE_DOMAIN_PDNS' => 'Borrar dominio %s de la base de datos PowerDNS', 'DELETE_DOMAIN_SSL' => 'Borrar archivos ssl de dominio %s' ], diff --git a/tests/Backup/CustomerBackupsTest.php b/tests/Backup/DataDumpTest.php similarity index 52% rename from tests/Backup/CustomerBackupsTest.php rename to tests/Backup/DataDumpTest.php index a2a4aca1..2e89118e 100644 --- a/tests/Backup/CustomerBackupsTest.php +++ b/tests/Backup/DataDumpTest.php @@ -4,22 +4,22 @@ use PHPUnit\Framework\TestCase; use Froxlor\Settings; use Froxlor\Database\Database; use Froxlor\Api\Commands\Customers; -use Froxlor\Api\Commands\CustomerBackups; +use Froxlor\Api\Commands\DataDump; /** * * @covers \Froxlor\Api\ApiCommand * @covers \Froxlor\Api\ApiParameter - * @covers \Froxlor\Api\Commands\CustomerBackups + * @covers \Froxlor\Api\Commands\DataDump */ -class CustomerBackupsTest extends TestCase +class DataDumpTest extends TestCase { - public function testAdminCustomerBackupsNotEnabled() + public function testAdminDataDumpNotEnabled() { global $admin_userdata; - Settings::Set('system.backupenabled', 0, true); + Settings::Set('system.exportenabled', 0, true); // get customer $json_result = Customers::getLocal($admin_userdata, array( @@ -28,18 +28,18 @@ class CustomerBackupsTest extends TestCase $customer_userdata = json_decode($json_result, true)['data']; $this->expectExceptionCode(405); $this->expectExceptionMessage("You cannot access this resource"); - CustomerBackups::getLocal($customer_userdata)->add(); + DataDump::getLocal($customer_userdata)->add(); } /** * - * @depends testAdminCustomerBackupsNotEnabled + * @depends testAdminDataDumpNotEnabled */ - public function testAdminCustomerBackupsExtrasHidden() + public function testAdminDataDumpExtrasHidden() { global $admin_userdata; - Settings::Set('system.backupenabled', 1, true); + Settings::Set('system.exportenabled', 1, true); Settings::Set('panel.customer_hide_options', 'extras', true); // get customer @@ -49,18 +49,18 @@ class CustomerBackupsTest extends TestCase $customer_userdata = json_decode($json_result, true)['data']; $this->expectExceptionCode(405); $this->expectExceptionMessage("You cannot access this resource"); - CustomerBackups::getLocal($customer_userdata)->add(); + DataDump::getLocal($customer_userdata)->add(); } /** * - * @depends testAdminCustomerBackupsExtrasHidden + * @depends testAdminDataDumpExtrasHidden */ - public function testAdminCustomerBackupsExtrasBackupHidden() + public function testAdminDataDumpExtrasExportHidden() { global $admin_userdata; - Settings::Set('panel.customer_hide_options', 'extras.backup', true); + Settings::Set('panel.customer_hide_options', 'extras.export', true); // get customer $json_result = Customers::getLocal($admin_userdata, array( @@ -69,14 +69,14 @@ class CustomerBackupsTest extends TestCase $customer_userdata = json_decode($json_result, true)['data']; $this->expectExceptionCode(405); $this->expectExceptionMessage("You cannot access this resource"); - CustomerBackups::getLocal($customer_userdata)->add(); + DataDump::getLocal($customer_userdata)->add(); } /** * - * @depends testAdminCustomerBackupsExtrasBackupHidden + * @depends testAdminDataDumpExtrasExportHidden */ - public function testCustomerCustomerBackupsAdd() + public function testCustomerDataDumpAdd() { global $admin_userdata; @@ -90,24 +90,24 @@ class CustomerBackupsTest extends TestCase $customer_userdata = json_decode($json_result, true)['data']; $data = [ - 'path' => '/my-backup', - 'backup_dbs' => 2, - 'backup_mail' => 3, - 'backup_web' => 4 + 'path' => '/my-dump', + 'dump_dbs' => 2, + 'dump_mail' => 3, + 'dump_web' => 4 ]; - $json_result = CustomerBackups::getLocal($customer_userdata, $data)->add(); + $json_result = DataDump::getLocal($customer_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; - $this->assertEquals($customer_userdata['documentroot'] . 'my-backup/', $result['destdir']); - $this->assertEquals('1', $result['backup_dbs']); - $this->assertEquals('1', $result['backup_mail']); - $this->assertEquals('1', $result['backup_web']); + $this->assertEquals($customer_userdata['documentroot'] . 'my-dump/', $result['destdir']); + $this->assertEquals('1', $result['dump_dbs']); + $this->assertEquals('1', $result['dump_mail']); + $this->assertEquals('1', $result['dump_web']); } /** * - * @depends testCustomerCustomerBackupsAdd + * @depends testCustomerDataDumpAdd */ - public function testCustomerCustomerBackupsAddPathNotDocroot() + public function testCustomerDataDumpAddPathNotDocroot() { global $admin_userdata; @@ -122,49 +122,49 @@ class CustomerBackupsTest extends TestCase ]; $this->expectExceptionCode(400); - $this->expectExceptionMessage('The folder for backups cannot be your homedir, please chose a folder within your homedir, e.g. /backups'); - $json_result = CustomerBackups::getLocal($customer_userdata, $data)->add(); + $this->expectExceptionMessage('The folder for data-dumps cannot be your homedir, please chose a folder within your homedir, e.g. /dumps'); + $json_result = DataDump::getLocal($customer_userdata, $data)->add(); } - public function testAdminCustomerBackupsGet() + public function testAdminDataDumpGet() { global $admin_userdata; $this->expectExceptionCode(303); - CustomerBackups::getLocal($admin_userdata)->get(); + DataDump::getLocal($admin_userdata)->get(); } - public function testAdminCustomerBackupsUpdate() + public function testAdminDataDumpUpdate() { global $admin_userdata; $this->expectExceptionCode(303); - CustomerBackups::getLocal($admin_userdata)->update(); + DataDump::getLocal($admin_userdata)->update(); } /** * - * @depends testCustomerCustomerBackupsAdd + * @depends testCustomerDataDumpAdd */ - public function testAdminCustomerBackupsListing() + public function testAdminDataDumpListing() { global $admin_userdata; - $json_result = CustomerBackups::getLocal($admin_userdata)->listing(); + $json_result = DataDump::getLocal($admin_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); - $this->assertEquals('1', $result['list'][0]['data']['backup_dbs']); - $this->assertEquals('1', $result['list'][0]['data']['backup_mail']); - $this->assertEquals('1', $result['list'][0]['data']['backup_web']); + $this->assertEquals('1', $result['list'][0]['data']['dump_dbs']); + $this->assertEquals('1', $result['list'][0]['data']['dump_mail']); + $this->assertEquals('1', $result['list'][0]['data']['dump_web']); - $json_result = CustomerBackups::getLocal($admin_userdata)->listingCount(); + $json_result = DataDump::getLocal($admin_userdata)->listingCount(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result); } /** * - * @depends testCustomerCustomerBackupsAdd + * @depends testCustomerDataDumpAdd */ - public function testCustomerCustomerBackupsDelete() + public function testCustomerDataDumpDelete() { global $admin_userdata; @@ -175,18 +175,18 @@ class CustomerBackupsTest extends TestCase $customer_userdata = json_decode($json_result, true)['data']; $data = [ - 'backup_job_entry' => 1 + 'job_entry' => 1 ]; - $json_result = CustomerBackups::getLocal($customer_userdata, $data)->delete(); + $json_result = DataDump::getLocal($customer_userdata, $data)->delete(); $result = json_decode($json_result, true)['data']; $this->assertTrue($result); } /** * - * @depends testAdminCustomerBackupsListing + * @depends testAdminDataDumpListing */ - public function testCustomerCustomerBackupsDeleteNotFound() + public function testCustomerDataDumpDeleteNotFound() { global $admin_userdata; @@ -200,7 +200,7 @@ class CustomerBackupsTest extends TestCase 'backup_job_entry' => 1337 ]; $this->expectExceptionCode(404); - $this->expectExceptionMessage('Backup job with id #1337 could not be found'); - CustomerBackups::getLocal($customer_userdata, $data)->delete(); + $this->expectExceptionMessage('Data export job with id #1337 could not be found'); + DataDump::getLocal($customer_userdata, $data)->delete(); } } diff --git a/tests/Cron/TaskIdTest.php b/tests/Cron/TaskIdTest.php index 9b24eeec..ba0321a3 100644 --- a/tests/Cron/TaskIdTest.php +++ b/tests/Cron/TaskIdTest.php @@ -30,7 +30,7 @@ class TaskIDTest extends TestCase 'DELETE_DOMAIN_SSL' => 12, - 'CREATE_CUSTOMER_BACKUP' => 20, + 'CREATE_CUSTOMER_DATADUMP' => 20, 'REBUILD_CRON' => 99, ); From eb9dded947e4f7e246a578128818157d45f339a3 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 7 Jun 2023 11:40:42 +0200 Subject: [PATCH 25/87] forgot to save one parameter change in the unit-test Signed-off-by: Michael Kaufmann --- tests/Backup/DataDumpTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Backup/DataDumpTest.php b/tests/Backup/DataDumpTest.php index 2e89118e..97d33907 100644 --- a/tests/Backup/DataDumpTest.php +++ b/tests/Backup/DataDumpTest.php @@ -197,7 +197,7 @@ class DataDumpTest extends TestCase $customer_userdata = json_decode($json_result, true)['data']; $data = [ - 'backup_job_entry' => 1337 + 'job_entry' => 1337 ]; $this->expectExceptionCode(404); $this->expectExceptionMessage('Data export job with id #1337 could not be found'); From 5afe5a8c466a9100b9492fbc9eedee5cf56707b9 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 7 Jun 2023 11:49:57 +0200 Subject: [PATCH 26/87] minor bugfixes for data export / UI Signed-off-by: Michael Kaufmann --- customer_index.php | 4 ++-- lib/Froxlor/Api/Commands/DataDump.php | 2 +- lib/tablelisting/customer/tablelisting.export.php | 2 +- lng/es.lng.php | 1 - templates/Froxlor/user/form-datatable.html.twig | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/customer_index.php b/customer_index.php index 02b65683..4d0d2a6f 100644 --- a/customer_index.php +++ b/customer_index.php @@ -41,7 +41,7 @@ use Froxlor\System\Cronjob; use Froxlor\Cron\TaskId; if ($action == 'logout') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'logged out'); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, 'logged out'); unset($_SESSION['userinfo']); CurrentUser::setData(); @@ -65,7 +65,7 @@ if ($action == 'logout') { } if ($page == 'overview') { - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_index"); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "viewed customer_index"); $domain_stmt = Database::prepare("SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :customerid diff --git a/lib/Froxlor/Api/Commands/DataDump.php b/lib/Froxlor/Api/Commands/DataDump.php index 00579733..fef7099d 100644 --- a/lib/Froxlor/Api/Commands/DataDump.php +++ b/lib/Froxlor/Api/Commands/DataDump.php @@ -132,7 +132,7 @@ class DataDump extends ApiCommand implements ResourceEntity // schedule export job Cronjob::inserttask(TaskId::CREATE_CUSTOMER_DATADUMP, $task_data); - $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer data-dump job for '" . $customer['loginname'] . "'. Target directory: " . $userpath); + $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer data export job for '" . $customer['loginname'] . "'. Target directory: " . $userpath); return $this->response($task_data); } diff --git a/lib/tablelisting/customer/tablelisting.export.php b/lib/tablelisting/customer/tablelisting.export.php index fe37c571..d485bb4c 100644 --- a/lib/tablelisting/customer/tablelisting.export.php +++ b/lib/tablelisting/customer/tablelisting.export.php @@ -74,7 +74,7 @@ return [ 'class' => 'btn-warning', 'href' => [ 'section' => 'extras', - 'page' => 'backup', + 'page' => 'export', 'action' => 'abort', 'id' => ':id' ], diff --git a/lng/es.lng.php b/lng/es.lng.php index 29973803..ab50024e 100644 --- a/lng/es.lng.php +++ b/lng/es.lng.php @@ -956,7 +956,6 @@ return [ 'autoupdate_10' => 'La versión mínima soportada de PHP es 7.4.0', 'autoupdate_11' => 'Webupdate está desactivado', 'mailaccistobedeleted' => 'Otra cuenta con el mismo nombre (%s) está siendo eliminada y por lo tanto no puede ser añadida en este momento.', - 'customerhasongoingbackupjob' => 'Ya hay un trabajo de copia de seguridad esperando a ser procesado, por favor sea paciente.', 'dns_domain_nodns' => 'DNS no está habilitado para este dominio', 'dns_content_empty' => 'No hay contenido', 'dns_content_invalid' => 'El contenido DNS no es válido', diff --git a/templates/Froxlor/user/form-datatable.html.twig b/templates/Froxlor/user/form-datatable.html.twig index e04ebd2d..9343c9b9 100644 --- a/templates/Froxlor/user/form-datatable.html.twig +++ b/templates/Froxlor/user/form-datatable.html.twig @@ -4,7 +4,7 @@ {% if tabledata.table.tr is not empty and tabledata.table.tr is iterable %}
{% set type = 'warning' %} - {% set alert_msg = lng('error.customerhasongoingbackupjob') %} + {% set alert_msg = lng('error.customerhasongoingexportjob') %} {% include 'Froxlor/misc/alertbox.html.twig' %}
{% import "Froxlor/table/table.html.twig" as table %} From bfc816a51efde55be504c2e2c18b4766ba1943f2 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 7 Jun 2023 15:08:36 +0200 Subject: [PATCH 27/87] add php-configuration to domain-listing, fixes #1141; move backup-menu to 'System' instead of 'Resources' Signed-off-by: Michael Kaufmann --- lib/Froxlor/UI/Callbacks/Domain.php | 29 +++++++++++++++---- lib/navigation/00.froxlor.main.php | 12 ++++---- .../admin/tablelisting.domains.php | 8 +++++ 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/Froxlor/UI/Callbacks/Domain.php b/lib/Froxlor/UI/Callbacks/Domain.php index d56c83d6..d476db9b 100644 --- a/lib/Froxlor/UI/Callbacks/Domain.php +++ b/lib/Froxlor/UI/Callbacks/Domain.php @@ -25,6 +25,7 @@ namespace Froxlor\UI\Callbacks; +use Froxlor\Database\Database; use Froxlor\Domain\Domain as DDomain; use Froxlor\FileDir; use Froxlor\Settings; @@ -79,11 +80,11 @@ class Domain { $result = ''; if ($attributes['fields']['parentdomainid'] != 0) { - $result = ''; + $result = ''; } $result .= '' . $attributes['data'] . ''; // check for statistics if parentdomainid==0 to show stats-link for customers - if ((int)UI::getCurrentUser()['adminsession'] == 0 && $attributes['fields']['parentdomainid'] == 0 && $attributes['fields']['deactivated'] == 0) { + if ((int)UI::getCurrentUser()['adminsession'] == 0 && $attributes['fields']['parentdomainid'] == 0 && $attributes['fields']['deactivated'] == 0) { $statsapp = Settings::Get('system.traffictool'); $result .= ' '; } @@ -189,13 +190,11 @@ class Domain // specified certificate for domain if ($attributes['fields']['domain_hascert'] == 1) { $result['icon'] .= ' text-success'; - } - // shared certificates (e.g. subdomain if domain where certificate is specified) + } // shared certificates (e.g. subdomain if domain where certificate is specified) elseif ($attributes['fields']['domain_hascert'] == 2) { $result['icon'] .= ' text-warning'; $result['title'] .= "\n" . lng('panel.ssleditor_infoshared'); - } - // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings) + } // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings) elseif ($attributes['fields']['domain_hascert'] == 0) { $result['icon'] .= ' text-danger'; $result['title'] .= "\n" . lng('panel.ssleditor_infoglobal'); @@ -217,4 +216,22 @@ class Domain } return lng('panel.empty'); } + + public static function getPhpConfigName(array $attributes): string + { + $sel_stmt = Database::prepare("SELECT `description` FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id"); + $phpconfig = Database::pexecute_first($sel_stmt, ['id' => $attributes['data']]); + if ((int)UI::getCurrentUser()['adminsession'] == 1) { + $linker = UI::getLinker(); + $result = '' . $phpconfig['description'] . ''; + } else { + $result = $phpconfig['description']; + } + return $result; + } } diff --git a/lib/navigation/00.froxlor.main.php b/lib/navigation/00.froxlor.main.php index 012308e0..e27c22b1 100644 --- a/lib/navigation/00.froxlor.main.php +++ b/lib/navigation/00.froxlor.main.php @@ -223,12 +223,6 @@ return [ 'required_resources' => 'customers', 'add_shortlink' => 'admin_plans.php?page=overview&action=add' ], - [ - 'url' => 'admin_backups.php?page=overview', - 'label' => lng('admin.backups.backups'), - 'required_resources' => 'change_serversettings', - 'add_shortlink' => 'admin_backups.php?page=overview&action=add' - ], [ 'url' => 'admin_settings.php?page=updatecounters', 'label' => lng('admin.updatecounters'), @@ -268,6 +262,12 @@ return [ 'label' => lng('admin.cron.cronsettings'), 'required_resources' => 'change_serversettings' ], + [ + 'url' => 'admin_backups.php?page=overview', + 'label' => lng('admin.backups.backups'), + 'required_resources' => 'change_serversettings', + 'add_shortlink' => 'admin_backups.php?page=overview&action=add' + ], [ 'url' => 'admin_logger.php?page=log', 'label' => lng('menue.logger.logger'), diff --git a/lib/tablelisting/admin/tablelisting.domains.php b/lib/tablelisting/admin/tablelisting.domains.php index 3c484e11..a5f578ee 100644 --- a/lib/tablelisting/admin/tablelisting.domains.php +++ b/lib/tablelisting/admin/tablelisting.domains.php @@ -23,6 +23,7 @@ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2 */ +use Froxlor\Settings; use Froxlor\UI\Callbacks\Domain; use Froxlor\UI\Callbacks\Impersonate; use Froxlor\UI\Callbacks\Style; @@ -114,6 +115,13 @@ return [ 'field' => 'phpenabled', 'callback' => [Text::class, 'boolean'], ], + 'd.phpsettingid' => [ + 'label' => lng('admin.phpsettings.title'), + 'field' => 'phpsettingid', + 'searchable' => false, + 'callback' => [Domain::class, 'getPhpConfigName'], + 'visible' => (int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1 + ], 'd.openbasedir' => [ 'label' => lng('domains.openbasedirenabled'), 'field' => 'openbasedir', From 3445472049362ef9652ebc96107027db179b7eec Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 7 Jun 2023 16:18:05 +0200 Subject: [PATCH 28/87] combine change-password, change-theme and change-language into 'my profile' Signed-off-by: Michael Kaufmann --- admin_index.php | 167 +++++++------ customer_index.php | 223 +++++++++--------- lng/de.lng.php | 1 + lng/en.lng.php | 1 + .../Froxlor/user/change_language.html.twig | 34 --- .../Froxlor/user/change_password.html.twig | 57 ----- templates/Froxlor/user/change_theme.html.twig | 34 --- templates/Froxlor/user/profile.html.twig | 142 +++++++++++ templates/Froxlor/userarea.html.twig | 14 +- 9 files changed, 337 insertions(+), 336 deletions(-) delete mode 100644 templates/Froxlor/user/change_language.html.twig delete mode 100644 templates/Froxlor/user/change_password.html.twig delete mode 100644 templates/Froxlor/user/change_theme.html.twig create mode 100644 templates/Froxlor/user/profile.html.twig diff --git a/admin_index.php b/admin_index.php index f5479e4e..91ea51a5 100644 --- a/admin_index.php +++ b/admin_index.php @@ -31,6 +31,7 @@ use Froxlor\Api\Commands\Froxlor as Froxlor; use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\FroxlorLogger; +use Froxlor\Language; use Froxlor\Settings; use Froxlor\System\Cronjob; use Froxlor\System\Crypt; @@ -38,7 +39,6 @@ use Froxlor\UI\Panel\UI; use Froxlor\UI\Request; use Froxlor\UI\Response; use Froxlor\Validate\Validate; -use Froxlor\Language; $id = (int)Request::any('id'); @@ -196,107 +196,104 @@ if ($page == 'overview') { 'outstanding_tasks' => $outstanding_tasks, 'cron_last_runs' => $cron_last_runs ]); -} elseif ($page == 'change_password') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $old_password = Validate::validate($_POST['old_password'], 'old password'); +} elseif ($page == 'profile') { + $languages = Language::getLanguages(); - if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_ADMINS, 'adminid')) { - Response::standardError('oldpasswordnotcorrect'); - } + if (!empty($_POST)) { + if ($_POST['send'] == 'changepassword') { + $old_password = Validate::validate($_POST['old_password'], 'old password'); - try { - $new_password = Crypt::validatePassword($_POST['new_password'], 'new password'); - $new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm'); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } + if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_ADMINS, 'adminid')) { + Response::standardError('oldpasswordnotcorrect'); + } - if ($old_password == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.old_password' - ]); - } elseif ($new_password == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.new_password' - ]); - } elseif ($new_password_confirm == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.new_password_confirm' - ]); - } elseif ($new_password != $new_password_confirm) { - Response::standardError('newpasswordconfirmerror'); - } else { try { - Admins::getLocal($userinfo, [ - 'id' => $userinfo['adminid'], - 'admin_password' => $new_password - ])->update(); + $new_password = Crypt::validatePassword($_POST['new_password'], 'new password'); + $new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm'); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } - $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'changed password'); + + if ($old_password == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.old_password' + ]); + } elseif ($new_password == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.new_password' + ]); + } elseif ($new_password_confirm == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.new_password_confirm' + ]); + } elseif ($new_password != $new_password_confirm) { + Response::standardError('newpasswordconfirmerror'); + } else { + try { + Admins::getLocal($userinfo, [ + 'id' => $userinfo['adminid'], + 'admin_password' => $new_password + ])->update(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, 'changed password'); + Response::redirectTo($filename); + } + } elseif ($_POST['send'] == 'changetheme') { + if (Settings::Get('panel.allow_theme_change_admin') == 1) { + $theme = Validate::validate($_POST['theme'], 'theme'); + try { + Admins::getLocal($userinfo, [ + 'id' => $userinfo['adminid'], + 'theme' => $theme + ])->update(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + + $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her theme to '" . $theme . "'"); + } + Response::redirectTo($filename); + } elseif ($_POST['send'] == 'changelanguage') { + $def_language = Validate::validate($_POST['def_language'], 'default language'); + + if (isset($languages[$def_language])) { + try { + Admins::getLocal($userinfo, [ + 'id' => $userinfo['adminid'], + 'def_language' => $def_language + ])->update(); + CurrentUser::setField('language', $def_language); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + } + $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her default language to '" . $def_language . "'"); Response::redirectTo($filename); } } else { - UI::view('user/change_password.html.twig'); - } -} elseif ($page == 'change_language') { - $languages = Language::getLanguages(); - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $def_language = Validate::validate($_POST['def_language'], 'default language'); - - if (isset($languages[$def_language])) { - try { - Admins::getLocal($userinfo, [ - 'id' => $userinfo['adminid'], - 'def_language' => $def_language - ])->update(); - CurrentUser::setField('language', $def_language); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } + // change theme + $default_theme = Settings::Get('panel.default_theme'); + if ($userinfo['theme'] != '') { + $default_theme = $userinfo['theme']; } - $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her default language to '" . $def_language . "'"); - Response::redirectTo($filename); - } else { + $themes_avail = UI::getThemes(); + + // change language $default_lang = Settings::Get('panel.standardlanguage'); if ($userinfo['def_language'] != '') { $default_lang = $userinfo['def_language']; } - UI::view('user/change_language.html.twig', [ - 'languages' => $languages, - 'default_lang' => $default_lang - ]); - } -} elseif ($page == 'change_theme') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $theme = Validate::validate($_POST['theme'], 'theme'); - try { - Admins::getLocal($userinfo, [ - 'id' => $userinfo['adminid'], - 'theme' => $theme - ])->update(); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } - - $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "changed his/her theme to '" . $theme . "'"); - Response::redirectTo($filename); - } else { - $default_theme = Settings::Get('panel.default_theme'); - if ($userinfo['theme'] != '') { - $default_theme = $userinfo['theme']; - } - - $themes_avail = UI::getThemes(); - - UI::view('user/change_theme.html.twig', [ + UI::view('user/profile.html.twig', [ 'themes' => $themes_avail, - 'default_theme' => $default_theme + 'default_theme' => $default_theme, + 'languages' => $languages, + 'default_lang' => $default_lang, ]); } } elseif ($page == 'send_error_report' && Settings::Get('system.allow_error_report_admin') == '1') { diff --git a/customer_index.php b/customer_index.php index 4d0d2a6f..17b48486 100644 --- a/customer_index.php +++ b/customer_index.php @@ -27,18 +27,18 @@ const AREA = 'customer'; require __DIR__ . '/lib/init.php'; use Froxlor\Api\Commands\Customers as Customers; +use Froxlor\Cron\TaskId; use Froxlor\CurrentUser; use Froxlor\Database\Database; use Froxlor\Froxlor; use Froxlor\FroxlorLogger; +use Froxlor\Language; use Froxlor\Settings; +use Froxlor\System\Cronjob; use Froxlor\System\Crypt; use Froxlor\UI\Panel\UI; use Froxlor\UI\Response; use Froxlor\Validate\Validate; -use Froxlor\Language; -use Froxlor\System\Cronjob; -use Froxlor\Cron\TaskId; if ($action == 'logout') { $log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, 'logged out'); @@ -115,13 +115,13 @@ if ($page == 'overview') { if ($usages) { $userinfo['diskspace_bytes_used'] = $usages['webspace'] * 1024; - $userinfo['mailspace_used'] = $usages['mail'] * 1024; + $userinfo['mailspace_used'] = $usages['mail'] * 1024; $userinfo['dbspace_used'] = $usages['mysql'] * 1024; $userinfo['total_bytes_used'] = ($usages['webspace'] + $usages['mail'] + $usages['mysql']) * 1024; } else { $userinfo['diskspace_bytes_used'] = 0; $userinfo['total_bytes_used'] = 0; - $userinfo['mailspace_used'] = 0; + $userinfo['mailspace_used'] = 0; $userinfo['dbspace_used'] = 0; } @@ -130,141 +130,138 @@ if ($page == 'overview') { 'domains' => $domainArray, 'stdsubdomain' => $stdsubdomain ]); -} elseif ($page == 'change_password') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $old_password = Validate::validate($_POST['old_password'], 'old password'); +} elseif ($page == 'profile') { + $languages = Language::getLanguages(); - if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_CUSTOMERS, 'customerid')) { - Response::standardError('oldpasswordnotcorrect'); - } + if (!empty($_POST)) { + if ($_POST['send'] == 'changepassword') { + $old_password = Validate::validate($_POST['old_password'], 'old password'); - try { - $new_password = Crypt::validatePassword($_POST['new_password'], 'new password'); - $new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm'); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } + if (!Crypt::validatePasswordLogin($userinfo, $old_password, TABLE_PANEL_CUSTOMERS, 'customerid')) { + Response::standardError('oldpasswordnotcorrect'); + } - if ($old_password == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.old_password' - ]); - } elseif ($new_password == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.new_password' - ]); - } elseif ($new_password_confirm == '') { - Response::standardError([ - 'stringisempty', - 'changepassword.new_password_confirm' - ]); - } elseif ($new_password != $new_password_confirm) { - Response::standardError('newpasswordconfirmerror'); - } else { - // Update user password try { - Customers::getLocal($userinfo, [ - 'id' => $userinfo['customerid'], - 'new_customer_password' => $new_password - ])->update(); + $new_password = Crypt::validatePassword($_POST['new_password'], 'new password'); + $new_password_confirm = Crypt::validatePassword($_POST['new_password_confirm'], 'new password confirm'); } catch (Exception $e) { Response::dynamicError($e->getMessage()); } - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed password'); - // Update ftp password - if (isset($_POST['change_main_ftp']) && $_POST['change_main_ftp'] == 'true') { - $cryptPassword = Crypt::makeCryptPassword($new_password); - $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` + if ($old_password == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.old_password' + ]); + } elseif ($new_password == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.new_password' + ]); + } elseif ($new_password_confirm == '') { + Response::standardError([ + 'stringisempty', + 'changepassword.new_password_confirm' + ]); + } elseif ($new_password != $new_password_confirm) { + Response::standardError('newpasswordconfirmerror'); + } else { + // Update user password + try { + Customers::getLocal($userinfo, [ + 'id' => $userinfo['customerid'], + 'new_customer_password' => $new_password + ])->update(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed password'); + + // Update ftp password + if (isset($_POST['change_main_ftp']) && $_POST['change_main_ftp'] == 'true') { + $cryptPassword = Crypt::makeCryptPassword($new_password); + $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` SET `password` = :password WHERE `customerid` = :customerid AND `username` = :username"); - $params = [ - "password" => $cryptPassword, - "customerid" => $userinfo['customerid'], - "username" => $userinfo['loginname'] - ]; - Database::pexecute($stmt, $params); - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed main ftp password'); - } + $params = [ + "password" => $cryptPassword, + "customerid" => $userinfo['customerid'], + "username" => $userinfo['loginname'] + ]; + Database::pexecute($stmt, $params); + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, 'changed main ftp password'); + } - // Update statistics password - if (isset($_POST['change_stats']) && $_POST['change_stats'] == 'true') { - $new_stats_password = Crypt::makeCryptPassword($new_password, true); + // Update statistics password + if (isset($_POST['change_stats']) && $_POST['change_stats'] == 'true') { + $new_stats_password = Crypt::makeCryptPassword($new_password, true); - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_HTPASSWDS . "` + $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_HTPASSWDS . "` SET `password` = :password WHERE `customerid` = :customerid AND `username` = :username"); - $params = [ - "password" => $new_stats_password, - "customerid" => $userinfo['customerid'], - "username" => $userinfo['loginname'] - ]; - Database::pexecute($stmt, $params); - Cronjob::inserttask(TaskId::REBUILD_VHOST); - } + $params = [ + "password" => $new_stats_password, + "customerid" => $userinfo['customerid'], + "username" => $userinfo['loginname'] + ]; + Database::pexecute($stmt, $params); + Cronjob::inserttask(TaskId::REBUILD_VHOST); + } + Response::redirectTo($filename); + } + } elseif ($_POST['send'] == 'changetheme') { + if (Settings::Get('panel.allow_theme_change_customer') == 1) { + $theme = Validate::validate($_POST['theme'], 'theme'); + try { + Customers::getLocal($userinfo, [ + 'id' => $userinfo['customerid'], + 'theme' => $theme + ])->update(); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default theme to '" . $theme . "'"); + } + Response::redirectTo($filename); + } elseif ($_POST['send'] == 'changelanguage') { + $def_language = Validate::validate($_POST['def_language'], 'default language'); + if (isset($languages[$def_language])) { + try { + Customers::getLocal($userinfo, [ + 'id' => $userinfo['customerid'], + 'def_language' => $def_language + ])->update(); + CurrentUser::setField('language', $def_language); + } catch (Exception $e) { + Response::dynamicError($e->getMessage()); + } + } + $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default language to '" . $def_language . "'"); Response::redirectTo($filename); } } else { - UI::view('user/change_password.html.twig'); - } -} elseif ($page == 'change_language') { - $languages = Language::getLanguages(); - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $def_language = Validate::validate($_POST['def_language'], 'default language'); - if (isset($languages[$def_language])) { - try { - Customers::getLocal($userinfo, [ - 'id' => $userinfo['customerid'], - 'def_language' => $def_language - ])->update(); - CurrentUser::setField('language', $def_language); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } + // change theme + $default_theme = Settings::Get('panel.default_theme'); + if ($userinfo['theme'] != '') { + $default_theme = $userinfo['theme']; } - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default language to '" . $def_language . "'"); - Response::redirectTo($filename); - } else { + $themes_avail = UI::getThemes(); + + // change language $default_lang = Settings::Get('panel.standardlanguage'); if ($userinfo['def_language'] != '') { $default_lang = $userinfo['def_language']; } - UI::view('user/change_language.html.twig', [ - 'languages' => $languages, - 'default_lang' => $default_lang - ]); - } -} elseif ($page == 'change_theme') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - $theme = Validate::validate($_POST['theme'], 'theme'); - try { - Customers::getLocal($userinfo, [ - 'id' => $userinfo['customerid'], - 'theme' => $theme - ])->update(); - } catch (Exception $e) { - Response::dynamicError($e->getMessage()); - } - - $log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "changed default theme to '" . $theme . "'"); - Response::redirectTo($filename); - } else { - $default_theme = Settings::Get('panel.default_theme'); - if ($userinfo['theme'] != '') { - $default_theme = $userinfo['theme']; - } - - $themes_avail = UI::getThemes(); - - UI::view('user/change_theme.html.twig', [ + UI::view('user/profile.html.twig', [ 'themes' => $themes_avail, - 'default_theme' => $default_theme + 'default_theme' => $default_theme, + 'languages' => $languages, + 'default_lang' => $default_lang, ]); } } elseif ($page == 'send_error_report' && Settings::Get('system.allow_error_report_customer') == '1') { diff --git a/lng/de.lng.php b/lng/de.lng.php index 46b64892..ce712eb4 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -1236,6 +1236,7 @@ Vielen Dank, Ihr Administrator', 'description' => 'Wähle das zu durchsuchende Feld aus' ], 'upload_import' => 'Hochladen und importieren', + 'profile' => 'Mein Profil', ], 'phpfpm' => [ 'vhost_httpuser' => 'Lokaler Benutzer für PHP-FPM (Froxlor-Vhost)', diff --git a/lng/en.lng.php b/lng/en.lng.php index 2a20e567..8994dbc0 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -1351,6 +1351,7 @@ Yours sincerely, your administrator', 'description' => 'Select the field you want to search in' ], 'upload_import' => 'Upload and import', + 'profile' => 'My profile', ], 'phpfpm' => [ 'vhost_httpuser' => 'Local user to use for PHP-FPM (Froxlor vHost)', diff --git a/templates/Froxlor/user/change_language.html.twig b/templates/Froxlor/user/change_language.html.twig deleted file mode 100644 index c035f1f9..00000000 --- a/templates/Froxlor/user/change_language.html.twig +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "Froxlor/userarea.html.twig" %} - -{% block content %} -
-
-
-
-
-
{{ lng('menue.main.changelanguage') }}
- -
- - -
-
- -
- - - - -
-
-
-
-
-{% endblock %} diff --git a/templates/Froxlor/user/change_password.html.twig b/templates/Froxlor/user/change_password.html.twig deleted file mode 100644 index 49d49761..00000000 --- a/templates/Froxlor/user/change_password.html.twig +++ /dev/null @@ -1,57 +0,0 @@ -{% extends "Froxlor/userarea.html.twig" %} - -{% block content %} -
-
-
-
-
-
{{ lng('menue.main.changepassword') }}
- -
- - -
-
- - -
-
- - -
- - {% if userinfo.adminsession == 0 %} - -
- -
- - -
-
- -
- -
- - -
-
- - {% endif %} -
- -
- - - - -
-
-
-
-
-{% endblock %} diff --git a/templates/Froxlor/user/change_theme.html.twig b/templates/Froxlor/user/change_theme.html.twig deleted file mode 100644 index 12ecae79..00000000 --- a/templates/Froxlor/user/change_theme.html.twig +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "Froxlor/userarea.html.twig" %} - -{% block content %} -
-
-
-
-
-
{{ lng('menue.main.changetheme') }}
- -
- - -
-
- -
- - - - -
-
-
-
-
-{% endblock %} diff --git a/templates/Froxlor/user/profile.html.twig b/templates/Froxlor/user/profile.html.twig new file mode 100644 index 00000000..842120eb --- /dev/null +++ b/templates/Froxlor/user/profile.html.twig @@ -0,0 +1,142 @@ +{% extends "Froxlor/userarea.html.twig" %} + +{% block content %} + + +
+
+ {# change password #} +
+
+
+
{{ lng('menue.main.changepassword') }}
+ +
+ + +
+
+ + +
+
+ + +
+ + {% if userinfo.adminsession == 0 %} + +
+ +
+ + +
+
+ +
+ +
+ + +
+
+ + {% endif %} +
+ +
+ + + + +
+
+
+
+ {% if (get_setting('panel.allow_theme_change_admin') == '1' and userinfo.adminsession == 1) or (get_setting('panel.allow_theme_change_customer') == '1' and userinfo.adminsession == 0) %} +
+ {# change theme #} +
+
+
+
{{ lng('menue.main.changetheme') }}
+ +
+ + +
+
+ +
+ + + + +
+
+
+
+ {% endif %} +
+ {# change language #} +
+
+
+
{{ lng('menue.main.changelanguage') }}
+ +
+ + +
+
+ +
+ + + + +
+
+
+
+ +
+{% endblock %} diff --git a/templates/Froxlor/userarea.html.twig b/templates/Froxlor/userarea.html.twig index 815df808..ddcccda5 100644 --- a/templates/Froxlor/userarea.html.twig +++ b/templates/Froxlor/userarea.html.twig @@ -51,25 +51,13 @@ - {% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %} + {% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
  • - {{ userinfo.custom_notes|nl2br|raw }} + {{ userinfo.custom_notes|markdown|raw }}
@@ -263,10 +263,10 @@
{% endif %} - {% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %} + {% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
  • - {{ userinfo.custom_notes|nl2br|raw }} + {{ userinfo.custom_notes|markdown|raw }}
  • {% endif %}