Compare commits

...

60 Commits

Author SHA1 Message Date
Michael Kaufmann
95abe465ef set version to 2.1.0-beta2
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-18 16:00:06 +02:00
Michael Kaufmann
780f607332 remove unnecessary vite-required; fix fonts-path on subdirectory-installation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-18 15:01:49 +02:00
Michael Kaufmann
a11d26522a fix js integrations
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-18 14:25:02 +02:00
Michael Kaufmann
462a798cb6 more beautification b/c of bootstrap 5.3 #2
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-17 15:00:52 +02:00
Michael Kaufmann
7556685881 more beautification b/c of bootstrap 5.3
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-17 14:25:02 +02:00
Michael Kaufmann
965e2dfd95 darkmode optimizations
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-17 10:17:20 +02:00
Michael Kaufmann
1f2cce6195 more work on bootstrap darkmode implementation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-16 18:19:36 +02:00
envoyr
f4f84aa397 update npm packages
Signed-off-by: envoyr <hello@envoyr.com>
2023-10-16 12:50:29 +02:00
envoyr
0f37dfb1eb remove mix; add vite
Signed-off-by: envoyr <hello@envoyr.com>
2023-10-16 12:48:35 +02:00
Michael Kaufmann
7438786a24 adjustments to support bootstrap 5.3 color-scheme; set gentoo config-templates to deprecated as there is no active maintainer for it; remove debian 10 and ubuntu 18.04 as they were deprecated in 2.0.x
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-15 15:49:12 +02:00
Michael Kaufmann
041c2d176c more bootstrap-5.3 adjustments in css-classes etc.
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-15 11:26:08 +02:00
Michael Kaufmann
597e765677 replace deprecated text-muted css class with bootstrap-5.3's text-body-secondary
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-15 10:29:43 +02:00
Michael Kaufmann
f757233d61 dont check for standardsubdomain in SubDomains.listingCount() as it was also removed from SubDomains.listing()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-13 16:29:53 +02:00
Michael Kaufmann
cfae3540fc set version to 2.1.0-beta1
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-13 10:25:23 +02:00
Michael Kaufmann
9e8f32f1e8 check for symlinks when required to be within customer-homedir
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-13 10:18:53 +02:00
dependabot[bot]
a7b66227e6 Bump postcss from 8.4.23 to 8.4.31 (#1192)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.23 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.23...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-10 08:39:16 +02:00
Michael Kaufmann
532982784f updated dependencies
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-10 08:33:30 +02:00
Michael Kaufmann
0754be3028 Merge remote-tracking branch 'origin/2.1.x' 2023-10-06 12:04:52 +02:00
Michael Kaufmann
166ec0575b set version to 2.0.24 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-06 11:18:18 +02:00
Michael Kaufmann
e8ed43056c enable markdown syntax in custom_notes field
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-02 15:40:50 +02:00
Michael Kaufmann
a808a3f782 fix ssl-enabled flag when using Domains.duplicate() and disable ssl-enabled if remove-ssl-ipandport parameter is set
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-02 14:12:55 +02:00
Michael Kaufmann
686065c294 some cleanup; hide ssl-related settings when ssl-usage is off when creating/updating domains; add database-update option to update-cli if files are already up-to-date
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-02 13:49:00 +02:00
Michael Kaufmann
41ac713325 make overview of customers faster by reducing mysql and php load when calculating traffic details; fixes #1161
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-24 17:54:05 +02:00
Michael Kaufmann
d1cb32b47f add formfield for domain-duplication; fix missing check for changed field in Domains.update() to force temporary disabling of ssl-vhost
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-24 17:03:15 +02:00
Michael Kaufmann
13b6ab0b07 add documentation links to customer-ui for certain entities; add setting to allow menu to be expanded
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-24 16:04:20 +02:00
Andreu Trepat Rubirola
215e749ba8 added ca language (#1184) 2023-09-24 15:22:33 +02:00
Michael Kaufmann
0b7d2358ed remove courier mda from gentoo configfiles
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-18 09:42:23 +02:00
Michael Kaufmann
f3c965fe53 more cleaning of planned backup-feature (postponed, see backup-feature branch)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-18 09:29:11 +02:00
Michael Kaufmann
5b58ab4371 fix unit-test as we have one less cronjob now
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-17 13:41:55 +02:00
Michael Kaufmann
3ad203535a adjust github actions buildscript
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-17 13:39:30 +02:00
Michael Kaufmann
6edc6553bd remove wip backup-feature for later releases, see branch backup-feature
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-17 13:19:00 +02:00
Michael Kaufmann
3fc18f9903 fix language-strings; disallow direct removing of certificates if issuer=lets encrypt; fix sql query in updater; porting nginx regex for vhost-merging
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-12 15:25:47 +02:00
Michael Kaufmann
506cccd7c8 fix vhost-cleaning regex for nginx-location directives; fixes #1185
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-12 15:20:56 +02:00
Michael Kaufmann
6ad1ca2ba9 fix API permission error in navigation when customer-hide-options include 'domains'; fixes #1183
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-07 15:35:53 +02:00
Michael Kaufmann
6d9014c29b fix API permission error in navigation when customer-hide-options include 'domains'; fixes #1183
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-07 15:34:06 +02:00
Ruben Barkow-Kuder
7e168f5a0e Add tabindex to search (#1182) 2023-09-06 10:47:48 +02:00
Michael Kaufmann
4fcf0606c7 and again more work on backup-storages
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-05 11:03:39 +02:00
Michael Kaufmann
9d2077ddee more work on backup-storages; add backup cli-command
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-04 10:54:59 +02:00
Michael Kaufmann
10555bff76 set version to 2.0.23 for upcoming bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-03 20:16:18 +02:00
Michael Kaufmann
338b855947 check for existing userinfo if settings are being imported via cli
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 17:36:37 +02:00
Michael Kaufmann
5d04b8c829 only check non-admin resources if user is not an admin in navigation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 17:36:31 +02:00
Michael Kaufmann
37aa7af4da check for existing userinfo if settings are being imported via cli
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 17:11:06 +02:00
Michael Kaufmann
4b75369597 only check non-admin resources if user is not an admin in navigation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 15:53:15 +02:00
Michael Kaufmann
9d0e463906 set version to 2.0.22 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-01 09:58:33 +02:00
Daniel
a0406932c3 Fix"Add" shortcut link in email address navigation (#1169)
Seems to have changed when adding the domain-filter overview for email addresses, but not updated in the navigation.
2023-08-13 08:22:49 +02:00
Daniel
a7198f58ce Fix"Add" shortcut link in email address navigation (#1169)
Seems to have changed when adding the domain-filter overview for email addresses, but not updated in the navigation.
2023-08-13 08:19:32 +02:00
Michael Kaufmann
47be4b2847 remove shortcode for --diff-params in configdiff command
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:04:58 +02:00
Daniel
b0fae4bd14 Add config-diff CLI Command (#1168)
---------

Co-authored-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-12 09:03:16 +02:00
Michael Kaufmann
4711a41436 correct validation of hostingplan name and description
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 13:57:21 +02:00
Michael Kaufmann
faa71ceaef forgot to save one file for the last commit
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 12:13:33 +02:00
Michael Kaufmann
2d30394150 correctly redirect to last-page if session is timed out and remove passing script/qrystr url parameters
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-08-11 12:09:23 +02:00
Michael Kaufmann
99c1182af8 adjustments in installation for debian 12 and fcgid / disabling mod_php; thx to Konstantin
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-27 11:25:03 +02:00
Michael Kaufmann
d9abe58dd2 adjust proftpd config for debian 12 bookworm
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-22 13:00:11 +02:00
Michael Kaufmann
23034b8ad2 rework path to certificates non-ecc/ecc, regardless of current setting
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-21 08:54:29 +02:00
Michael Kaufmann
1cae5638d3 fix optional-flag for IpsAndPorts.add() and IpsAndPorts.update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-16 17:09:45 +02:00
Michael Kaufmann
ce9a5f97a3 validate non-empy admin-name in Admins.update()
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-16 16:44:46 +02:00
Michael Kaufmann
c38b90deef Merge branch 'main' of github.com:Froxlor/Froxlor 2023-07-07 09:52:37 +02:00
Michael Kaufmann
13daa7d6fa set version to 2.0.21 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-07 09:50:50 +02:00
Michael Kaufmann
b0e43d332d validate generated config-json parameter string
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-07-07 09:47:17 +02:00
jabertwo
75c8754fb4 Fix typo in pathDescriptionSubdomain (#1156) 2023-06-26 11:03:48 +02:00
177 changed files with 5487 additions and 23469 deletions

View File

@@ -8,8 +8,8 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.4', '8.1']
mariadb-version: [10.5, 10.4]
php-versions: ['7.4', '8.2']
mariadb-version: [10.11, 10.5]
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -19,7 +19,7 @@ jobs:
with:
php-version: ${{ matrix.php-versions }}
tools: composer:v2
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
- name: Install tools
run: sudo apt-get install -y ant

View File

@@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.4', '8.1']
php-versions: ['7.4', '8.2']
mysql-version: [8.0, 5.7]
steps:
- name: Checkout
@@ -19,7 +19,7 @@ jobs:
with:
php-version: ${{ matrix.php-versions }}
tools: composer:v2
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
- name: Install tools
run: sudo apt-get install -y ant

5
.gitignore vendored
View File

@@ -22,8 +22,5 @@ fonts/
templates/*
!templates/index.html
!templates/Froxlor/
templates/Froxlor/assets/mix-manifest.json
templates/Froxlor/assets/css/
templates/Froxlor/assets/js/
templates/Froxlor/assets/webfonts/
templates/Froxlor/build/
!templates/misc/

View File

@@ -10,9 +10,10 @@ With that, good luck hacking us ;)
## Supported versions
- ️✅ **2.x** (`main` git-branch)
-0.10.x (`0.10.x` git-branch)
- ❌ 0.9.x (`0.9.x`git-branch)
- ️✅ **2.1.x** (`main` git-branch)
-2.0.x (`2.0.x`-tags)
- ❌ 0.10.x (`0.10.x`-tags)
- ❌ other git-branches
## Qualifying Vulnerabilities

View File

@@ -337,7 +337,15 @@ return [
'image_name' => 'logo_login',
'default' => '',
'save_method' => 'storeSettingImage'
]
],
'panel_menu_collapsed' => [
'label' => lng('serversettings.panel_menu_collapsed'),
'settinggroup' => 'panel',
'varname' => 'menu_collapsed',
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField',
],
]
]
]

View File

@@ -1,87 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
return [
'groups' => [
'backup' => [
'title' => lng('backup'),
'icon' => 'fa-solid fa-sliders',
'advanced_mode' => true,
'fields' => [
'backup_enabled' => [
'label' => lng('serversettings.backup_enabled'),
'settinggroup' => 'backup',
'varname' => 'enabled',
'type' => 'checkbox',
'default' => false,
'save_method' => 'storeSettingField',
'overview_option' => true,
'cronmodule' => 'froxlor/backup'
],
'backup_default_storage' => [
'label' => lng('serversettings.backup_default_storage'),
'settinggroup' => 'backup',
'varname' => 'default_storage',
'type' => 'select',
'default' => '1',
'option_options_method' => [
'\\Froxlor\\Backup\\Backup',
'getBackupStorages'
],
'save_method' => 'storeSettingField'
],
'backup_default_retention' => [
'label' => lng('serversettings.backup_default_retention'),
'settinggroup' => 'backup',
'varname' => 'default_retention',
'type' => 'number',
'default' => 3,
'min' => 0,
'save_method' => 'storeSettingField',
],
'backup_default_customer_access' => [
'label' => lng('serversettings.backup_default_customer_access'),
'settinggroup' => 'backup',
'varname' => 'default_customer_access',
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField',
],
'backup_default_pgp_public_key' => [
'label' => lng('serversettings.backup_default_pgp_public_key'),
'settinggroup' => 'backup',
'varname' => 'default_pgp_public_key',
'type' => 'textarea',
'default' => '',
'save_method' => 'storeSettingField',
'plausibility_check_method' => [
'\\Froxlor\\Validate\\Check',
'checkPgpPublicKeySetting'
],
],
]
]
]
];

View File

@@ -1,183 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
const AREA = 'admin';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Backups;
use Froxlor\Api\Commands\BackupStorages;
use Froxlor\FroxlorLogger;
use Froxlor\UI\Collection;
use Froxlor\UI\HTML;
use Froxlor\UI\Listing;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
$id = (int)Request::any('id');
if (($page == 'backups' || $page == 'overview')) {
if ($action == '') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "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' => 'restore']),
'label' => lng('admin.backups_restore'),
'icon' => 'fa-solid fa-file-import',
'class' => 'btn-outline-secondary'
],
[
'href' => $linker->getLink(['section' => 'backups', 'page' => 'storages']),
'label' => lng('admin.backup_storages'),
'icon' => 'fa-solid fa-hard-drive',
'class' => 'btn-outline-secondary',
'visible' => $userinfo['change_serversettings'] == '1'
]
]
]);
} elseif ($action == 'delete' && $id != 0) {
} elseif ($action == 'add') {
} elseif ($action == 'edit' && $id != 0) {
} elseif ($action == 'restore') {
}
} else if ($page == 'storages' && $userinfo['change_serversettings'] == '1') {
if ($action == '') {
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "list backup storages");
try {
$backup_storage_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/admin/tablelisting.backup_storages.php';
$collection = (new Collection(BackupStorages::class, $userinfo))
->withPagination($backup_storage_list_data['backup_storages_list']['columns'], $backup_storage_list_data['backup_storages_list']['default_sorting']);
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $backup_storage_list_data, 'backup_storages_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'backups', 'page' => 'backups']),
'label' => lng('admin.backups'),
'icon' => 'fa-solid fa-reply'
],
[
'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'add']),
'label' => lng('admin.backup_storage_add')
]
]
]);
} elseif ($action == 'delete' && $id != 0) {
try {
$json_result = BackupStorages::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ($result['id'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
BackupStorages::getLocal($userinfo, [
'id' => $id
])->delete();
Response::redirectTo($filename, [
'page' => $page
]);
} else {
HTML::askYesNo('backup_backup_server_reallydelete', $filename, [
'id' => $id,
'page' => $page,
'action' => $action
], $result['id']);
}
}
} elseif ($action == 'add') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
BackupStorages::getLocal($userinfo, $_POST)->add();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page
]);
} else {
$admin_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/backup_storages/formfield.backup_storage_add.php';
UI::view('user/form.html.twig', [
'formaction' => $linker->getLink(['section' => 'backups']),
'formdata' => $admin_add_data['backup_storage_add']
]);
}
} elseif ($action == 'edit' && $id != 0) {
try {
$json_result = BackupStorages::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
if ($result['id'] != '') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
BackupStorages::getLocal($userinfo, $_POST)->update();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page
]);
} else {
$backup_storage_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/backup_storages/formfield.backup_storage_edit.php';
UI::view('user/form.html.twig', [
'formaction' => $linker->getLink(['section' => 'backups', 'id' => $id]),
'formdata' => $backup_storage_edit_data['backup_storage_edit'],
'editid' => $id
]);
}
}
}
} else {
Response::dynamicError('403');
}

View File

@@ -27,7 +27,6 @@ const AREA = 'admin';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\BackupStorages;
use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\MysqlServer;
use Froxlor\CurrentUser;
@@ -226,23 +225,6 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$hosting_plans[$row['id']] = $row['name'];
}
// backup storages
$backup_storages = [];
if (Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1') {
$backup_storages = [
0 => lng('backup.storage_none')
];
try {
$result_json = BackupStorages::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $storagedata) {
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
}
} catch (Exception $e) {
/* just none */
}
}
$customer_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/customer/formfield.customer_add.php';
UI::view('user/form.html.twig', [
@@ -325,23 +307,6 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$hosting_plans[$row['id']] = $row['name'];
}
// backup storages
$backup_storages = [];
if (Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1') {
$backup_storages = [
0 => lng('backup.storage_none')
];
try {
$result_json = BackupStorages::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $storagedata) {
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
}
} catch (Exception $e) {
/* just none */
}
}
$available_admins_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_ADMINS . "`
WHERE (`customers` = '-1' OR `customers` > `customers_used`)

View File

@@ -30,9 +30,9 @@ use Froxlor\Api\Commands\Customers as Customers;
use Froxlor\Api\Commands\Domains as Domains;
use Froxlor\Bulk\DomainBulkAction;
use Froxlor\Cron\TaskId;
use Froxlor\CurrentUser;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
@@ -45,7 +45,6 @@ use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\User;
use Froxlor\Validate\Validate;
use Froxlor\CurrentUser;
$id = (int)Request::any('id');
@@ -646,7 +645,7 @@ if ($page == 'domains' || $page == 'overview') {
Response::redirectTo($filename, [
'page' => $page,
'searchfield' => 'd.domain_ace',
'searchtext' => $_POST['domain'] ?? ""
'searchtext' => Request::post('domain', "")
]);
} else {
Response::redirectTo($filename, [

View File

@@ -46,17 +46,17 @@
"ext-fileinfo": "*",
"ext-gmp": "*",
"ext-gd": "*",
"ext-ftp": "*",
"ext-gnupg": "*",
"phpmailer/phpmailer": "~6.0",
"monolog/monolog": "^1.24",
"robthree/twofactorauth": "^1.6",
"froxlor/idna-convert-legacy": "^2.1",
"voku/anti-xss": "^4.1",
"twig/twig": "^3.3",
"erusev/parsedown": "^1.7",
"symfony/console": "^5.4",
"pear/net_dns2": "^1.5",
"amnuts/opcache-gui": "^3.4"
"amnuts/opcache-gui": "^3.4",
"league/commonmark": "^2.4"
},
"require-dev": {
"phpunit/phpunit": "^9",

780
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,7 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\SubDomains as SubDomains;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Domain\Domain;
use Froxlor\FileDir;
@@ -40,7 +41,6 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
@@ -63,16 +63,21 @@ if ($page == 'overview' || $page == 'domains') {
Response::dynamicError($e->getMessage());
}
$actions_links = false;
$actions_links = [];
if (CurrentUser::canAddResource('subdomains')) {
$actions_links = [
[
'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']),
'label' => lng('domains.subdomain_add')
]
$actions_links[] = [
'href' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'add']),
'label' => lng('domains.subdomain_add')
];
}
$actions_links[] = [
'href' => 'https://docs.froxlor.org/v2/user-guide/domains/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
$table_tpl = 'table.html.twig';
if ($collection->count() == 0) {
$table_tpl = 'table-note.html.twig';
@@ -239,7 +244,7 @@ if ($page == 'overview' || $page == 'domains') {
if (isset($result['customerid']) && $result['customerid'] == $userinfo['customerid']) {
if ((int) $result['caneditdomain'] == 0) {
if ((int)$result['caneditdomain'] == 0) {
Response::standardError('domaincannotbeedited', $result['domain']);
}

View File

@@ -27,9 +27,10 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\EmailAccounts;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\Api\Commands\EmailForwarders;
use Froxlor\Api\Commands\Emails;
use Froxlor\Api\Commands\EmailDomains;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\PhpHelper;
@@ -41,7 +42,6 @@ use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\Validate\Check;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'email') || $userinfo['emails'] == 0) {
@@ -67,14 +67,24 @@ if ($page == 'overview' || $page == 'emails') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
if (CurrentUser::canAddResource('emails')) {
$actions_links[] = [
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']),
'label' => lng('emails.emails_add')
];
}
$actions_links[] = [
'href' => 'https://docs.froxlor.org/v2/user-guide/emails/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $emaildomain_list_data, 'emaildomain_list'),
'actions_links' => CurrentUser::canAddResource('emails') ? [
[
'href' => $linker->getLink(['section' => 'email', 'page' => 'email_domain', 'action' => 'add']),
'label' => lng('emails.emails_add')
]
] : null,
'actions_links' => $actions_links,
]);
} else {
// only emails for one domain -> show email address listing directly
@@ -127,6 +137,12 @@ if ($page == 'email_domain') {
'label' => lng('emails.emails_add')
];
}
$actions_links[] = [
'href' => 'https://docs.froxlor.org/v2/user-guide/emails/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $email_list_data, 'email_list'),

View File

@@ -68,14 +68,22 @@ if ($page == 'overview' || $page == 'htpasswds') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
$actions_links[] = [
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']),
'label' => lng('extras.directoryprotection_add')
];
$actions_links[] = [
'href' => 'https://docs.froxlor.org/v2/user-guide/extras/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $htpasswd_list_data, 'htpasswd_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htpasswds', 'action' => 'add']),
'label' => lng('extras.directoryprotection_add')
]
],
'actions_links' => $actions_links,
'entity_info' => lng('extras.description')
]);
} elseif ($action == 'delete' && $id != 0) {
@@ -185,14 +193,22 @@ if ($page == 'overview' || $page == 'htpasswds') {
Response::dynamicError($e->getMessage());
}
$actions_links = [];
$actions_links[] = [
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']),
'label' => lng('extras.pathoptions_add')
];
$actions_links[] = [
'href' => 'https://docs.froxlor.org/v2/user-guide/extras/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $htaccess_list_data, 'htaccess_list'),
'actions_links' => [
[
'href' => $linker->getLink(['section' => 'extras', 'page' => 'htaccess', 'action' => 'add']),
'label' => lng('extras.pathoptions_add')
]
],
'actions_links' => $actions_links,
'entity_info' => lng('extras.description')
]);
} elseif ($action == 'delete' && $id != 0) {
@@ -331,9 +347,19 @@ if ($page == 'overview' || $page == 'htpasswds') {
$pathSelect = FileDir::makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']);
$export_data = include_once dirname(__FILE__) . '/lib/formfields/customer/extras/formfield.export.php';
$actions_links = [
[
'href' => 'https://docs.froxlor.org/v2/user-guide/extras/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
]
];
UI::view('user/form-datatable.html.twig', [
'formaction' => $linker->getLink(['section' => 'extras']),
'formdata' => $export_data['export'],
'actions_links' => $actions_links,
'tabledata' => Listing::format($collection, $export_list_data, 'export_list'),
]);
}

View File

@@ -27,6 +27,7 @@ const AREA = 'customer';
require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Ftps as Ftps;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\FroxlorLogger;
@@ -37,7 +38,6 @@ use Froxlor\UI\Listing;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings
if (Settings::IsInList('panel.customer_hide_options', 'ftp')) {
@@ -57,15 +57,19 @@ if ($page == 'overview' || $page == 'accounts') {
Response::dynamicError($e->getMessage());
}
$actions_links = false;
$actions_links = [];
if (CurrentUser::canAddResource('ftps')) {
$actions_links = [
[
'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']),
'label' => lng('ftp.account_add')
]
$actions_links[] = [
'href' => $linker->getLink(['section' => 'ftp', 'page' => 'accounts', 'action' => 'add']),
'label' => lng('ftp.account_add')
];
}
$actions_links[] = [
'href' => 'https://docs.froxlor.org/v2/user-guide/ftp-accounts/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $ftp_list_data, 'ftp_list'),

View File

@@ -28,6 +28,7 @@ require __DIR__ . '/lib/init.php';
use Froxlor\Api\Commands\Mysqls;
use Froxlor\Api\Commands\MysqlServer;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
@@ -37,7 +38,6 @@ use Froxlor\UI\Listing;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
use Froxlor\CurrentUser;
// redirect if this customer page is hidden via settings or no resources given
if (Settings::IsInList('panel.customer_hide_options', 'mysql') || $userinfo['mysqls'] == 0) {
@@ -66,16 +66,21 @@ if ($page == 'overview' || $page == 'mysqls') {
Response::dynamicError($e->getMessage());
}
$actions_links = false;
$actions_links = [];
if (CurrentUser::canAddResource('mysqls')) {
$actions_links = [
[
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']),
'label' => lng('mysql.database_create')
]
$actions_links[] = [
'href' => $linker->getLink(['section' => 'mysql', 'page' => 'mysqls', 'action' => 'add']),
'label' => lng('mysql.database_create')
];
}
$actions_links[] = [
'href' => 'https://docs.froxlor.org/v2/user-guide/databases/',
'target' => '_blank',
'icon' => 'fa-solid fa-circle-info',
'class' => 'btn-outline-secondary'
];
UI::view('user/table.html.twig', [
'listing' => Listing::format($collection, $mysql_list_data, 'mysql_list'),
'actions_links' => $actions_links,
@@ -179,7 +184,7 @@ if ($page == 'overview' || $page == 'mysqls') {
$result_json = MysqlServer::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $dbserver => $dbdata) {
$mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '').')';
$mysql_servers[$dbserver] = $dbdata['caption'] . ' (' . $dbdata['host'] . (isset($dbdata['port']) && !empty($dbdata['port']) ? ':' . $dbdata['port'] : '') . ')';
}
} catch (Exception $e) {
/* just none */

View File

@@ -223,8 +223,6 @@ CREATE TABLE `panel_customers` (
`api_allowed` tinyint(1) NOT NULL default '1',
`logviewenabled` tinyint(1) NOT NULL default '0',
`allowed_mysqlserver` text NOT NULL,
`backup` int(11) NOT NULL default '1',
`access_backups` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`customerid`),
UNIQUE KEY `loginname` (`loginname`)
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
@@ -358,23 +356,6 @@ CREATE TABLE `panel_htpasswds` (
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
DROP TABLE IF EXISTS `panel_sessions`;
CREATE TABLE `panel_sessions` (
`hash` varchar(32) NOT NULL default '',
`userid` int(11) unsigned NOT NULL default '0',
`ipaddress` varchar(255) NOT NULL default '',
`useragent` varchar(255) NOT NULL default '',
`lastactivity` int(11) unsigned NOT NULL default '0',
`lastpaging` varchar(255) NOT NULL default '',
`formtoken` char(32) NOT NULL default '',
`language` varchar(64) NOT NULL default '',
`adminsession` tinyint(1) unsigned NOT NULL default '0',
`theme` varchar(255) NOT NULL default '',
PRIMARY KEY (`hash`),
KEY `userid` (`userid`)
) ENGINE=HEAP;
DROP TABLE IF EXISTS `panel_settings`;
CREATE TABLE `panel_settings` (
`settingid` int(11) unsigned NOT NULL auto_increment,
@@ -564,7 +545,7 @@ opcache.validate_timestamps'),
('system', 'mod_fcgid', '0'),
('system', 'apacheconf_vhost', '/etc/apache2/sites-enabled/'),
('system', 'apacheconf_diroptions', '/etc/apache2/sites-enabled/'),
('system', 'apacheconf_htpasswddir', '/etc/apache2/htpasswd/'),
('system', 'apacheconf_htpasswddir', '/etc/apache2/froxlor-htpasswd/'),
('system', 'webalizer_quiet', '2'),
('system', 'last_archive_run', '000000'),
('system', 'mod_fcgid_configdir', '/var/www/php-fcgi-scripts'),
@@ -698,15 +679,10 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.20'),
('system', 'update_notify_last', '2.1.0-beta2'),
('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60),
('backup', 'enabled', 0),
('backup', 'default_storage', '1'),
('backup', 'default_customer_access', '1'),
('backup', 'default_pgp_public_key', ''),
('backup', 'default_retention', '3'),
('api', 'enabled', '0'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'),
@@ -750,7 +726,8 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.20'),
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.1.0-beta2'),
('panel', 'db_version', '202305240');
@@ -920,8 +897,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 HOUR', '0', 'cron_export'),
(7, 'froxlor/backup', 'backup', '\\Froxlor\\Cron\\Backup\\BackupCron', '1 DAY', '0', 'cron_backup');
(6, 'froxlor/export', 'export', '\\Froxlor\\Cron\\System\\ExportCron', '1 HOUR', '0', 'cron_export');
DROP TABLE IF EXISTS `ftp_quotalimits`;
@@ -1069,38 +1045,4 @@ 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_backup_storages`;
CREATE TABLE `panel_backup_storages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) NOT NULL,
`type` varchar(255) NOT NULL DEFAULT 'local',
`region` varchar(255) NULL,
`bucket` varchar(255) NULL,
`destination_path` varchar(255) NOT NULL,
`hostname` varchar(255) NULL,
`username` varchar(255) NULL,
`password` text,
`pgp_public_key` text,
`retention` int(3) NOT NULL DEFAULT 3,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
INSERT INTO `panel_backup_storages` (`id`, `description`, `destination_path`) VALUES
(1, 'Local backup storage', '/var/customers/backups');
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,
`storage_id` int(11) NOT NULL,
`filename` varchar(255) NOT NULL,
`created_at` int(15) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
FROXLORSQL;

View File

@@ -149,7 +149,7 @@ if (Froxlor::isFroxlorVersion('0.10.38.3')) {
Update::showUpdateStep("Adding new settings");
$panel_settings_mode = isset($_POST['panel_settings_mode']) ? (int)$_POST['panel_settings_mode'] : 0;
Settings::AddNew("panel.settings_mode", $panel_settings_mode);
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : '';
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : 'bullseye';
Settings::AddNew("system.distribution", $system_distribution);
Settings::AddNew("system.update_channel", 'stable');
Settings::AddNew("system.updatecheck_data", '');
@@ -497,3 +497,23 @@ if (Froxlor::isFroxlorVersion('2.0.19')) {
Update::showUpdateStep("Updating from 2.0.19 to 2.0.20", false);
Froxlor::updateToVersion('2.0.20');
}
if (Froxlor::isFroxlorVersion('2.0.20')) {
Update::showUpdateStep("Updating from 2.0.20 to 2.0.21", false);
Froxlor::updateToVersion('2.0.21');
}
if (Froxlor::isFroxlorVersion('2.0.21')) {
Update::showUpdateStep("Updating from 2.0.21 to 2.0.22", false);
Froxlor::updateToVersion('2.0.22');
}
if (Froxlor::isFroxlorVersion('2.0.22')) {
Update::showUpdateStep("Updating from 2.0.22 to 2.0.23", false);
Froxlor::updateToVersion('2.0.23');
}
if (Froxlor::isFroxlorVersion('2.0.23')) {
Update::showUpdateStep("Updating from 2.0.23 to 2.0.24", false);
Froxlor::updateToVersion('2.0.24');
}

View File

@@ -36,7 +36,7 @@ if (!defined('_CRON_UPDATE')) {
}
}
if (Froxlor::isDatabaseVersion('202304260')) {
if (Froxlor::isFroxlorVersion('2.0.24')) {
Update::showUpdateStep("Cleaning domains table");
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
Update::lastStepStatus(0);
@@ -53,6 +53,10 @@ if (Froxlor::isDatabaseVersion('202304260')) {
Database::query($sql);
Update::lastStepStatus(0);
Update::showUpdateStep("Adding new settings");
Settings::AddNew('panel.menu_collapsed', 1);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting setting for deactivated webroot");
$current_deactivated_webroot = Settings::Get('system.deactivateddocroot');
if (empty($current_deactivated_webroot)) {
@@ -62,53 +66,6 @@ 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 `". TABLE_PANEL_BACKUP_STORAGES ."`;");
$sql = "CREATE TABLE `". TABLE_PANEL_BACKUP_STORAGES ."` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) NOT NULL,
`type` varchar(255) NOT NULL DEFAULT 'local',
`region` varchar(255) NULL,
`bucket` varchar(255) NULL,
`destination_path` varchar(255) NOT NULL,
`hostname` varchar(255) NULL,
`username` varchar(255) NULL,
`password` text,
`pgp_public_key` text,
`retention` int(3) NOT NULL DEFAULT 3,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
Database::query("
INSERT INTO `panel_backup_storages` (`id`, `description`, `destination_path`) VALUES
(1, 'Local backup storage', '/var/customers/backups');
");
Database::query("DROP TABLE IF EXISTS `". TABLE_PANEL_BACKUPS ."`;");
$sql = "CREATE TABLE `". 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,
`storage_id` int(11) NOT NULL,
`filename` varchar(255) NOT NULL,
`created_at` int(15) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
Database::query($sql);
// add customer backup-target-storage
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `backup` int(11) NOT NULL default '1' AFTER `allowed_mysqlserver`;");
Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD `access_backups` tinyint(1) NOT NULL default '1' AFTER `backup`;");
Update::lastStepStatus(0);
Update::showUpdateStep("Adding new backup settings");
Settings::AddNew('backup.enabled', 0);
Settings::AddNew('backup.default_storage', 1);
Settings::AddNew('backup.default_customer_access', 1);
Settings::AddNew('backup.default_pgp_public_key', '');
Settings::AddNew('backup.default_retention', 3);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting cronjobs");
Database::query("
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
@@ -118,24 +75,31 @@ if (Froxlor::isDatabaseVersion('202304260')) {
`interval` = '1 HOUR',
`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);
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 `varname` = 'exportenabled' WHERE `settinggroup`= 'system' AND `varname`= 'backupenabled'");
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "`SET `value` = REPLACE(`value`, 'extras.backup', 'extras.export') WHERE `settinggroup` = 'panel' AND `varname` = 'customer_hide_options'");
Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'backup_list'");
Database::query("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'");
Update::lastStepStatus(0);
Froxlor::updateToDbVersion('202305240');
Froxlor::updateToVersion('2.1.0-dev1');
}
if (Froxlor::isFroxlorVersion('2.1.0-dev1')) {
Update::showUpdateStep("Updating from 2.1.0-dev1 to 2.1.0-beta1", false);
Froxlor::updateToVersion('2.1.0-beta1');
}
if (Froxlor::isFroxlorVersion('2.1.0-beta1')) {
Update::showUpdateStep("Updating from 2.1.0-beta1 to 2.1.0-beta2", false);
Update::showUpdateStep("Removing unused table");
Database::query("DROP TABLE IF EXISTS `panel_sessions`;");
Update::lastStepStatus(0);
Froxlor::updateToVersion('2.1.0-beta2');
}

View File

@@ -54,7 +54,7 @@ if (Update::versionInUpdate($current_version, '2.0.0-beta1')) {
$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
// show list of available distro's
$distros = glob($config_dir . '*.xml');
$distributions_select[''] = '-';
// selection is required $distributions_select[''] = '-';
// read in all the distros
foreach ($distros as $_distribution) {
// get configparser object

View File

@@ -36,19 +36,7 @@ $preconfig = [
$return = [];
if (Update::versionInUpdate($current_version, '2.1.0-dev1')) {
// Backup
$description = 'Froxlor now comes with a backup capability (More info see [DOCS LINK].';
$question = '<strong>Would you like to enable the backup-feature (default: yes)</strong>';
$return['panel_settings_mode'] = [
'type' => 'select',
'select_var' => [
0 => 'No',
1 => 'Yes'
],
'selected' => 1,
'label' => $question,
'prior_infotext' => $description
];
}
$preconfig['fields'] = $return;

View File

@@ -1,487 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @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\FileDir;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use Froxlor\UI\Response;
use Froxlor\Validate\Validate;
use PDO;
/**
* @since 2.1.0
*/
class BackupStorages extends ApiCommand implements ResourceEntity
{
const SUPPORTED_TYPES = [
'local',
'ftp',
'sftp',
'rsync',
's3',
];
/**
* lists all backup storages 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_INFO, "[API] list backup storages");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ". $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()
);
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 backup storages
*
* @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_backup_storagess
FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
");
$result = Database::pexecute_first($result_stmt, null, true, true);
if ($result) {
return $this->response($result['num_backup_storagess']);
}
$this->response(0);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* create a backup storage
*
* @param string $type
* required, backup storage type
* @param string $destination_path
* required, destination path for backup storage
* @param string $description
* required, description for backup storage
* @param string $region
* optional, required if type=s3. Region for backup storage (used for S3)
* @param string $bucket
* optional, required if type=s3. Bucket for backup storage (used for S3)
* @param string $hostname
* optional, required if type != local. Hostname for backup storage
* @param string $username
* optional, required if type != local. Username for backup storage (also used as access key for S3)
* @param string $password
* optional, required if type != local. Password for backup storage (also used as secret key for S3)
* @param string $pgp_public_key
* optional, pgp public key for backup storage
* @param string $retention
* optional, retention for backup storage (default 3)
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function add()
{
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
// required parameters
$type = $this->getParam('type');
$destination_path = $this->getParam('destination_path');
$description = $this->getParam('description');
// type related requirements
$optional_flags = [
'region' => true,
'bucket' => true,
'hostname' => true,
'username' => true,
'password' => true,
];
if (!in_array($type, self::SUPPORTED_TYPES)) {
throw new Exception("Unsupported storage type: '" . $type . "'", 406);
}
if ($type != 'local') {
$optional_flags['hostname'] = false;
$optional_flags['username'] = false;
$optional_flags['password'] = false;
}
if ($type == 's3') {
$optional_flags['region'] = false;
$optional_flags['bucket'] = false;
}
// parameters
$region = $this->getParam('region', $optional_flags['region']);
$bucket = $this->getParam('bucket', $optional_flags['bucket']);
$hostname = $this->getParam('hostname', $optional_flags['hostname']);
$username = $this->getParam('username', $optional_flags['username']);
$password = $this->getParam('password', $optional_flags['password']);
$pgp_public_key = $this->getParam('pgp_public_key', true, null);
$retention = $this->getParam('retention', true, 3);
// validation
$destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true));
// TODO: add more validation
// 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);
}
}
// store
$stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_BACKUP_STORAGES . "` (
`description`,
`type`,
`region`,
`bucket`,
`destination_path`,
`hostname`,
`username`,
`password`,
`pgp_public_key`,
`retention`
) VALUES (
:description,
:type,
:region,
:bucket,
:destination_path,
:hostname,
:username,
:password,
:pgp_public_key,
:retention
)
");
$params = [
"description" => $description,
"type" => $type,
"region" => $region,
"bucket" => $bucket,
"destination_path" => $destination_path,
"hostname" => $hostname,
"username" => $username,
"password" => $password,
"pgp_public_key" => $pgp_public_key,
"retention" => $retention,
];
Database::pexecute($stmt, $params, true, true);
$id = Database::lastInsertId();
// return
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] added backup storage '" . $result['description'] . "' (" . $result['type'] . ")");
return $this->response($result);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* return a backup storage entry by id
*
* @param int $id
* the backup-storage-id
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function get()
{
$id = $this->getParam('id');
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
WHERE `id` = :id"
);
$params = [
'id' => $id
];
$result = Database::pexecute_first($result_stmt, $params, true, true);
if ($result) {
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get backup storage '" . $result['description'] . "'");
return $this->response($result);
}
throw new Exception("Backup storage with " . $id . " could not be found", 404);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* update a backup storage by given id
*
* @param int $id
* required, the backup-storage-id
* @param string $type
* optional, backup storage type
* @param string $destination_path
* optional, destination path for backup storage
* @param string $description
* required, description for backup storage
* @param string $region
* optional, region for backup storage (used for S3)
* @param string $bucket
* optional, bucket for backup storage (used for S3)
* @param string $hostname
* optional, hostname for backup storage
* @param string $username
* optional, username for backup storage (also used as access key for S3)
* @param string $password
* optional, password for backup storage (also used as secret key for S3)
* @param string $pgp_public_key
* optional, pgp public key for backup storage
* @param string $retention
* optional, retention for backup storage (default 3)
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function update()
{
$id = $this->getParam('id');
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
// validation
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
// parameters
$description = $this->getParam('description', true, $result['description']);
$type = $this->getParam('type', true, $result['type']);
$region = $this->getParam('region', true, $result['region']);
$bucket = $this->getParam('bucket', true, $result['bucket']);
$destination_path = $this->getParam('destination_path', true, $result['destination_path']);
$hostname = $this->getParam('hostname', true, $result['hostname']);
$username = $this->getParam('username', true, $result['username']);
$password = $this->getParam('password', true, '');
$pgp_public_key = $this->getParam('pgp_public_key', true, $result['pgp_public_key']);
$retention = $this->getParam('retention', true, $result['retention']);
if (!in_array($type, self::SUPPORTED_TYPES)) {
throw new Exception("Unsupported storage type: '" . $type . "'", 406);
}
if ($type != 'local') {
if (empty($hostname)) {
throw new Exception("Field 'hostname' cannot be empty", 406);
}
if (empty($username)) {
throw new Exception("Field 'username' cannot be empty", 406);
}
$password = Validate::validate($password, 'password', '', '', [], true);
}
if ($type == 's3') {
if (empty($region)) {
throw new Exception("Field 'region' cannot be empty", 406);
}
if (empty($bucket)) {
throw new Exception("Field 'bucket' cannot be empty", 406);
}
}
// validation
$destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true));
// TODO: add more validation
// pgp public key validation
if (!empty($pgp_public_key) && $pgp_public_key != $result['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 (!empty($password)) {
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_BACKUP_STORAGES . "`
SET `password` = :password
WHERE `id` = :id
");
Database::pexecute($stmt, [
"id" => $id,
"password" => $password
], true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] updated password for backup-storage '" . $result['description'] . "'");
}
// update
$stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_BACKUP_STORAGES . "`
SET `description` = :description,
`type` = :type,
`region` = :region,
`bucket` = :bucket,
`destination_path` = :destination_path,
`hostname` = :hostname,
`username` = :username,
`pgp_public_key` = :pgp_public_key,
`retention` = :retention
WHERE `id` = :id
");
$params = [
"id" => $id,
"description" => $description,
"type" => $type,
"region" => $region,
"bucket" => $bucket,
"destination_path" => $destination_path,
"hostname" => $hostname,
"username" => $username,
"pgp_public_key" => $pgp_public_key,
"retention" => $retention,
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] edited backup storage '" . $result['description'] . "'");
// return
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
return $this->response($result);
}
throw new Exception("Not allowed to execute given command.", 403);
}
/**
* delete a backup-storage entry by id
*
* @param int $id
* required, the backup-storage-id
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function delete()
{
$id = $this->getParam('id');
if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) {
// validation
$result = $this->apiCall('BackupStorages.get', [
'id' => $id
]);
// validate no-one's using it
// settings
if ($id == Settings::Get('backup.default_storage')) {
throw new Exception("Given backup storage is currently set as default storage and cannot be deleted.", 406);
}
// customers
$sel_stmt = Database::prepare("
SELECT COUNT(*) as num_storage_users
FROM `" . TABLE_PANEL_CUSTOMERS . "`
WHERE `backup` = :id
");
$storage_users_result = Database::pexecute_first($sel_stmt, ['id' => $id]);
if ($storage_users_result && $storage_users_result['num_storage_users'] > 0) {
throw new Exception("Given backup storage is currently assigned to " . $storage_users_result['num_storage_users'] . " customers and cannot be deleted.", 406);
}
// existing backups
$sel_stmt = Database::prepare("
SELECT COUNT(*) as num_storage_backups
FROM `" . TABLE_PANEL_BACKUPS . "`
WHERE `storage_id` = :id
");
$storage_backups_result = Database::pexecute_first($sel_stmt, ['id' => $id]);
if ($storage_backups_result && $storage_backups_result['num_storage_backups'] > 0) {
throw new Exception("Given backup storage has still " . $storage_backups_result['num_storage_backups'] . " backups on it and cannot be deleted.", 406);
}
// delete
$stmt = Database::prepare("
DELETE FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
WHERE `id` = :id
");
$params = [
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted backup storage '" . $result['description'] . "'");
// return
return $this->response(true);
}
throw new Exception("Not allowed to execute given command.", 403);
}
}

View File

@@ -1,211 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @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 PDO;
/**
* @since 2.1.0
*/
class Backups extends ApiCommand implements ResourceEntity
{
/**
* 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()) {
// if we're an admin, list all backups of all the admins customers
// or optionally for one specific customer identified by id or loginname
$customerid = $this->getParam('customerid', true, 0);
$loginname = $this->getParam('loginname', true, '');
if (!empty($customerid) || !empty($loginname)) {
$result = $this->apiCall('Customers.get', [
'id' => $customerid,
'loginname' => $loginname
]);
$custom_list_result = [
$result
];
} else {
$_custom_list_result = $this->apiCall('Customers.listing');
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
}
if (empty($customer_ids)) {
throw new Exception("Required resource unsatisfied.", 405);
}
} else {
$customer_ids = [
$this->getUserDetail('customerid')
];
}
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[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`)
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit()
);
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
]);
}
/**
* 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()) {
// if we're an admin, list all backups of all the admins customers
// or optionally for one specific customer identified by id or loginname
$customerid = $this->getParam('customerid', true, 0);
$loginname = $this->getParam('loginname', true, '');
if (!empty($customerid) || !empty($loginname)) {
$result = $this->apiCall('Customers.get', [
'id' => $customerid,
'loginname' => $loginname
]);
$custom_list_result = [
$result
];
} else {
$_custom_list_result = $this->apiCall('Customers.listing');
$custom_list_result = $_custom_list_result['list'];
}
$customer_ids = [];
foreach ($custom_list_result as $customer) {
$customer_ids[] = $customer['customerid'];
}
if (empty($customer_ids)) {
throw new Exception("Required resource unsatisfied.", 405);
}
} else {
$customer_ids = [
$this->getUserDetail('customerid')
];
}
$result_stmt = Database::prepare("
SELECT COUNT(*) as num_backups
FROM `" . TABLE_PANEL_BACKUPS . "` `b`
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
");
$result = Database::pexecute_first($result_stmt, null, true, true);
if ($result) {
return $this->response($result['num_backups']);
}
$this->response(0);
}
/**
* You cannot add a backup entry
*
* @throws Exception
*/
public function add()
{
throw new Exception('You cannot add a backup entry', 303);
}
/**
* return a backup entry by id
*
* @param int $id
* optional, the backup-entry-id
*
* @access admin, customers
* @return string json-encoded array
* @throws Exception
*/
public function get()
{
throw new Exception("@TODO", 303);
}
/**
* You cannot update a backup entry
*
* @throws Exception
*/
public function update()
{
throw new Exception('You cannot update a backup entry', 303);
}
/**
* delete a backup entry by id
*
* @param int $id
* required, the backup-entry-id
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function delete()
{
throw new Exception("@TODO", 303);
}
}

View File

@@ -273,13 +273,6 @@ class Customers extends ApiCommand implements ResourceEntity
* @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0)
* @param int $backup
* optional, either 0 to disable backup for this customer or a backup-storage-id
* where backups are to be stored, requires change_serversettings permissions,
* default is system-setting backup.default_storage
* @param bool $access_backups
* optional, where the customer is allowed to view backups, default is system-setting
* default_customer_access
*
* @access admin
* @return string json-encoded array
@@ -366,24 +359,6 @@ class Customers extends ApiCommand implements ResourceEntity
$p_allowed_mysqlserver = [];
}
if ($this->getUserDetail('change_serversettings')) {
$backup = $this->getParam('backup', true, Settings::Get('backup.default_storage'));
if ($backup > 0) {
try {
$this->apiCall('BackupStorages.get', [
'id' => $backup
]);
} catch (Exception $e) {
// not found or other issue, set default
$backup = Settings::Get('backup.default_storage');
}
}
$access_backups = $this->getBoolParam('access_backups', true, Settings::Get('backup.default_customer_access'));
} else {
$backup = Settings::Get('backup.default_storage');
$access_backups = Settings::Get('backup.default_customer_access');
}
// validation
$name = Validate::validate($name, 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$firstname = Validate::validate($firstname, 'first name', Validate::REGEX_DESC_TEXT, '', [], true);
@@ -562,9 +537,7 @@ class Customers extends ApiCommand implements ResourceEntity
'theme' => $_theme,
'custom_notes' => $custom_notes,
'custom_notes_show' => $custom_notes_show,
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver),
'backup' => $backup,
'access_backups' => $access_backups
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
];
$ins_stmt = Database::prepare("
@@ -607,9 +580,7 @@ class Customers extends ApiCommand implements ResourceEntity
`theme` = :theme,
`custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show,
`allowed_mysqlserver`= :allowed_mysqlserver,
`backup` = :backup,
`access_backups` = :access_backups
`allowed_mysqlserver`= :allowed_mysqlserver
");
Database::pexecute($ins_stmt, $ins_data, true, true);
@@ -1057,13 +1028,6 @@ class Customers extends ApiCommand implements ResourceEntity
* @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0)
* @param int $backup
* optional, either 0 to disable backup for this customer or a backup-storage-id
* where backups are to be stored, requires change_serversettings permissions,
* default is system-setting backup.default_storage
* @param bool $access_backups
* optional, where the customer is allowed to view backups, default is system-setting
* default_customer_access
*
* @access admin, customer
* @return string json-encoded array
@@ -1125,24 +1089,6 @@ class Customers extends ApiCommand implements ResourceEntity
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
$theme = $this->getParam('theme', true, $result['theme']);
$allowed_mysqlserver = $this->getParam('allowed_mysqlserver', true, json_decode($result['allowed_mysqlserver'], true));
if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) {
$backup = $this->getParam('backup', true, $result['backup']);
if ($backup > 0) {
try {
$this->apiCall('BackupStorages.get', [
'id' => $backup
]);
} catch (Exception $e) {
// not found or other issue, dont update
$backup = $result['backup'];
}
}
$access_backups = $this->getBoolParam('access_backups', true, Settings::Get('backup.default_customer_access'));
} else {
$backup = $result['backup'];
$access_backups = $result['access_backups'];
}
} else {
// allowed parameters
$def_language = $this->getParam('def_language', true, $result['def_language']);
@@ -1451,9 +1397,7 @@ class Customers extends ApiCommand implements ResourceEntity
'custom_notes' => $custom_notes,
'custom_notes_show' => $custom_notes_show,
'api_allowed' => $api_allowed,
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver),
'backup' => $backup,
'access_backups' => $access_backups
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
];
$upd_data += $admin_upd_data;
}
@@ -1496,9 +1440,7 @@ class Customers extends ApiCommand implements ResourceEntity
`custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show,
`api_allowed` = :api_allowed,
`allowed_mysqlserver` = :allowed_mysqlserver,
`backup`= :backup,
`access_backups` = :access_backups";
`allowed_mysqlserver` = :allowed_mysqlserver";
$upd_query .= $admin_upd_query;
}
$upd_query .= " WHERE `customerid` = :customerid";

View File

@@ -93,7 +93,7 @@ class DirOptions extends ApiCommand implements ResourceEntity
// validation
$path = FileDir::makeCorrectDir(Validate::validate($path, 'path', Validate::REGEX_DIR, '', [], true));
$userpath = $path;
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
if (!empty($error404path)) {
$error404path = $this->correctErrorDocument($error404path, true);

View File

@@ -84,7 +84,7 @@ class DirProtections extends ApiCommand implements ResourceEntity
// validation
$path = FileDir::makeCorrectDir(Validate::validate($path, 'path', Validate::REGEX_DIR, '', [], true));
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
$username = Validate::validate($username, 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/', '', [], true);
$authname = Validate::validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', [], true);
$password = Validate::validate($password, 'password', '', '', [], true);

View File

@@ -316,9 +316,9 @@ class Domains extends ApiCommand implements ResourceEntity
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
$ssl_redirect = $this->getBoolParam('ssl_redirect', true, 0);
$letsencrypt = $this->getBoolParam('letsencrypt', true, 0);
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
$dont_use_default_ssl_ipandport_if_empty = $this->getBoolParam('dont_use_default_ssl_ipandport_if_empty', true, 0);
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $dont_use_default_ssl_ipandport_if_empty ? [] : explode(',', Settings::Get('system.defaultsslip')));
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
$http2 = $this->getBoolParam('http2', true, 0);
$hsts_maxage = $this->getParam('hsts_maxage', true, 0);
$hsts_sub = $this->getBoolParam('hsts_sub', true, 0);
@@ -544,6 +544,10 @@ class Domains extends ApiCommand implements ResourceEntity
$ssl_specialsettings = Validate::validate(str_replace("\r\n", "\n", $ssl_specialsettings), 'ssl_specialsettings', '/^[^\0]*$/', '', [], true);
}
}
if (Settings::Get('system.use_ssl') == "1" && $sslenabled == 1 && empty($ssl_ipandports)) {
// enabled ssl for the domain but no ssl ip/port is selected
Response::standardError('nosslippportgiven', '', true);
}
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
$ssl_redirect = 0;
$letsencrypt = 0;
@@ -1207,7 +1211,7 @@ class Domains extends ApiCommand implements ResourceEntity
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $remove_ssl_ipandport ? [
-1
] : null);
$sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
$sslenabled = $remove_ssl_ipandport ? false : $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
$http2 = $this->getBoolParam('http2', true, $result['http2']);
$hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']);
$hsts_sub = $this->getBoolParam('hsts_sub', true, $result['hsts_sub']);
@@ -1517,6 +1521,10 @@ class Domains extends ApiCommand implements ResourceEntity
if ($remove_ssl_ipandport || (!empty($p_ssl_ipandports) && $p_ssl_ipandports[0] == -1)) {
$ssl_ipandports = [];
}
if (Settings::Get('system.use_ssl') == "1" && $sslenabled && empty($ssl_ipandports)) {
// enabled ssl for the domain but no ssl ip/port is selected
Response::standardError('nosslippportgiven', '', true);
}
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
$ssl_redirect = 0;
$letsencrypt = 0;
@@ -1553,7 +1561,7 @@ class Domains extends ApiCommand implements ResourceEntity
}
// Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated
if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) {
if (($result['letsencrypt'] != $letsencrypt || $result['ssl_redirect'] != $ssl_redirect) && $ssl_redirect > 0 && $letsencrypt == 1) {
$ssl_redirect = 2;
}
@@ -2313,6 +2321,10 @@ class Domains extends ApiCommand implements ResourceEntity
unset($result['wwwserveralias']);
unset($result['iswildcarddomain']);
// translate sslenabled flag
$result['sslenabled'] = $result['ssl_enabled'];
unset($result['ssl_enabled']);
$additional_params = $this->getParamList();
// unset unneeded params from this call
unset($additional_params['id']);

View File

@@ -202,7 +202,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
// validation
$description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true);
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\- ]+$/i', '', [], true);
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\-@ ]+$/i', '', [], true);
$sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc");
$dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]);
if ($dupcheck && $dupcheck['id']) {
@@ -327,7 +327,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
// validation
$description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true);
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\- ]+$/i', '', [], true);
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\-@ ]+$/i', '', [], true);
$sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc");
$dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]);
if ($dupcheck && $dupcheck['id'] != $id) {

View File

@@ -174,7 +174,7 @@ class Ftps extends ApiCommand implements ResourceEntity
} elseif ($username == $password) {
Response::standardError('passwordshouldnotbeusername', '', true);
} else {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
$cryptPassword = Crypt::makeCryptPassword($password, false, true);
$stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "`
@@ -469,7 +469,7 @@ class Ftps extends ApiCommand implements ResourceEntity
// path update?
if ($path != '') {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
if ($path != $result['homedir']) {
$stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "`

View File

@@ -201,7 +201,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
// validation
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;
@@ -383,7 +383,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
// validation
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
if (Settings::Get('system.mail_quota_enabled') != '1') {
$value_arr['email_quota'] = -1;

View File

@@ -564,9 +564,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
// If path is empty or '/' and 'Use domain name as default value for DocumentRoot path' is enabled in settings,
// set default path to subdomain or domain name
if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $completedomain);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $completedomain, $customer['documentroot']);
} else {
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path);
$path = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $path, $customer['documentroot']);
}
} else {
// no it's not, create a redirect
@@ -1078,10 +1078,8 @@ 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'];
}
} else {
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
@@ -1090,9 +1088,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
$customer_ids = [
$this->getUserDetail('customerid')
];
$customer_stdsubs = [
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
];
}
if (!empty($customer_ids)) {
// prepare select statement
@@ -1101,7 +1096,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0'
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")
");
$result = Database::pexecute_first($domains_stmt, null, true, true);
if ($result) {

View File

@@ -1,53 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Backup;
use Froxlor\Database\Database;
use PDO;
class Backup
{
/**
* returns an array of existing backup-storages
* in our database for the settings-array
*
* @return array
*/
public static function getBackupStorages(): array
{
$storages_array = [
0 => lng('backup.storage_none')
];
// get all storages
$result_stmt = Database::query("SELECT id, type, description FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ORDER BY type, description");
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
if (!isset($storages_array[$row['id']]) && !in_array($row['id'], $storages_array)) {
$storages_array[$row['id']] = "[" . $row['type'] . "] " . html_entity_decode($row['description']);
}
}
return $storages_array;
}
}

View File

@@ -1,102 +0,0 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\FileDir;
class Ftp extends Storage
{
private $ftp_conn = null;
/**
* @return bool
* @throws Exception
*/
public function init(): bool
{
$hostname = $this->sData['storage']['hostname'] ?? '';
$username = $this->sData['storage']['username'] ?? '';
$password = $this->sData['storage']['password'] ?? '';
if (!empty($hostname) && !empty($username) && !empty($password)) {
$tmp = explode(":", $hostname);
$hostname = $tmp[0];
$port = $tmp[1] ?? 21;
$this->ftp_conn = ftp_connect($hostname, $port);
if ($this->ftp_conn === false) {
throw new Exception('Unable to connect to ftp-server "' . $hostname . ':' . $port . '"');
}
if (!ftp_login($this->ftp_conn, $username, $password)) {
throw new Exception('Unable to login to ftp-server "' . $hostname . ':' . $port . '"');
}
return $this->changeToCorrectDirectory();
}
throw new Exception('Empty hostname for FTP backup storage');
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
* @throws Exception
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
if (file_exists($source) && ftp_size($this->ftp_conn, $filename) == -1) {
if (ftp_put($this->ftp_conn, $filename, $source, FTP_BINARY)) {
return FileDir::makeCorrectFile($this->getDestinationDirectory() . '/' . $filename);
}
}
return "";
}
/**
* @param string $filename
* @return bool
* @throws Exception
*/
protected function rmFile(string $filename): bool
{
$target = basename($filename);
if (ftp_size($this->ftp_conn, $target) >= 0) {
return ftp_delete($this->ftp_conn, $target);
}
return true;
}
/**
* @return bool
*/
public function shutdown(): bool
{
return ftp_close($this->ftp_conn);
}
/**
* @return bool
* @throws Exception
*/
private function changeToCorrectDirectory(): bool
{
$dirs = explode("/", $this->getDestinationDirectory());
array_shift($dirs);
if (count($dirs) > 0 && !empty($dirs[0])) {
foreach ($dirs as $dir) {
if (empty($dir)) {
continue;
}
if (!@ftp_chdir($this->ftp_conn, $dir)) {
ftp_mkdir($this->ftp_conn, $dir);
ftp_chmod($this->ftp_conn, 0700, $dir);
ftp_chdir($this->ftp_conn, $dir);
}
}
return true;
}
return ftp_chdir($this->ftp_conn, "/");
}
}

View File

@@ -1,64 +0,0 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\FileDir;
class Local extends Storage
{
/**
* @throws Exception
*/
public function init(): bool
{
// create destination_path
if (!file_exists($this->getDestinationDirectory())) {
return mkdir($this->getDestinationDirectory(), 0700, true);
}
return true;
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
* @throws Exception
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
if (file_exists($source) && !file_exists($target)) {
rename($source, $target);
return $target;
}
return "";
}
/**
* @param string $filename
* @return bool
* @throws Exception
*/
protected function rmFile(string $filename): bool
{
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
if (file_exists($target)) {
return @unlink($target);
}
return true;
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -1,45 +0,0 @@
<?php
namespace Froxlor\Backup\Storages;
class Rsync extends Storage
{
/**
* @return bool
*/
public function init(): bool
{
// TODO: Implement init() method.
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
return "";
}
/**
* @param string $filename
* @return bool
*/
protected function rmFile(string $filename): bool
{
// TODO: Implement removeOld() method.
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -1,45 +0,0 @@
<?php
namespace Froxlor\Backup\Storages;
class S3 extends Storage
{
/**
* @return bool
*/
public function init(): bool
{
// TODO: Implement init() method.
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
return "";
}
/**
* @param string $filename
* @return bool
*/
protected function rmFile(string $filename): bool
{
// TODO: Implement removeOld() method.
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -1,45 +0,0 @@
<?php
namespace Froxlor\Backup\Storages;
class Sftp extends Storage
{
/**
* @return bool
*/
public function init(): bool
{
// TODO: Implement init() method.
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
*/
protected function putFile(string $filename, string $tmp_source_directory): string
{
return "";
}
/**
* @param string $filename
* @return bool
*/
protected function rmFile(string $filename): bool
{
// TODO: Implement removeOld() method.
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -1,281 +0,0 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\Database\Database;
use Froxlor\FileDir;
abstract class Storage
{
private string $tmpDirectory;
protected array $sData;
protected array $filesToStore;
/**
* @throws Exception
*/
public function __construct(array $storage_data)
{
$this->sData = $storage_data;
$this->tmpDirectory = FileDir::makeCorrectDir(sys_get_temp_dir() . '/backup-' . $this->sData['loginname']);
}
/**
* Validate sData, open connection to target storage, etc.
*
* @return bool
*/
abstract public function init(): bool;
/**
* Disconnect / clean up connection if needed
*
* @return bool
*/
abstract public function shutdown(): bool;
/**
* prepare files to back up (e.g. create archive or similar) and fill $filesToStore
*
* @return void
* @throws Exception
*/
public function prepareFiles(): void
{
$this->filesToStore = [];
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
// create archive of web, mail and database data
$this->prepareWebData();
$this->prepareDatabaseData();
$this->prepareMailData();
// create json-info-file
}
/**
* @throws Exception
*/
private function prepareWebData(): void
{
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/web');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-web.tar.gz')) . ' -C ' . escapeshellarg($this->sData['documentroot']) . ' .');
$this->filesToStore[] = FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-web.tar.gz');
}
/**
* @throws Exception
*/
private function prepareDatabaseData(): void
{
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/mysql');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
// get all customer database-names
$sel_stmt = Database::prepare("
SELECT `databasename`, `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`
WHERE `customerid` = :cid ORDER BY `dbserver`
");
Database::pexecute($sel_stmt, [
'cid' => $this->sData['customerid']
]);
$has_dbs = false;
$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']) {
Database::needRoot(true, $row['dbserver']);
Database::needSqlData();
$sql_root = Database::getSqlData();
Database::needRoot(false);
// create temporary mysql-defaults file for the connection-credentials/details
$mysqlcnf_file = tempnam("/tmp", "frx");
$mysqlcnf = "[mysqldump]\npassword=" . $sql_root['passwd'] . "\nhost=" . $sql_root['host'] . "\n";
if (!empty($sql_root['port'])) {
$mysqlcnf .= "port=" . $sql_root['port'] . "\n";
} elseif (!empty($sql_root['socket'])) {
$mysqlcnf .= "socket=" . $sql_root['socket'] . "\n";
}
file_put_contents($mysqlcnf_file, $mysqlcnf);
}
$bool_false = false;
FileDir::safe_exec('mysqldump --defaults-file=' . escapeshellarg($mysqlcnf_file) . ' -u ' . escapeshellarg($sql_root['user']) . ' ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'), $bool_false, [
'>'
]);
$has_dbs = true;
$current_dbserver = $row['dbserver'];
}
if ($has_dbs) {
$this->filesToStore[] = $tmpdir;
}
if (@file_exists($mysqlcnf_file)) {
@unlink($mysqlcnf_file);
}
}
private function prepareMailData(): void
{
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/mail');
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
// get all customer mail-accounts
$sel_stmt = Database::prepare("
SELECT `homedir`, `maildir` FROM `" . TABLE_MAIL_USERS . "`
WHERE `customerid` = :cid
");
Database::pexecute($sel_stmt, [
'cid' => $this->sData['customerid']
]);
$tar_file_list = "";
$mail_homedir = "";
while ($row = $sel_stmt->fetch()) {
$tar_file_list .= escapeshellarg("./" . $row['maildir']) . " ";
if (empty($mail_homedir)) {
// this should be equal for all entries
$mail_homedir = $row['homedir'];
}
}
if (!empty($tar_file_list)) {
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list));
$this->filesToStore[] = FileDir::makeCorrectFile($tmpdir . '/' . $this->sData['loginname'] . '-mail.tar.gz');
}
}
/**
* Move/Upload file from tmp-source-directory. The file should be moved or deleted afterward.
* Must return the (relative) path including filename to the backup.
*
* @param string $filename
* @param string $tmp_source_directory
* @return string
*/
abstract protected function putFile(string $filename, string $tmp_source_directory): string;
/**
* @param string $filename
* @return bool
*/
abstract protected function rmFile(string $filename): bool;
/**
* @return bool
* @throws Exception
*/
public function removeOld(): bool
{
// retention in days
$retention = $this->sData['storage']['retention'] ?? 3;
// keep date
$keepDate = new \DateTime();
$keepDate->setTime(0, 0, 0, 1);
// subtract retention days
$keepDate->sub(new \DateInterval('P' . $retention . 'D'));
// select target backups to remove for this storage-id and customer
$sel_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUPS . "`
WHERE `created_at` < :keepdate
AND `storage_id` = :sid
AND `customerid` = :cid
");
Database::pexecute($sel_stmt, [
'keepdate' => $keepDate->format('U'),
'sid' => $this->sData['backup'],
'cid' => $this->sData['customerid']
]);
while ($oldBackup = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$this->rmFile($oldBackup['filename']);
}
}
/**
* Returns the storage configured destination path for all backups
*
* @return string
* @throws Exception
*/
public function getDestinationDirectory(): string
{
return FileDir::makeCorrectDir($this->sData['storage']['destination_path'] ?? "/");
}
/**
* Create backup-archive/file from $filesToStore and call putFile()
*
* @return bool
* @throws Exception
*/
public function createFromFiles(): bool
{
if (empty($this->filesToStore)) {
return false;
}
$filename = FileDir::makeCorrectFile($this->tmpDirectory . "/backup-" . $this->sData['loginname'] . "-" . date('c') . ".tar.gz");
$tmpdir = FileDir::makeCorrectDir($this->tmpDirectory . '/.tmp/');
$create_export_tar_data = implode(" ", $this->filesToStore);
FileDir::safe_exec('chown -R ' . (int)$this->sData['guid'] . ':' . (int)$this->sData['guid'] . ' ' . escapeshellarg($tmpdir));
if (!empty($data['pgp_public_key'])) {
// pack all archives in tmp-dir to one archive and encrypt it with gpg
$recipient_file = FileDir::makeCorrectFile($this->tmpDirectory . '/' . $this->sData['loginname'] . '-recipients.gpg');
file_put_contents($recipient_file, $data['pgp_public_key']);
$return_value = [];
FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($filename) . ' --trust-model always --batch --yes', $return_value, ['|']);
} else {
// pack all archives in tmp-dir to one archive
FileDir::safe_exec('tar cfz ' . escapeshellarg($filename) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data));
}
// determine filesize (use stat locally here b/c files are possibly large and php's filesize() can't handle them)
$fileSizeOutput = FileDir::safe_exec('/usr/bin/stat -c "%s" ' . escapeshellarg($filename));
$fileSize = (int)array_shift($fileSizeOutput);
// add entry to database and upload/store file
FileDir::safe_exec('rm -rf ' . escapeshellarg($tmpdir));
$fileDest = $this->putFile(basename($filename), $this->tmpDirectory);
if (!empty($fileDest)) {
$this->addEntry($fileDest, $fileSize);
return true;
}
return false;
}
/**
* @param string $filename
* @param int $fileSize
* @return void
* @throws Exception
*/
private function addEntry(string $filename, int $fileSize): void
{
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_BACKUPS . "` SET
`adminid` = :adminid,
`customerid` = :customerid,
`loginname` = :loginname,
`size` = :size,
`storage_id` = :sid,
`filename` = :filename,
`created_at` = UNIX_TIMESTAMP()
");
Database::pexecute($ins_stmt, [
'adminid' => $this->sData['adminid'],
'customerid' => $this->sData['customerid'],
'loginname' => $this->sData['loginname'],
'size' => $fileSize,
'sid' => $this->sData['backup'],
'filename' => $filename
]);
}
}

View File

@@ -1,39 +0,0 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\Database\Database;
class StorageFactory
{
public static function fromType(string $type, array $storage_data): Storage
{
$type = "\\Froxlor\\Backup\\Storages\\" . ucfirst($type);
return new $type($storage_data);
}
/**
* @throws Exception
*/
public static function fromStorageId(int $storage_id, array $user_data): Storage
{
$storage = self::readStorageData($storage_id);
$storage_data = $user_data;
$storage_data['storage'] = $storage;
return self::fromType($storage['type'], $storage_data);
}
/**
* @throws Exception
*/
private static function readStorageData(int $storage_id): array
{
$stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` WHERE `id` = :bid");
$storage = Database::pexecute_first($stmt, ['bid' => $storage_id]);
if (empty($storage)) {
throw new Exception("Invalid/empty backup-storage. Unable to continue");
}
return $storage;
}
}

View File

@@ -25,19 +25,18 @@
namespace Froxlor\Cli;
use PDO;
use Exception;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\Settings;
use Froxlor\Database\Database;
use PDO;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CliCommand extends Command
{
protected function validateRequirements(InputInterface $input, OutputInterface $output, bool $ignore_has_updates = false): int
protected function validateRequirements(OutputInterface $output, bool $ignore_has_updates = false): int
{
if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
$output->writeln("<error>Could not find froxlor's userdata.inc.php file. You should use this script only with an installed froxlor system.</>");
@@ -116,9 +115,11 @@ class CliCommand extends Command
return $userinfo;
}
private function runUpdate(OutputInterface $output): int
protected function runUpdate(OutputInterface $output, bool $manual = false): int
{
$output->writeln('<comment>Automatic update is activated and we are going to proceed without any notices</>');
if (!$manual) {
$output->writeln('<comment>Automatic update is activated and we are going to proceed without any notices</>');
}
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1);
ob_start([
@@ -127,11 +128,11 @@ class CliCommand extends Command
]);
include_once Froxlor::getInstallDir() . '/install/updatesql.php';
ob_end_flush();
$output->writeln('<info>Automatic update done - you should check your settings to be sure everything is fine</>');
$output->writeln('<info>' . ($manual ? 'Database' : 'Automatic') . ' update done - you should check your settings to be sure everything is fine</>');
return self::SUCCESS;
}
private function cleanUpdateOutput($buffer)
private function cleanUpdateOutput($buffer): string
{
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
}

View File

@@ -45,6 +45,9 @@ final class ConfigDiff extends CliCommand
->addOption('diff-params', '', InputOption::VALUE_REQUIRED, 'Additional parameters for `diff`, e.g. --diff-params="--color=always"');
}
/**
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
require Froxlor::getInstallDir() . '/lib/functions.php';

View File

@@ -25,6 +25,7 @@
namespace Froxlor\Cli;
use Exception;
use Froxlor\Config\ConfigParser;
use Froxlor\Database\Database;
use Froxlor\FileDir;
@@ -40,7 +41,6 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class ConfigServices extends CliCommand
{
private $yes_to_all_supported = [
'bookworm',
'bionic',
@@ -62,11 +62,9 @@ final class ConfigServices extends CliCommand
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Install packages without asking questions (Debian/Ubuntu only currently)');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -93,7 +91,7 @@ final class ConfigServices extends CliCommand
if ($result == self::SUCCESS) {
$io = new SymfonyStyle($input, $output);
if ($input->getOption('create')) {
$result = $this->createConfig($input, $output, $io);
$result = $this->createConfig($output, $io);
} elseif ($input->getOption('apply')) {
$result = $this->applyConfig($input, $output, $io);
} elseif ($input->getOption('list') || $input->getOption('daemon')) {
@@ -158,7 +156,10 @@ final class ConfigServices extends CliCommand
fclose($fp);
}
private function createConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
/**
* @throws Exception
*/
private function createConfig(OutputInterface $output, SymfonyStyle $io): int
{
$_daemons_config = [
'distro' => ""
@@ -285,7 +286,10 @@ final class ConfigServices extends CliCommand
return self::SUCCESS;
}
private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
/**
* @throws Exception
*/
private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
{
$applyFile = $input->getOption('apply');
@@ -429,7 +433,10 @@ final class ConfigServices extends CliCommand
}
}
private function getReplacerArray()
/**
* @throws Exception
*/
private function getReplacerArray(): array
{
$customer_tmpdir = '/tmp/';
if (Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_tmpdir') != '') {
@@ -438,7 +445,7 @@ final class ConfigServices extends CliCommand
$customer_tmpdir = Settings::Get('phpfpm.tmpdir');
}
// try to convert namserver hosts to ip's
// try to convert nameserver hosts to ip's
$ns_ips = "";
$known_ns_ips = [];
if (Settings::Get('system.nameservers') != '') {
@@ -484,12 +491,12 @@ final class ConfigServices extends CliCommand
Database::needSqlData();
$sql = Database::getSqlData();
$replace_arr = [
return [
'<SQL_UNPRIVILEGED_USER>' => $sql['user'],
'<SQL_UNPRIVILEGED_PASSWORD>' => $sql['passwd'],
'<SQL_DB>' => $sql['db'],
'<SQL_HOST>' => $sql['host'],
'<SQL_SOCKET>' => isset($sql['socket']) ? $sql['socket'] : null,
'<SQL_SOCKET>' => $sql['socket'] ?? null,
'<SERVERNAME>' => Settings::Get('system.hostname'),
'<SERVERIP>' => Settings::Get('system.ipaddress'),
'<NAMESERVERS>' => Settings::Get('system.nameservers'),
@@ -508,6 +515,5 @@ final class ConfigServices extends CliCommand
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
];
return $replace_arr;
}
}

View File

@@ -26,13 +26,13 @@
namespace Froxlor\Cli;
use Exception;
use Froxlor\Froxlor;
use Froxlor\Config\ConfigParser;
use Froxlor\Froxlor;
use Froxlor\Install\Install;
use Froxlor\Install\Install\Core;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -53,7 +53,10 @@ final class InstallCommand extends Command
->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process');
}
protected function execute(InputInterface $input, OutputInterface $output)
/**
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
@@ -137,10 +140,12 @@ final class InstallCommand extends Command
$decoded_input = [];
}
$result = $this->showStep(0, $extended, $decoded_input);
return $result;
return $this->showStep(0, $extended, $decoded_input);
}
/**
* @throws Exception
*/
private function showStep(int $step = 0, bool $extended = false, array $decoded_input = []): int
{
$result = self::SUCCESS;

View File

@@ -25,19 +25,20 @@
namespace Froxlor\Cli;
use PDO;
use Froxlor\Froxlor;
use Froxlor\FileDir;
use Froxlor\Settings;
use Froxlor\FroxlorLogger;
use Froxlor\Database\Database;
use Froxlor\System\Cronjob;
use Froxlor\Cron\TaskId;
use Exception;
use Froxlor\Cron\CronConfig;
use Froxlor\Cron\System\Extrausers;
use Froxlor\Cron\TaskId;
use Froxlor\Database\Database;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use Froxlor\System\Cronjob;
use PDO;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
final class MasterCron extends CliCommand
@@ -57,10 +58,12 @@ final class MasterCron extends CliCommand
->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).');
}
protected function execute(InputInterface $input, OutputInterface $output)
/**
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
if ($result != self::SUCCESS) {
// requirements failed, exit
@@ -76,7 +79,7 @@ final class MasterCron extends CliCommand
Cronjob::inserttask(TaskId::REBUILD_DNS);
Cronjob::inserttask(TaskId::CREATE_QUOTA);
Cronjob::inserttask(TaskId::REBUILD_CRON);
array_push($jobs, 'tasks');
$jobs[] = 'tasks';
}
define('CRON_IS_FORCED', 1);
}
@@ -94,7 +97,7 @@ final class MasterCron extends CliCommand
foreach ($tasks_to_run as $ttr) {
if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) {
Cronjob::inserttask($ttr);
array_push($jobs, 'tasks');
$jobs[] = 'tasks';
} else {
$output->writeln('<comment>Unknown task number "' . $ttr . '"</>');
}
@@ -140,12 +143,12 @@ final class MasterCron extends CliCommand
$cronfile::run();
}
// free the lockfile
$this->unlockJob($job);
$this->unlockJob();
}
}
// regenerate nss-extrausers files / invalidate nscd cache (if used)
$this->refreshUsers((int) $tasks_cnt['jobcnt']);
$this->refreshUsers((int)$tasks_cnt['jobcnt']);
// we have to check the system's last guid with every cron run
// in case the admin installed new software which added a new user
@@ -157,13 +160,13 @@ final class MasterCron extends CliCommand
CronConfig::checkCrondConfigurationFile();
// check for old/compatibility cronjob file
if (file_exists(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php')) {
@unlink(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php');
@rmdir(Froxlor::getInstallDir().'/scripts');
if (file_exists(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php')) {
@unlink(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php');
@rmdir(Froxlor::getInstallDir() . '/scripts');
}
// reset cronlog-flag if set to "once"
if ((int) Settings::Get('logger.log_cron') == 1) {
if ((int)Settings::Get('logger.log_cron') == 1) {
FroxlorLogger::getInstanceOf()->setCronLog(0);
}
@@ -173,27 +176,9 @@ final class MasterCron extends CliCommand
return $result;
}
private function refreshUsers(int $jobcount = 0)
{
if ($jobcount > 0) {
if (Settings::Get('system.nssextrausers') == 1) {
Extrausers::generateFiles($this->cronLog);
return;
}
// clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers
if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) {
$false_val = false;
FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [
'>'
]);
FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [
'>'
]);
}
}
}
/**
* @throws Exception
*/
private function validateOwnership(OutputInterface $output)
{
// when using fcgid or fpm for froxlor-vhost itself, we have to check
@@ -220,21 +205,6 @@ final class MasterCron extends CliCommand
$output->writeln('OK');
}
private function getCronModule(string $cronname, OutputInterface $output)
{
$upd_stmt = Database::prepare("
SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron;
");
$cron = Database::pexecute_first($upd_stmt, [
'cron' => $cronname
]);
if ($cron) {
return $cron['cronclass'];
}
$output->writeln("<error>Requested cronjob '" . $cronname . "' could not be found.</>");
return false;
}
private function lockJob(string $job, OutputInterface $output): bool
{
@@ -247,12 +217,12 @@ final class MasterCron extends CliCommand
system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return);
if ($check_pid_return == 1) {
// Process does not seem to run, most likely it has died
$this->unlockJob($job);
$this->unlockJob();
} else {
// cronjob still running, output info and stop
$output->writeln([
'<comment>Job "' . $jobinfo['job'] . '" is currently running.',
'Started: ' . date('d.m.Y H:i', (int) $jobinfo['startts']),
'Started: ' . date('d.m.Y H:i', (int)$jobinfo['startts']),
'PID: ' . $jobinfo['pid'] . '</>'
]);
return false;
@@ -268,8 +238,44 @@ final class MasterCron extends CliCommand
return true;
}
private function unlockJob(string $job): bool
private function unlockJob(): bool
{
return @unlink($this->lockFile);
}
private function getCronModule(string $cronname, OutputInterface $output)
{
$upd_stmt = Database::prepare("
SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron;
");
$cron = Database::pexecute_first($upd_stmt, [
'cron' => $cronname
]);
if ($cron) {
return $cron['cronclass'];
}
$output->writeln("<error>Requested cronjob '" . $cronname . "' could not be found.</>");
return false;
}
private function refreshUsers(int $jobcount = 0)
{
if ($jobcount > 0) {
if (Settings::Get('system.nssextrausers') == 1) {
Extrausers::generateFiles($this->cronLog);
return;
}
// clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers
if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) {
$false_val = false;
FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [
'>'
]);
FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [
'>'
]);
}
}
}
}

View File

@@ -43,9 +43,9 @@ final class PhpSessionclean extends CliCommand
$this->addArgument('max-lifetime', InputArgument::OPTIONAL, 'The number of seconds after which data will be seen as "garbage" and potentially cleaned up. Defaults to "1440"');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
if ($result == self::SUCCESS) {
if ((int)Settings::Get('phpfpm.enabled') == 1) {
@@ -89,7 +89,7 @@ final class PhpSessionclean extends CliCommand
if (count($paths_to_clean) > 0) {
foreach ($paths_to_clean as $ptc) {
// find all files older then maxlifetime and delete them
// find all files older than maxlifetime and delete them
FileDir::safe_exec("find -O3 \"" . $ptc . "\" -ignore_readdir_race -depth -mindepth 1 -name 'sess_*' -type f -cmin \"+" . $maxlifetime . "\" -delete");
}
}

View File

@@ -26,14 +26,12 @@
namespace Froxlor\Cli;
use Exception;
use PDO;
use Symfony\Component\Console\Input\InputInterface;
use Froxlor\Froxlor;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
final class RunApiCommand extends CliCommand
{
@@ -48,11 +46,9 @@ final class RunApiCommand extends CliCommand
$this->addOption('show-params', 's', InputOption::VALUE_NONE, 'Show possible parameters for given api-command (given command will *not* be called)');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
require Froxlor::getInstallDir() . '/lib/functions.php';
@@ -110,6 +106,9 @@ final class RunApiCommand extends CliCommand
return self::SUCCESS;
}
/**
* @throws Exception
*/
private function validateCommand(string $command): array
{
$command = explode(".", $command);

View File

@@ -43,11 +43,9 @@ final class SwitchServerIp extends CliCommand
->addOption('list', 'l', InputOption::VALUE_NONE, 'List all IP addresses currently added for this server in froxlor');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
if ($result == self::SUCCESS && $input->getOption('list') == false && $input->getOption('switch') == false) {
$output->writeln('<error>Either --list or --switch option must be provided. Nothing to do, exiting.</>');
@@ -83,6 +81,7 @@ final class SwitchServerIp extends CliCommand
$ip_list = $input->getOption('switch');
$has_error = false;
$ips_to_switch = [];
foreach ($ip_list as $ips_combo) {
$ip_pair = explode(",", $ips_combo);
if (count($ip_pair) != 2) {

View File

@@ -27,9 +27,9 @@ namespace Froxlor\Cli;
use Exception;
use Froxlor\Froxlor;
use Froxlor\Settings;
use Froxlor\Install\Update;
use Froxlor\Install\AutoUpdate;
use Froxlor\Install\Update;
use Froxlor\Settings;
use Froxlor\System\Mailer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -44,6 +44,7 @@ final class UpdateCommand extends CliCommand
$this->setName('froxlor:update');
$this->setDescription('Check for newer version and update froxlor');
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit')
->addOption('database', 'd', InputOption::VALUE_NONE, 'Only run database updates in case updates are done via apt or manually.')
->addOption('mail-notify', 'm', InputOption::VALUE_NONE, 'Additionally inform administrator via email if a newer version was found')
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for download, extract and database-update, just do it (if not --check-only is set)')
->addOption('integer-return', 'i', InputOption::VALUE_NONE, 'Return integer whether a new version is available or not (implies --check-only). Useful for programmatic use.');
@@ -53,8 +54,36 @@ final class UpdateCommand extends CliCommand
{
$result = self::SUCCESS;
// database update only
if ($input->getOption('database')) {
$result = $this->validateRequirements($input, $output, true);
if ($result == self::SUCCESS) {
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
$output->writeln('<info>' . lng('updates.dbupdate_required') . '</>');
if ($input->getOption('check-only')) {
$output->writeln('<comment>Doing nothing because of "check-only" flag.</>');
} else {
$yestoall = $input->getOption('yes-to-all') !== false;
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
if ($yestoall || $helper->ask($input, $output, $question)) {
$result = $this->runUpdate($output, true);
}
}
return $result;
}
$output->writeln('<info>' . lng('update.noupdatesavail', (Settings::Get('system.update_channel') == 'testing' ? lng('serversettings.uc_testing') . ' ' : '')) . '</>');
}
return $result;
}
$result = $this->validateRequirements($input, $output);
if ($result != self::SUCCESS) {
// requirements failed, exit
return $result;
}
require Froxlor::getInstallDir() . '/lib/functions.php';
// version check
@@ -182,22 +211,4 @@ final class UpdateCommand extends CliCommand
}
}
}
private function updateDatabase()
{
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
define('_CRON_UPDATE', 1);
ob_start([
$this,
'cleanUpdateOutput'
]);
include_once Froxlor::getInstallDir() . '/install/updatesql.php';
ob_end_flush();
return self::SUCCESS;
}
private function cleanUpdateOutput($buffer)
{
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
}
}

View File

@@ -26,15 +26,15 @@
namespace Froxlor\Cli;
use Exception;
use Symfony\Component\Console\Input\InputInterface;
use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\Customers;
use Froxlor\Froxlor;
use Froxlor\System\Crypt;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\Customers;
use Froxlor\System\Crypt;
use Froxlor\Froxlor;
final class UserCommand extends CliCommand
{
@@ -50,11 +50,11 @@ final class UserCommand extends CliCommand
->addOption('show-info', 's', InputOption::VALUE_NONE, 'Output information details of given user');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output);
$result = $this->validateRequirements($output);
require Froxlor::getInstallDir() . '/lib/functions.php';

View File

@@ -48,15 +48,16 @@ final class ValidateAcmeWebroot extends CliCommand
$this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
}
protected function execute(InputInterface $input, OutputInterface $output)
/**
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = self::SUCCESS;
$result = $this->validateRequirements($input, $output, true);
$result = $this->validateRequirements($output, true);
$io = new SymfonyStyle($input, $output);
if ((int) Settings::Get('system.leenabled') == 0) {
if ((int)Settings::Get('system.leenabled') == 0) {
$io->info("Let's Encrypt not activated in froxlor settings.");
$result = self::INVALID;
}
@@ -94,7 +95,7 @@ final class ValidateAcmeWebroot extends CliCommand
$acmesh_challenge_dir = $recommended;
// need to update the corresponding acme-alias config-file
$acme_alias_file = Settings::Get('system.letsencryptacmeconf');
$sed_params = "s@".$former_value."@" . $acmesh_challenge_dir . "@";
$sed_params = "s@" . $former_value . "@" . $acmesh_challenge_dir . "@";
FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_alias_file));
$count_changes++;
}
@@ -138,8 +139,6 @@ final class ValidateAcmeWebroot extends CliCommand
$io->info("Domain '" . $domain . "' Le_Webroot value is correct");
}
break;
} else {
continue;
}
}
}

View File

@@ -1,95 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Cron\Backup;
use Exception;
use Froxlor\Backup\Storages\StorageFactory;
use Froxlor\Cron\Forkable;
use Froxlor\Cron\FroxlorCron;
use Froxlor\Database\Database;
use Froxlor\FroxlorLogger;
use Froxlor\Settings;
use PDO;
class BackupCron extends FroxlorCron
{
use Forkable;
public static function run()
{
if (!Settings::Get('backup.enabled')) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'BackupCron: disabled - exiting');
return -1;
}
$stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`");
Database::pexecute($stmt);
$storages = [];
while ($storage = $stmt->fetch(PDO::FETCH_ASSOC)) {
$storages[$storage['id']] = $storage;
}
$stmt = Database::prepare("SELECT
customerid,
loginname,
adminid,
backup,
guid,
documentroot
FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `backup` > 0
");
Database::pexecute($stmt);
$customers = [];
while ($customer = $stmt->fetch(PDO::FETCH_ASSOC)) {
$customer['storage'] = $storages[$customer['backup']];
$customers[] = $customer;
}
self::runFork([self::class, 'handle'], $customers);
}
/**
* @throws Exception
*/
private static function handle(array $userdata)
{
echo "BackupCron: started - creating customer backup for user " . $userdata['loginname'] . "\n";
$backupStorage = StorageFactory::fromType($userdata['storage']['type'], $userdata);
// initialize storage
$backupStorage->init();
// do what is required to obtain files/archives and move/upload
$backupStorage->prepareFiles();
// upload/move to target
$backupStorage->createFromFiles();
// remove by retention
$backupStorage->removeOld();
echo "BackupCron: finished - creating customer backup for user " . $userdata['loginname'] . "\n";
}
}

View File

@@ -129,7 +129,7 @@ class Apache extends HttpConfigBase
if ($row_ipsandports['ssl'] == '0' && Settings::Get('system.le_froxlor_redirect') == '1') {
$is_redirect = true;
// check whether froxlor uses Let's Encrypt and not cert is being generated yet
// or a renew is ongoing - disable redirect
// or a renewal is ongoing - disable redirect
if (Settings::Get('system.le_froxlor_enabled') && ($this->froxlorVhostHasLetsEncryptCert() == false || $this->froxlorVhostLetsEncryptNeedsRenew())) {
$this->virtualhosts_data[$vhosts_filename] .= '# temp. disabled ssl-redirect due to Let\'s Encrypt certificate generation.' . PHP_EOL;
$is_redirect = false;
@@ -1255,7 +1255,7 @@ class Apache extends HttpConfigBase
// >=apache-2.4 enabled?
if (Settings::Get('system.apache24') == '1') {
$mypath_dir = new Directory($row_diroptions['path']);
// only create the require all granted if there is not active directory-protection
// only create the' require all granted' if there is no active directory-protection
// for this path, as this would be the first require and therefore grant all access
if ($mypath_dir->isUserProtected() == false) {
$this->diroptions_data[$diroptions_filename] .= ' Require all granted' . "\n";

View File

@@ -863,13 +863,7 @@ class Nginx extends HttpConfigBase
// remove comments
$vhost = implode("\n", preg_replace('/^(\s+)?#(.*)$/', '', explode("\n", $vhost)));
// Break blocks into lines
$vhost = str_replace([
"{",
"}"
], [
" {\n",
"\n}"
], $vhost);
$vhost = preg_replace("/^(\s+)?location(.+)\{(.+)\}$/misU", "location $2 {\n $3 \n}", $vhost);
// Break into array items
$vhost = explode("\n", preg_replace('/[ \t]+/', ' ', trim(preg_replace('/\t+/', '', $vhost))));
// Remove empty lines

View File

@@ -154,17 +154,21 @@ class CurrentUser
]);
$addition = $result['emaildomains'] != 0;
} elseif ($resource == 'subdomains') {
$parentDomainCollection = (new Collection(
SubDomains::class,
$_SESSION['userinfo'],
['sql_search' => [
'd.parentdomainid' => 0,
'd.deactivated' => 0,
'd.id' => ['op' => '<>', 'value' => $_SESSION['userinfo']['standardsubdomain']]
]
]
));
$addition = $parentDomainCollection->count() != 0;
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
$addition = false;
} else {
$parentDomainCollection = (new Collection(
SubDomains::class,
$_SESSION['userinfo'],
['sql_search' => [
'd.parentdomainid' => 0,
'd.deactivated' => 0,
'd.id' => ['op' => '<>', 'value' => $_SESSION['userinfo']['standardsubdomain']]
]
]
));
$addition = $parentDomainCollection->count() != 0;
}
} elseif ($resource == 'domains') {
$customerCollection = (new Collection(Customers::class, $_SESSION['userinfo']));
$addition = $customerCollection->count() != 0;

View File

@@ -55,6 +55,7 @@ class IpAddr
/**
* @return array
* @throws \Exception
*/
public static function getSslIpPortCombinations(): array
{
@@ -75,7 +76,7 @@ class IpAddr
$additional_conditions_params = [];
$additional_conditions_array = [];
if ($userinfo['ip'] != '-1') {
if (!empty($userinfo) && $userinfo['ip'] != '-1') {
$admin_ip_stmt = Database::prepare("
SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = IN (:ipid)
");

View File

@@ -26,10 +26,10 @@
namespace Froxlor;
use Exception;
use PDO;
use RecursiveCallbackFilterIterator;
use Froxlor\Customer\Customer;
use Froxlor\Database\Database;
use PDO;
use RecursiveCallbackFilterIterator;
class FileDir
{
@@ -51,11 +51,12 @@ class FileDir
public static function mkDirWithCorrectOwnership(
string $homeDir,
string $dirToCreate,
int $uid,
int $gid,
bool $placeindex = false,
bool $allow_notwithinhomedir = false
): bool {
int $uid,
int $gid,
bool $placeindex = false,
bool $allow_notwithinhomedir = false
): bool
{
if ($homeDir != '' && $dirToCreate != '') {
$homeDir = self::makeCorrectDir($homeDir);
$dirToCreate = self::makeCorrectDir($dirToCreate);
@@ -107,15 +108,16 @@ class FileDir
}
/**
* Function which returns a correct dirname, means to add slashes at the beginning and at the end if there weren't
* some
* Returns a correct/secure dirname, means to add slashes at the beginning and at the end if there weren't
* some. If $fixes_homedir is specified,
*
*
* @param string $dir the path to correct
*
* @return string the corrected path
* @throws Exception
*/
public static function makeCorrectDir(string $dir): string
public static function makeCorrectDir(string $dir, string $fixed_homedir = ""): string
{
if (strlen($dir) > 0) {
$dir = trim($dir);
@@ -125,6 +127,30 @@ class FileDir
if (substr($dir, 0, 1) != '/') {
$dir = '/' . $dir;
}
// if given, check that the target path is within the $fixed_homedir
// by checking each folder for being a symlink and whether it targets
// the customers homedir or points outside of it
if (!empty($fixed_homedir)) {
$to_check = explode("/", substr($dir, strlen($fixed_homedir) + 1), -1);
$check_dir = substr($fixed_homedir, 0, -1);
// Symlink check
foreach ($to_check as $sub_dir) {
$check_dir .= '/' . $sub_dir;
if (is_link($check_dir)) {
$original_target = $check_dir;
$check_dir = readlink($check_dir);
if (substr($check_dir, 0, strlen($fixed_homedir)) != $fixed_homedir) {
throw new Exception("Found symlink pointing outside of customer home directory: " . substr($original_target, strlen($fixed_homedir)));
}
}
}
// check for the path to be within the given homedir
if (substr($dir, 0, strlen($fixed_homedir)) != $fixed_homedir) {
throw new Exception("Target path not within the required customer home directory");
}
}
return self::makeSecurePath($dir);
}
throw new Exception("Cannot validate directory in " . __FUNCTION__ . " which is very dangerous.");
@@ -245,9 +271,10 @@ class FileDir
public static function storeDefaultIndex(
string $loginname,
string $destination,
$logger = null,
bool $force = false
) {
$logger = null,
bool $force = false
)
{
if ($force || (int)Settings::Get('system.store_index_file_subs') == 1) {
$result_stmt = Database::prepare("
SELECT `t`.`value`, `c`.`email` AS `customer_email`, `a`.`email` AS `admin_email`, `c`.`loginname` AS `customer_login`, `a`.`loginname` AS `admin_login`

View File

@@ -31,7 +31,7 @@ final class Froxlor
{
// Main version variable
const VERSION = '2.1.0-dev1';
const VERSION = '2.1.0-beta2';
// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202305240';

View File

@@ -23,11 +23,36 @@
* @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' => 'storages'],
'sections' => []
],
];
namespace Froxlor\System;
use League\CommonMark\Exception\CommonMarkException;
use League\CommonMark\GithubFlavoredMarkdownConverter;
class Markdown
{
private static $converter = null;
public static function converter(): ?GithubFlavoredMarkdownConverter
{
if (is_null(self::$converter)) {
self::$converter = new GithubFlavoredMarkdownConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
}
return self::$converter;
}
public static function cleanCustomNotes(string $note = ""): string
{
if (!empty($note)) {
try {
$note = self::converter()->convert($note)->getContent();
} catch (CommonMarkException $e) {
$note = "";
}
}
return $note;
}
}

View File

@@ -25,10 +25,10 @@
namespace Froxlor\Traffic;
use Froxlor\Database\Database;
use Froxlor\Api\Commands\Customers;
use Froxlor\UI\Collection;
use Froxlor\Api\Commands\Traffic as TrafficAPI;
use Froxlor\Database\Database;
use Froxlor\UI\Collection;
class Traffic
{
@@ -38,10 +38,10 @@ class Traffic
* @return array
* @throws \Exception
*/
public static function getCustomerStats(array $userinfo, string $range = null): array
public static function getCustomerStats(array $userinfo, string $range = null, bool $overview = false): array
{
$trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo,
self::getParamsByRange($range, ['customer_traffic' => true,])));
self::getParamsByRange($range, ['customer_traffic' => true])));
if ($userinfo['adminsession'] == 1) {
$trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid');
}
@@ -53,27 +53,36 @@ class Traffic
$months = [];
$days = [];
foreach ($trafficCollection['data']['list'] as $item) {
$http = $item['http'];
$ftp = ($item['ftp_up'] + $item['ftp_down']);
$mail = $item['mail'];
$total = $http + $ftp + $mail;
// per user total
$users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
$users[$item['customerid']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$users[$item['customerid']]['http'] += $item['http'];
$users[$item['customerid']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$users[$item['customerid']]['mail'] += $item['mail'];
// per year
$years[$item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$years[$item['year']]['http'] += $item['http'];
$years[$item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$years[$item['year']]['mail'] += $item['mail'];
// per month
$months[$item['month'] . '/' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$months[$item['month'] . '/' . $item['year']]['http'] += $item['http'];
$months[$item['month'] . '/' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$months[$item['month'] . '/' . $item['year']]['mail'] += $item['mail'];
// per day
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $item['http'];
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $item['mail'];
if ($userinfo['adminsession'] == 1) {
$users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
}
$users[$item['customerid']]['total'] += $total;
$users[$item['customerid']]['http'] += $http;
$users[$item['customerid']]['ftp'] += $ftp;
$users[$item['customerid']]['mail'] += $mail;
if (!$overview) {
// per year
$years[$item['year']]['total'] += $total;
$years[$item['year']]['http'] += $http;
$years[$item['year']]['ftp'] += $ftp;
$years[$item['year']]['mail'] += $mail;
// per month
$months[$item['month'] . '/' . $item['year']]['total'] += $total;
$months[$item['month'] . '/' . $item['year']]['http'] += $http;
$months[$item['month'] . '/' . $item['year']]['ftp'] += $ftp;
$months[$item['month'] . '/' . $item['year']]['mail'] += $mail;
// per day
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += $total;
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $http;
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += $ftp;
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $mail;
}
}
// calculate overview for given range from users
@@ -85,10 +94,13 @@ class Traffic
$metrics['mail'] += $user['mail'];
}
// get all possible years for filter
$sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC");
Database::pexecute($sel_stmt);
$years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC);
$years_avail = [];
if (!$overview) {
// get all possible years for filter
$sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC");
Database::pexecute($sel_stmt);
$years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC);
}
return [
'metrics' => $metrics,

View File

@@ -1,50 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\UI\Callbacks;
use Froxlor\Database\Database;
use Froxlor\UI\Panel\UI;
class Backup
{
public static function backupStorageLink(array $attributes)
{
$sel_stmt = Database::prepare("SELECT `description` FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` WHERE `id` = :id");
$backupstorage = Database::pexecute_first($sel_stmt, ['id' => $attributes['data']]);
if ((int)UI::getCurrentUser()['adminsession'] == 1 && UI::getCurrentUser()['change_serversettings']) {
$linker = UI::getLinker();
$result = '<a href="' . $linker->getLink([
'section' => 'backups',
'page' => 'storages',
'searchfield' => 'id',
'searchtext' => $attributes['data'],
]) . '">' . $backupstorage['description'] . '</a>';
} else {
$result = $backupstorage['description'];
}
return $result;
}
}

View File

@@ -26,17 +26,19 @@
namespace Froxlor\UI\Callbacks;
use Froxlor\Settings;
use Froxlor\System\Markdown;
class Customer
{
public static function isLocked(array $attributes)
public static function isLocked(array $attributes): bool
{
return $attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
&& $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'));
}
public static function hasNote(array $attributes)
public static function hasNote(array $attributes): bool
{
return !empty($attributes['fields']['custom_notes']);
$cleanNote = Markdown::cleanCustomNotes($attributes['fields']['custom_notes'] ?? "");
return !empty($cleanNote);
}
}

View File

@@ -33,6 +33,11 @@ use Froxlor\UI\Panel\UI;
class Domain
{
public static function domainLink(array $attributes)
{
return '<a href="https://' . $attributes['data'] . '" target="_blank">' . $attributes['data'] . '</a>';
}
public static function domainWithCustomerLink(array $attributes)
{
$linker = UI::getLinker();
@@ -190,7 +195,7 @@ class Domain
// specified certificate for domain
if ($attributes['fields']['domain_hascert'] == 1) {
$result['icon'] .= ' text-success';
} // shared certificates (e.g. subdomain if domain where certificate is specified)
} // shared certificates (e.g. subdomain of domain where certificate is specified)
elseif ($attributes['fields']['domain_hascert'] == 2) {
$result['icon'] .= ' text-warning';
$result['title'] .= "\n" . lng('panel.ssleditor_infoshared');

View File

@@ -95,7 +95,7 @@ class ProgressBar
$skip_customer_traffic = false;
try {
$attributes['fields']['deactivated'] = 0;
$result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth');
$result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth', true);
} catch (Exception $e) {
if ($e->getCode() === 405) {
$skip_customer_traffic = true;

View File

@@ -47,4 +47,9 @@ class SSLCertificate
}
return false;
}
public static function isNotLetsEncrypt(array $attributes): bool
{
return (int)$attributes['fields']['letsencrypt'] == 0;
}
}

View File

@@ -31,17 +31,17 @@ class Style
{
public static function deactivated(array $attributes): string
{
return $attributes['fields']['deactivated'] ? 'bg-danger' : '';
return $attributes['fields']['deactivated'] ? 'table-danger' : '';
}
public static function loginDisabled(array $attributes): string
{
return $attributes['fields']['login_enabled'] == 'N' ? 'bg-danger' : '';
return $attributes['fields']['login_enabled'] == 'N' ? 'table-danger' : '';
}
public static function resultIntegrityBad(array $attributes): string
{
return $attributes['fields']['result'] ? '' : 'bg-warning';
return $attributes['fields']['result'] ? '' : 'table-warning';
}
public static function invalidApiKey(array $attributes): string
@@ -53,7 +53,7 @@ class Style
$isValid = false;
}
}
return $isValid ? '' : 'bg-danger';
return $isValid ? '' : 'table-danger';
}
public static function resultDomainTerminatedOrDeactivated(array $attributes): string
@@ -63,25 +63,24 @@ class Style
if (!empty($termination_date)) {
$cdate = strtotime($termination_date . " 23:59:59");
$today = time();
$termination_css = 'bg-warning';
$termination_css = 'table-warning';
if ($cdate < $today) {
$termination_css = 'bg-danger text-light';
$termination_css = 'table-danger';
}
}
$deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated'];
return $deactivated ? 'bg-info text-light' : $termination_css;
return $deactivated ? 'table-info' : $termination_css;
}
public static function resultCustomerLockedOrDeactivated(array $attributes): string
{
$row_css = '';
if ((int)$attributes['fields']['deactivated'] == 1) {
$row_css = 'bg-info text-light';
} elseif (
$attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
$row_css = 'table-info';
} elseif ($attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
&& $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'))
) {
$row_css = 'bg-warning';
$row_css = 'table-warning';
}
return $row_css;
@@ -97,9 +96,9 @@ class Style
$style = '';
if ((int)$attributes[$field] >= 0) {
if (($attributes[$field] / 100) * $report_max < $attributes[$field . '_used']) {
$style = 'bg-danger';
$style = 'table-danger';
} elseif (($attributes[$field] / 100) * ($report_max - 15) < $attributes[$field . '_used']) {
$style = 'bg-warning';
$style = 'table-warning';
}
}
return $style;

View File

@@ -29,6 +29,7 @@ use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Froxlor;
use Froxlor\PhpHelper;
use Froxlor\System\Markdown;
use Froxlor\UI\Panel\UI;
use Froxlor\User;
use PDO;
@@ -93,7 +94,7 @@ class Text
'entry' => $attributes['fields']['id'],
'id' => 'cnModal' . $attributes['fields']['id'],
'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']),
'body' => nl2br($note)
'body' => nl2br(Markdown::cleanCustomNotes($note))
];
}

View File

@@ -25,6 +25,8 @@
namespace Froxlor\UI;
use Froxlor\Settings;
class HTML
{
@@ -116,7 +118,7 @@ class HTML
'label' => $navlabel,
'icon' => $icon,
'items' => $navigation_links,
'active' => $box_active
'active' => ((int)Settings::Get('panel.menu_collapsed') == 0 ? 1 : $box_active)
];
}
}

View File

@@ -29,6 +29,7 @@ namespace Froxlor\UI\Panel;
use Froxlor\Idna\IdnaWrapper;
use Froxlor\Settings;
use Froxlor\System\Markdown;
use Parsedown;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
@@ -53,9 +54,9 @@ class FroxlorTwig extends AbstractExtension
$this,
'idnDecodeFilter'
]),
new TwigFilter('parsedown', [
new TwigFilter('markdown', [
$this,
'callParsedown'
'callMarkdown'
])
];
}
@@ -91,6 +92,10 @@ class FroxlorTwig extends AbstractExtension
new TwigFunction('mix', [
$this,
'getMix'
]),
new TwigFunction('vite', [
$this,
'getVite'
])
];
}
@@ -148,10 +153,9 @@ class FroxlorTwig extends AbstractExtension
return UI::getLinker()->getLink($linkopts);
}
public function callParsedown($string)
public function callMarkdown($string): string
{
$pd = new Parsedown();
return $pd->line($string);
return Markdown::cleanCustomNotes($string ?? "");
}
/**
@@ -167,4 +171,9 @@ class FroxlorTwig extends AbstractExtension
{
return mix($mix);
}
public function getVite($basehref = '', $vite = [], $defaults = [])
{
return vite($basehref, $vite ?? $defaults);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,95 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Settings;
return [
'backup_storage_add' => [
'title' => lng('backups.backup_storage_add'),
'image' => 'fa-solid fa-file-archive',
'self_overview' => ['section' => 'backups', 'page' => 'storages'],
'sections' => [
'section_a' => [
'title' => lng('backup.backup_storage_create'),
'fields' => [
'description' => [
'label' => lng('backup.backup_storage.description'),
'type' => 'text',
'maxlength' => 200,
'mandatory' => true,
],
'type' => [
'label' => lng('backup.backup_storage.type'),
'type' => 'select',
'selected' => 'local',
'select_var' => [
'local' => lng('backup.backup_storage.type_local'),
'ftp' => lng('backup.backup_storage.type_ftp'),
'sftp' => lng('backup.backup_storage.type_sftp'),
'rsync' => lng('backup.backup_storage.type_rsync'),
's3' => lng('backup.backup_storage.type_s3'),
],
'mandatory' => true,
],
'region' => [
'label' => lng('backup.backup_storage.region'),
'type' => 'text'
],
'bucket' => [
'label' => lng('backup.backup_storage.bucket'),
'type' => 'text'
],
'destination_path' => [
'label' => lng('backup.backup_storage.destination_path'),
'type' => 'text'
],
'hostname' => [
'label' => lng('backup.backup_storage.hostname'),
'type' => 'text'
],
'username' => [
'label' => lng('backup.backup_storage.username'),
'type' => 'text'
],
'password' => [
'label' => lng('backup.backup_storage.password'),
'type' => 'password',
'autocomplete' => 'off',
],
'pgp_public_key' => [
'label' => lng('backup.backup_storage.pgp_public_key'),
'type' => 'textarea',
'value' => Settings::Get('backup.default_pgp_public_key')
],
'retention' => [
'label' => lng('backup.backup_storage.retention'),
'type' => 'number',
'min' => 0,
]
]
]
]
],
];

View File

@@ -1,99 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
return [
'backup_storage_edit' => [
'title' => lng('backups.backup_storage_edit'),
'image' => 'fa-solid fa-file-archive',
'self_overview' => ['section' => 'backups', 'page' => 'storages'],
'sections' => [
'section_a' => [
'title' => lng('backup.backup_storage_edit'),
'fields' => [
'description' => [
'label' => lng('backup.backup_storage.description'),
'type' => 'text',
'value' => $result['description'],
'mandatory' => true,
],
'type' => [
'label' => lng('backup.backup_storage.type'),
'type' => 'select',
'selected' => $result['type'],
'select_var' => [
'local' => lng('backup.backup_storage.type_local'),
'ftp' => lng('backup.backup_storage.type_ftp'),
'sftp' => lng('backup.backup_storage.type_sftp'),
'rsync' => lng('backup.backup_storage.type_rsync'),
's3' => lng('backup.backup_storage.type_s3'),
],
'mandatory' => true,
],
'region' => [
'label' => lng('backup.backup_storage.region'),
'type' => 'text',
'value' => $result['region']
],
'bucket' => [
'label' => lng('backup.backup_storage.bucket'),
'type' => 'text',
'value' => $result['bucket']
],
'destination_path' => [
'label' => lng('backup.backup_storage.destination_path'),
'type' => 'text',
'value' => $result['destination_path']
],
'hostname' => [
'label' => lng('backup.backup_storage.hostname'),
'type' => 'text',
'value' => $result['hostname']
],
'username' => [
'label' => lng('backup.backup_storage.username'),
'type' => 'text',
'value' => $result['username']
],
'password' => [
'label' => lng('backup.backup_storage.password') . '&nbsp;(' . lng('panel.emptyfornochanges') . ')',
'type' => 'password',
'autocomplete' => 'off'
],
'pgp_public_key' => [
'label' => lng('backup.backup_storage.pgp_public_key'),
'type' => 'textarea',
'value' => $result['pgp_public_key']
],
'retention' => [
'label' => lng('backup.backup_storage.retention'),
'type' => 'number',
'min' => 0,
'value' => $result['retention']
]
]
]
]
],
];

View File

@@ -1,33 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @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' => 'storages'],
'sections' => []
],
];

View File

@@ -316,21 +316,6 @@ return [
'value' => '1',
'checked' => true
],
'backup' => [
'label' => lng('backup.backup_storage.title'),
'desc' => lng('backup.backup_storage.description'),
'type' => 'select',
'select_var' => $backup_storages,
'selected' => Settings::Get('backup.default_storage'),
'visible' => Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1'
],
'access_backups' => [
'label' => lng('backup.access_backups'),
'type' => 'checkbox',
'value' => '1',
'checked' => Settings::Get('backup.enabled') == '1' && Settings::Get('backup.default_customer_access'),
'visible' => Settings::Get('backup.enabled') == '1' && ($userinfo['change_serversettings'] == '1' || Settings::Get('backup.default_customer_access'))
],
]
]
]

View File

@@ -324,21 +324,6 @@ return [
'value' => '1',
'checked' => $result['logviewenabled']
],
'backup' => [
'label' => lng('backup.backup_storage.title'),
'desc' => lng('backup.backup_storage.description'),
'type' => 'select',
'select_var' => $backup_storages,
'selected' => $result['backup'],
'visible' => Settings::Get('backup.enabled') == '1' && $userinfo['change_serversettings'] == '1'
],
'access_backups' => [
'label' => lng('backup.access_backups'),
'type' => 'checkbox',
'value' => '1',
'checked' => $result['access_backups'],
'visible' => Settings::Get('backup.enabled') == '1' && ($userinfo['change_serversettings'] == '1' || Settings::Get('backup.default_customer_access'))
],
]
],
'section_d' => [

View File

@@ -24,10 +24,35 @@
*/
return [
'backups_edit' => [
'title' => lng('backups.backups_edit'),
'image' => 'fa-solid fa-file-archive',
'self_overview' => ['section' => 'backups', 'page' => 'admins'],
'sections' => []
],
'domain_duplicate' => [
'title' => lng('admin.domain_duplicate'),
'image' => 'fa-solid fa-globe',
'self_overview' => ['section' => 'domains', 'page' => 'domains'],
'id' => 'domain_add',
'sections' => [
'section_a' => [
'title' => lng('domains.domainsettings'),
'image' => 'icons/domain_add.png',
'fields' => [
'domain' => [
'label' => 'Domain',
'type' => 'text',
'mandatory' => true
],
'customerid' => [
'label' => lng('admin.customer'),
'type' => 'select',
'select_var' => $customers,
'selected' => $result['customerid'],
'mandatory' => true
],
]
]
],
'buttons' => [
[
'label' => lng('admin.domain_duplicate')
]
]
]
];

View File

@@ -74,6 +74,7 @@ function old(string $identifier, string $default = null, string $session = null)
* This file contains the hashed filenames of the assets.
* It must be always placed in the theme assets folder.
*
* @deprecated since 2.1.x no longer in use
* @param $filename
* @return mixed|string
*/
@@ -91,3 +92,45 @@ function mix($filename)
}
return $filename;
}
/**
* Loading the vite manifest file from given theme.
* This file contains the hashed filenames of the assets.
* It must be always placed in the theme assets folder.
*
* @param string|null $basehref
* @param array $filenames
* @return string
* @throws Exception
*/
function vite($basehref, array $filenames): string
{
// Get the hashed filenames from the manifest file
$links = [];
foreach ($filenames as $filename) {
if (preg_match("/templates\/([^\/]+)(.*)/", $filename, $matches)) {
$assetDirectory = '/templates/' . $matches[1] . '/build/';
$viteManifest = dirname(__DIR__) . $assetDirectory . '/manifest.json';
$manifest = json_decode(file_get_contents($viteManifest), true);
$links[] = $basehref . ltrim($assetDirectory, '/') . $manifest[$filename]['file'];
} else {
$links = $filenames;
}
}
// Update the links to the correct html tags
foreach ($links as $key => $link) {
switch (pathinfo($link, PATHINFO_EXTENSION)) {
case 'css':
$links[$key] = '<link rel="stylesheet" href="'. $link . '">';
break;
case 'js':
$links[$key] = '<script src="' . $link . '" type="module"></script>';
break;
default:
throw new Exception('Unknown file extension for file '. $link .' from manifest.json');
}
}
return implode("\n", $links);
}

View File

@@ -188,6 +188,10 @@ if (is_array($_themeoptions) && (!array_key_exists('variants', $_themeoptions) |
$themevariant = "default";
}
if (array_key_exists('global', $_themeoptions)) {
$_themeoptions['variants'][$themevariant] = array_merge_recursive($_themeoptions['variants'][$themevariant], $_themeoptions['global']);
}
// check for custom header-graphic
$hl_path = 'templates/' . $theme . '/assets/img';
@@ -209,8 +213,11 @@ if (Settings::Get('panel.logo_overridecustom') == 0 && file_exists($hl_path . '/
}
}
$color_scheme = $_themeoptions['variants'][$themevariant]['color-scheme'] ?? 'auto';
UI::twig()->addGlobal('header_logo_login', $header_logo_login);
UI::twig()->addGlobal('header_logo', $header_logo);
UI::twig()->addGlobal('color_scheme', $color_scheme);
/**
* Redirects to index.php (login page) if no session exists
@@ -274,29 +281,21 @@ if (AREA == 'admin' || AREA == 'customer') {
}
UI::twig()->addGlobal('nav_entries', $navigation);
$js = "";
$css = "";
if (is_array($_themeoptions) && array_key_exists('js', $_themeoptions['variants'][$themevariant])) {
if (is_array($_themeoptions['variants'][$themevariant]['js'])) {
foreach ($_themeoptions['variants'][$themevariant]['js'] as $jsfile) {
if (file_exists('templates/' . $theme . '/assets/js/' . $jsfile)) {
$js .= '<script type="text/javascript" src="' . mix('templates/' . $theme . '/assets/js/' . $jsfile) . '"></script>' . "\n";
}
}
}
if (is_array($_themeoptions['variants'][$themevariant]['css'])) {
foreach ($_themeoptions['variants'][$themevariant]['css'] as $cssfile) {
if (file_exists('templates/' . $theme . '/assets/css/' . $cssfile)) {
$css .= '<link href="' . mix('templates/' . $theme . '/assets/css/' . $cssfile) . '" rel="stylesheet" type="text/css" />' . "\n";
$theme_assets = [];
foreach (['css', 'js'] as $asset) {
if (is_array($_themeoptions) && array_key_exists($asset, $_themeoptions['variants'][$themevariant])) {
if (is_array($_themeoptions['variants'][$themevariant][$asset])) {
foreach ($_themeoptions['variants'][$themevariant][$asset] as $assetfile) {
if (file_exists('templates/' . $theme . '/' . $assetfile)) {
$theme_assets[] .= 'templates/' . $theme . '/' . $assetfile;
}
}
}
}
}
UI::twig()->addGlobal('theme_js', $js);
UI::twig()->addGlobal('theme_css', $css);
unset($js);
unset($css);
UI::twig()->addGlobal('theme_assets', $theme_assets);
unset($theme_assets);
$action = Request::any('action');
$page = Request::any('page', 'overview');

View File

@@ -38,7 +38,7 @@ return [
'url' => 'customer_email.php?page=emails',
'label' => lng('menue.email.emails'),
'required_resources' => 'emails',
'add_shortlink' => CurrentUser::canAddResource('emails') ? 'customer_email.php?page=emails&action=add' : null,
'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('emails') ? 'customer_email.php?page=email_domain&action=add' : null,
],
[
'url' => Settings::Get('panel.webmail_url'),
@@ -60,7 +60,7 @@ return [
'url' => 'customer_mysql.php?page=mysqls',
'label' => lng('menue.mysql.databases'),
'required_resources' => 'mysqls',
'add_shortlink' => CurrentUser::canAddResource('mysqls')? 'customer_mysql.php?page=mysqls&action=add' : null,
'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('mysqls')? 'customer_mysql.php?page=mysqls&action=add' : null,
],
[
'url' => Settings::Get('panel.phpmyadmin_url'),
@@ -81,7 +81,7 @@ return [
[
'url' => 'customer_domains.php?page=domains',
'label' => lng('menue.domains.settings'),
'add_shortlink' => CurrentUser::canAddResource('subdomains') ? 'customer_domains.php?page=domains&action=add' : null,
'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('subdomains') ? 'customer_domains.php?page=domains&action=add' : null,
],
[
'url' => 'customer_domains.php?page=sslcertificates',
@@ -98,7 +98,7 @@ return [
[
'url' => 'customer_ftp.php?page=accounts',
'label' => lng('menue.ftp.accounts'),
'add_shortlink' => CurrentUser::canAddResource('ftps') ? 'customer_ftp.php?page=accounts&action=add' : null,
'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('ftps') ? 'customer_ftp.php?page=accounts&action=add' : null,
],
[
'url' => Settings::Get('panel.webftp_url'),
@@ -261,11 +261,6 @@ return [
'label' => lng('admin.cron.cronsettings'),
'required_resources' => 'change_serversettings'
],
[
'url' => 'admin_backups.php?page=overview',
'label' => lng('admin.backups.backups'),
'show_element' => (Settings::Get('backup.enabled') == true)
],
[
'url' => 'admin_logger.php?page=log',
'label' => lng('menue.logger.logger'),

View File

@@ -1,123 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @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\PHPConf;
use Froxlor\UI\Callbacks\ProgressBar;
use Froxlor\UI\Callbacks\Style;
use Froxlor\UI\Callbacks\Text;
use Froxlor\UI\Listing;
return [
'backup_storages_list' => [
'title' => lng('backup.backup_storages.list'),
'icon' => 'fa-solid fa-file-archive',
'self_overview' => ['section' => 'backups', 'page' => 'storages'],
'default_sorting' => ['description' => 'asc'],
'columns' => [
'id' => [
'label' => 'ID',
'field' => 'id',
'sortable' => true,
],
'description' => [
'label' => lng('description'),
'field' => 'description',
'sortable' => true,
],
'type' => [
'label' => lng('type'),
'field' => 'type',
'sortable' => true,
],
'region' => [
'label' => lng('region'),
'field' => 'region',
'sortable' => true,
],
'bucket' => [
'label' => lng('bucket'),
'field' => 'bucket',
'sortable' => true,
],
'destination_path' => [
'label' => lng('destination_path'),
'field' => 'destination_path',
'sortable' => true,
],
'hostname' => [
'label' => lng('hostname'),
'field' => 'hostname',
'sortable' => true,
],
'username' => [
'label' => lng('username'),
'field' => 'username',
'sortable' => true,
],
'retention' => [
'label' => lng('retention'),
'field' => 'retention',
'sortable' => true,
],
],
'visible_columns' => Listing::getVisibleColumnsForListing('backup_storages_list', [
'id',
'description',
'type',
'retention',
]),
'actions' => [
'show' => [
'icon' => 'fa-solid fa-eye',
'title' => lng('usersettings.custom_notes.title'),
],
'edit' => [
'icon' => 'fa-solid fa-edit',
'title' => lng('panel.edit'),
'href' => [
'section' => 'backups',
'page' => 'storages',
'action' => 'edit',
'id' => ':id'
],
],
'delete' => [
'icon' => 'fa-solid fa-trash',
'title' => lng('panel.delete'),
'class' => 'btn-danger',
'href' => [
'section' => 'backups',
'page' => 'storages',
'action' => 'delete',
'id' => ':id'
],
'visible' => [PHPConf::class, 'isNotDefault']
],
],
]
];

View File

@@ -1,114 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\UI\Callbacks\Admin;
use Froxlor\UI\Callbacks\Backup;
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'],
],
'size' => [
'label' => lng('backup.size'),
'field' => 'size',
'sortable' => true,
'callback' => [Text::class, 'size'],
],
'storage_id' => [
'label' => lng('backup.backup_storage.title'),
'field' => 'storage_id',
'class' => 'text-center',
'callback' => [Backup::class, 'backupStorageLink'],
],
'filename' => [
'label' => lng('backup.size'),
'field' => 'filename',
'sortable' => true,
],
'created_at' => [
'label' => lng('backup.created_at'),
'field' => 'created_at',
'sortable' => true,
'callback' => [Text::class, 'timestamp'],
],
],
'visible_columns' => Listing::getVisibleColumnsForListing('backups_list', [
'id',
'adminname',
'loginname',
'size',
'created_at',
]),
'actions' => [
'delete' => [
'icon' => 'fa-solid fa-trash',
'title' => lng('panel.delete'),
'class' => 'btn-danger',
'href' => [
'section' => 'backups',
'page' => 'storages',
'action' => 'delete',
'id' => ':id'
],
'visible' => [Admin::class, 'canChangeServerSettings'],
],
]
]
];

View File

@@ -23,13 +23,11 @@
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
use Froxlor\Settings;
use Froxlor\UI\Callbacks\Backup;
use Froxlor\UI\Callbacks\Customer;
use Froxlor\UI\Callbacks\Impersonate;
use Froxlor\UI\Callbacks\ProgressBar;
use Froxlor\UI\Callbacks\Text;
use Froxlor\UI\Callbacks\Style;
use Froxlor\UI\Callbacks\Text;
use Froxlor\UI\Listing;
return [
@@ -151,20 +149,6 @@ return [
'class' => 'text-center',
'callback' => [Text::class, 'boolean'],
],
'c.backup' => [
'label' => lng('backup.backup_storage.title'),
'field' => 'backup',
'class' => 'text-center',
'callback' => [Backup::class, 'backupStorageLink'],
'visible' => (bool)Settings::Get('backup.enabled'),
],
'c.access_backups' => [
'label' => lng('backup.access_backups'),
'field' => 'access_backups',
'class' => 'text-center',
'callback' => [Text::class, 'boolean'],
'visible' => (bool)Settings::Get('backup.enabled'),
],
],
'visible_columns' => Listing::getVisibleColumnsForListing('customer_list', [
'c.name',

View File

@@ -50,6 +50,7 @@ return [
'label' => lng('domains.domainname'),
'field' => 'domain_ace',
'isdefaultsearchfield' => true,
'callback' => [Domain::class, 'domainLink'],
],
'ipsandports' => [
'label' => lng('admin.ipsandports.ipsandports'),

View File

@@ -89,6 +89,9 @@ return [
'action' => 'delete',
'id' => ':id'
],
// Let's Encrypt certificates can be removed 'correctly'
// by disabling let's encrypt for the domain
'visible' => [SSLCertificate::class, 'isNotLetsEncrypt']
],
]
]

View File

@@ -32,8 +32,6 @@ 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_BACKUP_STORAGES = 'panel_backup_storages';
const TABLE_PANEL_CUSTOMERS = 'panel_customers';
const TABLE_PANEL_DATABASES = 'panel_databases';
const TABLE_PANEL_DOMAINS = 'panel_domains';

2406
lng/ca.lng.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -922,6 +922,7 @@ return [
'domain_nopunycode' => 'Die Eingabe von Punycode (IDNA) ist nicht notwendig. Die Domain wird automatisch konvertiert.',
'dns_record_toolong' => 'Records/Labels können maximal 63 Zeichen lang sein',
'noipportgiven' => 'Keine IP/Port angegeben',
'nosslippportgiven' => 'Wenn SSL aktiviert ist, muss eine SSL IP/Port angegeben werden',
'jsonextensionnotfound' => 'Diese Funktion benötigt die PHP json-Erweiterung.',
'cannotdeletesuperadmin' => 'Der erste Administrator kann nicht gelöscht werden.',
'no_wwwcnamae_ifwwwalias' => 'Es kann kein CNAME Eintrag für "www" angelegt werden, da die Domain einen www-Alias aktiviert hat. Ändere diese Einstellung auf "Kein Alias" oder "Wildcard Alias"',
@@ -2096,6 +2097,10 @@ Vielen Dank, Ihr Administrator',
'description' => 'Zeit in Sekunden für die maximale Anzahl von HTTP-Anfragen, Standard ist "60".',
],
'option_requires_otp' => 'Das Ändern dieser Einstellung erfordert OTP Validierung',
'panel_menu_collapsed' => [
'title' => 'Menüabschnitte einklappen',
'description' => 'Bei Deaktivierung werden die Menübereiche auf der linken Seite immer aufgeklappt angezeigt.',
],
],
'spf' => [
'use_spf' => 'Aktiviere SPF für Domains?',
@@ -2208,7 +2213,7 @@ Vielen Dank, Ihr Administrator',
'usersettings' => [
'custom_notes' => [
'title' => 'Eigene Notizen',
'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.',
'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.<br>Markdown ist unterstützt, HTML wird entfernt.',
'show' => 'Zeige die Notizen auf dem Dashboard des Benutzers',
],
'api_allowed' => [

View File

@@ -34,6 +34,7 @@ return [
'pt' => 'Portuguese',
'se' => 'Swedish',
'es' => 'Spanish',
'ca' => 'Catalan',
],
'2fa' => [
'2fa' => '2FA options',
@@ -993,6 +994,7 @@ return [
'domain_nopunycode' => 'You must not specify punycode (IDNA). The domain will automatically be converted',
'dns_record_toolong' => 'Records/labels can only be up to 63 characters',
'noipportgiven' => 'No IP/port given',
'nosslippportgiven' => 'When enabling SSL you need to select a SSL IP/port',
'jsonextensionnotfound' => 'This feature requires the php json-extension.',
'cannotdeletesuperadmin' => 'The first admin cannot be deleted.',
'no_wwwcnamae_ifwwwalias' => 'Cannot set CNAME record for "www" as domain is set to generate a www-alias. Please change settings to either "No alias" or "Wildcard alias"',
@@ -1277,7 +1279,7 @@ Yours sincerely, your administrator',
'reset' => 'Discard changes',
'pathDescription' => 'If the directory doesn\'t exist, it will be created automatically.',
'pathDescriptionEx' => '<br /><br /><span class="text-danger">Please note:</span> The path <code>/</code> is not allowed due to administrative settings, it will automatically be set to <code>/chosen.subdomain.tld/</code> if not set to another directory.',
'pathDescriptionSubdomain' => 'If the directory doesn\'t exist, it will be created automatically.<br /><br />If you want a redirect to another domain than this entry has to start with http:// or https://.<br /><br />If the URL ends with / it is considered a folder, if not, it is treated as file.',
'pathDescriptionSubdomain' => 'If the directory doesn\'t exist, it will be created automatically.<br /><br />If you want a redirect to another domain then this entry has to start with http:// or https://.<br /><br />If the URL ends with / it is considered a folder, if not, it is treated as file.',
'back' => 'Back',
'reseller' => 'reseller',
'admin' => 'admin',
@@ -2210,7 +2212,7 @@ Yours sincerely, your administrator',
'toolselect' => 'Traffic analyzer',
'webalizer' => 'Webalizer',
'awstats' => 'AWStats',
'goaccess' => 'goacccess'
'goaccess' => 'goaccess'
],
'requires_reconfiguration' => 'Changing this settings might require a reconfiguration of the following services:<br><strong>%s</strong>',
'req_limit_per_interval' => [
@@ -2222,6 +2224,10 @@ Yours sincerely, your administrator',
'description' => 'Specify the time in seconds for the number of HTTP requests, default is "60"',
],
'option_requires_otp' => 'This setting requires an OTP validation',
'panel_menu_collapsed' => [
'title' => 'Collapse menu-sections',
'description' => 'If deactivated, the left-side menu sections will always be expanded.',
],
],
'spf' => [
'use_spf' => 'Activate SPF for domains?',
@@ -2341,7 +2347,7 @@ Yours sincerely, your administrator',
'usersettings' => [
'custom_notes' => [
'title' => 'Custom notes',
'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.',
'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.<br>Markdown is supported, HTML will be removed.',
'show' => 'Show your notes on the dashboard of the user',
],
'api_allowed' => [
@@ -2351,7 +2357,7 @@ Yours sincerely, your administrator',
],
],
'install' => [
'slogal' => 'froxlor Server Management Panel',
'slogan' => 'froxlor Server Management Panel',
'preflight' => 'System check',
'critical_error' => 'Critical error',
'suggestions' => 'Not required but recommended',
@@ -2430,9 +2436,4 @@ 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',
],
];

View File

@@ -2176,7 +2176,7 @@ Atentamente, su administrador'
'toolselect' => 'Analizador de tráfico',
'webalizer' => 'Webalizer',
'awstats' => 'AWStats',
'goaccess' => 'goacccess'
'goaccess' => 'goaccess'
],
'requires_reconfiguration' => 'El cambio de esta configuración podría requerir una reconfiguración de los siguientes servicios:<br/><strong>%s</strong>'
],
@@ -2304,7 +2304,7 @@ Atentamente, su administrador'
]
],
'install' => [
'slogal' => 'Panel de gestión del servidor froxlor',
'slogan' => 'Panel de gestión del servidor froxlor',
'preflight' => 'Comprobación del sistema',
'critical_error' => 'Error crítico',
'suggestions' => 'No requerido pero recomendado',

9148
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,19 +2,24 @@
"name": "froxlor",
"private": true,
"scripts": {
"dev": "mix watch",
"build": "mix --production"
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"@fortawesome/fontawesome-free": "^6.2.0",
"@popperjs/core": "^2.11.6",
"bootstrap": "^5.2.1",
"chart.js": "^3.9.1",
"@fortawesome/fontawesome-free": "^6.4.2",
"@popperjs/core": "^2.11.8",
"@vitejs/plugin-vue": "^4.0.0",
"axios": "^1.1.2",
"bootstrap": "^5.3.2",
"chart.js": "^4.4.0",
"jquery": "^3.6.1",
"jquery-validation": "^1.19.5",
"laravel-mix": "^6.0.42",
"jquery-validation": "^1.20.0",
"laravel-vite-plugin": "^0.8.0",
"lodash": "^4.17.19",
"postcss": "^8.1.14",
"resolve-url-loader": "^5.0.0",
"sass": "^1.49.7",
"sass-loader": "^12.5.0"
"sass": "^1.69.3",
"vite": "^4.0.0",
"vue": "^3.2.37"
}
}

View File

@@ -0,0 +1,14 @@
import '@fortawesome/fontawesome-free';
import './bootstrap';
// Vue
import {createApp} from 'vue';
const app = createApp({});
// Load jquery components
Object.entries(import.meta.glob('./jquery/*.js', {eager: true})).forEach(([path, definition]) => {
definition.default();
});
app.mount('#app');

View File

@@ -0,0 +1,20 @@
import _ from 'lodash';
window._ = _;
// jQuery
import jQuery from 'jquery';
window.$ = jQuery;
import 'jquery-validation';
// Bootstrap
import * as bootstrap from 'bootstrap';
window.bootstrap = bootstrap;
// ChartJS
import Chart from 'chart.js/auto';
window.Chart = Chart;
// Axios
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

View File

@@ -0,0 +1,68 @@
export default function () {
$(function () {
var timer, delay = 500;
$('div[data-action="apikeys"] #allowed_from').on('keyup change', function () {
var _this = $(this);
clearTimeout(timer);
timer = setTimeout(function () {
var akid = _this.closest('div[data-action="apikeys"]').data('entry');
$.ajax({
url: "lib/ajax.php?action=editapikey",
type: "POST",
dataType: "json",
data: {
id: akid,
allowed_from: _this.val(),
valid_until: $('div[data-entry="' + akid + '"] #valid_until').val()
},
success: function (data) {
if (data.message) {
_this.removeClass('is-valid');
_this.addClass('is-invalid');
} else {
_this.removeClass('is-invalid');
_this.addClass('is-valid');
_this.val(data.allowed_from);
}
},
error: function (request, status, error) {
_this.removeClass('is-valid');
_this.addClass('is-invalid');
}
});
}, delay);
});
$('div[data-action="apikeys"] #valid_until').on('keyup change', function () {
var _this = $(this);
clearTimeout(timer);
timer = setTimeout(function () {
var akid = _this.closest('div[data-action="apikeys"]').data('entry');
$.ajax({
url: "lib/ajax.php?action=editapikey",
type: "POST",
dataType: "json",
data: {
id: akid,
valid_until: _this.val(),
allowed_from: $('div[data-entry="' + akid + '"] #allowed_from').val()
},
success: function (data) {
if (data.message) {
_this.removeClass('is-valid');
_this.addClass('is-invalid');
} else {
_this.removeClass('is-invalid');
_this.addClass('is-valid');
_this.val(data.valid_until);
}
},
error: function (request, status, error) {
_this.removeClass('is-valid');
_this.addClass('is-invalid');
}
});
}, delay);
});
});
}

View File

@@ -0,0 +1,54 @@
export default function () {
$(function () {
/*
* config files - select all recommended
*/
$('#selectRecommendedConfig').on('click', function () {
$('input[data-recommended]').each(function () {
if ($(this).data('recommended') == 1) {
$(this).prop('checked', true);
} else {
$(this).prop('checked', false);
}
})
});
/*
* export/download JSON file (e.g. for usage with config-services)
*/
$('#downloadSelectionAsJson').on('click', function () {
var formData = $(this).closest('form').serialize();
window.location = "lib/ajax.php?action=getConfigJsonExport&" + formData;
});
/*
* open modal window to show selected config-commands/files
* for selected daemon
*/
$('.show-config').on('click', function () {
const distro = $(this).data('dist');
const section = $(this).data('section');
const daemon = $(this).data('daemon');
$.ajax({
url: "lib/ajax.php?action=getConfigDetails",
type: "POST",
dataType: "json",
data: {distro: distro, section: section, daemon: daemon},
success: function (data) {
$('#configTplShowLabel').html(data.title);
$('#configTplShow .modal-body').html(data.content);
const myModal = new bootstrap.Modal(document.getElementById('configTplShow'));
myModal.show();
},
error: function (request, status, error) {
$('#configTplShowLabel').html('Error');
$('#configTplShow .modal-body').html('<div class="alert alert-danger" role="alert">' + request.responseJSON.message + '</div>');
const myModal = new bootstrap.Modal(document.getElementById('configTplShow'));
myModal.show();
}
});
});
});
}

View File

@@ -0,0 +1,77 @@
export default function () {
$(function () {
// Make inputs with enabled unlimited checked disabled
$("input[name$='_ul']").each(function () {
var fieldname = $(this).attr("name").substring(0, $(this).attr("name").length - 3);
$("input[name='" + fieldname + "']").prop({
readonly: $(this).is(":checked"),
required: !$(this).is(":checked")
});
});
// change state when unlimited checkboxes are clicked
$("input[name$='_ul']").on('change', function () {
var fieldname = $(this).attr("name").substring(0, $(this).attr("name").length - 3);
$("input[name='" + fieldname + "']").prop({
readonly: $(this).is(":checked"),
required: !$(this).is(":checked")
});
if (!$(this).is(":checked")) {
$("input[name='" + fieldname + "']").focus()
}
});
// set values from hosting plan when adding/editing a customer according to the plan's values
$('#use_plan').on('change', function () {
var pid = $(this).val();
if (pid > 0) {
$.ajax({
url: "admin_plans.php?page=overview&action=jqGetPlanValues",
type: "POST",
data: {
planid: pid
},
dataType: "json",
success: function (json) {
for (var i in json) {
if (i == 'email_imap' || i == 'email_pop3' || i == 'perlenabled' || i == 'phpenabled' || i == 'dnsenabled' || i == 'logviewenabled') {
/** handle checkboxes **/
if (json[i] == 1) {
$("input[name='" + i + "']").prop('checked', true);
} else {
$("input[name='" + i + "']").prop('checked', false);
}
} else if (i == 'allowed_phpconfigs') {
/** handle array of values **/
$("input[name='allowed_phpconfigs[]']").each(function (index) {
$(this).prop('checked', false);
for (var j in json[i]) {
if ($(this).val() == json[i][j]) {
$(this).prop('checked', true);
break;
}
}
});
} else if (json[i] == -1) {
/** handle unlimited checkboxes **/
$("input[name='" + i + "_ul']").attr('checked', 'checked');
$("input[name='" + i + "']").prop({
readonly: true
});
} else {
/** handle normal value **/
$("input[name='" + i + "']").val(json[i]);
$("input[name='" + i + "']").prop({
readonly: false
});
$("input[name='" + i + "_ul']").prop('checked', false);
}
}
},
error: function (a, b) {
console.log(a, b);
}
});
}
});
});
}

View File

@@ -0,0 +1,20 @@
export default function () {
$(function () {
// Display helptext to content box according to dns-record type selected
$("select[name='dns_type']").on('change', function () {
var selVal = $(this).val();
$.ajax({
url: "lib/ajax.php?action=loadLanguageString",
type: "POST",
dataType: "json",
data: {langid: 'dnseditor.notes.' + selVal},
success: function (data) {
$("#dns_content").next().html(data);
},
error: function (request, status, error) {
console.log(request, status, error)
}
});
});
});
}

View File

@@ -0,0 +1,111 @@
export default function () {
$(function () {
/*
* domains
*/
// disable unusable php-configuration by customer settings
$('#customerid').on('change', function () {
var cid = $(this).val();
$.ajax({
url: "admin_domains.php?page=domains&action=jqGetCustomerPHPConfigs",
type: "POST",
data: {
customerid: cid
},
dataType: "json",
success: function (json) {
if (json.length > 0) {
$('#phpsettingid option').each(function () {
var pid = $(this).val();
$(this).attr("disabled", "disabled");
for (var i in json) {
if (pid == json[i]) {
$(this).removeAttr("disabled");
}
}
});
}
},
error: function (a, b) {
console.log(a, b);
}
});
});
// show warning if speciallogfile option is toggled
if ($('input[name=speciallogverified]')) {
$('input[name=speciallogfile]').on('click', function () {
$('#speciallogfilenote').remove();
$('#speciallogfile').removeClass('is-invalid');
$('#speciallogverified').val(0);
$.ajax({
url: window.location.pathname.substring(1) + "?page=overview&action=jqSpeciallogfileNote",
type: "POST",
data: {
id: $('input[name=id]').val(), newval: +$('#speciallogfile').is(':checked')
},
dataType: "json",
success: function (json) {
if (json.changed) {
$('#speciallogfile').addClass('is-invalid');
$('#speciallogfile').parent().append(json.info);
$('#speciallogverified').val(1);
}
},
error: function (a, b) {
console.log(a, b);
}
});
});
}
/**
* email only domain - hide unnecessary/unused sections
*/
if ($('#id') && $('#email_only').is(':checked')) {
$('#section_b').hide();
$('#section_bssl').hide();
$('#section_c').hide();
$('#section_d').hide();
}
/**
* toggle show/hide of sections in case of email only flag
*/
$('#email_only').on('click', function () {
if ($(this).is(':checked')) {
// hide unnecessary sections
$('#section_b').hide();
$('#section_bssl').hide();
$('#section_c').hide();
$('#section_d').hide();
} else {
// show sections
$('#section_b').show();
$('#section_bssl').show();
$('#section_c').show();
$('#section_d').show();
}
})
/**
* ssl enabled domain - hide unnecessary/unused sections
*/
if ($('#id') && !$('#sslenabled').is(':checked')) {
$('#section_bssl>.formfields>.row').not(":first").addClass("d-none");
}
/**
* toggle show/hide of sections in case of ssl enabled flag
*/
$('#sslenabled').on('click', function () {
if ($(this).is(':checked')) {
// show sections
$('#section_bssl>.formfields>.row').removeClass("d-none");
} else {
// hide unnecessary sections
$('#section_bssl>.formfields>.row').not(":first").addClass("d-none");
}
})
});
}

Some files were not shown because too many files have changed in this diff Show More