Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
775d50306c | ||
|
|
3821144c3b | ||
|
|
a1da70c221 | ||
|
|
bb2db0fed0 | ||
|
|
9680f24640 | ||
|
|
c732fbd81b | ||
|
|
7980b8d14d | ||
|
|
13e88f5b47 | ||
|
|
031596301b | ||
|
|
b34ab45746 | ||
|
|
dbf83c6f24 | ||
|
|
4cb974839c | ||
|
|
1fa714ef2c | ||
|
|
63bbcd4e00 | ||
|
|
49d67d7c27 | ||
|
|
7cc4c9fedb | ||
|
|
afd110a6ed | ||
|
|
7cdf6c8d64 | ||
|
|
60621da243 | ||
|
|
96ccdda304 | ||
|
|
4073984fd7 | ||
|
|
ea31c8a64d | ||
|
|
832ee07e0e | ||
|
|
b542b140c6 | ||
|
|
ac89fc7120 | ||
|
|
150858485d | ||
|
|
e7810e2066 | ||
|
|
4879446567 | ||
|
|
43eff78088 | ||
|
|
55a2ae3801 | ||
|
|
a3b0332d13 | ||
|
|
4b1846883d | ||
|
|
778fd3ba65 | ||
|
|
00456a35e5 | ||
|
|
5958f0516b | ||
|
|
166ffedf04 | ||
|
|
36dfee1263 | ||
|
|
ec0026ecfd | ||
|
|
a721bb3f21 | ||
|
|
83de3dd719 | ||
|
|
5615decd96 | ||
|
|
0348b1ec7e | ||
|
|
1467dab58f | ||
|
|
3a8f48de35 | ||
|
|
46391c06ec | ||
|
|
7103f7dd51 | ||
|
|
9fc1dfee41 | ||
|
|
82dc76fdc6 | ||
|
|
02ae52e3df | ||
|
|
5c06683e27 | ||
|
|
2684372156 | ||
|
|
d80c6d5714 | ||
|
|
1ae5311b81 | ||
|
|
e1e7555cce | ||
|
|
4f79d7cf4b | ||
|
|
b13b1e8ac7 | ||
|
|
6a1e7cc539 | ||
|
|
2e87633ef7 | ||
|
|
8a23d0b72c | ||
|
|
735ef85088 | ||
|
|
75cf44a6d2 | ||
|
|
7e0073f4a3 | ||
|
|
c9291df345 | ||
|
|
fd5e97d48c | ||
|
|
64a9fb163a | ||
|
|
b0256ffb7d | ||
|
|
e606bdc97f | ||
|
|
b53b3a924a | ||
|
|
539ea7c8fc | ||
|
|
5e8763e160 | ||
|
|
d52f33a50c | ||
|
|
287ad84b18 | ||
|
|
3f1b792f60 | ||
|
|
d94317421d | ||
|
|
7717a82d5c | ||
|
|
ace1651ceb | ||
|
|
1f74bf059c | ||
|
|
c98e912fc5 | ||
|
|
d04a8e7bbf | ||
|
|
d4a940b723 | ||
|
|
0dd20bc29a | ||
|
|
f71ee9f1f2 | ||
|
|
dd61302445 | ||
|
|
0bee1f03de | ||
|
|
a59aaa3dc9 | ||
|
|
1debe9d939 | ||
|
|
3d2e81b457 | ||
|
|
ac759cd9a4 | ||
|
|
05c77929e4 | ||
|
|
cefd9226bd | ||
|
|
762f295d3d | ||
|
|
d3e6063027 | ||
|
|
f18c14e119 | ||
|
|
77bcd10729 | ||
|
|
6ee990af0a | ||
|
|
a3fe37b69b | ||
|
|
56388ede54 | ||
|
|
b98035bf3a | ||
|
|
95abe465ef | ||
|
|
780f607332 | ||
|
|
a11d26522a | ||
|
|
462a798cb6 | ||
|
|
7556685881 | ||
|
|
965e2dfd95 | ||
|
|
1f2cce6195 | ||
|
|
f4f84aa397 | ||
|
|
0f37dfb1eb | ||
|
|
7438786a24 | ||
|
|
041c2d176c | ||
|
|
597e765677 | ||
|
|
f757233d61 | ||
|
|
cfae3540fc | ||
|
|
9e8f32f1e8 | ||
|
|
a7b66227e6 | ||
|
|
532982784f | ||
|
|
0754be3028 | ||
|
|
166ec0575b | ||
|
|
e8ed43056c | ||
|
|
a808a3f782 | ||
|
|
686065c294 | ||
|
|
41ac713325 | ||
|
|
d1cb32b47f | ||
|
|
13b6ab0b07 | ||
|
|
215e749ba8 | ||
|
|
0b7d2358ed | ||
|
|
f3c965fe53 | ||
|
|
5b58ab4371 | ||
|
|
3ad203535a | ||
|
|
6edc6553bd | ||
|
|
3fc18f9903 | ||
|
|
506cccd7c8 | ||
|
|
6ad1ca2ba9 | ||
|
|
6d9014c29b | ||
|
|
7e168f5a0e | ||
|
|
4fcf0606c7 | ||
|
|
9d2077ddee | ||
|
|
10555bff76 | ||
|
|
338b855947 | ||
|
|
5d04b8c829 | ||
|
|
37aa7af4da | ||
|
|
4b75369597 | ||
|
|
9d0e463906 | ||
|
|
a0406932c3 | ||
|
|
a7198f58ce | ||
|
|
47be4b2847 | ||
|
|
b0fae4bd14 | ||
|
|
4711a41436 | ||
|
|
faa71ceaef | ||
|
|
2d30394150 | ||
|
|
99c1182af8 | ||
|
|
d9abe58dd2 | ||
|
|
23034b8ad2 | ||
|
|
1cae5638d3 | ||
|
|
ce9a5f97a3 | ||
|
|
c38b90deef | ||
|
|
13daa7d6fa | ||
|
|
b0e43d332d | ||
|
|
75c8754fb4 |
40
.drone.yml
40
.drone.yml
@@ -1,40 +0,0 @@
|
||||
kind: pipeline
|
||||
name: deploy-froxlor
|
||||
type: docker
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- upgrade-2
|
||||
event:
|
||||
include:
|
||||
- push
|
||||
|
||||
steps:
|
||||
- name: deploy
|
||||
image: cr.wks/drone/drone-rsync:latest
|
||||
settings:
|
||||
hosts: ["rechner02.maketank.net"]
|
||||
source: ./
|
||||
target: ~/froxlor-test
|
||||
user: www-data
|
||||
exclude: ['vendor', '.git*', '*drone.yml', '.settings', '.buildpath', '.editorconfig', '.project', '.travis.yml']
|
||||
args: '-v --delete'
|
||||
log_level: quiet
|
||||
key:
|
||||
from_secret: ssh-www-data-maketank-rsa
|
||||
command_timeout: 10m
|
||||
- name: compose-install
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host:
|
||||
- rechner02.maketank.net
|
||||
username: www-data
|
||||
key:
|
||||
from_secret: ssh-www-data-maketank-rsa
|
||||
script:
|
||||
- cd ~/froxlor-test && composer install --no-dev
|
||||
|
||||
5
.github/workflows/build-docs.yml
vendored
5
.github/workflows/build-docs.yml
vendored
@@ -2,7 +2,8 @@ name: build-documentation
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
# only run for stable releases
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
build_docs:
|
||||
@@ -11,4 +12,4 @@ jobs:
|
||||
- env:
|
||||
GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh workflow run --repo Froxlor/Documentation build-and-deploy.yml -f type=tags ref=${{github.ref_name}}
|
||||
gh workflow run --repo Froxlor/Documentation build-and-deploy.yml -f type=tags -f ref=${{github.ref_name}}
|
||||
|
||||
112
.github/workflows/build-mariadb.yml
vendored
112
.github/workflows/build-mariadb.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Froxlor-CI-MariaDB
|
||||
on: ['push', 'pull_request', 'create']
|
||||
on: [ 'push', 'pull_request', 'create' ]
|
||||
|
||||
jobs:
|
||||
froxlor:
|
||||
@@ -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
|
||||
@@ -49,33 +49,81 @@ jobs:
|
||||
- name: Run testing
|
||||
run: ant quick-build
|
||||
|
||||
# - name: irc push
|
||||
# uses: rectalogic/notify-irc@v1
|
||||
# if: github.event_name == 'push'
|
||||
# with:
|
||||
# channel: "#froxlor"
|
||||
# server: "irc.libera.chat"
|
||||
# nickname: froxlor-ci
|
||||
# message: |
|
||||
# ${{ github.actor }} pushed ${{ github.event.ref }} ${{ github.event.compare }}
|
||||
# ${{ join(github.event.commits.*.message) }}
|
||||
nightly:
|
||||
name: Create nightly/testing tarball
|
||||
runs-on: ubuntu-latest
|
||||
needs: froxlor
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
|
||||
# - name: irc pull request
|
||||
# uses: rectalogic/notify-irc@v1
|
||||
# if: github.event_name == 'pull_request'
|
||||
# with:
|
||||
# channel: "#froxlor"
|
||||
# server: "irc.libera.chat"
|
||||
# nickname: froxlor-ci
|
||||
# message: |
|
||||
# ${{ github.actor }} opened PR ${{ github.event.pull_request.html_url }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# - name: irc tag created
|
||||
# uses: rectalogic/notify-irc@v1
|
||||
# if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
# with:
|
||||
# channel: "#froxlor"
|
||||
# server: "irc.libera.chat"
|
||||
# nickname: froxlor-ci
|
||||
# message: |
|
||||
# ${{ github.actor }} tagged ${{ github.repository }} ${{ github.event.ref }}
|
||||
- name: Setup PHP with PECL extension
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer:v2
|
||||
extensions: mbstring, xml, ctype, pdo_mysql, mysql, curl, json, zip, session, filter, posix, openssl, fileinfo, bcmath, gmp, gnupg
|
||||
|
||||
- name: Install composer dependencies
|
||||
run: composer install --no-dev
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build assets
|
||||
run: npm run build
|
||||
working-directory: .
|
||||
|
||||
- name: Setting file/directory permissions
|
||||
run: |
|
||||
find -exec chmod ugo+r,u+w,go-w {} \;
|
||||
find -type f -exec chmod ugo-x {} \;
|
||||
find -type d -exec chmod ugo+x {} \;
|
||||
chmod 0755 bin/froxlor-cli
|
||||
|
||||
- name: Remove vcs and unneeded files
|
||||
run: |
|
||||
rm .gitignore
|
||||
rm .editorconfig
|
||||
rm -rf node_modules
|
||||
rm composer.json
|
||||
rm composer.lock
|
||||
rm package.json
|
||||
rm package-lock.json
|
||||
rm *.xml
|
||||
rm vite.config.js
|
||||
|
||||
- name: Create empty index.html in built assets directory
|
||||
run: |
|
||||
touch templates/Froxlor/build/index.html
|
||||
touch templates/Froxlor/build/assets/index.html
|
||||
|
||||
- name: Set outputs
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set nightly branding
|
||||
run: |
|
||||
sed -i "s/const BRANDING = '';/const BRANDING = '+nightly.${{steps.vars.outputs.sha_short}}';/" lib/Froxlor/Froxlor.php
|
||||
zip -r froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip . -x "*.git*"
|
||||
sha256sum froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip > froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip.sha256
|
||||
mkdir dist
|
||||
mv froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip dist/
|
||||
mv froxlor-nightly.${{steps.vars.outputs.sha_short}}.zip.sha256 dist/
|
||||
|
||||
- name: Deploy nightly to server
|
||||
uses: easingthemes/ssh-deploy@v3.4.3
|
||||
env:
|
||||
ARGS: "-rltDzvO --chown=${{ secrets.WEB_USER }}:${{ secrets.WEB_USER }}"
|
||||
SOURCE: "dist/"
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
|
||||
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.REMOTE_USER }}
|
||||
TARGET: "${{ secrets.REMOTE_TARGET }}"
|
||||
|
||||
4
.github/workflows/build-mysql.yml
vendored
4
.github/workflows/build-mysql.yml
vendored
@@ -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
5
.gitignore
vendored
@@ -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/
|
||||
|
||||
10
README.md
10
README.md
@@ -34,19 +34,13 @@ You may find help in the following places:
|
||||
|
||||
The froxlor community discord server can be found here: https://discord.froxlor.org
|
||||
|
||||
### IRC
|
||||
|
||||
froxlor may be found on libera.chat, channel #froxlor:
|
||||
irc://irc.libera.chat/froxlor
|
||||
|
||||
### Forum
|
||||
|
||||
The community is located on https://forum.froxlor.org/
|
||||
|
||||
### Wiki
|
||||
### Documentation
|
||||
|
||||
More documentation may be found in the froxlor - documentation:
|
||||
https://docs.froxlor.org/
|
||||
The documentation may be found at https://docs.froxlor.org/
|
||||
|
||||
## License
|
||||
|
||||
|
||||
14
SECURITY.md
14
SECURITY.md
@@ -10,9 +10,11 @@ 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.2.x** (`main` git-branch)
|
||||
- ️✅ **2.1.x** (`v2.1` git-branch)
|
||||
- ❌ 2.0.x (`2.0.x`-tags)
|
||||
- ❌ 0.10.x (`0.10.x`-tags)
|
||||
- ❌ other git-branches
|
||||
|
||||
## Qualifying Vulnerabilities
|
||||
|
||||
@@ -26,7 +28,7 @@ With that, good luck hacking us ;)
|
||||
|
||||
### Vulnerabilities we accept
|
||||
|
||||
Only reproducable issues on a default/clean setup from the latest stable release of a supported version will be accepted.
|
||||
Only reproducible issues on a default/clean setup from the latest stable release of a supported version will be accepted.
|
||||
|
||||
## Non-Qualifying Vulnerabilities
|
||||
|
||||
@@ -34,6 +36,8 @@ Only reproducable issues on a default/clean setup from the latest stable release
|
||||
- Theoretical attacks without proof of exploitability
|
||||
- Attacks that are the result of a third party library should be reported to the library maintainers
|
||||
- Social engineering
|
||||
- Attacks that require disabling security features or reducing the security level of the environment
|
||||
- Exploits by an admin user itself (privileged user and implicitly trusted)
|
||||
- Reflected file download
|
||||
- Physical attacks
|
||||
- Weak SSL/TLS/SSH algorithms or protocols
|
||||
@@ -44,4 +48,4 @@ Only reproducable issues on a default/clean setup from the latest stable release
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you think you have found a vulnerability in froxlor, please head over to [https://huntr.dev/repos/froxlor/froxlor](https://huntr.dev/repos/froxlor/froxlor) and use the reporting possibilities there as we are funding the prize-pot for froxlor on this platform. Also, please give us appropriate time to fix the issue and build update-packages before publishing anything into the wild. Alternatively you can send us an email to [team@froxlor.org](team@froxlor.org).
|
||||
If you think you have found a vulnerability in froxlor, please head over to [https://github.com/Froxlor/Froxlor/security/advisories](https://github.com/Froxlor/Froxlor/security/advisories/new) and use the reporting possibilities there. Also, please give us appropriate time to fix the issue and build update-packages before publishing anything into the wild. Alternatively you can email us to [team@froxlor.org](team@froxlor.org).
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
@@ -130,7 +130,8 @@ return [
|
||||
'default' => 'stable',
|
||||
'select_var' => [
|
||||
'stable' => lng('serversettings.uc_stable'),
|
||||
'testing' => lng('serversettings.uc_testing')
|
||||
'testing' => lng('serversettings.uc_testing'),
|
||||
'nightly' => lng('serversettings.uc_nightly')
|
||||
],
|
||||
'save_method' => 'storeSettingField',
|
||||
'advanced_mode' => true
|
||||
@@ -171,16 +172,6 @@ return [
|
||||
'default' => false,
|
||||
'save_method' => 'storeSettingField'
|
||||
],
|
||||
'system_index_file_extension' => [
|
||||
'label' => lng('serversettings.index_file_extension'),
|
||||
'settinggroup' => 'system',
|
||||
'varname' => 'index_file_extension',
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^[a-zA-Z0-9]{1,6}$/',
|
||||
'default' => 'html',
|
||||
'save_method' => 'storeSettingField',
|
||||
'advanced_mode' => true
|
||||
],
|
||||
'system_store_index_file_subs' => [
|
||||
'label' => lng('serversettings.system_store_index_file_subs'),
|
||||
'settinggroup' => 'system',
|
||||
|
||||
@@ -248,7 +248,7 @@ return [
|
||||
'settinggroup' => 'system',
|
||||
'varname' => 'le_domain_dnscheck_resolver',
|
||||
'type' => 'text',
|
||||
'string_regexp' => '/^(([0-9]+ [a-z0-9\-\._]+, ?)*[0-9]+ [a-z0-9\-\._]+)?$/i',
|
||||
'string_type' => 'validate_ip',
|
||||
'string_emptyallowed' => true,
|
||||
'default' => '',
|
||||
'save_method' => 'storeSettingField'
|
||||
|
||||
@@ -43,7 +43,8 @@ return [
|
||||
'settinggroup' => 'spf',
|
||||
'varname' => 'spf_entry',
|
||||
'type' => 'text',
|
||||
'default' => '"v=spf1 a mx -all"',
|
||||
'string_regexp' => '/^v=spf[a-z0-9:~?\s.-]+$/i',
|
||||
'default' => 'v=spf1 a mx -all',
|
||||
'save_method' => 'storeSettingField'
|
||||
]
|
||||
]
|
||||
|
||||
@@ -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'
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
@@ -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');
|
||||
}
|
||||
@@ -60,7 +60,9 @@ if ($userinfo['change_serversettings'] == '1') {
|
||||
|
||||
if (!empty($distribution)) {
|
||||
if (!file_exists($config_dir . '/' . $distribution . ".xml")) {
|
||||
Response::dynamicError("Unknown distribution");
|
||||
// unknown distribution -> redirect to select a valid distribution for config-templates
|
||||
Settings::Set('system.distribution', '');
|
||||
Response::redirectTo('admin_configfiles.php', ['reselect' => 1]);
|
||||
}
|
||||
|
||||
// update setting if different
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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, [
|
||||
|
||||
@@ -60,7 +60,8 @@ if (Settings::Get('panel.sendalternativemail') == 1) {
|
||||
}
|
||||
|
||||
$file_templates = [
|
||||
'index_html'
|
||||
'index_html',
|
||||
'unconfigured_html'
|
||||
];
|
||||
|
||||
$languages = Language::getLanguages();
|
||||
|
||||
@@ -24,20 +24,8 @@
|
||||
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Froxlor\Cli\ConfigDiff;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Froxlor\Cli\RunApiCommand;
|
||||
use Froxlor\Cli\ConfigServices;
|
||||
use Froxlor\Cli\PhpSessionclean;
|
||||
use Froxlor\Cli\SwitchServerIp;
|
||||
use Froxlor\Cli\UpdateCommand;
|
||||
use Froxlor\Cli\InstallCommand;
|
||||
use Froxlor\Cli\MasterCron;
|
||||
use Froxlor\Cli\UserCommand;
|
||||
use Froxlor\Cli\ValidateAcmeWebroot;
|
||||
use Froxlor\Froxlor;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
// validate correct php version
|
||||
if (version_compare("7.4.0", PHP_VERSION, ">=")) {
|
||||
@@ -53,14 +41,31 @@ require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
require dirname(__DIR__) . '/lib/tables.inc.php';
|
||||
|
||||
$application = new Application('froxlor-cli', Froxlor::getFullVersion());
|
||||
$application->add(new RunApiCommand());
|
||||
$application->add(new ConfigServices());
|
||||
$application->add(new PhpSessionclean());
|
||||
$application->add(new SwitchServerIp());
|
||||
$application->add(new UpdateCommand());
|
||||
$application->add(new InstallCommand());
|
||||
$application->add(new MasterCron());
|
||||
$application->add(new UserCommand());
|
||||
$application->add(new ValidateAcmeWebroot());
|
||||
$application->add(new ConfigDiff());
|
||||
|
||||
// files that are no commands
|
||||
$fileIgnoreList = [
|
||||
// Current non-command files
|
||||
'CliCommand.php',
|
||||
'index.html',
|
||||
'install.functions.php',
|
||||
];
|
||||
// directory of commands to include
|
||||
$cmd_files = glob(Froxlor::getInstallDir() . '/lib/Froxlor/Cli/*.php');
|
||||
|
||||
// include and add commands
|
||||
foreach ($cmd_files as $cmdFile) {
|
||||
// check ignore-list
|
||||
if (!in_array(basename($cmdFile), $fileIgnoreList)) {
|
||||
// include class-file
|
||||
require $cmdFile;
|
||||
// create class-name including namespace
|
||||
$cmdClass = "\\Froxlor\\Cli\\" . substr(basename($cmdFile), 0, -4);
|
||||
// check whether it exists
|
||||
if (class_exists($cmdClass) && is_subclass_of($cmdClass, '\Symfony\Component\Console\Command\Command')) {
|
||||
// add to cli application
|
||||
$application->add(new $cmdClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$application->run();
|
||||
|
||||
@@ -46,18 +46,18 @@
|
||||
"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",
|
||||
"ext-pcntl": "*",
|
||||
|
||||
816
composer.lock
generated
816
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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' => \Froxlor\Froxlor::DOCS_URL . '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']);
|
||||
}
|
||||
|
||||
|
||||
@@ -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' => \Froxlor\Froxlor::DOCS_URL . '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' => \Froxlor\Froxlor::DOCS_URL . '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'),
|
||||
|
||||
@@ -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' => \Froxlor\Froxlor::DOCS_URL . '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' => \Froxlor\Froxlor::DOCS_URL . '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' => \Froxlor\Froxlor::DOCS_URL . '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'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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' => \Froxlor\Froxlor::DOCS_URL . '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'),
|
||||
|
||||
@@ -115,8 +115,8 @@ if ($page == 'overview') {
|
||||
$userinfo['traffic_bytes_used'] = $userinfo['traffic_used'] * 1024;
|
||||
|
||||
if (Settings::Get('system.mail_quota_enabled')) {
|
||||
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 : -1;
|
||||
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024;
|
||||
$userinfo['email_quota_bytes'] = ($userinfo['email_quota'] > -1) ? $userinfo['email_quota'] * 1024 * 1024 : -1;
|
||||
$userinfo['email_quota_bytes_used'] = $userinfo['email_quota_used'] * 1024 * 1024;
|
||||
}
|
||||
|
||||
if ($usages) {
|
||||
|
||||
@@ -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' => \Froxlor\Froxlor::DOCS_URL . '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 */
|
||||
|
||||
24
index.php
24
index.php
@@ -74,27 +74,26 @@ if ($action == '2fa_entercode') {
|
||||
$code = isset($_POST['2fa_code']) ? $_POST['2fa_code'] : null;
|
||||
// verify entered code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3));
|
||||
// get user-data
|
||||
$table = $_SESSION['uidtable_2fa'];
|
||||
$field = $_SESSION['uidfield_2fa'];
|
||||
$uid = $_SESSION['uid_2fa'];
|
||||
$isadmin = $_SESSION['unfo_2fa'];
|
||||
if ($_SESSION['secret_2fa'] == 'email') {
|
||||
// verify code set to user's data_2fa field
|
||||
$sel_stmt = Database::prepare("SELECT `data_2fa` FROM " . $table . " WHERE `" . $field . "` = :uid");
|
||||
$userinfo_code = Database::pexecute_first($sel_stmt, ['uid' => $uid]);
|
||||
$result = $tfa->verifyCode($userinfo_code['data_2fa'], $code);
|
||||
} else {
|
||||
$result = $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3);
|
||||
}
|
||||
// either the code is valid when using authenticator-app, or we will select userdata by id and entered code
|
||||
// which is temporarily stored for the customer when using email-2fa
|
||||
if ($result) {
|
||||
$sel_param = [
|
||||
'uid' => $uid
|
||||
];
|
||||
if ($_SESSION['secret_2fa'] == 'email') {
|
||||
// verify code by selecting user by id and the temp. stored code,
|
||||
// so only if it's the correct code, we get the user-data
|
||||
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid AND `data_2fa` = :code");
|
||||
$sel_param['code'] = $code;
|
||||
} else {
|
||||
// Authenticator-verification has already happened at this point, so just get the user-data
|
||||
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
|
||||
}
|
||||
$sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid");
|
||||
$userinfo = Database::pexecute_first($sel_stmt, $sel_param);
|
||||
// whoops, no (valid) user? Start again
|
||||
if (empty($userinfo)) {
|
||||
@@ -327,11 +326,12 @@ if ($action == '2fa_entercode') {
|
||||
if ($userinfo['type_2fa'] == 1) {
|
||||
// generate code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$code = $tfa->getCode($tfa->createSecret());
|
||||
$secret = $tfa->createSecret();
|
||||
$code = $tfa->getCode($secret);
|
||||
// set code for user
|
||||
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
|
||||
Database::pexecute($stmt, [
|
||||
"d2fa" => $code,
|
||||
"d2fa" => $secret,
|
||||
"uid" => $userinfo[$uid]
|
||||
]);
|
||||
// build up & send email
|
||||
|
||||
@@ -157,7 +157,7 @@ CREATE TABLE `panel_admins` (
|
||||
`api_allowed` tinyint(1) NOT NULL default '1',
|
||||
PRIMARY KEY (`adminid`),
|
||||
UNIQUE KEY `loginname` (`loginname`)
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_customers`;
|
||||
@@ -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;
|
||||
@@ -301,7 +299,7 @@ CREATE TABLE `panel_domains` (
|
||||
KEY `customerid` (`customerid`),
|
||||
KEY `parentdomain` (`parentdomainid`),
|
||||
KEY `domain` (`domain`)
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_ipsandports`;
|
||||
@@ -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,
|
||||
@@ -410,7 +391,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
|
||||
('admin', 'show_version_footer', '0'),
|
||||
('caa', 'caa_entry', ''),
|
||||
('spf', 'use_spf', '0'),
|
||||
('spf', 'spf_entry', '"v=spf1 a mx -all"'),
|
||||
('spf', 'spf_entry', 'v=spf1 a mx -all'),
|
||||
('dkim', 'dkim_algorithm', 'all'),
|
||||
('dkim', 'dkim_keylength', '1024'),
|
||||
('dkim', 'dkim_servicetype', '0'),
|
||||
@@ -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'),
|
||||
@@ -581,7 +562,6 @@ opcache.validate_timestamps'),
|
||||
('system', 'mod_fcgid_wrapper', '1'),
|
||||
('system', 'mod_fcgid_starter', '0'),
|
||||
('system', 'mod_fcgid_peardir', '/usr/share/php/:/usr/share/php5/'),
|
||||
('system', 'index_file_extension', 'html'),
|
||||
('system', 'mod_fcgid_maxrequests', '250'),
|
||||
('system', 'ssl_key_file','/etc/ssl/froxlor_selfsigned.key'),
|
||||
('system', 'ssl_ca_file', ''),
|
||||
@@ -698,20 +678,15 @@ opcache.validate_timestamps'),
|
||||
('system', 'distribution', ''),
|
||||
('system', 'update_channel', 'stable'),
|
||||
('system', 'updatecheck_data', ''),
|
||||
('system', 'update_notify_last', '2.0.20'),
|
||||
('system', 'update_notify_last', ''),
|
||||
('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'),
|
||||
('panel', 'decimal_places', '4'),
|
||||
('panel', 'adminmail', 'admin@SERVERNAME'),
|
||||
('panel', 'adminmail', 'ADMIN_MAIL'),
|
||||
('panel', 'phpmyadmin_url', ''),
|
||||
('panel', 'webmail_url', ''),
|
||||
('panel', 'webftp_url', ''),
|
||||
@@ -750,8 +725,9 @@ opcache.validate_timestamps'),
|
||||
('panel', 'logo_overridetheme', '0'),
|
||||
('panel', 'logo_overridecustom', '0'),
|
||||
('panel', 'settings_mode', '0'),
|
||||
('panel', 'version', '2.0.20'),
|
||||
('panel', 'db_version', '202305240');
|
||||
('panel', 'menu_collapsed', '1'),
|
||||
('panel', 'version', '2.1.6'),
|
||||
('panel', 'db_version', '202312120');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `panel_tasks`;
|
||||
@@ -774,6 +750,7 @@ CREATE TABLE `panel_templates` (
|
||||
`templategroup` varchar(255) NOT NULL default '',
|
||||
`varname` varchar(255) NOT NULL default '',
|
||||
`value` longtext NOT NULL,
|
||||
`file_extension` varchar(50) NOT NULL default 'html',
|
||||
PRIMARY KEY (id),
|
||||
KEY adminid (adminid)
|
||||
) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -36,9 +36,10 @@ if (!defined('_CRON_UPDATE')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202304260')) {
|
||||
//Update::showUpdateStep("Cleaning domains table");
|
||||
//Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
|
||||
if (Froxlor::isFroxlorVersion('2.0.24')) {
|
||||
Update::showUpdateStep("Cleaning domains table");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` DROP COLUMN `ismainbutsubto`;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Update::showUpdateStep("Creating new tables and fields");
|
||||
@@ -53,6 +54,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,80 +67,225 @@ 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("
|
||||
$cfupd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_CRONRUNS . "` SET
|
||||
`module`= 'froxlor/export',
|
||||
`cronfile` = 'export',
|
||||
`cronclass` = '\\Froxlor\\Cron\\System\\ExportCron',
|
||||
`cronclass` = :cc,
|
||||
`interval` = '1 HOUR',
|
||||
`desc_lng_key` = 'cron_export'
|
||||
WHERE `module` = 'froxlor/backup'
|
||||
");
|
||||
Database::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'
|
||||
");
|
||||
Database::pexecute($cfupd_stmt, [
|
||||
'cc' => '\\Froxlor\\Cron\\System\\ExportCron'
|
||||
]);
|
||||
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');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-beta2')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-beta2 to 2.1.0-rc1", false);
|
||||
Froxlor::updateToVersion('2.1.0-rc1');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-rc1')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-rc1 to 2.1.0-rc2", false);
|
||||
|
||||
Update::showUpdateStep("Adjusting setting spf_entry");
|
||||
$spf_entry = Settings::Get('spf.spf_entry');
|
||||
if (!preg_match('/^v=spf[a-z0-9:~?\s.-]+$/i', $spf_entry)) {
|
||||
Settings::Set('spf.spf_entry', 'v=spf1 a mx -all');
|
||||
Update::lastStepStatus(1, 'corrected');
|
||||
} else {
|
||||
Update::lastStepStatus(0);
|
||||
}
|
||||
|
||||
Froxlor::updateToVersion('2.1.0-rc2');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202305240')) {
|
||||
|
||||
Update::showUpdateStep("Adjusting file-template file extension setttings");
|
||||
$current_fileextension = Settings::Get('system.index_file_extension');
|
||||
Database::query("DELETE FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup`= 'system' AND `varname`= 'index_file_extension'");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_TEMPLATES . "` ADD `file_extension` varchar(50) NOT NULL default 'html';");
|
||||
if (!empty(trim($current_fileextension)) && strtolower(trim($current_fileextension)) != 'html') {
|
||||
$stmt = Database::prepare("UPDATE `" . TABLE_PANEL_TEMPLATES . "` SET `file_extension` = :ext WHERE `templategroup` = 'files'");
|
||||
Database::pexecute($stmt, ['ext' => strtolower(trim($current_fileextension))]);
|
||||
}
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202311260');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-rc2')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-rc2 to 2.1.0-rc3", false);
|
||||
Froxlor::updateToVersion('2.1.0-rc3');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202311260')) {
|
||||
Update::showUpdateStep("Cleaning up old files");
|
||||
$to_clean = array(
|
||||
"install/updates/froxlor/update_2.x.inc.php",
|
||||
"install/updates/preconfig/preconfig_2.x.inc.php",
|
||||
"lib/Froxlor/Api/Commands/CustomerBackups.php",
|
||||
"lib/Froxlor/Cli/Action",
|
||||
"lib/Froxlor/Cli/Action.php",
|
||||
"lib/Froxlor/Cli/CmdLineHandler.php",
|
||||
"lib/Froxlor/Cli/ConfigServicesCmd.php",
|
||||
"lib/Froxlor/Cli/PhpSessioncleanCmd.php",
|
||||
"lib/Froxlor/Cli/SwitchServerIpCmd.php",
|
||||
"lib/Froxlor/Cli/UpdateCliCmd.php",
|
||||
"lib/Froxlor/Cron/System/BackupCron.php",
|
||||
"lib/formfields/customer/extras/formfield.backup.php",
|
||||
"lib/tablelisting/customer/tablelisting.backups.php",
|
||||
"templates/Froxlor/assets/mix-manifest.json",
|
||||
"templates/Froxlor/assets/css",
|
||||
"templates/Froxlor/assets/webfonts",
|
||||
"templates/Froxlor/assets/js/main.js",
|
||||
"templates/Froxlor/assets/js/main.js.LICENSE.txt",
|
||||
"templates/Froxlor/src",
|
||||
"templates/Froxlor/user/change_language.html.twig",
|
||||
"templates/Froxlor/user/change_password.html.twig",
|
||||
"templates/Froxlor/user/change_theme.html.twig",
|
||||
"tests/Backup/CustomerBackupsTest.php"
|
||||
);
|
||||
$disabled = explode(',', ini_get('disable_functions'));
|
||||
$exec_allowed = !in_array('exec', $disabled);
|
||||
$del_list = "";
|
||||
foreach ($to_clean as $filedir) {
|
||||
$complete_filedir = Froxlor::getInstallDir() . $filedir;
|
||||
if (file_exists($complete_filedir)) {
|
||||
if ($exec_allowed) {
|
||||
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
|
||||
} else {
|
||||
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($exec_allowed) {
|
||||
Update::lastStepStatus(0);
|
||||
} else {
|
||||
if (empty($del_list)) {
|
||||
// none of the files existed
|
||||
Update::lastStepStatus(0);
|
||||
} else {
|
||||
Update::lastStepStatus(
|
||||
1,
|
||||
'manual commands needed',
|
||||
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
|
||||
);
|
||||
}
|
||||
}
|
||||
Froxlor::updateToDbVersion('202312050');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0-rc3')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0-rc3 to 2.1.0 stable", false);
|
||||
Froxlor::updateToVersion('2.1.0');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.0')) {
|
||||
Update::showUpdateStep("Updating from 2.1.0 to 2.1.1", false);
|
||||
Froxlor::updateToVersion('2.1.1');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202312050')) {
|
||||
Update::showUpdateStep("Cleaning up old files");
|
||||
$to_clean = array(
|
||||
"lib/configfiles/centos7.xml",
|
||||
"lib/configfiles/centos8.xml",
|
||||
"lib/configfiles/stretch.xml",
|
||||
"lib/configfiles/xenial.xml",
|
||||
"lib/configfiles/buster.xml",
|
||||
"lib/configfiles/bionic.xml",
|
||||
);
|
||||
$disabled = explode(',', ini_get('disable_functions'));
|
||||
$exec_allowed = !in_array('exec', $disabled);
|
||||
$del_list = "";
|
||||
foreach ($to_clean as $filedir) {
|
||||
$complete_filedir = Froxlor::getInstallDir() . $filedir;
|
||||
if (file_exists($complete_filedir)) {
|
||||
if ($exec_allowed) {
|
||||
FileDir::safe_exec("rm -rf " . escapeshellarg($complete_filedir));
|
||||
} else {
|
||||
$del_list .= "rm -rf " . escapeshellarg($complete_filedir) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($exec_allowed) {
|
||||
Update::lastStepStatus(0);
|
||||
} else {
|
||||
if (empty($del_list)) {
|
||||
// none of the files existed
|
||||
Update::lastStepStatus(0);
|
||||
} else {
|
||||
Update::lastStepStatus(
|
||||
1,
|
||||
'manual commands needed',
|
||||
'Please run the following commands manually:<br><pre>' . $del_list . '</pre>'
|
||||
);
|
||||
}
|
||||
}
|
||||
Froxlor::updateToDbVersion('202312100');
|
||||
}
|
||||
|
||||
if (Froxlor::isDatabaseVersion('202312100')) {
|
||||
|
||||
Update::showUpdateStep("Adjusting table row format of larger tables");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_ADMINS . "` ROW_FORMAT=DYNAMIC;");
|
||||
Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAINS . "` ROW_FORMAT=DYNAMIC;");
|
||||
Update::lastStepStatus(0);
|
||||
|
||||
Froxlor::updateToDbVersion('202312120');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.1')) {
|
||||
Update::showUpdateStep("Updating from 2.1.1 to 2.1.2", false);
|
||||
Froxlor::updateToVersion('2.1.2');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.2')) {
|
||||
Update::showUpdateStep("Updating from 2.1.2 to 2.1.3", false);
|
||||
Froxlor::updateToVersion('2.1.3');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.3')) {
|
||||
Update::showUpdateStep("Updating from 2.1.3 to 2.1.4", false);
|
||||
Froxlor::updateToVersion('2.1.4');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.4')) {
|
||||
Update::showUpdateStep("Updating from 2.1.4 to 2.1.5", false);
|
||||
Froxlor::updateToVersion('2.1.5');
|
||||
}
|
||||
|
||||
if (Froxlor::isFroxlorVersion('2.1.5')) {
|
||||
Update::showUpdateStep("Updating from 2.1.5 to 2.1.6", false);
|
||||
Froxlor::updateToVersion('2.1.6');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -425,7 +400,10 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
|
||||
|
||||
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
|
||||
Response::standardError('customerphpenabledbutnoconfig', '', true);
|
||||
// only required if not using mod_php
|
||||
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
|
||||
Response::standardError('customerphpenabledbutnoconfig', '', true);
|
||||
}
|
||||
}
|
||||
|
||||
$allowed_mysqlserver = array();
|
||||
@@ -562,9 +540,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 +583,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 +1031,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
|
||||
@@ -1089,7 +1056,7 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
$email = $this->getParam('email', true, $idna_convert->decode($result['email']));
|
||||
$name = $this->getParam('name', true, $result['name']);
|
||||
$firstname = $this->getParam('firstname', true, $result['firstname']);
|
||||
$company_required = empty($result['company']) && ((!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname)));
|
||||
$company_required = (!empty($name) && empty($firstname)) || (empty($name) && !empty($firstname)) || (empty($name) && empty($firstname));
|
||||
$company = $this->getParam('company', !$company_required, $result['company']);
|
||||
$street = $this->getParam('street', true, $result['street']);
|
||||
$zipcode = $this->getParam('zipcode', true, $result['zipcode']);
|
||||
@@ -1125,24 +1092,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']);
|
||||
@@ -1168,7 +1117,10 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
|
||||
}
|
||||
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
|
||||
Response::standardError('customerphpenabledbutnoconfig', '', true);
|
||||
// only required if not using mod_php
|
||||
if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) {
|
||||
Response::standardError('customerphpenabledbutnoconfig', '', true);
|
||||
}
|
||||
}
|
||||
|
||||
// add permission for allowed mysql usage if customer was not allowed to use mysql prior
|
||||
@@ -1451,9 +1403,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 +1446,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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -349,6 +349,8 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
|
||||
if (substr($p_domain, 0, 4) == 'xn--') {
|
||||
Response::standardError('domain_nopunycode', '', true);
|
||||
} elseif (Validate::validate_ip2($p_domain, true, '', true, true)) {
|
||||
Response::standardError('domain_noipaddress', '', true);
|
||||
}
|
||||
|
||||
$idna_convert = new IdnaWrapper();
|
||||
@@ -544,6 +546,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 +1213,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 +1523,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 +1563,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;
|
||||
}
|
||||
|
||||
@@ -1642,6 +1652,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
|| $iswildcarddomain != $result['iswildcarddomain']
|
||||
|| $phpenabled != $result['phpenabled']
|
||||
|| $openbasedir != $result['openbasedir']
|
||||
|| $openbasedir_path != $result['openbasedir_path']
|
||||
|| $phpsettingid != $result['phpsettingid']
|
||||
|| $mod_fcgid_starter != $result['mod_fcgid_starter']
|
||||
|| $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests']
|
||||
@@ -1659,6 +1670,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
|| $hsts_sub != $result['hsts_sub']
|
||||
|| $hsts_preload != $result['hsts_preload']
|
||||
|| $ocsp_stapling != $result['ocsp_stapling']
|
||||
|| $sslenabled != $result['ssl_enabled']
|
||||
) {
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
}
|
||||
@@ -1807,7 +1819,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$update_data['wwwserveralias'] = $wwwserveralias;
|
||||
$update_data['iswildcarddomain'] = $iswildcarddomain;
|
||||
$update_data['phpenabled'] = $phpenabled;
|
||||
$update_data['openbasedir'] = $openbasedir;;
|
||||
$update_data['openbasedir'] = $openbasedir;
|
||||
$update_data['openbasedir_path'] = $openbasedir_path;
|
||||
$update_data['speciallogfile'] = $speciallogfile;
|
||||
$update_data['phpsettingid'] = $phpsettingid;
|
||||
@@ -2313,6 +2325,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']);
|
||||
|
||||
@@ -157,10 +157,10 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
|
||||
|
||||
// prefix hash-algo
|
||||
switch (Settings::Get('system.passwordcryptfunc')) {
|
||||
case PASSWORD_ARGON2I:
|
||||
case 'argon2i':
|
||||
$cpPrefix = '{ARGON2I}';
|
||||
break;
|
||||
case PASSWORD_ARGON2ID:
|
||||
case 'argon2id':
|
||||
$cpPrefix = '{ARGON2ID}';
|
||||
break;
|
||||
default:
|
||||
@@ -404,10 +404,10 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
|
||||
$password = Crypt::validatePassword($password, true);
|
||||
// prefix hash-algo
|
||||
switch (Settings::Get('system.passwordcryptfunc')) {
|
||||
case PASSWORD_ARGON2I:
|
||||
case 'argon2i':
|
||||
$cpPrefix = '{ARGON2I}';
|
||||
break;
|
||||
case PASSWORD_ARGON2ID:
|
||||
case 'argon2id':
|
||||
$cpPrefix = '{ARGON2ID}';
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -82,7 +82,7 @@ class Froxlor extends ApiCommand
|
||||
if ($aucheck == 1) {
|
||||
// anzeige über version-status mit ggfls. formular
|
||||
// zum update schritt #1 -> download
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), $this->version]);
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), $this->version]);
|
||||
$response = [
|
||||
'isnewerversion' => (int) !AutoUpdate::getFromResult('has_latest'),
|
||||
'version' => $this->version,
|
||||
@@ -91,7 +91,7 @@ class Froxlor extends ApiCommand
|
||||
'additional_info' => AutoUpdate::getFromResult('info'),
|
||||
'aucheck' => $aucheck
|
||||
];
|
||||
} else if ($aucheck < 0 || $aucheck > 1) {
|
||||
} elseif ($aucheck < 0 || $aucheck > 1) {
|
||||
// errors
|
||||
if ($aucheck < 0) {
|
||||
$errmsg = AutoUpdate::getLastError();
|
||||
@@ -259,14 +259,15 @@ class Froxlor extends ApiCommand
|
||||
* returns a random password based on froxlor settings for min-length, included characters, etc.
|
||||
*
|
||||
* @param int $length
|
||||
* optional length of password, defaults to 10
|
||||
* optional length of password, defaults to 0 (panel.password_min_length)
|
||||
*
|
||||
* @access admin, customer
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function generatePassword()
|
||||
public function generatePassword(): string
|
||||
{
|
||||
$length = $this->getParam('length', true, 10);
|
||||
$length = $this->getParam('length', true, 0);
|
||||
return $this->response(Crypt::generatePassword($length));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 . "`
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -176,8 +176,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
|
||||
|
||||
if ((int)Settings::Get('system.use_ssl') == 1) {
|
||||
$ssl = (bool)$this->getBoolParam('ssl', true, 0);
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, ''), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, ''), 'ssl_key_file', '', '', [], true);
|
||||
$cert_optional = !($ssl && empty(Settings::Get('system.ssl_cert_file')));
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $cert_optional, ''), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, ''), 'ssl_key_file', '', '', [], true);
|
||||
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, ''), 'ssl_ca_file', '', '', [], true);
|
||||
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, ''), 'ssl_cert_chainfile', '', '', [], true);
|
||||
$sslss = $this->getParam('ssl_specialsettings', true, '');
|
||||
@@ -415,8 +416,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity
|
||||
|
||||
if ((int)Settings::Get('system.use_ssl') == 1) {
|
||||
$ssl = (bool)$this->getBoolParam('ssl', true, $result['ssl']);
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', !$ssl, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', !$ssl, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
|
||||
$cert_optional = !($ssl && empty(Settings::Get('system.ssl_cert_file')));
|
||||
$ssl_cert_file = Validate::validate($this->getParam('ssl_cert_file', $cert_optional, $result['ssl_cert_file']), 'ssl_cert_file', '', '', [], true);
|
||||
$ssl_key_file = Validate::validate($this->getParam('ssl_key_file', $cert_optional, $result['ssl_key_file']), 'ssl_key_file', '', '', [], true);
|
||||
$ssl_ca_file = Validate::validate($this->getParam('ssl_ca_file', true, $result['ssl_ca_file']), 'ssl_ca_file', '', '', [], true);
|
||||
$ssl_cert_chainfile = Validate::validate($this->getParam('ssl_cert_chainfile', true, $result['ssl_cert_chainfile']), 'ssl_cert_chainfile', '', '', [], true);
|
||||
$sslss = $this->getParam('ssl_specialsettings', true, $result['ssl_specialsettings']);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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, "/");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -398,7 +402,7 @@ final class ConfigServices extends CliCommand
|
||||
case "file":
|
||||
if (array_key_exists('content', $action)) {
|
||||
$output->writeln('<comment>Creating file "' . $action['name'] . '"</>');
|
||||
file_put_contents($action['name'], trim(strtr($action['content'], $replace_arr)));
|
||||
file_put_contents($action['name'], trim(strtr($action['content'], $replace_arr)) . PHP_EOL);
|
||||
} elseif (array_key_exists('subcommands', $action)) {
|
||||
foreach ($action['subcommands'] as $fileaction) {
|
||||
if (array_key_exists('execute', $fileaction) && $fileaction['execute'] == "pre") {
|
||||
@@ -407,7 +411,7 @@ final class ConfigServices extends CliCommand
|
||||
exec(strtr($fileaction['content'], $replace_arr));
|
||||
} elseif ($fileaction['type'] == 'file') {
|
||||
$output->writeln('<comment>Creating file "' . $fileaction['name'] . '"</>');
|
||||
file_put_contents($fileaction['name'], trim(strtr($fileaction['content'], $replace_arr)));
|
||||
file_put_contents($fileaction['name'], trim(strtr($fileaction['content'], $replace_arr)) . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'),
|
||||
@@ -507,7 +514,7 @@ final class ConfigServices extends CliCommand
|
||||
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup'),
|
||||
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
|
||||
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
|
||||
'<ADMIN_MAIL>' => Settings::Get('panel.adminmail'),
|
||||
];
|
||||
return $replace_arr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -206,7 +211,7 @@ final class InstallCommand extends Command
|
||||
$ask_field = false;
|
||||
}
|
||||
$fielddata['value'] = $this->formfielddata[$fieldname] ?? ($fielddata['value'] ?? null);
|
||||
$fielddata['label'] = strip_tags(str_replace("<br>", " ", $fielddata['label']));
|
||||
$fielddata['label'] = $this->cliTextFormat($fielddata['label'], " ");
|
||||
if ($ask_field) {
|
||||
if ($fielddata['type'] == 'password') {
|
||||
$this->formfielddata[$fieldname] = $this->io->askHidden($fielddata['label'], function ($value) use ($fielddata) {
|
||||
@@ -262,14 +267,16 @@ final class InstallCommand extends Command
|
||||
case 4:
|
||||
$section = $inst->formfield['install']['sections']['step' . $step] ?? [];
|
||||
$this->io->section($section['title']);
|
||||
$this->io->note($section['description']);
|
||||
$this->io->note($this->cliTextFormat($section['description']));
|
||||
$cmdfield = $section['fields']['system'];
|
||||
$this->io->success([
|
||||
$cmdfield['label'],
|
||||
$cmdfield['value']
|
||||
]);
|
||||
if (!empty($decoded_input) || $this->io->confirm('Execute command now?', false)) {
|
||||
passthru($cmdfield['value']);
|
||||
if (!isset($decoded_input['manual_config']) || (bool)$decoded_input['manual_config'] === false) {
|
||||
if (!empty($decoded_input) || $this->io->confirm('Execute command now?', false)) {
|
||||
passthru($cmdfield['value']);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -300,7 +307,7 @@ final class InstallCommand extends Command
|
||||
$json_output = [];
|
||||
foreach ($fields['install']['sections'] as $section => $section_fields) {
|
||||
foreach ($section_fields['fields'] as $name => $field) {
|
||||
if ($name == 'system' || $name == 'manual_config' || $name == 'target_servername') {
|
||||
if ($name == 'system' || $name == 'target_servername') {
|
||||
continue;
|
||||
}
|
||||
if ($field['type'] == 'text' || $field['type'] == 'email') {
|
||||
@@ -313,7 +320,7 @@ final class InstallCommand extends Command
|
||||
$fieldval = '******';
|
||||
} elseif ($field['type'] == 'select') {
|
||||
$fieldval = implode("|", array_keys($field['select_var']));
|
||||
} else if ($field['type'] == 'checkbox') {
|
||||
} elseif ($field['type'] == 'checkbox') {
|
||||
$fieldval = "1|0";
|
||||
} else {
|
||||
$fieldval = "?";
|
||||
@@ -341,4 +348,10 @@ final class InstallCommand extends Command
|
||||
curl_close($ch);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
private function cliTextFormat(string $text, string $nl_char = "\n"): string
|
||||
{
|
||||
$text = str_replace(['<br>', '<br/>', '<br />'], [$nl_char, $nl_char, $nl_char], $text);
|
||||
return strip_tags($text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,48 @@ 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);
|
||||
// reload crond as shell users might use crontab and the user is only known to crond if reloaded
|
||||
FileDir::safe_exec(escapeshellcmd(Settings::Get('system.crondreload')));
|
||||
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, [
|
||||
'>'
|
||||
]);
|
||||
// reload crond as shell users might use crontab and the user is only known to crond if reloaded
|
||||
FileDir::safe_exec(escapeshellcmd(Settings::Get('system.crondreload')));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,7 +54,35 @@ final class UpdateCommand extends CliCommand
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
// database update only
|
||||
if ($input->getOption('database')) {
|
||||
$result = $this->validateRequirements($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($output);
|
||||
|
||||
if ($result != self::SUCCESS) {
|
||||
// requirements failed, exit
|
||||
return $result;
|
||||
}
|
||||
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
@@ -71,7 +100,7 @@ final class UpdateCommand extends CliCommand
|
||||
}
|
||||
// there is a new version
|
||||
if ($input->getOption('check-only')) {
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
} else {
|
||||
$text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
}
|
||||
@@ -81,7 +110,7 @@ final class UpdateCommand extends CliCommand
|
||||
$newversionavail = true;
|
||||
$output->writeln('<comment>' . $text . '</>');
|
||||
$result = self::SUCCESS;
|
||||
} else if ($aucheck < 0 || $aucheck > 1) {
|
||||
} elseif ($aucheck < 0 || $aucheck > 1) {
|
||||
if ($input->getOption('integer-return')) {
|
||||
$output->write(-1);
|
||||
return self::INVALID;
|
||||
@@ -146,7 +175,7 @@ final class UpdateCommand extends CliCommand
|
||||
$result = self::SUCCESS;
|
||||
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
||||
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||
$result = $this->updateDatabase();
|
||||
$result = $this->runUpdate($output, true);
|
||||
}
|
||||
} else {
|
||||
$errmsg = 'error.autoupdate_' . $auex;
|
||||
@@ -170,7 +199,7 @@ final class UpdateCommand extends CliCommand
|
||||
if ($input->getOption('mail-notify')) {
|
||||
$last_check_version = Settings::Get('system.update_notify_last');
|
||||
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) {
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') == 'testing' ? 'testing ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||
$mail = new Mailer(true);
|
||||
$mail->Body = $text;
|
||||
$mail->Subject = "[froxlor] " . lng('update.notify_subject');
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ class ConfigDisplay
|
||||
'<SQL_UNPRIVILEGED_PASSWORD>' => 'FROXLOR_MYSQL_PASSWORD',
|
||||
'<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'),
|
||||
@@ -127,12 +127,15 @@ class ConfigDisplay
|
||||
'<VIRTUAL_GID_MAPS>' => Settings::Get('system.vmail_gid'),
|
||||
'<SSLPROTOCOLS>' => (Settings::Get('system.use_ssl') == '1') ? 'imaps pop3s' : '',
|
||||
'<CUSTOMER_TMP>' => FileDir::makeCorrectDir($customer_tmpdir),
|
||||
'<BASE_PATH>' => FileDir::makeCorrectDir(Froxlor::getInstallDir()),
|
||||
'<BASE_PATH>' => Froxlor::getInstallDir(),
|
||||
'<BIND_CONFIG_PATH>' => FileDir::makeCorrectDir(Settings::Get('system.bindconf_directory')),
|
||||
'<WEBSERVER_RELOAD_CMD>' => Settings::Get('system.apachereload_command'),
|
||||
'<CUSTOMER_LOGS>' => FileDir::makeCorrectDir(Settings::Get('system.logfiles_directory')),
|
||||
'<FPM_IPCDIR>' => FileDir::makeCorrectDir(Settings::Get('phpfpm.fastcgi_ipcdir')),
|
||||
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup')
|
||||
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup'),
|
||||
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
|
||||
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
|
||||
'<ADMIN_MAIL>' => Settings::Get('panel.adminmail'),
|
||||
];
|
||||
|
||||
$commands_pre = "";
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -55,18 +55,17 @@ class Bind extends DnsBase
|
||||
$domains = $this->getDomainList();
|
||||
|
||||
if (empty($domains)) {
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->bindconf_file = '# ' . Settings::Get('system.bindconf_directory') . 'froxlor_bind.conf' . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n\n";
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
if ($domain['is_child']) {
|
||||
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
|
||||
continue;
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, not creating any zones...');
|
||||
$this->bindconf_file = '';
|
||||
} else {
|
||||
$this->bindconf_file = '# ' . Settings::Get('system.bindconf_directory') . 'froxlor_bind.conf' . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n\n";
|
||||
foreach ($domains as $domain) {
|
||||
if ($domain['is_child']) {
|
||||
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
|
||||
continue;
|
||||
}
|
||||
$this->walkDomainList($domain, $domains);
|
||||
}
|
||||
$this->walkDomainList($domain, $domains);
|
||||
}
|
||||
|
||||
$bindconf_file_handler = fopen(FileDir::makeCorrectFile(Settings::Get('system.bindconf_directory') . '/froxlor_bind.conf'), 'w');
|
||||
|
||||
@@ -244,7 +244,7 @@ abstract class DnsBase
|
||||
'zonefile' => '',
|
||||
'froxlorhost' => '1'
|
||||
];
|
||||
$domains['none'] = $hostname_arr;
|
||||
$domains[0] = $hostname_arr;
|
||||
}
|
||||
|
||||
if (empty($domains)) {
|
||||
|
||||
@@ -45,18 +45,16 @@ class PowerDNS extends DnsBase
|
||||
$this->clearZoneTables($domains);
|
||||
|
||||
if (empty($domains)) {
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
if ($domain['is_child']) {
|
||||
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
|
||||
continue;
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, not creating any zones...');
|
||||
} else {
|
||||
foreach ($domains as $domain) {
|
||||
if ($domain['is_child']) {
|
||||
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
|
||||
continue;
|
||||
}
|
||||
$this->walkDomainList($domain, $domains);
|
||||
}
|
||||
$this->walkDomainList($domain, $domains);
|
||||
}
|
||||
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'PowerDNS database updated');
|
||||
$this->reloadDaemon();
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Task4 finished');
|
||||
|
||||
@@ -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;
|
||||
@@ -515,13 +515,7 @@ class Apache extends HttpConfigBase
|
||||
*/
|
||||
private function createStandardDirectoryEntry()
|
||||
{
|
||||
$vhosts_folder = '';
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
} else {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
}
|
||||
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_dirfix_nofcgid.conf');
|
||||
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_dirfix_nofcgid.conf');
|
||||
|
||||
if (!isset($this->virtualhosts_data[$vhosts_filename])) {
|
||||
$this->virtualhosts_data[$vhosts_filename] = '';
|
||||
@@ -545,7 +539,7 @@ class Apache extends HttpConfigBase
|
||||
}
|
||||
$this->virtualhosts_data[$vhosts_filename] .= ' </Directory>' . "\n";
|
||||
|
||||
$ocsp_cache_filename = FileDir::makeCorrectFile($vhosts_folder . '/03_froxlor_ocsp_cache.conf');
|
||||
$ocsp_cache_filename = $this->getCustomVhostFilename('03_froxlor_ocsp_cache.conf');
|
||||
if (Settings::Get('system.use_ssl') == '1' && Settings::Get('system.apache24') == 1) {
|
||||
$this->virtualhosts_data[$ocsp_cache_filename] = 'SSLStaplingCache ' . Settings::Get('system.apache24_ocsp_cache_path') . "\n";
|
||||
} else {
|
||||
@@ -562,14 +556,7 @@ class Apache extends HttpConfigBase
|
||||
private function createStandardErrorHandler()
|
||||
{
|
||||
if (Settings::Get('defaultwebsrverrhandler.enabled') == '1' && (Settings::Get('defaultwebsrverrhandler.err401') != '' || Settings::Get('defaultwebsrverrhandler.err403') != '' || Settings::Get('defaultwebsrverrhandler.err404') != '' || Settings::Get('defaultwebsrverrhandler.err500') != '')) {
|
||||
$vhosts_folder = '';
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
} else {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
}
|
||||
|
||||
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_default_errorhandler.conf');
|
||||
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_default_errorhandler.conf');
|
||||
|
||||
if (!isset($this->virtualhosts_data[$vhosts_filename])) {
|
||||
$this->virtualhosts_data[$vhosts_filename] = '';
|
||||
@@ -1255,7 +1242,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";
|
||||
|
||||
@@ -43,23 +43,29 @@ class DomainSSL
|
||||
* domain-array as reference so we can set the corresponding array-indices
|
||||
*
|
||||
* @return null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setDomainSSLFilesArray(array &$domain = null)
|
||||
{
|
||||
// check if the domain itself has a certificate defined
|
||||
$dom_certs_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :domid
|
||||
SELECT s.*, d.domain
|
||||
FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` s
|
||||
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON d.id = s.domainid
|
||||
WHERE s.`domainid` = :domid
|
||||
");
|
||||
$dom_certs = Database::pexecute_first($dom_certs_stmt, [
|
||||
'domid' => $domain['id']
|
||||
]);
|
||||
|
||||
$parent_certificate = false;
|
||||
if (!is_array($dom_certs) || !isset($dom_certs['ssl_cert_file']) || $dom_certs['ssl_cert_file'] == '') {
|
||||
// maybe its parent?
|
||||
if (isset($domain['parentdomainid']) && $domain['parentdomainid'] != 0) {
|
||||
$dom_certs = Database::pexecute_first($dom_certs_stmt, [
|
||||
'domid' => $domain['parentdomainid']
|
||||
]);
|
||||
$parent_certificate = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +79,8 @@ class DomainSSL
|
||||
}
|
||||
// make correct files for the certificates
|
||||
$ssl_files = [
|
||||
'ssl_cert_file' => FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '.crt'),
|
||||
'ssl_key_file' => FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '.key')
|
||||
'ssl_cert_file' => FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '.crt'),
|
||||
'ssl_key_file' => FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '.key')
|
||||
];
|
||||
|
||||
if (!$this->validateCertificate($dom_certs)) {
|
||||
@@ -93,19 +99,19 @@ class DomainSSL
|
||||
$ssl_files['ssl_cert_chainfile'] = '';
|
||||
// set them if they are != empty
|
||||
if ($dom_certs['ssl_ca_file'] != '') {
|
||||
$ssl_files['ssl_ca_file'] = FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '_CA.pem');
|
||||
$ssl_files['ssl_ca_file'] = FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '_CA.pem');
|
||||
}
|
||||
if ($dom_certs['ssl_cert_chainfile'] != '') {
|
||||
if (Settings::Get('system.webserver') == 'nginx') {
|
||||
// put ca.crt in my.crt, as nginx does not support a separate chain file.
|
||||
$dom_certs['ssl_cert_file'] = trim($dom_certs['ssl_cert_file']) . "\n" . trim($dom_certs['ssl_cert_chainfile']) . "\n";
|
||||
} else {
|
||||
$ssl_files['ssl_cert_chainfile'] = FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '_chain.pem');
|
||||
$ssl_files['ssl_cert_chainfile'] = FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '_chain.pem');
|
||||
}
|
||||
}
|
||||
// will only be generated to be used externally, froxlor does not need this
|
||||
if ($dom_certs['ssl_fullchain_file'] != '') {
|
||||
$ssl_files['ssl_fullchain_file'] = FileDir::makeCorrectFile($sslcertpath . '/' . $domain['domain'] . '_fullchain.pem');
|
||||
$ssl_files['ssl_fullchain_file'] = FileDir::makeCorrectFile($sslcertpath . '/' . ($parent_certificate ? $dom_certs['domain'] : $domain['domain']) . '_fullchain.pem');
|
||||
}
|
||||
// create them on the filesystem
|
||||
foreach ($ssl_files as $type => $filename) {
|
||||
@@ -131,7 +137,7 @@ class DomainSSL
|
||||
return;
|
||||
}
|
||||
|
||||
private function validateCertificate($dom_certs = [])
|
||||
private function validateCertificate($dom_certs = []): bool
|
||||
{
|
||||
return openssl_x509_check_private_key($dom_certs['ssl_cert_file'], $dom_certs['ssl_key_file']);
|
||||
}
|
||||
|
||||
@@ -202,4 +202,13 @@ class HttpConfigBase
|
||||
}
|
||||
return FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $filename);
|
||||
}
|
||||
|
||||
protected function getCustomVhostFilename(string $name)
|
||||
{
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
}
|
||||
return FileDir::makeCorrectFile($vhosts_folder . '/' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
namespace Froxlor\Cron\Http\LetsEncrypt;
|
||||
|
||||
use Froxlor\Cron\FroxlorCron;
|
||||
use Froxlor\Cron\TaskId;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Domain\Domain;
|
||||
use Froxlor\FileDir;
|
||||
@@ -83,7 +84,7 @@ class AcmeSh extends FroxlorCron
|
||||
$renew_domains = self::renewDomains(true);
|
||||
if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) {
|
||||
// insert task to generate certificates and vhost-configs
|
||||
Cronjob::inserttask(1);
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -203,7 +204,7 @@ class AcmeSh extends FroxlorCron
|
||||
// This is easiest done by just creating a new task ;)
|
||||
if ($changedetected) {
|
||||
if (self::$no_inserttask == false) {
|
||||
Cronjob::inserttask(1);
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
}
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Let's Encrypt certificates have been updated");
|
||||
} else {
|
||||
|
||||
@@ -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
|
||||
@@ -1167,14 +1161,7 @@ class Nginx extends HttpConfigBase
|
||||
private function createStandardErrorHandler()
|
||||
{
|
||||
if (Settings::Get('defaultwebsrverrhandler.enabled') == '1' && (Settings::Get('defaultwebsrverrhandler.err401') != '' || Settings::Get('defaultwebsrverrhandler.err403') != '' || Settings::Get('defaultwebsrverrhandler.err404') != '' || Settings::Get('defaultwebsrverrhandler.err500') != '')) {
|
||||
$vhosts_folder = '';
|
||||
if (is_dir(Settings::Get('system.apacheconf_vhost'))) {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(Settings::Get('system.apacheconf_vhost'));
|
||||
} else {
|
||||
$vhosts_folder = FileDir::makeCorrectDir(dirname(Settings::Get('system.apacheconf_vhost')));
|
||||
}
|
||||
|
||||
$vhosts_filename = FileDir::makeCorrectFile($vhosts_folder . '/05_froxlor_default_errorhandler.conf');
|
||||
$vhosts_filename = $this->getCustomVhostFilename('05_froxlor_default_errorhandler.conf');
|
||||
|
||||
if (!isset($this->nginx_data[$vhosts_filename])) {
|
||||
$this->nginx_data[$vhosts_filename] = '';
|
||||
|
||||
@@ -47,7 +47,7 @@ class TrafficCron extends FroxlorCron
|
||||
|
||||
public static function run()
|
||||
{
|
||||
self::runFork([self::class, 'handle']);
|
||||
self::runFork([self::class, 'handle'], [true]);
|
||||
}
|
||||
|
||||
public static function handle()
|
||||
@@ -163,14 +163,14 @@ class TrafficCron extends FroxlorCron
|
||||
|
||||
if (isset($domainlist[$row['customerid']]) && is_array($domainlist[$row['customerid']]) && count($domainlist[$row['customerid']]) != 0) {
|
||||
// Examining which caption to use for default webalizer stats...
|
||||
if ($row['standardsubdomain'] != '0') {
|
||||
if ($row['standardsubdomain'] != '0' && isset($domainlist[$row['customerid']][$row['standardsubdomain']])) {
|
||||
// ... of course we'd prefer to use the standardsubdomain ...
|
||||
$caption = $domainlist[$row['customerid']][$row['standardsubdomain']];
|
||||
} else {
|
||||
// ... but if there is no standardsubdomain, we have to use the loginname ...
|
||||
$caption = $row['loginname'];
|
||||
|
||||
// ... which results in non-usable links to files in the stats, so lets have a look if we find a domain which is not speciallogfiledomain
|
||||
// ... which results in non-usable links to files in the stats, so let's have a look if we find a domain which is not speciallogfiledomain
|
||||
foreach ($domainlist[$row['customerid']] as $domainid => $domain) {
|
||||
if (!isset($speciallogfile_domainlist[$row['customerid']]) || !isset($speciallogfile_domainlist[$row['customerid']][$domainid])) {
|
||||
$caption = $domain;
|
||||
@@ -193,6 +193,8 @@ class TrafficCron extends FroxlorCron
|
||||
} else {
|
||||
$httptraffic += floatval(self::callWebalizerGetTraffic($row['loginname'] . '-' . $domain, $row['documentroot'] . '/webalizer/' . $domain . '/', $domain, $domainlist[$row['customerid']]));
|
||||
}
|
||||
// kind of a keep-alive-call as this unsets the link which leads to a new connection to the database
|
||||
Database::needRoot();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,6 +212,8 @@ class TrafficCron extends FroxlorCron
|
||||
} else {
|
||||
$httptraffic += floatval(self::callWebalizerGetTraffic($row['loginname'], $row['documentroot'] . '/webalizer/', $caption, $domainlist[$row['customerid']]));
|
||||
}
|
||||
// kind of a keep-alive-call as this unsets the link which leads to a new connection to the database
|
||||
Database::needRoot();
|
||||
|
||||
// make the stuff readable for the customer, #258
|
||||
Statistics::makeChownWithNewStats($row);
|
||||
@@ -618,7 +622,7 @@ class TrafficCron extends FroxlorCron
|
||||
$format = Settings::Get('system.logfiles_type') == '2' ? 'VCOMBINED' : 'COMBINED';
|
||||
$monthyear = $monthyear_arr['month'] . '/' . $monthyear_arr['year'];
|
||||
$return_value = false;
|
||||
FileDir::safe_exec("grep '" . $monthyear . "' " . escapeshellarg($logfile) . " | goaccess " . $keep_params . " --db-path=" . escapeshellarg($outputdir) . " -o " . escapeshellarg($outputdir . '.tmp.json') . " -o " . escapeshellarg($outputdir . 'index.html') . " --html-report-title=" . escapeshellarg($caption) . " --log-format=" . $format . " - ", $return_value, ['|']);
|
||||
FileDir::safe_exec("grep '" . $monthyear . "' " . escapeshellarg($logfile) . " | goaccess " . $keep_params . " --db-path=" . escapeshellarg($outputdir) . " -o " . escapeshellarg($outputdir . '.tmp.json') . " -o " . escapeshellarg($outputdir . 'index.html') . " --html-report-title=" . escapeshellarg($caption) . " --log-format=" . $format . " --no-parsing-spinner --no-progress - ", $return_value, ['|']);
|
||||
|
||||
if (file_exists($outputdir . '.tmp.json')) {
|
||||
// need jq here because of potentially LARGE json files
|
||||
@@ -787,6 +791,8 @@ class TrafficCron extends FroxlorCron
|
||||
// 'real' domains and no subdomains which are aliases in the
|
||||
// model-config-file.
|
||||
$returnval += self::awstatsDoSingleDomain($singledomain, $outputdir, $current_stamp);
|
||||
// kind of a keep-alive-call as this unsets the link which leads to a new connection to the database
|
||||
Database::needRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
@@ -183,7 +187,8 @@ class CurrentUser
|
||||
if (self::getField('type_2fa') == 1) {
|
||||
// generate code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$code = $tfa->getCode($tfa->createSecret());
|
||||
$secret = $tfa->createSecret();
|
||||
$code = $tfa->getCode($secret);
|
||||
// set code for user
|
||||
$table = TABLE_PANEL_CUSTOMERS;
|
||||
$uid = 'customerid';
|
||||
@@ -193,7 +198,7 @@ class CurrentUser
|
||||
}
|
||||
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
|
||||
Database::pexecute($stmt, [
|
||||
"d2fa" => $code,
|
||||
"d2fa" => $secret,
|
||||
"uid" => self::getField($uid)
|
||||
]);
|
||||
// build up & send email
|
||||
|
||||
@@ -256,7 +256,7 @@ class Domain
|
||||
]);
|
||||
$result = [];
|
||||
while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$result = $entry['id'];
|
||||
$result[] = $entry['id'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
@@ -320,12 +320,15 @@ class Domain
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function triggerLetsEncryptCSRForAliasDestinationDomain(
|
||||
int $aliasDestinationDomainID,
|
||||
int $aliasDestinationDomainID,
|
||||
FroxlorLogger $log
|
||||
) {
|
||||
if ($aliasDestinationDomainID > 0) {
|
||||
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO,
|
||||
"LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID);
|
||||
$log->logAction(
|
||||
FroxlorLogger::ADM_ACTION,
|
||||
LOG_INFO,
|
||||
"LetsEncrypt CSR triggered for domain ID " . $aliasDestinationDomainID
|
||||
);
|
||||
$upd_stmt = Database::prepare("UPDATE
|
||||
`" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
|
||||
SET
|
||||
@@ -349,15 +352,20 @@ class Domain
|
||||
$acmesh = AcmeSh::getAcmeSh();
|
||||
if (file_exists($acmesh)) {
|
||||
$certificate_folder = AcmeSh::getWorkingDirFromEnv($domainname);
|
||||
if (file_exists($certificate_folder)) {
|
||||
$certificate_ecc_folder = AcmeSh::getWorkingDirFromEnv($domainname, true);
|
||||
if (file_exists($certificate_folder) || file_exists($certificate_ecc_folder)) {
|
||||
$params = " --remove -d " . $domainname;
|
||||
if (Settings::Get('system.leecc') > 0) {
|
||||
if (file_exists($certificate_ecc_folder)) {
|
||||
$params .= " --ecc";
|
||||
}
|
||||
// run remove command
|
||||
FileDir::safe_exec($acmesh . $params);
|
||||
// remove certificates directory
|
||||
FileDir::safe_exec('rm -rf ' . $certificate_folder);
|
||||
if (file_exists($certificate_folder)) {
|
||||
FileDir::safe_exec('rm -rf ' . $certificate_folder);
|
||||
} elseif (file_exists($certificate_ecc_folder)) {
|
||||
FileDir::safe_exec('rm -rf ' . $certificate_ecc_folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -43,9 +43,6 @@ class IpAddr
|
||||
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
if (!isset($system_ipaddress_array[$row['ip']]) && !in_array($row['ip'], $system_ipaddress_array)) {
|
||||
if (filter_var($row['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$row['ip'] = '[' . $row['ip'] . ']';
|
||||
}
|
||||
$system_ipaddress_array[$row['ip']] = $row['ip'];
|
||||
}
|
||||
}
|
||||
@@ -55,6 +52,7 @@ class IpAddr
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getSslIpPortCombinations(): array
|
||||
{
|
||||
@@ -75,7 +73,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)
|
||||
");
|
||||
|
||||
@@ -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.");
|
||||
@@ -231,6 +257,41 @@ class FileDir
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read unconfigured-domain template from database if exists or fallback to default
|
||||
*
|
||||
* @param string $servername
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getUnknownDomainTemplate(string $servername = "")
|
||||
{
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_TEMPLATES . "` WHERE `templategroup` = 'files' AND `varname` = 'unconfigured_html'
|
||||
");
|
||||
Database::pexecute($result_stmt);
|
||||
if (Database::num_rows() > 0) {
|
||||
$template = $result_stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$replace_arr = [
|
||||
'SERVERNAME' => $servername,
|
||||
];
|
||||
$tpl_content = PhpHelper::replaceVariables($template['value'], $replace_arr);
|
||||
$tpl_ext = $template['file_extension'];
|
||||
} else {
|
||||
$tpl_ext = 'html';
|
||||
$unconfiguredPath = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/templates/misc/unconfigured/index.html');
|
||||
if (file_exists($unconfiguredPath)) {
|
||||
$tpl_content = file_get_contents($unconfiguredPath);
|
||||
} else {
|
||||
$tpl_content = lng('admin.templates.unconfigured_content_fallback');
|
||||
}
|
||||
}
|
||||
$redirect_file = FileDir::makeCorrectFile(Froxlor::getInstallDir().'/notice.'.$tpl_ext);
|
||||
file_put_contents($redirect_file, $tpl_content);
|
||||
return basename($redirect_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* store the default index-file in a given destination folder
|
||||
*
|
||||
@@ -245,12 +306,13 @@ 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`
|
||||
SELECT `t`.`value`, `t`.`file_extension`, `c`.`email` AS `customer_email`, `a`.`email` AS `admin_email`, `c`.`loginname` AS `customer_login`, `a`.`loginname` AS `admin_login`
|
||||
FROM `" . TABLE_PANEL_CUSTOMERS . "` AS `c` INNER JOIN `" . TABLE_PANEL_ADMINS . "` AS `a`
|
||||
ON `c`.`adminid` = `a`.`adminid`
|
||||
INNER JOIN `" . TABLE_PANEL_TEMPLATES . "` AS `t`
|
||||
@@ -273,7 +335,7 @@ class FileDir
|
||||
|
||||
// replaceVariables
|
||||
$htmlcontent = PhpHelper::replaceVariables($template['value'], $replace_arr);
|
||||
$indexhtmlpath = self::makeCorrectFile($destination . '/index.' . Settings::Get('system.index_file_extension'));
|
||||
$indexhtmlpath = self::makeCorrectFile($destination . '/index.' . $template['file_extension']);
|
||||
$index_html_handler = fopen($indexhtmlpath, 'w');
|
||||
fwrite($index_html_handler, $htmlcontent);
|
||||
fclose($index_html_handler);
|
||||
@@ -281,7 +343,7 @@ class FileDir
|
||||
$logger->logAction(
|
||||
FroxlorLogger::CRON_ACTION,
|
||||
LOG_NOTICE,
|
||||
'Creating \'index.' . Settings::Get('system.index_file_extension') . '\' for Customer \'' . $template['customer_login'] . '\' based on template in directory ' . escapeshellarg($indexhtmlpath)
|
||||
'Creating \'index.' . $template['file_extension'] . '\' for Customer \'' . $template['customer_login'] . '\' based on template in directory ' . escapeshellarg($indexhtmlpath)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -31,14 +31,16 @@ final class Froxlor
|
||||
{
|
||||
|
||||
// Main version variable
|
||||
const VERSION = '2.1.0-dev1';
|
||||
const VERSION = '2.1.6';
|
||||
|
||||
// Database version (YYYYMMDDC where C is a daily counter)
|
||||
const DBVERSION = '202305240';
|
||||
const DBVERSION = '202312120';
|
||||
|
||||
// Distribution branding-tag (used for Debian etc.)
|
||||
const BRANDING = '';
|
||||
|
||||
const DOCS_URL = 'https://docs.froxlor.org/v2.1/';
|
||||
|
||||
/**
|
||||
* return path to where froxlor is installed, e.g.
|
||||
* /var/www/froxlor/
|
||||
|
||||
@@ -104,17 +104,15 @@ class FroxlorLogger
|
||||
self::$ml->pushHandler(new SyslogHandler('froxlor', LOG_USER, Logger::DEBUG));
|
||||
break;
|
||||
case 'file':
|
||||
$setings_logfile = Settings::Get('logger.logfile');
|
||||
if (empty($setings_logfile)) {
|
||||
Settings::Set('logger.logfile', 'froxlor.log');
|
||||
}
|
||||
$logger_logfile = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/logs/' . Settings::Get('logger.logfile'));
|
||||
// is_writable needs an existing file to check if it's actually writable
|
||||
@touch($logger_logfile);
|
||||
if (empty($logger_logfile) || !is_writable($logger_logfile)) {
|
||||
Settings::Set('logger.logfile', 'froxlor.log');
|
||||
$logger_logfile = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/logs/froxlor.log');
|
||||
@touch($logger_logfile);
|
||||
if (empty($logger_logfile) || !is_writable($logger_logfile)) {
|
||||
// not writable in our own directory? Skip
|
||||
break;
|
||||
}
|
||||
if (!@touch($logger_logfile) || !is_writable($logger_logfile)) {
|
||||
// not writable in our own directory? Skip
|
||||
break;
|
||||
}
|
||||
self::$ml->pushHandler(new StreamHandler($logger_logfile, Logger::DEBUG));
|
||||
break;
|
||||
|
||||
@@ -64,7 +64,7 @@ class IdnaWrapper
|
||||
*/
|
||||
public function encode(string $to_encode): string
|
||||
{
|
||||
$to_encode = $this->isUtf8($to_encode) ? $to_encode : utf8_encode($to_encode);
|
||||
$to_encode = $this->isUtf8($to_encode) ? $to_encode : mb_convert_encoding($to_encode, 'UTF-8');
|
||||
try {
|
||||
return $this->idna_converter->encode($to_encode);
|
||||
} catch (InvalidArgumentException $iae) {
|
||||
|
||||
@@ -51,13 +51,13 @@ class AutoUpdate
|
||||
|
||||
/**
|
||||
* returns status about whether there is a newer version
|
||||
*
|
||||
*
|
||||
* 0 = no new version available
|
||||
* 1 = new version available
|
||||
* -1 = remote error message
|
||||
* >1 = local error message
|
||||
*
|
||||
* @return int
|
||||
* @return int
|
||||
*/
|
||||
public static function checkVersion(): int
|
||||
{
|
||||
@@ -68,6 +68,12 @@ class AutoUpdate
|
||||
$channel = '';
|
||||
if (Settings::Get('system.update_channel') == 'testing') {
|
||||
$channel = '/testing';
|
||||
} elseif (Settings::Get('system.update_channel') == 'nightly') {
|
||||
if (empty(Froxlor::BRANDING) || substr(Froxlor::BRANDING, 0, 1) == '-') {
|
||||
$channel = '/nightly.0000000';
|
||||
} else {
|
||||
$channel = '/' . substr(Froxlor::BRANDING, 1);
|
||||
}
|
||||
}
|
||||
$latestversion = HttpClient::urlGet(self::UPDATE_URI . Froxlor::VERSION . $channel, true, 3);
|
||||
} catch (Exception $e) {
|
||||
@@ -81,7 +87,7 @@ class AutoUpdate
|
||||
if (!empty(self::$latestversion['error']) && self::$latestversion['error']) {
|
||||
$result = -1;
|
||||
self::$lasterror = self::$latestversion['message'];
|
||||
} else if (isset(self::$latestversion['has_latest']) && self::$latestversion['has_latest'] == false) {
|
||||
} elseif (isset(self::$latestversion['has_latest']) && self::$latestversion['has_latest'] == false) {
|
||||
$result = 1;
|
||||
}
|
||||
}
|
||||
@@ -145,6 +151,8 @@ class AutoUpdate
|
||||
$zip->close();
|
||||
// success - remove unused archive
|
||||
@unlink($localArchive);
|
||||
// reset cached version check
|
||||
Settings::Set('system.updatecheck_data', '');
|
||||
// wait a bit before we redirect to be sure
|
||||
sleep(3);
|
||||
return 0;
|
||||
|
||||
@@ -26,13 +26,14 @@
|
||||
namespace Froxlor\Install;
|
||||
|
||||
use Exception;
|
||||
use PDO;
|
||||
use Froxlor\Config\ConfigParser;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Install\Install\Core;
|
||||
use Froxlor\System\IPTools;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\UI\Request;
|
||||
use Froxlor\Config\ConfigParser;
|
||||
use Froxlor\Validate\Validate;
|
||||
use Froxlor\System\IPTools;
|
||||
use PDO;
|
||||
|
||||
class Install
|
||||
{
|
||||
@@ -41,34 +42,37 @@ class Install
|
||||
public $maxSteps;
|
||||
public $phpVersion;
|
||||
public $formfield;
|
||||
public string $requiredVersion = '7.4.0';
|
||||
public array $requiredExtensions = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'curl', 'gmp', 'json', 'gd'];
|
||||
public array $suggestedExtensions = ['bcmath', 'zip', 'gnupg'];
|
||||
public array $suggestions = [];
|
||||
public array $criticals = [];
|
||||
public array $loadedExtensions;
|
||||
public array $supportedOS = [];
|
||||
public array $webserverBackend = [
|
||||
'php-fpm' => 'PHP-FPM',
|
||||
'fcgid' => 'FCGID',
|
||||
'fcgid' => 'FCGID (apache2 only)',
|
||||
'mod_php' => 'mod_php (not recommended)',
|
||||
];
|
||||
|
||||
public function __construct(array $cliData = [])
|
||||
{
|
||||
// set actual php version and extensions
|
||||
$this->phpVersion = phpversion();
|
||||
$this->loadedExtensions = get_loaded_extensions();
|
||||
|
||||
// get all supported OS
|
||||
// show list of available distro's
|
||||
$distros = glob(dirname(__DIR__, 3) . '/lib/configfiles/*.xml');
|
||||
$distributions_select[''] = '-';
|
||||
// read in all the distros
|
||||
foreach ($distros as $distribution) {
|
||||
// get configparser object
|
||||
$dist = new ConfigParser($distribution);
|
||||
// store in tmp array
|
||||
$this->supportedOS[str_replace(".xml", "", strtolower(basename($distribution)))] = $dist->getCompleteDistroName();
|
||||
if (in_array('xml', $this->loadedExtensions)) {
|
||||
// read in all the distros
|
||||
foreach ($distros as $distribution) {
|
||||
// get configparser object
|
||||
$dist = new ConfigParser($distribution);
|
||||
// store in tmp array
|
||||
$this->supportedOS[str_replace(".xml", "", strtolower(basename($distribution)))] = $dist->getCompleteDistroName();
|
||||
}
|
||||
// sort by distribution name
|
||||
asort($this->supportedOS);
|
||||
}
|
||||
// sort by distribution name
|
||||
asort($this->supportedOS);
|
||||
|
||||
// guess distribution and webserver to preselect in formfield
|
||||
$webserverBackend = $this->webserverBackend;
|
||||
@@ -84,10 +88,6 @@ class Install
|
||||
$this->extendedView = $cliData['extended'] ?? Request::any('extended', 0);
|
||||
$this->maxSteps = count($this->formfield['install']['sections']);
|
||||
|
||||
// set actual php version and extensions
|
||||
$this->phpVersion = phpversion();
|
||||
$this->loadedExtensions = get_loaded_extensions();
|
||||
|
||||
if (empty($cliData)) {
|
||||
// set global variables
|
||||
UI::twig()->addGlobal('install_mode', true);
|
||||
@@ -99,7 +99,7 @@ class Install
|
||||
}
|
||||
|
||||
// check for url manipulation or wrong step
|
||||
if ((isset($_SESSION['installation']['stepCompleted']) && ($this->currentStep + 1) > ($_SESSION['installation']['stepCompleted'] ?? 0))
|
||||
if ((isset($_SESSION['installation']['stepCompleted']) && $this->currentStep > $_SESSION['installation']['stepCompleted'])
|
||||
|| (!isset($_SESSION['installation']['stepCompleted']) && $this->currentStep > 0)
|
||||
) {
|
||||
$this->currentStep = isset($_SESSION['installation']['stepCompleted']) ? $_SESSION['installation']['stepCompleted'] + 1 : 1;
|
||||
@@ -136,6 +136,7 @@ class Install
|
||||
'section' => $this->formfield['install']['sections']['step' . $this->currentStep] ?? [],
|
||||
'error' => $error ?? null,
|
||||
'extended' => $this->extendedView,
|
||||
'csrf_token' => Froxlor::genSessionId(20),
|
||||
]);
|
||||
|
||||
// output view
|
||||
@@ -151,16 +152,14 @@ class Install
|
||||
if ($this->currentStep <= $this->maxSteps) {
|
||||
// Validate user data
|
||||
$validatedData = $this->validateRequest($formfield['sections']['step' . $this->currentStep]['fields']);
|
||||
// Check database connection (
|
||||
if ($this->currentStep == 1) {
|
||||
// Check database connection
|
||||
$this->checkDatabase($validatedData);
|
||||
}
|
||||
// Check validity of admin user data
|
||||
elseif ($this->currentStep == 2) {
|
||||
} elseif ($this->currentStep == 2) {
|
||||
// Check validity of admin user data
|
||||
$this->checkAdminUser($validatedData);
|
||||
}
|
||||
// Check validity of system data
|
||||
elseif ($this->currentStep == 3) {
|
||||
} elseif ($this->currentStep == 3) {
|
||||
// Check validity of system data
|
||||
$this->checkSystem($validatedData);
|
||||
}
|
||||
$validatedData['stepCompleted'] = ($this->currentStep < $this->maxSteps) ? $this->currentStep : ($this->maxSteps - 1);
|
||||
@@ -192,7 +191,7 @@ class Install
|
||||
private function checkInstallStateFinished(): bool
|
||||
{
|
||||
$core = new Core($_SESSION['installation']);
|
||||
if (isset($_SESSION['installation']['manual_config']) && (int) $_SESSION['installation']['manual_config'] == 1) {
|
||||
if (isset($_SESSION['installation']['manual_config']) && (int)$_SESSION['installation']['manual_config'] == 1) {
|
||||
$core->createUserdataConf();
|
||||
return true;
|
||||
}
|
||||
@@ -200,7 +199,7 @@ class Install
|
||||
$stmt = $pdo->prepare("SELECT `value` FROM `panel_settings` WHERE `settinggroup` = 'panel' AND `varname` = 'is_configured'");
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($result && (int) $result['value'] == 1) {
|
||||
if ($result && (int)$result['value'] == 1) {
|
||||
$core->createUserdataConf();
|
||||
return true;
|
||||
}
|
||||
@@ -223,7 +222,7 @@ class Install
|
||||
}
|
||||
|
||||
// check for required extensions
|
||||
foreach ($this->requiredExtensions as $requiredExtension) {
|
||||
foreach (Requirements::REQUIRED_EXTENSIONS as $requiredExtension) {
|
||||
if (in_array($requiredExtension, $this->loadedExtensions)) {
|
||||
continue;
|
||||
}
|
||||
@@ -231,7 +230,7 @@ class Install
|
||||
}
|
||||
|
||||
// check for suggested extensions
|
||||
foreach ($this->suggestedExtensions as $suggestedExtension) {
|
||||
foreach (Requirements::SUGGESTED_EXTENSIONS as $suggestedExtension) {
|
||||
if (in_array($suggestedExtension, $this->loadedExtensions)) {
|
||||
continue;
|
||||
}
|
||||
@@ -250,11 +249,11 @@ class Install
|
||||
*/
|
||||
private function getInformationText(): string
|
||||
{
|
||||
if (version_compare($this->requiredVersion, PHP_VERSION, "<")) {
|
||||
if (version_compare(Requirements::REQUIRED_VERSION, PHP_VERSION, "<")) {
|
||||
$text = lng('install.phpinfosuccess', [$this->phpVersion]);
|
||||
} else {
|
||||
$text = lng('install.phpinfowarn', [$this->requiredVersion]);
|
||||
$this->criticals[] = lng('install.phpinfoupdate', [$this->phpVersion, $this->requiredVersion]);
|
||||
$text = lng('install.phpinfowarn', [Requirements::REQUIRED_VERSION]);
|
||||
$this->criticals[] = lng('install.phpinfoupdate', [$this->phpVersion, Requirements::REQUIRED_VERSION]);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
@@ -302,9 +301,9 @@ class Install
|
||||
throw new Exception(lng('install.errors.nov4andnov6ip'));
|
||||
} elseif (!empty($serveripv4) && (!Validate::validate_ip2($serveripv4, true, '', false, true) || IPTools::is_ipv6($serveripv4))) {
|
||||
throw new Exception(lng('error.invalidip', [$serveripv4]));
|
||||
} elseif (!empty($serveripv6) && (!Validate::validate_ip2($serveripv6, true, '', false, true) || IPTools::is_ipv6($serveripv6) == false)) {
|
||||
} elseif (!empty($serveripv6) && (!Validate::validate_ip2($serveripv6, true, '', false, true) || !IPTools::is_ipv6($serveripv6))) {
|
||||
throw new Exception(lng('error.invalidip', [$serveripv6]));
|
||||
} elseif (!Validate::validateDomain($servername) && !Validate::validateLocalHostname($servername)) {
|
||||
} elseif (!Validate::validateDomain($servername)) {
|
||||
throw new Exception(lng('install.errors.servernameneedstobevalid'));
|
||||
} elseif (posix_getpwnam($httpuser) === false) {
|
||||
throw new Exception(lng('install.errors.websrvuserdoesnotexist'));
|
||||
@@ -323,6 +322,8 @@ class Install
|
||||
$email = $validatedData['admin_email'] ?? '';
|
||||
$password = $validatedData['admin_pass'] ?? '';
|
||||
$password_confirm = $validatedData['admin_pass_confirm'] ?? '';
|
||||
$useadminmailassender = $validatedData['use_admin_email_as_sender'] ?? '1';
|
||||
$senderemail = $validatedData['sender_email'] ?? '';
|
||||
|
||||
if (!preg_match('/^[^\r\n\t\f\0]*$/D', $name)) {
|
||||
throw new Exception(lng('error.stringformaterror', ['admin_name']));
|
||||
@@ -330,6 +331,8 @@ class Install
|
||||
throw new Exception(lng('error.loginnameiswrong', [$loginname]));
|
||||
} elseif (empty(trim($email)) || !Validate::validateEmail($email)) {
|
||||
throw new Exception(lng('error.emailiswrong', [$email]));
|
||||
} elseif ((int)$useadminmailassender == 0 && !empty(trim($senderemail)) && !Validate::validateEmail($senderemail)) {
|
||||
throw new Exception(lng('error.emailiswrong', [$senderemail]));
|
||||
} elseif (empty($password) || $password != $password_confirm) {
|
||||
throw new Exception(lng('error.newpasswordconfirmerror'));
|
||||
} elseif ($password == $loginname) {
|
||||
@@ -410,7 +413,7 @@ class Install
|
||||
} else {
|
||||
$osrf = explode("\n", file_get_contents('/etc/os-release'));
|
||||
foreach ($osrf as $line) {
|
||||
$osrfline = explode("\n", $line);
|
||||
$osrfline = explode("=", $line);
|
||||
if ($osrfline[0] == 'VERSION_CODENAME') {
|
||||
$os_dist['VERSION_CODENAME'] = $osrfline[1];
|
||||
} else if ($osrfline[0] == 'ID') {
|
||||
|
||||
@@ -301,8 +301,8 @@ class Core
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
if (version_compare($db_root->getAttribute(PDO::ATTR_SERVER_VERSION), '10.0.0', '>=')) {
|
||||
// mariadb compatibility
|
||||
if (version_compare($db_root->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.11', '>=')) {
|
||||
// mariadb & mysql8
|
||||
// create user
|
||||
$stmt = $db_root->prepare("CREATE USER '" . $username . "'@'" . $access_host . "' IDENTIFIED BY :password");
|
||||
$stmt->execute([
|
||||
@@ -314,19 +314,6 @@ class Core
|
||||
"username" => $username,
|
||||
"host" => $access_host
|
||||
]);
|
||||
} elseif (version_compare($db_root->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.11', '>=')) {
|
||||
// mysql8 compatibility
|
||||
// create user
|
||||
$stmt = $db_root->prepare("CREATE USER '" . $username . "'@'" . $access_host . "' IDENTIFIED WITH mysql_native_password BY :password");
|
||||
$stmt->execute([
|
||||
"password" => $password
|
||||
]);
|
||||
// grant privileges
|
||||
$stmt = $db_root->prepare("GRANT ALL ON `" . $database . "`.* TO :username@:host");
|
||||
$stmt->execute([
|
||||
"username" => $username,
|
||||
"host" => $access_host
|
||||
]);
|
||||
} else {
|
||||
// grant privileges
|
||||
$stmt = $db_root->prepare("GRANT ALL PRIVILEGES ON `" . $database . "`.* TO :username@:host IDENTIFIED BY :password");
|
||||
@@ -378,7 +365,14 @@ class Core
|
||||
|
||||
$mainip = !empty($this->validatedData['serveripv6']) ? $this->validatedData['serveripv6'] : $this->validatedData['serveripv4'];
|
||||
|
||||
$this->updateSetting($upd_stmt, 'admin@' . $this->validatedData['servername'], 'panel', 'adminmail');
|
||||
if ($this->validatedData['use_admin_email_as_sender'] == '1') {
|
||||
$adminmail_value = $this->validatedData['admin_email'];
|
||||
} elseif ($this->validatedData['use_admin_email_as_sender'] == '0' && !empty($this->validatedData['sender_email'])) {
|
||||
$adminmail_value = $this->validatedData['sender_email'];
|
||||
} else {
|
||||
$adminmail_value = 'admin@' . $this->validatedData['servername'];
|
||||
}
|
||||
$this->updateSetting($upd_stmt, $adminmail_value, 'panel', 'adminmail');
|
||||
$this->updateSetting($upd_stmt, $mainip, 'system', 'ipaddress');
|
||||
if ($this->validatedData['use_ssl']) {
|
||||
$this->updateSetting($upd_stmt, 1, 'system', 'use_ssl');
|
||||
@@ -576,7 +570,7 @@ class Core
|
||||
'password' => password_hash($this->validatedData['admin_pass'], PASSWORD_DEFAULT),
|
||||
'adminname' => $this->validatedData['admin_name'],
|
||||
'email' => $this->validatedData['admin_email'],
|
||||
'deflang' => 'en' // TODO: set lanuage
|
||||
'deflang' => 'en' // TODO: set language
|
||||
];
|
||||
$ins_stmt = $db_user->prepare("
|
||||
INSERT INTO `" . TABLE_PANEL_ADMINS . "` SET
|
||||
|
||||
10
lib/Froxlor/Install/Requirements.php
Normal file
10
lib/Froxlor/Install/Requirements.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Install;
|
||||
|
||||
class Requirements
|
||||
{
|
||||
const REQUIRED_VERSION = '7.4.0';
|
||||
const REQUIRED_EXTENSIONS = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'pdo_mysql', 'curl', 'gmp', 'json', 'gd'];
|
||||
const SUGGESTED_EXTENSIONS = ['bcmath', 'zip', 'gnupg'];
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class Update
|
||||
self::$update_tasks[self::$task_counter]['result'] = 1;
|
||||
break;
|
||||
default:
|
||||
self::$update_tasks[self::$task_counter]['result'] = -1;
|
||||
self::$update_tasks[self::$task_counter]['result'] = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,8 +220,11 @@ class PhpHelper
|
||||
if (is_dir($data_dirname)) {
|
||||
$data_dirhandle = opendir($data_dirname);
|
||||
while (false !== ($data_filename = readdir($data_dirhandle))) {
|
||||
if ($data_filename != '.' && $data_filename != '..' && $data_filename != '' && substr($data_filename,
|
||||
-4) == '.php') {
|
||||
if ($data_filename != '.'
|
||||
&& $data_filename != '..'
|
||||
&& $data_filename != ''
|
||||
&& substr($data_filename, -4) == '.php'
|
||||
) {
|
||||
$data_files[] = $data_dirname . $data_filename;
|
||||
}
|
||||
}
|
||||
@@ -415,8 +418,8 @@ class PhpHelper
|
||||
*/
|
||||
public static function recursive_array_search(
|
||||
string $needle,
|
||||
array $haystack,
|
||||
array &$keys = [],
|
||||
array $haystack,
|
||||
array &$keys = [],
|
||||
string $currentKey = ''
|
||||
): bool {
|
||||
foreach ($haystack as $key => $value) {
|
||||
@@ -458,6 +461,10 @@ class PhpHelper
|
||||
'directory_password',
|
||||
'ftp_password',
|
||||
'mysql_password',
|
||||
'mysql_root_pass',
|
||||
'mysql_unprivileged_pass',
|
||||
'admin_pass',
|
||||
'admin_pass_confirm',
|
||||
];
|
||||
if (!empty($global)) {
|
||||
$tmp = $global;
|
||||
@@ -557,4 +564,17 @@ class PhpHelper
|
||||
}
|
||||
return $tab . $str;
|
||||
}
|
||||
|
||||
public static function array_merge_recursive_distinct(array &$array1, array &$array2)
|
||||
{
|
||||
$merged = $array1;
|
||||
foreach ($array2 as $key => &$value) {
|
||||
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
|
||||
$merged[$key] = self::array_merge_recursive_distinct($merged[$key], $value);
|
||||
} else {
|
||||
$merged[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $merged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class SImExporter
|
||||
public static function export()
|
||||
{
|
||||
$settings_definitions = [];
|
||||
foreach (PhpHelper::loadConfigArrayDir('./actions/admin/settings/')['groups'] as $group) {
|
||||
foreach (PhpHelper::loadConfigArrayDir(Froxlor::getInstallDir() . '/actions/admin/settings/')['groups'] as $group) {
|
||||
foreach ($group['fields'] as $field) {
|
||||
$settings_definitions[$field['settinggroup']][$field['varname']] = $field;
|
||||
}
|
||||
|
||||
@@ -23,11 +23,36 @@
|
||||
* @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' => []
|
||||
],
|
||||
];
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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,21 @@ 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);
|
||||
}
|
||||
|
||||
// sort users by total traffic
|
||||
usort($users, function ($user_a, $user_b) {
|
||||
if ($user_a['total'] == $user_b['total']) {
|
||||
return 0;
|
||||
}
|
||||
return ($user_a['total'] < $user_b['total']) ? 1 : -1;
|
||||
});
|
||||
|
||||
return [
|
||||
'metrics' => $metrics,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -76,7 +81,7 @@ class Domain
|
||||
return lng('domains.aliasdomain') . ' ' . $attributes['fields']['aliasdomain'];
|
||||
}
|
||||
|
||||
public static function domainExternalLinkInfo(array $attributes)
|
||||
public static function domainExternalLinkInfo(array $attributes): string
|
||||
{
|
||||
$result = '';
|
||||
if ($attributes['fields']['parentdomainid'] != 0) {
|
||||
@@ -84,7 +89,11 @@ class Domain
|
||||
}
|
||||
$result .= '<a href="http://' . $attributes['data'] . '" target="_blank">' . $attributes['data'] . '</a>';
|
||||
// check for statistics if parentdomainid==0 to show stats-link for customers
|
||||
if ((int)UI::getCurrentUser()['adminsession'] == 0 && $attributes['fields']['parentdomainid'] == 0 && $attributes['fields']['deactivated'] == 0) {
|
||||
if ((int)UI::getCurrentUser()['adminsession'] == 0
|
||||
&& $attributes['fields']['parentdomainid'] == 0
|
||||
&& $attributes['fields']['deactivated'] == 0
|
||||
&& preg_match('/^https?:\/\/(.*)/i', $attributes['fields']['documentroot']) == false
|
||||
) {
|
||||
$statsapp = Settings::Get('system.traffictool');
|
||||
$result .= ' <a href="http://' . $attributes['data'] . '/' . $statsapp . '" rel="external" target="_blank" title="' . lng('domains.statstics') . '"><i class="fa-solid fa-chart-line text-secondary"></i></a>';
|
||||
}
|
||||
@@ -190,7 +199,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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -47,4 +47,9 @@ class SSLCertificate
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isNotLetsEncrypt(array $attributes): bool
|
||||
{
|
||||
return (int)$attributes['fields']['letsencrypt'] == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -193,10 +193,14 @@ class Form
|
||||
if (!$do_show) {
|
||||
$fielddata['note'] = lng('serversettings.option_requires_otp');
|
||||
if (!$otp_enabled_system) {
|
||||
$fielddata['disabled'] = true;
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated');
|
||||
} elseif (!$otp_enabled_user) {
|
||||
$fielddata['disabled'] = true;
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated_for_user');
|
||||
}
|
||||
// show field in any case
|
||||
$do_show = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +217,7 @@ class Form
|
||||
{
|
||||
$returnvalue = [];
|
||||
if (is_array($fielddata) && isset($fielddata['type']) && $fielddata['type'] == 'select') {
|
||||
if ((!isset($fielddata['select_var']) || !is_array($fielddata['select_var']) || empty($fielddata['select_var'])) && (isset($fielddata['option_options_method']))) {
|
||||
if ((!is_array($fielddata['select_var']) || empty($fielddata['select_var'])) && (isset($fielddata['option_options_method']))) {
|
||||
$returnvalue['select_var'] = call_user_func($fielddata['option_options_method']);
|
||||
}
|
||||
}
|
||||
@@ -232,8 +236,8 @@ class Form
|
||||
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
|
||||
// Prefetch form fields
|
||||
foreach ($groupdetails['fields'] as $fieldname => $fielddetails) {
|
||||
if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) {
|
||||
$groupdetails['fields'][$fieldname] = self::arrayMergePrefix($fielddetails, $fielddetails['type'], self::prefetchFormFieldData($fieldname, $fielddetails));
|
||||
if (!$only_enabledisable || isset($fielddetails['overview_option'])) {
|
||||
$groupdetails['fields'][$fieldname] = array_merge($fielddetails, self::prefetchFormFieldData($fieldname, $fielddetails));
|
||||
$form['groups'][$groupname]['fields'][$fieldname] = $groupdetails['fields'][$fieldname];
|
||||
}
|
||||
}
|
||||
@@ -343,7 +347,7 @@ class Form
|
||||
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
|
||||
// Save fields
|
||||
foreach ($groupdetails['fields'] as $fieldname => $fielddetails) {
|
||||
if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) {
|
||||
if (!$only_enabledisable || (isset($fielddetails['overview_option']))) {
|
||||
if (isset($changed_fields[$fieldname])) {
|
||||
if (($saved_field = self::saveFormField($fieldname, $fielddetails, self::manipulateFormFieldData($fieldname, $fielddetails, $changed_fields[$fieldname]))) !== false) {
|
||||
$saved_fields = array_merge($saved_fields, $saved_field);
|
||||
@@ -360,24 +364,7 @@ class Form
|
||||
// Save form
|
||||
return self::saveForm($form, $saved_fields);
|
||||
}
|
||||
}
|
||||
|
||||
private static function arrayMergePrefix($array1, $key_prefix, $array2)
|
||||
{
|
||||
if (is_array($array1) && is_array($array2)) {
|
||||
if ($key_prefix != '') {
|
||||
foreach ($array2 as $key => $value) {
|
||||
$array1[$key_prefix . '_' . $key] = $value;
|
||||
unset($array2[$key]);
|
||||
}
|
||||
unset($array2);
|
||||
return $array1;
|
||||
} else {
|
||||
return array_merge($array1, $array2);
|
||||
}
|
||||
} else {
|
||||
return $array1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getFormFieldData($fieldname, $fielddata, &$input)
|
||||
|
||||
@@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user