diff --git a/.github/actions/test-api/admin/environment/point/action.yml b/.github/actions/test-api/admin/environment/point/action.yml
new file mode 100644
index 000000000..47fcbe665
--- /dev/null
+++ b/.github/actions/test-api/admin/environment/point/action.yml
@@ -0,0 +1,23 @@
+name: API - admin - environment point tests
+description: Run environment point tests as admin user
+
+inputs:
+ api-host:
+ description: API test host URL
+ required: true
+ api-key:
+ description: Admin API key
+ required: true
+
+runs:
+ using: composite
+ steps:
+ - name: (admin) Point environment prod to snapshot 1
+ shell: bash
+ run: |
+ RESULT=$(curl --fail-with-body -L -s -X PATCH -H "Authorization: Bearer ${{ inputs.api-key }}" -H "Content-Type: application/json" -d '{"snapshot":1}' ${{ inputs.api-host }}/api/v2/environment/prod/point)
+ if [ $? -ne 0 ]; then
+ echo "Failed to point environment"
+ exit 1
+ fi
+ echo "$RESULT" | jq
diff --git a/.github/actions/test-api/admin/snapshot/diff/action.yml b/.github/actions/test-api/admin/snapshot/diff/action.yml
new file mode 100644
index 000000000..f771aa470
--- /dev/null
+++ b/.github/actions/test-api/admin/snapshot/diff/action.yml
@@ -0,0 +1,23 @@
+name: API - admin - snapshot diff tests
+description: Run snapshot diff tests as admin user
+
+inputs:
+ api-host:
+ description: API test host URL
+ required: true
+ api-key:
+ description: Admin API key
+ required: true
+
+runs:
+ using: composite
+ steps:
+ - name: (admin) Snapshot diff
+ shell: bash
+ run: |
+ RESULT=$(curl --fail-with-body -L -s -X GET -H "Authorization: Bearer ${{ inputs.api-key }}" ${{ inputs.api-host }}/api/v2/snapshot/1/diff/3)
+ if [ $? -ne 0 ]; then
+ echo "Failed to get snapshot diff"
+ exit 1
+ fi
+ echo "$RESULT" | jq
diff --git a/.github/actions/test-api/admin/tasks/action.yml b/.github/actions/test-api/admin/tasks/action.yml
new file mode 100644
index 000000000..2b72ccead
--- /dev/null
+++ b/.github/actions/test-api/admin/tasks/action.yml
@@ -0,0 +1,63 @@
+name: API - admin - tasks listing tests
+description: Run tasks listing tests as admin user
+
+inputs:
+ api-host:
+ description: API test host URL
+ required: true
+ api-key:
+ description: Admin API key
+ required: true
+
+runs:
+ using: composite
+ steps:
+ - name: (admin) List all tasks
+ shell: bash
+ run: |
+ RESULT=$(curl --fail-with-body -L -s -X GET -H "Authorization: Bearer ${{ inputs.api-key }}" ${{ inputs.api-host }}/api/v2/tasks/)
+ if [ $? -ne 0 ]; then
+ echo "Failed to list all tasks"
+ exit 1
+ fi
+ echo "$RESULT" | jq
+
+ - name: (admin) List running tasks
+ shell: bash
+ run: |
+ RESULT=$(curl --fail-with-body -L -s -X GET -H "Authorization: Bearer ${{ inputs.api-key }}" ${{ inputs.api-host }}/api/v2/tasks/running)
+ if [ $? -ne 0 ]; then
+ echo "Failed to list running tasks"
+ exit 1
+ fi
+ echo "$RESULT" | jq
+
+ - name: (admin) List queued tasks
+ shell: bash
+ run: |
+ RESULT=$(curl --fail-with-body -L -s -X GET -H "Authorization: Bearer ${{ inputs.api-key }}" ${{ inputs.api-host }}/api/v2/tasks/queued)
+ if [ $? -ne 0 ]; then
+ echo "Failed to list queued tasks"
+ exit 1
+ fi
+ echo "$RESULT" | jq
+
+ - name: (admin) List scheduled tasks
+ shell: bash
+ run: |
+ RESULT=$(curl --fail-with-body -L -s -X GET -H "Authorization: Bearer ${{ inputs.api-key }}" ${{ inputs.api-host }}/api/v2/tasks/scheduled)
+ if [ $? -ne 0 ]; then
+ echo "Failed to list scheduled tasks"
+ exit 1
+ fi
+ echo "$RESULT" | jq
+
+ - name: (admin) List done tasks
+ shell: bash
+ run: |
+ RESULT=$(curl --fail-with-body -L -s -X GET -H "Authorization: Bearer ${{ inputs.api-key }}" ${{ inputs.api-host }}/api/v2/tasks/done)
+ if [ $? -ne 0 ]; then
+ echo "Failed to list done tasks"
+ exit 1
+ fi
+ echo "$RESULT" | jq
diff --git a/.github/scripts/validate-source-repository-templates.py b/.github/scripts/validate-source-repository-templates.py
new file mode 100644
index 000000000..a7e1d0750
--- /dev/null
+++ b/.github/scripts/validate-source-repository-templates.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+from pathlib import Path
+import sys
+
+import yaml
+
+
+ROOT = Path(__file__).resolve().parents[2]
+TEMPLATES_DIR = ROOT / "www" / "templates" / "source-repositories"
+VALID_TYPES = {"deb", "rpm"}
+
+
+def error(errors, path, message):
+ errors.append(f"{path.relative_to(ROOT)}: {message}")
+
+
+def is_non_empty_string(value):
+ return isinstance(value, str) and value.strip() != ""
+
+
+def require_non_empty_string(errors, path, value, field):
+ if not is_non_empty_string(value):
+ error(errors, path, f"missing or empty `{field}`")
+
+
+def require_list(errors, path, value, field):
+ if not isinstance(value, list) or len(value) == 0:
+ error(errors, path, f"`{field}` must be a non-empty list")
+ return False
+
+ return True
+
+
+def validate_gpgkeys(errors, path, gpgkeys, context):
+ if not require_list(errors, path, gpgkeys, f"{context}.gpgkeys"):
+ return
+
+ for index, gpgkey in enumerate(gpgkeys):
+ key_context = f"{context}.gpgkeys[{index}]"
+
+ if not isinstance(gpgkey, dict):
+ error(errors, path, f"`{key_context}` must be a mapping")
+ continue
+
+ fingerprint = gpgkey.get("fingerprint")
+ link = gpgkey.get("link")
+
+ if not is_non_empty_string(fingerprint) and not is_non_empty_string(link):
+ error(errors, path, f"`{key_context}` must define `fingerprint` or `link`")
+
+
+def validate_deb_distribution(errors, path, distribution, context):
+ if not isinstance(distribution, dict):
+ error(errors, path, f"`{context}` must be a mapping")
+ return
+
+ require_non_empty_string(errors, path, distribution.get("name"), f"{context}.name")
+ require_non_empty_string(errors, path, distribution.get("description"), f"{context}.description")
+
+ components = distribution.get("components")
+ if require_list(errors, path, components, f"{context}.components"):
+ for index, component in enumerate(components):
+ component_context = f"{context}.components[{index}]"
+
+ if not isinstance(component, dict):
+ error(errors, path, f"`{component_context}` must be a mapping")
+ continue
+
+ require_non_empty_string(errors, path, component.get("name"), f"{component_context}.name")
+
+ validate_gpgkeys(errors, path, distribution.get("gpgkeys"), context)
+
+
+def validate_rpm_releasever(errors, path, releasever, context):
+ if not isinstance(releasever, dict):
+ error(errors, path, f"`{context}` must be a mapping")
+ return
+
+ if releasever.get("name") in (None, ""):
+ error(errors, path, f"missing or empty `{context}.name`")
+
+ require_non_empty_string(errors, path, releasever.get("description"), f"{context}.description")
+ validate_gpgkeys(errors, path, releasever.get("gpgkeys"), context)
+
+
+def validate_repository(errors, path, repository, index, expected_type):
+ context = f"repositories[{index}]"
+
+ if not isinstance(repository, dict):
+ error(errors, path, f"`{context}` must be a mapping")
+ return
+
+ require_non_empty_string(errors, path, repository.get("name"), f"{context}.name")
+ require_non_empty_string(errors, path, repository.get("description"), f"{context}.description")
+ require_non_empty_string(errors, path, repository.get("url"), f"{context}.url")
+
+ repo_type = repository.get("type")
+ if repo_type != expected_type:
+ error(errors, path, f"`{context}.type` must be `{expected_type}`")
+
+ if expected_type == "deb":
+ distributions = repository.get("distributions")
+ if require_list(errors, path, distributions, f"{context}.distributions"):
+ for distribution_index, distribution in enumerate(distributions):
+ validate_deb_distribution(errors, path, distribution, f"{context}.distributions[{distribution_index}]")
+
+ if expected_type == "rpm":
+ releasevers = repository.get("releasever")
+ if require_list(errors, path, releasevers, f"{context}.releasever"):
+ for releasever_index, releasever in enumerate(releasevers):
+ validate_rpm_releasever(errors, path, releasever, f"{context}.releasever[{releasever_index}]")
+
+
+def validate_file(path):
+ errors = []
+
+ try:
+ with path.open("r", encoding="utf-8") as stream:
+ data = yaml.safe_load(stream)
+ except yaml.YAMLError as exception:
+ error(errors, path, f"invalid YAML: {exception}")
+ return errors
+
+ if not isinstance(data, dict):
+ error(errors, path, "root document must be a mapping")
+ return errors
+
+ require_non_empty_string(errors, path, data.get("description"), "description")
+
+ template_type = data.get("type")
+ if template_type not in VALID_TYPES:
+ error(errors, path, "`type` must be `deb` or `rpm`")
+
+ repositories = data.get("repositories")
+ if require_list(errors, path, repositories, "repositories") and template_type in VALID_TYPES:
+ names = set()
+
+ for index, repository in enumerate(repositories):
+ if isinstance(repository, dict) and repository.get("name") in names:
+ error(errors, path, f"duplicate repository name `{repository.get('name')}`")
+ elif isinstance(repository, dict) and is_non_empty_string(repository.get("name")):
+ names.add(repository.get("name"))
+
+ validate_repository(errors, path, repository, index, template_type)
+
+ return errors
+
+
+def main():
+ paths = sorted(TEMPLATES_DIR.glob("*/*.yml"))
+
+ if not paths:
+ print("No source repository templates found.", file=sys.stderr)
+ return 1
+
+ errors = []
+
+ for path in paths:
+ errors.extend(validate_file(path))
+
+ if errors:
+ print("Source repository template validation failed:", file=sys.stderr)
+
+ for validation_error in errors:
+ print(f"- {validation_error}", file=sys.stderr)
+
+ return 1
+
+ print(f"Validated {len(paths)} source repository templates.")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml
index c380c85ea..e36aaad01 100644
--- a/.github/workflows/phpcs.yml
+++ b/.github/workflows/phpcs.yml
@@ -1,4 +1,4 @@
-name: PHP_CodeSniffer
+name: PHP CodeSniffer
on:
push:
diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
new file mode 100644
index 000000000..e722514af
--- /dev/null
+++ b/.github/workflows/phpstan.yml
@@ -0,0 +1,28 @@
+name: PHPStan
+
+on:
+ push:
+ branches: [ main, devel ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ phpstan:
+ name: Run PHPStan
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.3'
+
+ - name: Run PHPStan
+ run: |
+ php $GITHUB_WORKSPACE/www/libs/vendor/bin/phpstan analyse \
+ -c $GITHUB_WORKSPACE/phpstan.neon \
+ $GITHUB_WORKSPACE/www
diff --git a/.github/workflows/source-repository-templates.yml b/.github/workflows/source-repository-templates.yml
new file mode 100644
index 000000000..470abac7f
--- /dev/null
+++ b/.github/workflows/source-repository-templates.yml
@@ -0,0 +1,27 @@
+name: Validate source repository templates
+
+on:
+ push:
+ branches: [ devel ]
+ pull_request:
+ branches: [ main, devel ]
+
+jobs:
+ validate:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+
+ - name: Setup Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.x'
+
+ - name: Install dependencies
+ run: python -m pip install PyYAML
+
+ - name: Validate source repository templates
+ run: python .github/scripts/validate-source-repository-templates.py
diff --git a/.github/workflows/test-api.yml b/.github/workflows/test-api.yml
index 56a691021..38f21b2dd 100644
--- a/.github/workflows/test-api.yml
+++ b/.github/workflows/test-api.yml
@@ -54,6 +54,18 @@ jobs:
#
# Run tests as admin user
#
+ - name: Run admin tasks tests
+ uses: ./.github/actions/test-api/admin/tasks
+ with:
+ api-host: ${{ secrets.API_TEST_HOST }}
+ api-key: ${{ secrets.API_TEST_ADMIN_API_KEY }}
+
+ - name: Run admin environment point test
+ uses: ./.github/actions/test-api/admin/environment/point
+ with:
+ api-host: ${{ secrets.API_TEST_HOST }}
+ api-key: ${{ secrets.API_TEST_ADMIN_API_KEY }}
+
- name: Run admin repositories tests
uses: ./.github/actions/test-api/admin/repositories
with:
@@ -72,6 +84,12 @@ jobs:
api-host: ${{ secrets.API_TEST_HOST }}
api-key: ${{ secrets.API_TEST_ADMIN_API_KEY }}
+ - name: Run admin snapshot diff tests
+ uses: ./.github/actions/test-api/admin/snapshot/diff
+ with:
+ api-host: ${{ secrets.API_TEST_HOST }}
+ api-key: ${{ secrets.API_TEST_ADMIN_API_KEY }}
+
- name: Run admin package list tests
uses: ./.github/actions/test-api/admin/snapshot/package/list
with:
@@ -90,7 +108,7 @@ jobs:
api-host: ${{ secrets.API_TEST_HOST }}
api-key: ${{ secrets.API_TEST_ADMIN_API_KEY }}
- - name: Run admin host tests
+ - name: Run admin single host tests
uses: ./.github/actions/test-api/admin/host
with:
api-host: ${{ secrets.API_TEST_HOST }}
@@ -124,6 +142,7 @@ jobs:
api-host: ${{ secrets.API_TEST_HOST }}
api-key: ${{ secrets.API_TEST_TEST_USER1_API_KEY }}
+
#
# Run tests as a regular user: test-user2
# This user has permissions to upload packages and rebuild snapshots metadata
diff --git a/README.md b/README.md
index 6b45ddc1b..4a814d38c 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,20 @@
-
+
-
+
-
+
+
A web mirroring tool for rpm and deb package repositories
+
-# Repomanager - A web mirroring tool for `rpm` and `deb` package repositories
+[](https://github.com/lbr38/repomanager/actions/workflows/tasks.yml)
+[](https://github.com/lbr38/repomanager/actions/workflows/test-api.yml)
+[](https://github.com/lbr38/repomanager/actions/workflows/source-repository-templates.yml)
+[](https://github.com/lbr38/repomanager/actions/workflows/phpcs.yml)
+[](https://github.com/lbr38/repomanager/actions/workflows/phpstan.yml)
## Main features
@@ -49,5 +55,5 @@ This will help you with **installing** and getting started with Repomanager.
## Contact
- For bug reports, issues, or feature requests, please open a new issue in the GitHub [Issues](https://github.com/lbr38/repomanager/issues) section
-- A Discord channel is available [here](https://discord.gg/34yeNsMmkQ) for questions or quick help/debugging (English and French spoken)
+- A Discord channel is available [here](https://discord.gg/xuNxGv9U9) for questions or quick help/debugging (English and French spoken)
- You can also contact me at [repomanager@protonmail.com](mailto:repomanager@protonmail.com) (English or French spoken)
diff --git a/documentation/docs/index.md b/documentation/docs/index.md
index 35f01d7e1..42b5ada6d 100644
--- a/documentation/docs/index.md
+++ b/documentation/docs/index.md
@@ -49,7 +49,7 @@ This will help you with **installing** and getting started with Repomanager.
## Contact
- For bug reports, issues, or feature requests, please open a new issue in the GitHub [Issues](https://github.com/lbr38/repomanager/issues) section
-- A Discord channel is available [here](https://discord.gg/34yeNsMmkQ) for questions or quick help/debugging (English and French spoken)
+- A Discord channel is available [here](https://discord.gg/xuNxGv9U9) for questions or quick help/debugging (English and French spoken)
- You can also contact me at [repomanager@protonmail.com](mailto:repomanager@protonmail.com) (English or French spoken)
diff --git a/documentation/docs/reference/api.md b/documentation/docs/reference/api.md
index a896be2d7..4f0fbcca4 100644
--- a/documentation/docs/reference/api.md
+++ b/documentation/docs/reference/api.md
@@ -268,9 +268,47 @@ Once generated, copy the key and keep it safe. This key is used to authenticate
```
+
+
+
/snapshot/<SNAPSHOT_ID>/diff/<SNAPSHOT_ID_2> GET
+
<APIKEY>
+
+
Compare two snapshots and list added/removed packages
+
diff --git a/documentation/requirements.txt b/documentation/requirements.txt
index d7b890c37..0bae87c26 100644
--- a/documentation/requirements.txt
+++ b/documentation/requirements.txt
@@ -5,7 +5,7 @@ charset-normalizer==3.4.6
click==8.3.1
colorama==0.4.6
ghp-import==2.1.0
-idna==3.11
+idna==3.15
Jinja2==3.1.6
Markdown==3.10.2
MarkupSafe==3.0.3
@@ -19,11 +19,11 @@ paginate==0.5.7
pathspec==1.0.4
platformdirs==4.9.4
Pygments==2.19.2
-pymdown-extensions==10.21
+pymdown-extensions==10.21.3
python-dateutil==2.9.0.post0
PyYAML==6.0.3
pyyaml_env_tag==1.1
requests==2.33.0
six==1.17.0
-urllib3==2.6.3
+urllib3==2.7.0
watchdog==6.0.0
diff --git a/notifications/notifications.json b/notifications/notifications.json
index 6f7d190c6..53ebac2bc 100644
--- a/notifications/notifications.json
+++ b/notifications/notifications.json
@@ -5,7 +5,7 @@
},
"NT02" : {
"title" : "New! Discord channel",
- "message" : "A Discord channel is available here for any questions or quick help/debugging (English or French spoken)."
+ "message" : "A Discord channel is available here for any questions or quick help/debugging (English or French spoken)."
},
"NT03" : {
"title" : "You like it? Star it! ⭐",
diff --git a/phpstan-bootstrap.php b/phpstan-bootstrap.php
new file mode 100644
index 000000000..c5fa0dde6
--- /dev/null
+++ b/phpstan-bootstrap.php
@@ -0,0 +1,6 @@
+ [
- 'env'
+ 'env',
+ 'advanced-params' // Advanced params include package include/exclude and metadata custom fields, it is optional
],
// Some required params are conditional
@@ -38,17 +39,6 @@
]
],
- // Some optional params are conditional
- 'conditional-optional-params' => [
- // Based on repo type
- 'repo-type' => [
- 'mirror' => [
- 'package-include',
- 'package-exclude'
- ]
- ]
- ],
-
// Conditional params must be compared with current repository values
'conditional-compare-with' => 'current-repo',
];
diff --git a/www/controllers/Api/Api.php b/www/controllers/Api/Api.php
index 98e9be03a..58495231c 100644
--- a/www/controllers/Api/Api.php
+++ b/www/controllers/Api/Api.php
@@ -23,9 +23,9 @@ public function __construct()
/**
* Exit if method is not allowed
*/
- if ($_SERVER['REQUEST_METHOD'] != 'GET' and $_SERVER['REQUEST_METHOD'] != 'POST' and $_SERVER['REQUEST_METHOD'] != 'PUT' and $_SERVER['REQUEST_METHOD'] != 'DELETE') {
+ if (!in_array($_SERVER['REQUEST_METHOD'], ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])) {
http_response_code(405);
- echo json_encode(["return" => "405", "message_error" => array('Method not allowed')]);
+ echo json_encode(["return" => "405", "message_error" => ['Method not allowed']]);
exit;
}
diff --git a/www/controllers/Api/Environment/Environment.php b/www/controllers/Api/Environment/Environment.php
new file mode 100644
index 000000000..c56a6c23c
--- /dev/null
+++ b/www/controllers/Api/Environment/Environment.php
@@ -0,0 +1,73 @@
+method == 'PATCH') {
+ /**
+ * Point an environment to a snapshot
+ * https://repomanager.mydomain.net/api/v2/env/prod/point?snapshot=1
+ */
+ if (!empty($this->uri[5]) and $this->uri[5] == 'point') {
+ $repoSnapshotController = new Snapshot();
+ $envController = new EnvController();
+
+ // Check that the user has permission to point the environment
+ if (!RepoPermission::allowedAction('env')) {
+ throw new Exception('You are not allowed to point an environment to a snapshot');
+ }
+
+ // Check if the snapshot parameter is provided
+ if (empty($this->data['snapshot'])) {
+ throw new Exception('Snapshot Id is required');
+ }
+
+ // Retrieve the environment from the URI
+ $env = Validate::string($this->uri[4]);
+
+ // Retrieve the snapshot Id from the query parameters
+ $snapId = $this->data['snapshot'];
+
+ // Check if the environment exists
+ if (!$envController->exists($env)) {
+ throw new Exception('Environment ' . $env . ' does not exist');
+ }
+
+ // Check if the snapshot exists
+ if (!$repoSnapshotController->exists($snapId)) {
+ throw new Exception('Snapshot #' . $snapId . ' does not exist');
+ }
+
+ // Create a json file that defines the task to execute
+ $params = [];
+ $params['action'] = 'env';
+ $params['snap-id'] = $snapId;
+ $params['env'] = ['prod'];
+ $params['description'] = '';
+ $params['schedule']['scheduled'] = 'false';
+
+ // Execute the task
+ $taskController = new Task();
+ $taskId = $taskController->execute([$params]);
+
+ return [
+ 'rc' => 202,
+ 'results' => 'Environment point started',
+ 'task-id' => $taskId
+ ];
+ }
+ }
+
+ throw new Exception('Invalid request');
+ }
+}
diff --git a/www/controllers/Api/Host/Host.php b/www/controllers/Api/Host/Host.php
index ecab70d52..f30f5cf8b 100644
--- a/www/controllers/Api/Host/Host.php
+++ b/www/controllers/Api/Host/Host.php
@@ -2,35 +2,62 @@
namespace Controllers\Api\Host;
+use Controllers\Host\Package\Package as HostPackage;
+use Controllers\Host\Registering as HostRegistering;
use Controllers\Host\Host as HostController;
+use Controllers\Host\Update as HostUpdate;
use Exception;
class Host extends \Controllers\Api\Controller
{
public function execute(): array
{
- $myhost = new HostController();
- $hostRegisteringController = new \Controllers\Host\Registering();
- $hostUpdateController = new \Controllers\Host\Update();
+ $hostController = new HostController();
+ $hostRegisteringController = new HostRegistering();
+ $hostUpdateController = new HostUpdate();
/**
* If a component is specified
* https://repomanager.mydomain.net/api/v2/host/
*/
if (!empty($this->uri[4])) {
+ if (is_numeric($this->uri[4])) {
+ // Get host ID from URI
+ $id = (int)$this->uri[4];
+
+ // Check if host exists
+ if (!$hostController->existsId($id)) {
+ throw new Exception('Host #' . $id . ' does not exist.');
+ }
+
+ /**
+ * Get installed packages for a host
+ * https://repomanager.mydomain.net/api/v2/host/{id}/installed
+ */
+ if ($this->uri[5] == 'installed' and $this->method == 'GET') {
+ $hostPackageController = new HostPackage($id);
+ return ['results' => $hostPackageController->getInstalled()];
+ }
+
+ /**
+ * Get available updates for a host
+ * https://repomanager.mydomain.net/api/v2/host/{id}/updates
+ */
+ if (in_array($this->uri[5], ['available', 'updates']) and $this->method == 'GET') {
+ $hostPackageController = new HostPackage($id);
+ return ['results' => $hostPackageController->getAvailable()];
+ }
+ }
+
/**
* Register a new host
* https://repomanager.mydomain.net/api/v2/host/registering
*/
if ($this->uri[4] == 'registering' and $this->method == 'POST') {
- /**
- * Try registering the host
- */
+ // Try registering the host
$result = $hostRegisteringController->register($this->data['ip'], $this->data['hostname']);
- /**
- * If register is successful, then return generated id and token
- */
+ // If register is successful, then return generated id and token
return [
'rc' => 201,
'message' => ['Host successfully registered.'],
@@ -41,13 +68,11 @@ public function execute(): array
];
}
- /**
- * Retrieve host database Id if its authId has been specified, it will be useful for next tasks
- */
+ // Retrieve host database Id if its authId has been specified, it will be useful for next tasks
if (defined('HOST_AUTH_ID')) {
try {
// Get host database Id from its authId
- $this->hostId = $myhost->getIdByAuth(HOST_AUTH_ID);
+ $this->hostId = $hostController->getIdByAuth(HOST_AUTH_ID);
if (empty($this->hostId)) {
throw new Exception('No host Id has been found from this authId identifier');
@@ -68,10 +93,10 @@ public function execute(): array
if ($this->uri[4] == 'registering' and $this->method == 'DELETE') {
// Using authId and token authentication (host Id required)
if (defined('HOST_AUTH_ID') and !empty($this->hostId)) {
- $myhost->deleteById($this->hostId);
+ $hostController->deleteById($this->hostId);
// Using Api key authentication (hostname required)
} else if (defined('API_KEY') and !empty($this->data['hostname'])) {
- $myhost->deleteByHostname($this->data['hostname']);
+ $hostController->deleteByHostname($this->data['hostname']);
} else {
throw new Exception('To unregister a host, you must use either host authId+token authentication or Api key authentication with hostname specified.');
}
@@ -91,9 +116,7 @@ public function execute(): array
if ($this->uri[4] == 'status' and $this->method == 'PUT') {
$message = [];
- /**
- * If hostname has been specified then update it in database
- */
+ // If hostname has been specified then update it in database
if (!empty($this->data['hostname'])) {
try {
$hostUpdateController->updateHostname($this->hostId, $this->data['hostname']);
@@ -103,9 +126,7 @@ public function execute(): array
}
}
- /**
- * If OS has been specified then update it in database
- */
+ // If OS has been specified then update it in database
if (!empty($this->data['os'])) {
try {
$hostUpdateController->updateOS($this->hostId, $this->data['os']);
@@ -115,9 +136,7 @@ public function execute(): array
}
}
- /**
- * If OS release version has been specified then update it in database
- */
+ // If OS release version has been specified then update it in database
if (!empty($this->data['os_version'])) {
try {
$hostUpdateController->updateOsVersion($this->hostId, $this->data['os_version']);
@@ -127,9 +146,7 @@ public function execute(): array
}
}
- /**
- * If OS family has been specified then update it in database
- */
+ // If OS family has been specified then update it in database
if (!empty($this->data['os_family'])) {
try {
$hostUpdateController->updateOsFamily($this->hostId, $this->data['os_family']);
@@ -139,9 +156,7 @@ public function execute(): array
}
}
- /**
- * If virtualization type has been specified then update it in database
- */
+ // If virtualization type has been specified then update it in database
if (!empty($this->data['type'])) {
try {
$hostUpdateController->updateType($this->hostId, $this->data['type']);
@@ -151,9 +166,7 @@ public function execute(): array
}
}
- /**
- * If CPU has been specified then update it in database
- */
+ // If CPU has been specified then update it in database
if (!empty($this->data['cpu'])) {
try {
$hostUpdateController->updateCpu($this->hostId, $this->data['cpu']);
@@ -163,9 +176,7 @@ public function execute(): array
}
}
- /**
- * If RAM has been specified then update it in database
- */
+ // If RAM has been specified then update it in database
if (!empty($this->data['ram'])) {
try {
$hostUpdateController->updateRam($this->hostId, $this->data['ram']);
@@ -185,9 +196,7 @@ public function execute(): array
}
}
- /**
- * If kernel has been specified then update it in database
- */
+ // If kernel has been specified then update it in database
if (!empty($this->data['kernel'])) {
try {
$hostUpdateController->updateKernel($this->hostId, $this->data['kernel']);
@@ -197,9 +206,7 @@ public function execute(): array
}
}
- /**
- * If architecture has been specified then update it in database
- */
+ // If architecture has been specified then update it in database
if (!empty($this->data['arch'])) {
try {
$hostUpdateController->updateArch($this->hostId, $this->data['arch']);
@@ -209,9 +216,7 @@ public function execute(): array
}
}
- /**
- * If profile has been specified then update it in database
- */
+ // If profile has been specified then update it in database
if (!empty($this->data['profile'])) {
try {
$hostUpdateController->updateProfile($this->hostId, $this->data['profile']);
@@ -221,9 +226,7 @@ public function execute(): array
}
}
- /**
- * If environment has been specified then update it in database
- */
+ // If environment has been specified then update it in database
if (!empty($this->data['env'])) {
try {
$hostUpdateController->updateEnv($this->hostId, $this->data['env']);
@@ -233,9 +236,7 @@ public function execute(): array
}
}
- /**
- * If agent status has been specified then update it in database
- */
+ // If agent status has been specified then update it in database
if (!empty($this->data['agent_status'])) {
try {
$hostUpdateController->updateAgentStatus($this->hostId, $this->data['agent_status']);
@@ -245,9 +246,7 @@ public function execute(): array
}
}
- /**
- * If linupdate version has been specified then update it in database
- */
+ // If linupdate version has been specified then update it in database
if (!empty($this->data['linupdate_version'])) {
try {
$hostUpdateController->updateLinupdateVersion($this->hostId, $this->data['linupdate_version']);
@@ -257,9 +256,7 @@ public function execute(): array
}
}
- /**
- * If reboot required status has been specified then update it in database
- */
+ // If reboot required status has been specified then update it in database
if (!empty($this->data['reboot_required'])) {
try {
$hostUpdateController->updateRebootRequired($this->hostId, $this->data['reboot_required']);
@@ -269,9 +266,7 @@ public function execute(): array
}
}
- /**
- * If uptime has been specified then update it in database
- */
+ // If uptime has been specified then update it in database
if (!empty($this->data['uptime'])) {
try {
$hostUpdateController->updateUptime($this->hostId, $this->data['uptime']);
@@ -326,9 +321,7 @@ public function execute(): array
* https://repomanager.mydomain.net/api/v2/host/packages/event
*/
if ($this->uri[5] == 'event' and $this->method == 'PUT') {
- /**
- * If packages events history has been specified then update it in database
- */
+ // If packages events history has been specified then update it in database
if (!empty($this->data['events'])) {
try {
$hostEventController->setHistory($this->data['events']);
diff --git a/www/controllers/Api/Snapshot/Snapshot.php b/www/controllers/Api/Snapshot/Snapshot.php
index a28dce151..66d9b5412 100644
--- a/www/controllers/Api/Snapshot/Snapshot.php
+++ b/www/controllers/Api/Snapshot/Snapshot.php
@@ -5,6 +5,7 @@
use Controllers\User\Permission\Repo as RepoPermission;
use Controllers\Repo\Snapshot\Package;
use Controllers\Utils\Convert;
+use Controllers\Task\Task;
use Exception;
class Snapshot extends \Controllers\Api\Controller
@@ -43,7 +44,7 @@ public function execute(): array
if ($this->method == 'GET') {
/**
* List snapshot details
- * https://repomanager.mydomain.net/api/v2/snapshot/$this->snapId/
+ * https://repomanager.mydomain.net/api/v2/snapshot/{snap-id}/
*/
if (empty($this->uri[5])) {
return ['results' => $repoSnapshotController->getById($this->snapId)];
@@ -51,17 +52,34 @@ public function execute(): array
/**
* List all packages of a snapshot
- * https://repomanager.mydomain.net/api/v2/snapshot/$this->snapId/packages
+ * https://repomanager.mydomain.net/api/v2/snapshot/{snap-id}/packages
*/
if ($this->uri[5] == 'packages') {
return ['results' => $repoSnapshotPackageController->list()];
}
+
+ /**
+ * Make a diff between two snapshots
+ * https://repomanager.mydomain.net/api/v2/snapshot/{snap-id}/diff/{snap2-id}
+ */
+ if ($this->uri[5] == 'diff') {
+ // Check if second snapshot ID is provided in query parameters and is valid
+ if (empty($this->uri[6])) {
+ throw new Exception('No second snapshot Id specified for diff');
+ }
+
+ if (!is_numeric($this->uri[6])) {
+ throw new Exception('Invalid snapshot Id #' . $this->uri[6]);
+ }
+
+ return ['results' => $repoSnapshotController->diff($this->snapId, $this->uri[6])];
+ }
}
if ($this->method == 'POST') {
/**
* Upload packages to a snapshot
- * https://repomanager.mydomain.net/api/v2/snapshot/$this->snapId/upload
+ * https://repomanager.mydomain.net/api/v2/snapshot/{snap-id}/upload
*/
if ($this->uri[5] == 'upload' and !empty($this->postFiles)) {
$overwrite = false;
@@ -87,14 +105,14 @@ public function execute(): array
if ($this->method == 'PUT') {
/**
* Rebuild a snapshot
- * https://repomanager.mydomain.net/api/v2/snapshot/$this->snapId/rebuild
+ * https://repomanager.mydomain.net/api/v2/snapshot/{snap-id}/rebuild
*/
if ($this->uri[5] == 'rebuild' and !empty($this->data['gpgSign'])) {
/**
* Same code as controllers/ajax/browse.php
* TODO : find a way to not duplicate code
*/
- $mytask = new \Controllers\Task\Task();
+ $taskController = new Task();
if (!RepoPermission::allowedAction('rebuild')) {
throw new Exception('You are not allowed to rebuild a repository snapshot');
@@ -117,13 +135,12 @@ public function execute(): array
$params['schedule']['scheduled'] = 'false';
// Execute the task
- $mytask->execute([$params]);
-
- unset($mytask);
+ $taskId = $taskController->execute([$params]);
return [
'rc' => 202,
- 'results' => 'Snapshot metadata rebuild started'
+ 'results' => 'Snapshot metadata rebuild started',
+ 'task-id' => $taskId
];
}
}
@@ -131,7 +148,7 @@ public function execute(): array
if ($this->method == 'DELETE') {
/**
* Delete packages from a snapshot
- * https://repomanager.mydomain.net/api/v2/snapshot/$this->snapId/packages
+ * https://repomanager.mydomain.net/api/v2/snapshot/{snap-id}/packages
*/
if ($this->uri[5] == 'packages' and !empty($this->data['packages'])) {
return ['results' => ['Packages deleted successfully:' => $repoSnapshotPackageController->deleteByName($this->data['packages'])]];
diff --git a/www/controllers/Api/Task/Task.php b/www/controllers/Api/Task/Task.php
new file mode 100644
index 000000000..9d36df917
--- /dev/null
+++ b/www/controllers/Api/Task/Task.php
@@ -0,0 +1,80 @@
+uri[4])) {
+ if (!is_numeric($this->uri[4])) {
+ throw new Exception('Invalid task ID.');
+ }
+
+ // Get task ID from URI
+ $id = (int)$this->uri[4];
+
+ // Check if task exists
+ if (!$taskController->exists($id)) {
+ throw new Exception('Task #' . $id . ' does not exist.');
+ }
+
+ /**
+ * List task details by task ID
+ * https://repomanager.mydomain.net/api/v2/task/{id}/
+ */
+ if (empty($this->uri[5]) and $this->method == 'GET') {
+ return ['results' => $taskController->getById($id)];
+ }
+
+ if (!empty($this->uri[5])) {
+ /**
+ * Enable a task by task ID
+ * https://repomanager.mydomain.net/api/v2/task/{id}/enable
+ */
+ if ($this->uri[5] == 'enable' and $this->method == 'POST') {
+ $taskController->enable([$id]);
+
+ return ['results' => 'Task enabled successfully.'];
+ }
+
+ /**
+ * Disable a task by task ID
+ * https://repomanager.mydomain.net/api/v2/task/{id}/disable
+ */
+ if ($this->uri[5] == 'disable' and $this->method == 'POST') {
+ $taskController->disable([$id]);
+
+ return ['results' => 'Task disabled successfully.'];
+ }
+
+ /**
+ * Delete a task by task ID
+ * https://repomanager.mydomain.net/api/v2/task/{id}/delete
+ */
+ if ($this->uri[5] == 'delete' and $this->method == 'DELETE') {
+ $taskController->delete([$id]);
+
+ return ['results' => 'Task deleted successfully.'];
+ }
+
+ /**
+ * Stop a task by task ID
+ * https://repomanager.mydomain.net/api/v2/task/{id}/stop
+ */
+ if ($this->uri[5] == 'stop' and $this->method == 'POST') {
+ $taskController->stop($id);
+
+ return ['results' => 'Task stopped successfully.'];
+ }
+ }
+ }
+
+ throw new Exception('Invalid request');
+ }
+}
diff --git a/www/controllers/Api/Tasks/Tasks.php b/www/controllers/Api/Tasks/Tasks.php
new file mode 100644
index 000000000..6de8d19b2
--- /dev/null
+++ b/www/controllers/Api/Tasks/Tasks.php
@@ -0,0 +1,60 @@
+uri[4])) {
+ if ($this->method == 'GET') {
+ return ['results' => $taskListingController->get()];
+ }
+ }
+
+ if (!empty($this->uri[4])) {
+ /**
+ * List all running tasks
+ * https://repomanager.mydomain.net/api/v2/tasks/running
+ */
+ if ($this->uri[4] == 'running' and $this->method == 'GET') {
+ return ['results' => $taskListingController->getRunning()];
+ }
+
+ /**
+ * List all queued tasks
+ * https://repomanager.mydomain.net/api/v2/tasks/queued
+ */
+ if ($this->uri[4] == 'queued' and $this->method == 'GET') {
+ return ['results' => $taskListingController->getQueued()];
+ }
+
+ /**
+ * List all scheduled tasks
+ * https://repomanager.mydomain.net/api/v2/tasks/scheduled
+ */
+ if ($this->uri[4] == 'scheduled' and $this->method == 'GET') {
+ return ['results' => $taskListingController->getScheduled()];
+ }
+
+ /**
+ * List all done tasks
+ * https://repomanager.mydomain.net/api/v2/tasks/done
+ */
+ if ($this->uri[4] == 'done' and $this->method == 'GET') {
+ return ['results' => $taskListingController->getDone()];
+ }
+ }
+
+ throw new Exception('Invalid request');
+ }
+}
diff --git a/www/controllers/App/Config/Settings.php b/www/controllers/App/Config/Settings.php
index 6167740a6..b7e8767a7 100644
--- a/www/controllers/App/Config/Settings.php
+++ b/www/controllers/App/Config/Settings.php
@@ -35,7 +35,7 @@ public static function get()
/**
* Following parameters can be empty (or equal to 0), we don't increment the error counter in their case
*/
- $ignoreEmptyParam = ['EMAIL_RECIPIENT', 'PROXY', 'LOGIN_BANNER', 'RPM_DEFAULT_ARCH', 'DEB_DEFAULT_ARCH', 'DEB_DEFAULT_TRANSLATION', 'REPO_CONF_FILES_PREFIX', 'RETENTION', 'OIDC_PROVIDER_URL', 'OIDC_AUTHORIZATION_ENDPOINT', 'OIDC_TOKEN_ENDPOINT', 'OIDC_USERINFO_ENDPOINT', 'OIDC_SCOPES', 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_HTTP_PROXY', 'OIDC_CERT_PATH'];
+ $ignoreEmptyParam = ['EMAIL_RECIPIENT', 'PROXY', 'LOGIN_BANNER', 'RPM_DEFAULT_ARCH', 'DEB_DEFAULT_ARCH', 'REPO_CONF_FILES_PREFIX', 'RETENTION', 'OIDC_PROVIDER_URL', 'OIDC_AUTHORIZATION_ENDPOINT', 'OIDC_TOKEN_ENDPOINT', 'OIDC_USERINFO_ENDPOINT', 'OIDC_SCOPES', 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_HTTP_PROXY', 'OIDC_CERT_PATH'];
if (in_array($key, $ignoreEmptyParam)) {
continue;
@@ -297,14 +297,6 @@ public static function get()
}
}
- if (!defined('DEB_DEFAULT_TRANSLATION')) {
- if (!empty($settings['DEB_DEFAULT_TRANSLATION'])) {
- define('DEB_DEFAULT_TRANSLATION', explode(',', $settings['DEB_DEFAULT_TRANSLATION']));
- } else {
- define('DEB_DEFAULT_TRANSLATION', array());
- }
- }
-
if (!defined('DEB_ALLOW_EMPTY_REPO')) {
if (!empty($settings['DEB_ALLOW_EMPTY_REPO'])) {
define('DEB_ALLOW_EMPTY_REPO', $settings['DEB_ALLOW_EMPTY_REPO']);
diff --git a/www/controllers/Group/Group.php b/www/controllers/Group/Group.php
index 80830e25f..98c62c227 100644
--- a/www/controllers/Group/Group.php
+++ b/www/controllers/Group/Group.php
@@ -3,13 +3,13 @@
namespace Controllers\Group;
use Exception;
-use Controllers\History\Save as History;
+use Controllers\Repo\Repo;
+use Controllers\Host\Host;
use Controllers\Utils\Validate;
+use Controllers\History\Save as History;
class Group
{
- private $id;
- private $name;
private $type;
private $model;
@@ -19,73 +19,44 @@ public function __construct(string $type)
$this->model = new \Models\Group\Group($type);
}
- public function setId(string $id)
- {
- $this->id = $id;
- }
-
- public function setName(string $name)
- {
- $this->name = $name;
- }
-
- public function getId()
- {
- return $this->id;
- }
-
- public function getName()
- {
- return $this->name;
- }
-
- public function getType()
- {
- return $this->type;
- }
-
/**
* Return false if group does not exist
*/
- public function getIdByName(string $name)
+ public function getIdByName(string $name): int
{
- /**
- * Check if group exists
- */
- if ($this->exists($name) === false) {
- throw new Exception("Group $name does not exist");
+ // Check if group exists
+ if (!$this->exists($name)) {
+ throw new Exception('Group $name does not exist');
}
return $this->model->getIdByName($name);
}
/**
- * Retourne le nom du groupe à partir de son Id
+ * Return the group's name from its ID
*/
- public function getNameById(string $id)
+ public function getNameById(int $id): string
{
- /**
- * On vérifie que le groupe existe
- */
- if ($this->existsId($id) === false) {
- throw new Exception("Group Id $id does not exist");
+ // Check if group exists
+ if (!$this->existsId($id)) {
+ throw new Exception('Group Id $id does not exist');
}
return $this->model->getNameById($id);
}
/**
- * Retourne true si l'Id du groupe existe en base de données
+ * Return true if the group's ID exists in the database
*/
- public function existsId(string $groupId)
+ public function existsId(int $groupId): bool
{
return $this->model->existsId($groupId);
}
/**
- * Vérifie si le groupe existe en base de données, à partir de son nom
+ * Check if the group exists in the database by its name
*/
- public function exists(string $name = '')
+ public function exists(string $name): bool
{
return $this->model->exists($name);
}
@@ -93,34 +64,26 @@ public function exists(string $name = '')
/**
* Create a new group
*/
- public function new(string $name) : void
+ public function new(string $name): void
{
$name = Validate::string($name);
- /**
- * Check that group name does not contain invalid characters
- */
- if (!Validate::alphaNumericHyphen($name)) {
- throw new Exception("Group $name contains invalid characters");
+ // Check that the group name does not contain invalid characters
+ if (!Validate::alphaNumericHyphen($name, ['.', ' '])) {
+ throw new Exception('Group ' . $name . ' contains invalid characters');
}
- /**
- * Group name cannot be 'Default'
- */
+ // Group name cannot be 'Default'
if (strtolower($name) === 'default') {
throw new Exception('Default is a reserved group name');
}
- /**
- * Check if group name already exists
- */
+ // Check if group name already exists
if ($this->exists($name) === true) {
- throw new Exception("Group name $name already exists");
+ throw new Exception('Group name ' . $name . ' already exists');
}
- /**
- * Add the new group to the database
- */
+ // Add the new group to the database
$this->model->add($name);
if ($this->type == 'repo') {
@@ -135,59 +98,48 @@ public function new(string $name) : void
/**
* Edit a group
*/
- public function edit(int $id, string $name, array $data) : void
+ public function edit(int $id, string $name, array $data): void
{
- /**
- * Check if group exists
- */
+ // Check if group exists
if (!$this->existsId($id)) {
throw new Exception('Group does not exist');
}
- /**
- * Check if group name is valid
- */
- if (!Validate::alphaNumericHyphen($name)) {
- throw new Exception("Group name $name contains invalid characters");
+ // Check if group name is valid
+ if (!Validate::alphaNumericHyphen($name, ['.', ' '])) {
+ throw new Exception('Group name ' . $name . ' contains invalid characters');
}
- /**
- * Edit group name
- */
+ // Edit group name
$this->updateName($id, $name);
- /**
- * Edit group data
- */
-
- /**
- * If group type is 'repo'
- */
+ // If group type is 'repo'
if ($this->type == 'repo') {
- $myrepo = new \Controllers\Repo\Repo();
- $myrepo->addReposIdToGroup($data, $id);
+ $repoController = new Repo();
+ $repoController->addReposIdToGroup($id, $data);
}
- /**
- * If group type is 'host'
- */
+ // If group type is 'host'
if ($this->type == 'host') {
- $myhost = new \Controllers\Host\Host();
- $myhost->addHostsIdToGroup($data, $id);
+ $hostController = new Host();
+ $hostController->addHostsIdToGroup($id, $data);
}
if ($this->type == 'repo') {
History::set('Repository group ' . $name . ' edited');
}
+
if ($this->type == 'host') {
History::set('Host group ' . $name . ' edited');
}
+
+ unset($repoController, $hostController);
}
/**
* Delete one or more groups
*/
- public function delete(array $groups) : void
+ public function delete(array $groups): void
{
foreach ($groups as $id) {
// Check if group exists
@@ -212,16 +164,14 @@ public function delete(array $groups) : void
}
/**
- * Retourne les informations de tous les groupes en base de données
- * Sauf le groupe par défaut
+ * Return information for all groups in the database
+ * Except the default group
*/
- public function listAll($withDefault = false)
+ public function listAll($withDefault = false): array
{
$groups = $this->model->listAll();
- /**
- * Add default group 'Default' to the end of the list
- */
+ // Add default group 'Default' to the end of the list
if ($withDefault === true) {
$groups[] = [
'Id' => 0,
@@ -233,9 +183,9 @@ public function listAll($withDefault = false)
}
/**
- * Supprime des groupes les repos qui n'existent plus
+ * Remove repositories that no longer exist from groups
*/
- public function cleanRepos()
+ public function cleanRepos(): void
{
$this->model->cleanRepos();
}
@@ -243,7 +193,7 @@ public function cleanRepos()
/**
* Update group name in database
*/
- private function updateName(int $id, string $name)
+ private function updateName(int $id, string $name): void
{
$this->model->updateName($id, $name);
}
@@ -251,7 +201,7 @@ private function updateName(int $id, string $name)
/**
* Return the list of repos in a group
*/
- public function getReposMembers(int $id)
+ public function getReposMembers(int $id): array
{
return $this->model->getReposMembers($id);
}
@@ -259,7 +209,7 @@ public function getReposMembers(int $id)
/**
* Return the list of repos not in any group
*/
- public function getReposNotMembers()
+ public function getReposNotMembers(): array
{
return $this->model->getReposNotMembers();
}
@@ -267,7 +217,7 @@ public function getReposNotMembers()
/**
* Return the list of hosts in a group
*/
- public function getHostsMembers(int $id)
+ public function getHostsMembers(int $id): array
{
return $this->model->getHostsMembers($id);
}
@@ -275,7 +225,7 @@ public function getHostsMembers(int $id)
/**
* Return the list of hosts not in any group
*/
- public function getHostsNotMembers()
+ public function getHostsNotMembers(): array
{
return $this->model->getHostsNotMembers();
}
diff --git a/www/controllers/Host/Host.php b/www/controllers/Host/Host.php
index 2fcec0e19..7fa204b86 100644
--- a/www/controllers/Host/Host.php
+++ b/www/controllers/Host/Host.php
@@ -184,7 +184,7 @@ public function deleteById(int $id): void
/**
* Add/delete hosts to/from a group
*/
- public function addHostsIdToGroup(array $hostsId = [], int $groupId): void
+ public function addHostsIdToGroup(int $groupId, array $hostsId = []): void
{
$mygroup = new HostGroup();
diff --git a/www/controllers/Layout/Container/vars/header/menu.vars.inc.php b/www/controllers/Layout/Container/vars/header/menu.vars.inc.php
index 14db9e1f6..3bc13241c 100644
--- a/www/controllers/Layout/Container/vars/header/menu.vars.inc.php
+++ b/www/controllers/Layout/Container/vars/header/menu.vars.inc.php
@@ -1,10 +1,11 @@
listRunning();
+$tasksRunning = $taskListingController->getRunning();
/**
* Count running tasks
diff --git a/www/controllers/Layout/Container/vars/tasks/log.vars.inc.php b/www/controllers/Layout/Container/vars/tasks/log.vars.inc.php
index 841ba2d72..0ae7ab669 100644
--- a/www/controllers/Layout/Container/vars/tasks/log.vars.inc.php
+++ b/www/controllers/Layout/Container/vars/tasks/log.vars.inc.php
@@ -93,12 +93,6 @@
if (!empty($rawParams['arch'])) {
$repoController->setArch($rawParams['arch']);
}
- if (!empty($rawParams['packages-include'])) {
- $repoController->setPackagesToInclude($rawParams['packages-include']);
- }
- if (!empty($rawParams['packages-exclude'])) {
- $repoController->setPackagesToExclude($rawParams['packages-exclude']);
- }
if (!empty($rawParams['package-type'])) {
$repoController->setPackageType($rawParams['package-type']);
}
diff --git a/www/controllers/Layout/Table/vars/tasks/list-done.vars.inc.php b/www/controllers/Layout/Table/vars/tasks/list-done.vars.inc.php
index 8c50eb242..c6635dc6c 100644
--- a/www/controllers/Layout/Table/vars/tasks/list-done.vars.inc.php
+++ b/www/controllers/Layout/Table/vars/tasks/list-done.vars.inc.php
@@ -1,5 +1,6 @@
listDone('', true, $reloadableTableOffset);
+$reloadableTableContent = $taskListingController->getDone('', true, $reloadableTableOffset);
/**
* Get list of ALL done tasks, without offset, for the total count
*/
-$reloadableTableTotalItems = count($myTask->listDone());
+$reloadableTableTotalItems = count($taskListingController->getDone());
/**
* Count total pages for the pagination
diff --git a/www/controllers/Layout/Table/vars/tasks/list-queued.vars.inc.php b/www/controllers/Layout/Table/vars/tasks/list-queued.vars.inc.php
index 60bc6a534..99bb55784 100644
--- a/www/controllers/Layout/Table/vars/tasks/list-queued.vars.inc.php
+++ b/www/controllers/Layout/Table/vars/tasks/list-queued.vars.inc.php
@@ -1,5 +1,6 @@
listQueued('', true, $reloadableTableOffset);
+$reloadableTableContent = $taskListingController->getQueued('', true, $reloadableTableOffset);
/**
* Get list of ALL queued tasks, without offset, for the total count
*/
-$reloadableTableTotalItems = count($myTask->listQueued());
+$reloadableTableTotalItems = count($taskListingController->getQueued());
/**
* Count total pages for the pagination
diff --git a/www/controllers/Layout/Table/vars/tasks/list-running.vars.inc.php b/www/controllers/Layout/Table/vars/tasks/list-running.vars.inc.php
index 1af4ba1de..65f9a5728 100644
--- a/www/controllers/Layout/Table/vars/tasks/list-running.vars.inc.php
+++ b/www/controllers/Layout/Table/vars/tasks/list-running.vars.inc.php
@@ -1,5 +1,6 @@
listRunning('', true, $reloadableTableOffset);
+$reloadableTableContent = $taskListingController->getRunning('', true, $reloadableTableOffset);
/**
* Get list of ALL running tasks, without offset, for the total count
*/
-$reloadableTableTotalItems = count($myTask->listRunning());
+$reloadableTableTotalItems = count($taskListingController->getRunning());
/**
* Count total pages for the pagination
diff --git a/www/controllers/Layout/Table/vars/tasks/list-scheduled.vars.inc.php b/www/controllers/Layout/Table/vars/tasks/list-scheduled.vars.inc.php
index e777056fa..f08221dcb 100644
--- a/www/controllers/Layout/Table/vars/tasks/list-scheduled.vars.inc.php
+++ b/www/controllers/Layout/Table/vars/tasks/list-scheduled.vars.inc.php
@@ -1,5 +1,6 @@
listScheduled(true, $reloadableTableOffset);
+$reloadableTableContent = $taskListingController->getScheduled(true, $reloadableTableOffset);
/**
* Get list of ALL scheduled tasks, without offset, for the total count
*/
-$reloadableTableTotalItems = count($myTask->listScheduled());
+$reloadableTableTotalItems = count($taskListingController->getScheduled());
/**
* Count total pages for the pagination
diff --git a/www/controllers/Mail.php b/www/controllers/Mail.php
index cdbbc191c..e0532a149 100644
--- a/www/controllers/Mail.php
+++ b/www/controllers/Mail.php
@@ -2,9 +2,8 @@
namespace Controllers;
-require_once(ROOT . '/libs/PHPMailer/Exception.php');
-require_once(ROOT . '/libs/PHPMailer/PHPMailer.php');
-require_once(ROOT . '/libs/PHPMailer/SMTP.php');
+// Composer autoload
+require ROOT . '/libs/vendor/autoload.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
diff --git a/www/controllers/Notification.php b/www/controllers/Notification.php
index 31f733b64..9544e120c 100644
--- a/www/controllers/Notification.php
+++ b/www/controllers/Notification.php
@@ -37,9 +37,7 @@ public function retrieve(): void
$title = Validate::string($notification['title']);
$message = Validate::string($notification['message']);
- /**
- * Insert notficiation in database if not already exists
- */
+ // Insert notification in database if not already exists
if (!$this->exists($id)) {
$this->add($id, $title, $message);
}
diff --git a/www/controllers/Repo/Environment.php b/www/controllers/Repo/Environment.php
index 1f549ecb7..f99a3f937 100644
--- a/www/controllers/Repo/Environment.php
+++ b/www/controllers/Repo/Environment.php
@@ -25,9 +25,9 @@ public function getByRepoId(int $repoId): array
/**
* Associate a new environment to a snapshot
*/
- public function add(string $env, string $description = '', int $snapId) : void
+ public function add(int $snapId, string $env, string $description = '') : void
{
- $this->model->add($env, $description, $snapId);
+ $this->model->add($snapId, $env, $description);
}
/**
diff --git a/www/controllers/Repo/Metadata/Create.php b/www/controllers/Repo/Metadata/Create.php
index 399f8cbef..408ffc7c9 100644
--- a/www/controllers/Repo/Metadata/Create.php
+++ b/www/controllers/Repo/Metadata/Create.php
@@ -35,9 +35,7 @@ private function createMetadata() : void
$workingDir = $snapshotPath;
}
- /**
- * Generate repository metadata
- */
+ // Generate repository metadata
if ($this->repoController->getPackageType() == 'rpm') {
$mymetadata = new RpmMetadata($this->taskId);
$mymetadata->setRoot($workingDir);
@@ -52,6 +50,7 @@ private function createMetadata() : void
$mymetadata->setSection($this->repoController->getSection());
$mymetadata->setArch($this->repoController->getArch());
$mymetadata->setGpgSign($this->repoController->getGpgSign());
+ $mymetadata->setMetadataCustomFields($this->repoController->getAdvancedParams()['metadata-custom-fields'] ?? []);
$mymetadata->create();
}
@@ -94,18 +93,14 @@ private function createMetadata() : void
$link = REPOS_DIR . '/deb/' . $this->repoController->getName() . '/' . $this->repoController->getDist() . '/' . $this->repoController->getSection() . '/' . $env;
}
- /**
- * If a symlink with the same name already exists, we remove it
- */
+ // If a symlink with the same name already exists, we remove it
if (is_link($link)) {
if (!unlink($link)) {
throw new Exception('Could not remove existing symlink ' . $link);
}
}
- /**
- * Create symlink
- */
+ // Create symlink
if (!symlink($this->repoController->getDate(), $link)) {
throw new Exception('Could not point environment to the repository');
}
diff --git a/www/controllers/Repo/Metadata/Deb.php b/www/controllers/Repo/Metadata/Deb.php
index b10a498ed..4a31d4b21 100644
--- a/www/controllers/Repo/Metadata/Deb.php
+++ b/www/controllers/Repo/Metadata/Deb.php
@@ -3,6 +3,7 @@
namespace Controllers\Repo\Metadata;
use Controllers\Filesystem\Directory;
+use Controllers\Process;
use Exception;
class Deb extends Metadata
@@ -14,69 +15,66 @@ class Deb extends Metadata
private $arch;
private $gpgSign = false;
private $aptftparchive = '/usr/bin/apt-ftparchive';
- private $task;
+ private $customFields = [];
- public function setRoot(string $root)
+ public function setRoot(string $root): void
{
$this->root = $root;
}
- public function setRepo(string $repo)
+ public function setRepo(string $repo): void
{
$this->repo = $repo;
}
- public function setDist(string $dist)
+ public function setDist(string $dist): void
{
$this->dist = $dist;
}
- public function setSection(string $section)
+ public function setSection(string $section): void
{
$this->section = $section;
}
- public function setArch(array $arch)
+ public function setArch(array $arch): void
{
$this->arch = $arch;
}
- public function setGpgSign(string $gpgSign)
+ public function setGpgSign(string $gpgSign): void
{
$this->gpgSign = $gpgSign;
}
+ public function setMetadataCustomFields(array $fields): void
+ {
+ $this->customFields = $fields;
+ }
+
/**
* Create metadata files
*/
- public function create()
+ public function create(): void
{
- /**
- * Check which of apt-ftparchive is present on the system
- */
+ // Check if apt-ftparchive is present on the system
if (!file_exists($this->aptftparchive)) {
throw new Exception('Could not find apt-ftparchive on the system');
}
- /**
- * Check if root path exists
- */
+ // Check if root path exists
if (!is_dir($this->root)) {
throw new Exception("Repository root directory '" . $this->root . "' does not exist");
}
- /**
- * Target arch must be specified
- */
+ // Target arch must be specified
if (empty($this->arch)) {
throw new Exception('Packages architecture(s) must be specified');
}
$this->taskLogSubStepController->new('create-metadata', 'GENERATING REPOSITORY METADATA');
- /**
- * Define directory to create for the repository
- */
+ // Define directory to create for the repository
$dirs = [
'dists',
'dists/' . $this->dist,
@@ -85,9 +83,7 @@ public function create()
'cache'
];
- /**
- * Append binary arch directories to the list of directories to create
- */
+ // Append binary arch directories to the list of directories to create
foreach ($this->arch as $arch) {
if ($arch == 'src') {
$dirs[] = 'dists/' . $this->dist . '/' . $this->section . '/source';
@@ -101,16 +97,12 @@ public function create()
* Clean all but 'pool' directory, because it might contain packages to add to the repository (e.g. if the repository is being rebuilt or it is a duplicate of another one)
*/
foreach ($dirs as $dir) {
- /**
- * Skip pool directory
- */
+ // Skip pool directory
if ($dir == 'pool') {
continue;
}
- /**
- * Clean directory if it exists
- */
+ // Clean directory if it exists
if (is_dir($this->root . '/' . $dir)) {
if (!Directory::deleteRecursive($this->root . '/' . $dir)) {
throw new Exception('Cannot delete existing directory: ' . $this->root . '/' . $dir);
@@ -118,9 +110,7 @@ public function create()
}
}
- /**
- * Then create directory structure
- */
+ // Then create directory structure
foreach ($dirs as $dir) {
if (!is_dir($this->root . '/' . $dir)) {
if (!mkdir($this->root . '/' . $dir, 0770, true)) {
@@ -129,36 +119,25 @@ public function create()
}
}
- /**
- * Create apt-ftparchive.conf file
- */
+ // Create apt-ftparchive.conf file
if (!file_put_contents($this->root . '/apt-ftparchive.conf', $this->generateAptFtpArchiveConf())) {
throw new Exception('Failed to create apt-ftparchive.conf file');
}
- /**
- * Create dist.conf file
- */
+ // Create dist.conf file
if (!file_put_contents($this->root . '/dist.conf', $this->generateDistConf())) {
throw new Exception('Failed to create dist.conf file');
}
- /**
- * Create Packages file
- */
- $myprocess = new \Controllers\Process($this->aptftparchive . ' generate ' . $this->root . '/apt-ftparchive.conf');
+ // Create Packages file
+ $myprocess = new Process($this->aptftparchive . ' generate ' . $this->root . '/apt-ftparchive.conf');
$myprocess->setBackground(true);
$myprocess->execute();
- /**
- * Retrieve PID of the launched process
- * Then write PID to main PID file
- */
+ // Retrieve PID of the launched process, then write PID to main PID file
$this->taskController->addsubpid($myprocess->getPid());
- /**
- * Retrieve output from process
- */
+ // Retrieve output from process
$output = $myprocess->getOutput();
$this->taskLogSubStepController->output($output, 'pre');
@@ -169,22 +148,15 @@ public function create()
$myprocess->close();
- /**
- * Generate Release file
- */
- $myprocess = new \Controllers\Process($this->aptftparchive . ' -c ' . $this->root . '/dist.conf release ' . $this->root . '/dists/' . $this->dist . ' > ' . $this->root . '/dists/' . $this->dist . '/Release');
+ // Generate Release file
+ $myprocess = new Process($this->aptftparchive . ' -c ' . $this->root . '/dist.conf release ' . $this->root . '/dists/' . $this->dist . ' > ' . $this->root . '/dists/' . $this->dist . '/Release');
$myprocess->setBackground(true);
$myprocess->execute();
- /**
- * Retrieve PID of the launched process
- * Then write PID to main PID file
- */
+ // Retrieve PID of the launched process, then write PID to main PID file
$this->taskController->addsubpid($myprocess->getPid());
- /**
- * Retrieve output from process
- */
+ // Retrieve output from process
$output = $myprocess->getOutput();
$this->taskLogSubStepController->output($output, 'pre');
@@ -195,38 +167,29 @@ public function create()
$myprocess->close();
- /**
- * Clean configuration files
- */
+ // Clean configuration files
if (file_exists($this->root . '/apt-ftparchive.conf')) {
if (!unlink($this->root . '/apt-ftparchive.conf')) {
- throw new Exception("Failed to clean '" . $this->root . "/apt-ftparchive.conf' file");
+ throw new Exception("Failed to remove '" . $this->root . "/apt-ftparchive.conf' file");
}
}
if (file_exists($this->root . '/dist.conf')) {
if (!unlink($this->root . '/dist.conf')) {
- throw new Exception("Failed to clean '" . $this->root . "/dist.conf' file");
+ throw new Exception("Failed to remove '" . $this->root . "/dist.conf' file");
}
}
- /**
- * Quit here if GPG signature is not enabled
- */
+ // Quit here if GPG signature is not enabled
if ($this->gpgSign != 'true') {
$this->taskLogSubStepController->completed();
return;
}
- /**
- * Sign Release file with GPG key
- */
- $myprocess = new \Controllers\Process('/usr/bin/gpg --homedir ' . GPGHOME . ' -u ' . GPG_SIGNING_KEYID . ' --output ' . $this->root . '/dists/' . $this->dist . '/Release.gpg -ba ' . $this->root . '/dists/' . $this->dist . '/Release');
+ // Sign Release file with GPG key
+ $myprocess = new Process('/usr/bin/gpg --homedir ' . GPGHOME . ' -u ' . GPG_SIGNING_KEYID . ' --output ' . $this->root . '/dists/' . $this->dist . '/Release.gpg -ba ' . $this->root . '/dists/' . $this->dist . '/Release');
$myprocess->execute();
- /**
- * Retrieve PID of the launched process
- * Then write PID to main PID file
- */
+ // Retrieve PID of the launched process, then write PID to main PID file
$this->taskController->addsubpid($myprocess->getPid());
if ($myprocess->getExitCode() != 0) {
@@ -241,13 +204,11 @@ public function create()
/**
* Generate apt-ftparchive.conf file
*/
- private function generateAptFtpArchiveConf()
+ private function generateAptFtpArchiveConf(): string
{
$architectures = '';
- /**
- * Generate architectures string
- */
+ // Generate architectures string
foreach ($this->arch as $arch) {
if ($arch == 'src') {
$architectures .= 'source ';
@@ -296,13 +257,27 @@ private function generateAptFtpArchiveConf()
/**
* Generate dist.conf file
*/
- private function generateDistConf()
+ private function generateDistConf(): string
{
$architectures = '';
- /**
- * Generate architectures string
- */
+ // Default values for Release fields
+ $origin = $this->repo . ' > ' . $this->dist . ' > ' . $this->section . ' repository';
+ $label = 'deb packages repository';
+ $description = $this->repo . ' > ' . $this->dist . ' > ' . $this->section . ' repository';
+
+ // Overwrite with custom fields if defined
+ if (!empty($this->customFields['origin'])) {
+ $origin = $this->customFields['origin'];
+ }
+ if (!empty($this->customFields['label'])) {
+ $label = $this->customFields['label'];
+ }
+ if (!empty($this->customFields['description'])) {
+ $description = $this->customFields['description'];
+ }
+
+ // Generate architectures string
foreach ($this->arch as $arch) {
if ($arch == 'src') {
$architectures .= 'source ';
@@ -320,13 +295,13 @@ private function generateDistConf()
$template = <<repo > $this->dist > $this->section repository";
- Label "deb packages repository";
+ Origin "$origin";
+ Label "$label";
Suite "$this->dist";
Codename "$this->dist";
Architectures "$architectures";
Components "$this->section";
- Description "$this->repo > $this->dist > $this->section repository";
+ Description "$description";
}
EOT;
diff --git a/www/controllers/Repo/Metadata/Rpm.php b/www/controllers/Repo/Metadata/Rpm.php
index edc57a47d..9a0a6d9fd 100644
--- a/www/controllers/Repo/Metadata/Rpm.php
+++ b/www/controllers/Repo/Metadata/Rpm.php
@@ -3,6 +3,7 @@
namespace Controllers\Repo\Metadata;
use Exception;
+use Controllers\Process;
class Rpm extends Metadata
{
@@ -47,7 +48,7 @@ public function create()
/**
* Create repository metadata
*/
- $myprocess = new \Controllers\Process($this->createrepo . ' ' . $this->createrepoArgs . ' ' . $this->root . '/');
+ $myprocess = new Process($this->createrepo . ' ' . $this->createrepoArgs . ' ' . $this->root . '/');
$myprocess->setBackground(true);
$myprocess->execute();
@@ -99,7 +100,7 @@ public function create()
/**
* Include modules.yaml in the metadata
*/
- $myprocess = new \Controllers\Process($this->modifyrepo . ' ' . $this->root . '/modules.yaml ' . $this->root . '/repodata/');
+ $myprocess = new Process($this->modifyrepo . ' ' . $this->root . '/modules.yaml ' . $this->root . '/repodata/');
$myprocess->setBackground(true);
$myprocess->execute();
@@ -143,7 +144,7 @@ public function create()
/**
* Include updateinfo.xml in the metadata
*/
- $myprocess = new \Controllers\Process($this->modifyrepo . ' ' . $this->root . '/updateinfo.xml ' . $this->root . '/repodata/');
+ $myprocess = new Process($this->modifyrepo . ' ' . $this->root . '/updateinfo.xml ' . $this->root . '/repodata/');
$myprocess->setBackground(true);
$myprocess->execute();
diff --git a/www/controllers/Repo/Mirror/Deb.php b/www/controllers/Repo/Mirror/Deb.php
index 3dda4a38b..1eddc6585 100644
--- a/www/controllers/Repo/Mirror/Deb.php
+++ b/www/controllers/Repo/Mirror/Deb.php
@@ -718,10 +718,10 @@ private function downloadDebPackages($url) : void
* If a list of package(s) to include has been provided, check if the package is in the list
* If not, skip the package
*/
- if (!empty($this->packagesToInclude)) {
+ if (!empty($this->advancedParams['packages']['include'])) {
$isIn = false;
- foreach ($this->packagesToInclude as $packageToInclude) {
+ foreach ($this->advancedParams['packages']['include'] as $packageToInclude) {
if (preg_match('/' . $packageToInclude . '/', $debPackageName)) {
$isIn = true;
}
@@ -740,10 +740,10 @@ private function downloadDebPackages($url) : void
* If a list of package(s) to exclude has been provided, check if the package is in the list
* If so, skip the package
*/
- if (!empty($this->packagesToExclude)) {
+ if (!empty($this->advancedParams['packages']['exclude'])) {
$isIn = false;
- foreach ($this->packagesToExclude as $packageToExclude) {
+ foreach ($this->advancedParams['packages']['exclude'] as $packageToExclude) {
if (preg_match('/' . $packageToExclude . '/', $debPackageName)) {
$isIn = true;
}
diff --git a/www/controllers/Repo/Mirror/Mirror.php b/www/controllers/Repo/Mirror/Mirror.php
index 9895d9094..9adad5b42 100644
--- a/www/controllers/Repo/Mirror/Mirror.php
+++ b/www/controllers/Repo/Mirror/Mirror.php
@@ -13,14 +13,12 @@ class Mirror
protected $section;
protected $releasever;
protected $arch;
- protected $translation;
protected $checkSignature = 'true';
protected $gpgKeyUrl;
protected $primaryLocation;
protected $primaryChecksum;
protected $packagesIndicesLocation = [];
protected $sourcesIndicesLocation = [];
- protected $translationsLocation = [];
protected $debPackagesLocation = [];
protected $sourcesPackagesLocation = [];
protected $rpmPackagesLocation = [];
@@ -32,8 +30,7 @@ class Mirror
protected $sslCustomCaCertificate;
protected $curlHandle;
protected $previousSnapshotDirPath;
- protected $packagesToInclude = [];
- protected $packagesToExclude = [];
+ protected $advancedParams = [];
protected $taskLogStepController;
protected $taskLogSubStepController;
@@ -95,11 +92,6 @@ public function setGpgKeyUrl(string $url) : void
$this->gpgKeyUrl = $url;
}
- public function setTranslation(array $translation) : void
- {
- $this->translation = $translation;
- }
-
public function setSslCustomCertificate(string $path) : void
{
$this->sslCustomCertificate = $path;
@@ -120,14 +112,9 @@ public function setPreviousSnapshotDirPath(string $path) : void
$this->previousSnapshotDirPath = $path;
}
- public function setPackagesToInclude(array $packages) : void
- {
- $this->packagesToInclude = $packages;
- }
-
- public function setPackagesToExclude(array $packages) : void
+ public function setAdvancedParams(array $advancedParams): void
{
- $this->packagesToExclude = $packages;
+ $this->advancedParams = $advancedParams;
}
public function getPackagesToSign() : array
diff --git a/www/controllers/Repo/Mirror/Rpm.php b/www/controllers/Repo/Mirror/Rpm.php
index b8d94b393..ac1cd4cbf 100644
--- a/www/controllers/Repo/Mirror/Rpm.php
+++ b/www/controllers/Repo/Mirror/Rpm.php
@@ -115,11 +115,11 @@ private function downloadModules(string $url) : void
* We'll give this modules file a temporary name, to avoid it being included automatically by createrepo_c (it fails every time with modules.yaml file)
* It will be renamed and imported by modifyrepo_c later
*/
- $modulesFileExtension == 'gz' ? $modulesFileTargetName = 'modules-temp.yaml.gz' : null;
- $modulesFileExtension == 'bz2' ? $modulesFileTargetName = 'modules-temp.yaml.bz2' : null;
- $modulesFileExtension == 'xz' ? $modulesFileTargetName = 'modules-temp.yaml.xz' : null;
- $modulesFileExtension == 'zst' ? $modulesFileTargetName = 'modules-temp.yaml.zst' : null;
- $modulesFileExtension == 'yaml' ? $modulesFileTargetName = 'modules-temp.yaml' : null;
+ $modulesFileExtension == 'gz' ? $modulesFileTargetName = 'modules.yaml.gz' : null;
+ $modulesFileExtension == 'xz' ? $modulesFileTargetName = 'modules.yaml.xz' : null;
+ $modulesFileExtension == 'bz2' ? $modulesFileTargetName = 'modules.yaml.bz2' : null;
+ $modulesFileExtension == 'zst' ? $modulesFileTargetName = 'modules.yaml.zst' : null;
+ $modulesFileExtension == 'yaml' ? $modulesFileTargetName = 'modules.yaml' : null;
/**
* Download modules file
@@ -158,9 +158,11 @@ private function downloadModules(string $url) : void
throw new Exception('Error while uncompressing ' . $modulesFileTargetName . '
' . $e->getMessage() . '
');
}
- // Delete original (uncompressed) file
- if (!unlink($this->workingDir . '/' . $modulesFileTargetName)) {
- throw new Exception('Could not delete ' . $this->workingDir . '/' . $modulesFileTargetName . '');
+ // Delete original file (only if it was compressed and not already a plain .yaml file)
+ if ($modulesFileExtension != 'yaml') {
+ if (!unlink($this->workingDir . '/' . $modulesFileTargetName)) {
+ throw new Exception('Could not delete ' . $this->workingDir . '/' . $modulesFileTargetName . '');
+ }
}
$this->taskLogSubStepController->completed();
@@ -196,10 +198,10 @@ private function downloadUpdateInfo(string $url) : void
* We'll give this updateinfo file a target name according to its file extension
*/
$updateInfoFileExtension == 'gz' ? $updateInfoFileTargetName = 'updateinfo.xml.gz' : null;
- $updateInfoFileExtension == 'bz2' ? $updateInfoFileTargetName = 'updateinfo.xml.bz2' : null;
$updateInfoFileExtension == 'xz' ? $updateInfoFileTargetName = 'updateinfo.xml.xz' : null;
- $updateInfoFileExtension == 'xml' ? $updateInfoFileTargetName = 'updateinfo.xml' : null;
+ $updateInfoFileExtension == 'bz2' ? $updateInfoFileTargetName = 'updateinfo.xml.bz2' : null;
$updateInfoFileExtension == 'zst' ? $updateInfoFileTargetName = 'updateinfo.xml.zst' : null;
+ $updateInfoFileExtension == 'xml' ? $updateInfoFileTargetName = 'updateinfo.xml' : null;
/**
* Download updateinfo file
@@ -238,9 +240,11 @@ private function downloadUpdateInfo(string $url) : void
throw new Exception('Error while uncompressing ' . $updateInfoFileTargetName . '
' . $e->getMessage() . '
');
}
- // Delete original (uncompressed) file
- if (!unlink($this->workingDir . '/' . $updateInfoFileTargetName)) {
- throw new Exception('Could not delete ' . $this->workingDir . '/' . $updateInfoFileTargetName . '');
+ // Delete original file (only if it was compressed and not already a plain .xml file)
+ if ($updateInfoFileExtension != 'xml') {
+ if (!unlink($this->workingDir . '/' . $updateInfoFileTargetName)) {
+ throw new Exception('Could not delete ' . $this->workingDir . '/' . $updateInfoFileTargetName . '');
+ }
}
$this->taskLogSubStepController->completed();
@@ -539,8 +543,6 @@ private function parsePrimaryPackagesList(string $primaryFile) : void
if ($error == 0) {
$this->taskLogSubStepController->completed();
}
-
- unset($jsonArray, $data);
}
/**
@@ -594,10 +596,10 @@ private function downloadRpmPackages(string $url) : void
* If a list of package(s) to include has been provided, check if the package is in the list
* If not, skip the package
*/
- if (!empty($this->packagesToInclude)) {
+ if (!empty($this->advancedParams['packages']['include'])) {
$isIn = false;
- foreach ($this->packagesToInclude as $packageToInclude) {
+ foreach ($this->advancedParams['packages']['include'] as $packageToInclude) {
if (preg_match('/' . $packageToInclude . '/', $rpmPackageName)) {
$isIn = true;
}
@@ -616,10 +618,10 @@ private function downloadRpmPackages(string $url) : void
* If a list of package(s) to exclude has been provided, check if the package is in the list
* If so, skip the package
*/
- if (!empty($this->packagesToExclude)) {
+ if (!empty($this->advancedParams['packages']['exclude'])) {
$isIn = false;
- foreach ($this->packagesToExclude as $packageToExclude) {
+ foreach ($this->advancedParams['packages']['exclude'] as $packageToExclude) {
if (preg_match('/' . $packageToExclude . '/', $rpmPackageName)) {
$isIn = true;
}
diff --git a/www/controllers/Repo/Package/Deb.php b/www/controllers/Repo/Package/Deb.php
new file mode 100644
index 000000000..d74db0291
--- /dev/null
+++ b/www/controllers/Repo/Package/Deb.php
@@ -0,0 +1,64 @@
+/dev/null';
+
+ try {
+ // Execute the command and capture the output
+ exec($cmd, $out, $rc);
+
+ if ($rc !== 0) {
+ throw new Exception('DEB query command failed with return code ' . $rc);
+ }
+
+ if (empty($out)) {
+ throw new Exception('no output from DEB query command');
+ }
+
+ $info = [];
+
+ foreach ($out as $line) {
+ $parts = explode(':', $line, 2);
+
+ if (count($parts) !== 2) {
+ throw new Exception('unexpected output format from DEB query command: ' . $line);
+ }
+
+ $field = trim($parts[0]);
+ $value = trim($parts[1]);
+
+ switch ($field) {
+ case 'Package':
+ $info['name'] = $value;
+ break;
+
+ case 'Version':
+ $info['version'] = $value;
+ break;
+
+ case 'Architecture':
+ $info['arch'] = $value;
+ break;
+ }
+ }
+
+ if (!isset($info['name'], $info['version'], $info['arch'])) {
+ throw new Exception('missing required fields in DEB query output');
+ }
+ } catch (Exception $e) {
+ throw new Exception('Failed to extract DEB info from file: ' . $path . ' (' . $e->getMessage() . ')');
+ }
+
+ return $info;
+ }
+}
diff --git a/www/controllers/Repo/Package/Rpm.php b/www/controllers/Repo/Package/Rpm.php
new file mode 100644
index 000000000..8b3402973
--- /dev/null
+++ b/www/controllers/Repo/Package/Rpm.php
@@ -0,0 +1,50 @@
+/dev/null';
+
+ try {
+ // Execute the command and capture the output
+ exec($cmd, $out, $rc);
+
+ if ($rc !== 0) {
+ throw new Exception('RPM query command failed with return code ' . $rc);
+ }
+
+ if (empty($out)) {
+ throw new Exception('no output from RPM query command');
+ }
+
+ // The output should be in the format: name|version|release|arch
+ $parts = explode('|', $out[0], 4);
+
+ // Validate that we got exactly 4 parts
+ if (count($parts) !== 4) {
+ throw new Exception('unexpected output format from RPM query command: ' . $out[0]);
+ }
+ } catch (Exception $e) {
+ throw new Exception('Failed to extract RPM info from file:' . $path . ' (' . $e->getMessage() . ')');
+ }
+
+ return [
+ 'name' => $parts[0],
+ 'version' => $parts[1],
+ 'release' => $parts[2],
+ 'arch' => $parts[3],
+ ];
+ }
+}
diff --git a/www/controllers/Repo/Package/Sign.php b/www/controllers/Repo/Package/Sign.php
index d9902be3a..21f2735a8 100644
--- a/www/controllers/Repo/Package/Sign.php
+++ b/www/controllers/Repo/Package/Sign.php
@@ -3,7 +3,6 @@
namespace Controllers\Repo\Package;
use Controllers\Filesystem\File;
-use Controllers\Filesystem\Directory;
use Controllers\Process;
use Exception;
diff --git a/www/controllers/Repo/Package/Sync.php b/www/controllers/Repo/Package/Sync.php
index 0a914b9df..bad8438bf 100644
--- a/www/controllers/Repo/Package/Sync.php
+++ b/www/controllers/Repo/Package/Sync.php
@@ -146,8 +146,7 @@ private function syncPackage()
$mymirror->setUrl($sourceUrl);
$mymirror->setArch($this->repoController->getArch());
$mymirror->setCheckSignature($this->repoController->getGpgCheck());
- $mymirror->setPackagesToInclude($this->repoController->getPackagesToInclude());
- $mymirror->setPackagesToExclude($this->repoController->getPackagesToExclude());
+ $mymirror->setAdvancedParams($this->repoController->getAdvancedParams());
/**
* If the task is an update, set the previous repo directory path
diff --git a/www/controllers/Repo/Repo.php b/www/controllers/Repo/Repo.php
index 12b4e373c..eb34138d1 100644
--- a/www/controllers/Repo/Repo.php
+++ b/www/controllers/Repo/Repo.php
@@ -4,7 +4,9 @@
use Exception;
use DateTime;
+use JsonException;
use Controllers\Utils\Validate;
+use Controllers\Group\Repo as RepoGroup;
class Repo
{
@@ -26,8 +28,7 @@ class Repo
private $env;
private $description;
private $group;
- private $packagesToInclude = [];
- private $packagesToExclude = [];
+ private $advancedParams = [];
private $signed;
private $status;
private $rebuild;
@@ -70,6 +71,11 @@ public function setSection(string $section): void
$this->section = $section;
}
+ public function setReleasever(string $releasever): void
+ {
+ $this->releasever = $releasever;
+ }
+
public function setEnv(string|array $env): void
{
$this->env = $env;
@@ -108,10 +114,6 @@ public function setStatus(string $status): void
public function setDescription($description = ''): void
{
- if ($description == 'nodescription') {
- $description = '';
- }
-
$this->description = Validate::string($description);
}
@@ -125,13 +127,9 @@ public function setPackageType(string $type): void
$this->packageType = $type;
}
- public function setGroup(string $group): void
+ public function setGroup(string $group = ''): void
{
- if ($group == 'nogroup') {
- $this->group = '';
- } else {
- $this->group = $group;
- }
+ $this->group = Validate::string($group);
}
public function setGpgCheck(string $gpgCheck): void
@@ -149,19 +147,9 @@ public function setArch(array $arch): void
$this->arch = $arch;
}
- public function setPackagesToInclude(array $packages): void
- {
- $this->packagesToInclude = $packages;
- }
-
- public function setPackagesToExclude(array $packages): void
+ public function setAdvancedParams(array $advancedParams): void
{
- $this->packagesToExclude = $packages;
- }
-
- public function setReleasever(string $releasever): void
- {
- $this->releasever = $releasever;
+ $this->advancedParams = $advancedParams;
}
public function setTaskId(int $taskId): void
@@ -179,7 +167,7 @@ public function getSnapId(): int
return $this->snapId;
}
- public function getEnvId()
+ public function getEnvId(): int
{
return $this->envId;
}
@@ -199,12 +187,17 @@ public function getSection(): string
return $this->section;
}
+ public function getReleasever()
+ {
+ return $this->releasever;
+ }
+
public function getPackageType(): string
{
return $this->packageType;
}
- public function getEnv()
+ public function getEnv(): string|array
{
return $this->env;
}
@@ -224,12 +217,12 @@ public function getTime(): string
return $this->time;
}
- public function getRebuild()
+ public function getRebuild(): string|null
{
return $this->rebuild;
}
- public function getStatus()
+ public function getStatus(): string
{
return $this->status;
}
@@ -239,34 +232,24 @@ public function getSource(): string
return $this->source;
}
- public function getType()
+ public function getType(): string
{
return $this->type;
}
- public function getSigned()
+ public function getSigned(): string
{
return $this->signed;
}
- public function getArch()
+ public function getArch(): array
{
return $this->arch;
}
- public function getPackagesToInclude()
+ public function getAdvancedParams(): array
{
- return $this->packagesToInclude;
- }
-
- public function getPackagesToExclude()
- {
- return $this->packagesToExclude;
- }
-
- public function getReleasever()
- {
- return $this->releasever;
+ return $this->advancedParams;
}
public function getDescription(): string|null
@@ -274,17 +257,17 @@ public function getDescription(): string|null
return $this->description;
}
- public function getGroup()
+ public function getGroup(): string|null
{
return $this->group;
}
- public function getGpgCheck()
+ public function getGpgCheck(): string
{
return $this->gpgCheck;
}
- public function getGpgSign()
+ public function getGpgSign(): string
{
return $this->gpgSign;
}
@@ -309,6 +292,15 @@ public function getAllById(string|int|null $repoId = null, string|int|null $snap
{
$data = $this->model->getAllById($repoId, $snapId, $envId);
+ if (!empty($data['repoId'])) {
+ $this->setRepoId($data['repoId']);
+ }
+ if (!empty($data['snapId'])) {
+ $this->setSnapId($data['snapId']);
+ }
+ if (!empty($data['envId'])) {
+ $this->setEnvId($data['envId']);
+ }
if (!empty($data['Source'])) {
$this->setSource($data['Source']);
}
@@ -351,30 +343,30 @@ public function getAllById(string|int|null $repoId = null, string|int|null $snap
if (!empty($data['Description'])) {
$this->setDescription($data['Description']);
}
- if (!empty($data['repoId'])) {
- $this->setRepoId($data['repoId']);
- }
- if (!empty($data['snapId'])) {
- $this->setSnapId($data['snapId']);
- }
- if (!empty($data['envId'])) {
- $this->setEnvId($data['envId']);
- }
if (!empty($data['Arch'])) {
$this->setArch(explode(',', $data['Arch']));
}
- if (!empty($data['Pkg_included'])) {
- $this->setPackagesToInclude(explode(',', $data['Pkg_included']));
- }
- if (!empty($data['Pkg_excluded'])) {
- $this->setPackagesToExclude(explode(',', $data['Pkg_excluded']));
+
+ /**
+ * Extract Advanced_params JSON
+ * This includes package include/exclude, metadata custom fields and potentially other parameters in the future
+ * It is optional and can be empty
+ */
+ if (!empty($data['Advanced_params'])) {
+ try {
+ $advancedParams = json_decode($data['Advanced_params'], true, 512, JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ throw new Exception('Failed to decode advanced parameters JSON: ' . $e->getMessage());
+ }
+
+ $this->setAdvancedParams($advancedParams);
}
}
/**
* Get unused repos Id (repos that have no active snapshot and so are not visible from web UI)
*/
- public function getUnused() : array
+ public function getUnused(): array
{
return $this->model->getUnused();
}
@@ -382,7 +374,7 @@ public function getUnused() : array
/**
* Return true if a repo Id exists in database
*/
- public function existsId(string $repoId) : bool
+ public function existsId(string $repoId): bool
{
return $this->model->existsId($repoId);
}
@@ -390,7 +382,7 @@ public function existsId(string $repoId) : bool
/**
* Return true if a snapshot Id exists in database
*/
- public function existsSnapId(string $snapId) : bool
+ public function existsSnapId(string $snapId): bool
{
return $this->model->existsSnapId($snapId);
}
@@ -398,7 +390,7 @@ public function existsSnapId(string $snapId) : bool
/**
* Return true if env exists, based on its name and the snapshot Id it points to
*/
- public function existsSnapIdEnv(string $snapId, string $env)
+ public function existsSnapIdEnv(string $snapId, string $env): bool
{
return $this->model->existsSnapIdEnv($snapId, $env);
}
@@ -406,7 +398,7 @@ public function existsSnapIdEnv(string $snapId, string $env)
/**
* Return the total number of repositories
*/
- public function count()
+ public function count(): int
{
return $this->model->count();
}
@@ -414,9 +406,9 @@ public function count()
/**
* Add / remove repositories to/from a group
*/
- public function addReposIdToGroup(array $reposId = [], int $groupId)
+ public function addReposIdToGroup(int $groupId, array $reposId = []): void
{
- $mygroup = new \Controllers\Group\Repo();
+ $groupController = new RepoGroup();
if (!empty($reposId)) {
foreach ($reposId as $repoId) {
@@ -431,7 +423,7 @@ public function addReposIdToGroup(array $reposId = [], int $groupId)
}
// Retrieve the list of repositories currently in the group in order to remove those that were not specified by the user
- $actualReposMembers = $mygroup->getReposMembers($groupId);
+ $actualReposMembers = $groupController->getReposMembers($groupId);
// Among this list, only retrieve the Ids of the currently member repositories
$actualReposId = [];
@@ -446,17 +438,21 @@ public function addReposIdToGroup(array $reposId = [], int $groupId)
$this->model->removeFromGroup($actualRepoId, $groupId);
}
}
+
+ unset($groupController);
}
/**
* Add a repository to a group
*/
- public function addRepoIdToGroup(string $repoId, string $groupName)
+ public function addRepoIdToGroup(int $repoId, string $groupName): void
{
- $mygroup = new \Controllers\Group\Repo();
- $groupId = $mygroup->getIdByName($groupName);
+ $groupController = new RepoGroup();
+ $groupId = $groupController->getIdByName($groupName);
$this->model->addToGroup($repoId, $groupId);
+
+ unset($groupController);
}
/**
@@ -493,7 +489,7 @@ public function cleanGroups(): void
}
}
- public function getLastInsertRowID()
+ public function getLastInsertRowID(): int
{
return $this->model->getLastInsertRowID();
}
diff --git a/www/controllers/Repo/Snapshot/Package.php b/www/controllers/Repo/Snapshot/Package.php
index 9acafe968..65d73dfa5 100644
--- a/www/controllers/Repo/Snapshot/Package.php
+++ b/www/controllers/Repo/Snapshot/Package.php
@@ -2,6 +2,8 @@
namespace Controllers\Repo\Snapshot;
+use Controllers\Repo\Package\Rpm as RpmPackage;
+use Controllers\Repo\Package\Deb as DebPackage;
use Controllers\User\Permission\Repo as RepoPermission;
use Controllers\Repo\Snapshot\Snapshot;
use Controllers\Exception\AppException;
@@ -67,11 +69,27 @@ public function list(): array
// Extract package name from path and add it to the list of packages
foreach ($scan as $path) {
- $package = end(explode('/', $path));
+ $filename = explode('/', $path);
+ $filename = end($filename);
$size = filesize($path);
+ $ext = pathinfo($path, PATHINFO_EXTENSION);
+
+ // Extract package info (name, version, release, arch) when deb
+ if ($ext == 'deb') {
+ $info = DebPackage::getInfo($path);
+ }
+
+ // Extract package info (name, version, release, arch) when rpm
+ if ($ext == 'rpm') {
+ $info = RpmPackage::getInfo($path);
+ }
$packages[] = [
- 'name' => $package,
+ 'filename' => $filename,
+ 'name' => $info['name'],
+ 'version' => $info['version'],
+ 'release' => $info['release'] ?? null,
+ 'arch' => $info['arch'],
'size' => $size,
'size-human' => Convert::sizeToHuman($size),
'relative-path' => str_replace($this->snapshotPath . '/', '', $path),
diff --git a/www/controllers/Repo/Snapshot/Snapshot.php b/www/controllers/Repo/Snapshot/Snapshot.php
index d9847ae72..31b3b15a6 100644
--- a/www/controllers/Repo/Snapshot/Snapshot.php
+++ b/www/controllers/Repo/Snapshot/Snapshot.php
@@ -3,6 +3,8 @@
namespace Controllers\Repo\Snapshot;
use Controllers\Filesystem\Directory;
+use Controllers\Repo\Repo;
+use JsonException;
use Exception;
use DateTime;
@@ -64,9 +66,16 @@ public function getLastInsertRowID()
/**
* Add a snapshot in database
*/
- public function add(string $date, string $time, string $gpgSignature, array $arch, array $includeTranslation, array $packagesIncluded, array $packagesExcluded, string $type, string $status, int $repoId): void
+ public function add(string $date, string $time, string $gpgSignature, array $arch, array $advancedParams, string $type, string $status, int $repoId): void
{
- $this->model->add($date, $time, $gpgSignature, $arch, $includeTranslation, $packagesIncluded, $packagesExcluded, $type, $status, $repoId);
+ // Convert advanced params to JSON string
+ try {
+ $advancedParams = json_encode($advancedParams, JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ throw new Exception('Error while encoding advanced parameters to JSON: ' . $e->getMessage());
+ }
+
+ $this->model->add($date, $time, $gpgSignature, $arch, $advancedParams, $type, $status, $repoId);
}
/**
@@ -93,22 +102,6 @@ public function updateGpgSignature(int $snapId, string $gpgSignature): void
$this->model->updateGpgSignature($snapId, $gpgSignature);
}
- /**
- * Update snapshot included packages in the database
- */
- public function updatePackagesIncluded(int $snapId, array $packagesIncluded): void
- {
- $this->model->updatePackagesIncluded($snapId, implode(',', $packagesIncluded));
- }
-
- /**
- * Update snapshot excluded packages in the database
- */
- public function updatePackagesExcluded(int $snapId, array $packagesExcluded): void
- {
- $this->model->updatePackagesExcluded($snapId, implode(',', $packagesExcluded));
- }
-
/**
* Update snapshot status in the database
*/
@@ -133,6 +126,21 @@ public function updateArch(int $snapId, array $arch): void
$this->model->updateArch($snapId, $arch);
}
+ /**
+ * Update snapshot advanced parameters in the database
+ */
+ public function updateAdvancedParams(int $snapId, array $advancedParams): void
+ {
+ // Convert advanced params to JSON string
+ try {
+ $advancedParams = json_encode($advancedParams, JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ throw new Exception('Error while encoding advanced parameters to JSON: ' . $e->getMessage());
+ }
+
+ $this->model->updateAdvancedParams($snapId, $advancedParams);
+ }
+
/**
* Clean unused snapshots and return a message
*/
@@ -278,4 +286,59 @@ public function taskRunning(int $snapId) : bool
{
return $this->model->taskRunning($snapId);
}
+
+ /**
+ * Return the difference between two snapshots (list of added, removed and common packages)
+ */
+ public function diff(int $snapId1, int $snapId2): array
+ {
+ $repoController = new Repo();
+ $diff = [
+ 'added' => [],
+ 'removed' => []
+ ];
+
+ // Check if both snapshots exist
+ if (!$this->exists($snapId1)) {
+ throw new Exception('Unknown snapshot ID #' . $snapId1);
+ }
+
+ if (!$this->exists($snapId2)) {
+ throw new Exception('Unknown snapshot ID #' . $snapId2);
+ }
+
+ // Get snapshot #1 package type
+ $repoController->getAllById(null, $snapId1);
+ $packageType1 = $repoController->getPackageType();
+
+ // Get snapshot #2 package type
+ $repoController->getAllById(null, $snapId2);
+ $packageType2 = $repoController->getPackageType();
+
+ // Quit if the two snapshots do not have the same package type
+ if ($packageType1 != $packageType2) {
+ throw new Exception('Cannot compare snapshots with different package types');
+ }
+
+ // Get snapshot #1 packages list
+ $snapshotPackageController1 = new Package($snapId1);
+ $packagesList1 = $snapshotPackageController1->list();
+
+ // Get snapshot #2 packages list
+ $snapshotPackageController2 = new Package($snapId2);
+ $packagesList2 = $snapshotPackageController2->list();
+
+ unset($repoController, $snapshotPackageController1, $snapshotPackageController2);
+
+ // Compare the two snapshots packages list to get the list of added, removed and common packages
+ $diff['added'] = array_values(array_udiff($packagesList2, $packagesList1, function ($a, $b) {
+ return strcmp($a['relative-path'], $b['relative-path']);
+ }));
+
+ $diff['removed'] = array_values(array_udiff($packagesList1, $packagesList2, function ($a, $b) {
+ return strcmp($a['relative-path'], $b['relative-path']);
+ }));
+
+ return $diff;
+ }
}
diff --git a/www/controllers/Repo/Task/Create.php b/www/controllers/Repo/Task/Create.php
index 0b56d4be6..cd9f25a87 100644
--- a/www/controllers/Repo/Task/Create.php
+++ b/www/controllers/Repo/Task/Create.php
@@ -245,7 +245,7 @@ private function local()
/**
* Add snapshot to database
*/
- $this->repoSnapshotController->add($this->repoController->getDate(), $this->repoController->getTime(), 'false', $this->repoController->getArch(), array(), array(), array(), $this->repoController->getType(), 'active', $this->repoController->getRepoId());
+ $this->repoSnapshotController->add($this->repoController->getDate(), $this->repoController->getTime(), 'false', $this->repoController->getArch(), $this->repoController->getAdvancedParams(), $this->repoController->getType(), 'active', $this->repoController->getRepoId());
/**
* Retrieve snapshot Id from the last insert row
@@ -257,7 +257,7 @@ private function local()
*/
if (!empty($this->repoController->getEnv())) {
foreach ($this->repoController->getEnv() as $env) {
- $this->repoEnvController->add($env, $this->repoController->getDescription(), $this->repoController->getSnapId());
+ $this->repoEnvController->add($this->repoController->getSnapId(), $env, $this->repoController->getDescription());
}
}
diff --git a/www/controllers/Repo/Task/Duplicate.php b/www/controllers/Repo/Task/Duplicate.php
index 1498da00a..9d0e7bc07 100644
--- a/www/controllers/Repo/Task/Duplicate.php
+++ b/www/controllers/Repo/Task/Duplicate.php
@@ -17,6 +17,11 @@ public function __construct(string $taskId)
// Get source repository details from its snapshot Id
$this->sourceRepoController->getAllById(null, $this->params['snap-id'], null);
+ // Reset some params for the new repo, to avoid carrying over values that are not relevant for the new repo (e.g. metadata custom fields)
+ $advancedParams = $this->sourceRepoController->getAdvancedParams();
+ unset($advancedParams['metadata-custom-fields']);
+ $this->repoController->setAdvancedParams($advancedParams);
+
// Execute the task
try {
$this->execute();
@@ -270,7 +275,7 @@ public function execute()
/**
* Add the new repo snapshot in database
*/
- $this->repoSnapshotController->add($this->repoController->getDate(), $this->repoController->getTime(), $this->repoController->getSigned(), $this->repoController->getArch(), array(), $this->repoController->getPackagesToInclude(), $this->repoController->getPackagesToExclude(), $this->repoController->getType(), $this->repoController->getStatus(), $targetRepoId);
+ $this->repoSnapshotController->add($this->repoController->getDate(), $this->repoController->getTime(), $this->repoController->getSigned(), $this->repoController->getArch(), $this->repoController->getAdvancedParams(), $this->repoController->getType(), $this->repoController->getStatus(), $targetRepoId);
/**
* Retrieve the Id of the new repo snapshot in database
@@ -282,7 +287,7 @@ public function execute()
*/
if (!empty($this->repoController->getEnv())) {
foreach ($this->repoController->getEnv() as $env) {
- $this->repoEnvController->add($env, $this->repoController->getDescription(), $targetSnapId);
+ $this->repoEnvController->add($targetSnapId, $env, $this->repoController->getDescription());
}
}
diff --git a/www/controllers/Repo/Task/Env.php b/www/controllers/Repo/Task/Env.php
index 7514a3b81..4d9634645 100644
--- a/www/controllers/Repo/Task/Env.php
+++ b/www/controllers/Repo/Task/Env.php
@@ -171,7 +171,7 @@ public function execute()
* Add environment to database
*/
$this->taskLogSubStepController->new('update-database', 'UPDATING DATABASE');
- $this->repoEnvController->add($env, $this->repoController->getDescription(), $this->repoController->getSnapId());
+ $this->repoEnvController->add($this->repoController->getSnapId(), $env, $this->repoController->getDescription());
$this->taskLogSubStepController->completed();
$this->taskLogStepController->completed();
}
diff --git a/www/controllers/Repo/Task/Finalize.php b/www/controllers/Repo/Task/Finalize.php
index e24665f00..aad2e24f8 100644
--- a/www/controllers/Repo/Task/Finalize.php
+++ b/www/controllers/Repo/Task/Finalize.php
@@ -29,63 +29,44 @@ protected function finalize()
* If the task is a 'create' then we add the repository to the database.
*/
if ($this->action == 'create') {
- /**
- * If currently no rpm repo of this name exists in the database then we add it
- */
+ // If currently no rpm repo of this name exists in the database then we add it
if ($this->repoController->getPackageType() == 'rpm') {
if (!$this->rpmRepoController->exists($this->repoController->getName(), $this->repoController->getReleasever())) {
$this->rpmRepoController->add($this->repoController->getName(), $this->repoController->getReleasever(), $this->repoController->getSource());
- /**
- * Repository Id becomes the Id of the last inserted row in the database
- */
+ // Repository Id becomes the Id of the last inserted row in the database
$this->repoController->setRepoId($this->rpmRepoController->getLastInsertRowID());
- /**
- * Otherwise, if a repo of the same name exists, we retrieve its Id from the database
- */
+ // Otherwise, if a repo of the same name exists, we retrieve its Id from the database
} else {
$this->repoController->setRepoId($this->rpmRepoController->getIdByNameReleasever($this->repoController->getName(), $this->repoController->getReleasever()));
}
}
- /**
- * If currently no deb repo of this name exists in the database then we add it
- */
+ // If currently no deb repo of this name exists in the database then we add it
if ($this->repoController->getPackageType() == 'deb') {
if (!$this->debRepoController->exists($this->repoController->getName(), $this->repoController->getDist(), $this->repoController->getSection())) {
$this->debRepoController->add($this->repoController->getName(), $this->repoController->getDist(), $this->repoController->getSection(), $this->repoController->getSource());
- /**
- * Repository Id becomes the Id of the last inserted row in the database
- */
+ // Repository Id becomes the Id of the last inserted row in the database
$this->repoController->setRepoId($this->debRepoController->getLastInsertRowID());
- /**
- * Otherwise, if a repo of the same name exists, we retrieve its Id from the database
- */
+ // Otherwise, if a repo of the same name exists, we retrieve its Id from the database
} else {
$this->repoController->setRepoId($this->debRepoController->getIdByNameDistComponent($this->repoController->getName(), $this->repoController->getDist(), $this->repoController->getSection()));
}
}
- /**
- * Add snapshot in database
- * Empty array() for package translation because it's not used for the moment
- */
- $this->repoSnapshotController->add($this->repoController->getDate(), $this->repoController->getTime(), $this->repoController->getGpgSign(), $this->repoController->getArch(), array(), $this->repoController->getPackagesToInclude(), $this->repoController->getPackagesToExclude(), $this->repoController->getType(), 'active', $this->repoController->getRepoId());
+ // Add snapshot in database
+ $this->repoSnapshotController->add($this->repoController->getDate(), $this->repoController->getTime(), $this->repoController->getGpgSign(), $this->repoController->getArch(), $this->repoController->getAdvancedParams(), $this->repoController->getType(), 'active', $this->repoController->getRepoId());
- /**
- * Retrieve the last insert row ID
- */
+ // Retrieve the last insert row ID
$this->repoController->setSnapId($this->repoSnapshotController->getLastInsertRowID());
- /**
- * Add env in database if an env has been specified by the user
- */
+ // Add env in database if an env has been specified by the user
if (!empty($this->repoController->getEnv())) {
foreach ($this->repoController->getEnv() as $env) {
- $this->repoEnvController->add($env, $this->repoController->getDescription(), $this->repoController->getSnapId());
+ $this->repoEnvController->add($this->repoController->getSnapId(), $env, $this->repoController->getDescription());
}
}
}
@@ -96,49 +77,27 @@ protected function finalize()
* We only update the repository information in the database and nothing else.
*/
if ($this->sourceRepoController->getDate() == $this->repoController->getDate()) {
- /**
- * Update GPG signature state
- */
+ // Update GPG signature state
$this->repoSnapshotController->updateGpgSignature($this->repoController->getSnapId(), $this->repoController->getGpgSign());
- /**
- * Update architecture (it could be different from the previous one)
- */
+ // Update architecture (it could be different from the previous one)
$this->repoSnapshotController->updateArch($this->repoController->getSnapId(), $this->repoController->getArch());
- /**
- * Update packages to include (it could be different from the previous one)
- */
- $this->repoSnapshotController->updatePackagesIncluded($this->repoController->getSnapId(), $this->repoController->getPackagesToInclude());
-
- /**
- * Update packages to exclude (it could be different from the previous one)
- */
- $this->repoSnapshotController->updatePackagesExcluded($this->repoController->getSnapId(), $this->repoController->getPackagesToExclude());
-
- /**
- * Update date
- */
+ // Update date
$this->repoSnapshotController->updateDate($this->repoController->getSnapId(), $this->repoController->getDate());
- /**
- * Update time
- */
+ // Update time
$this->repoSnapshotController->updateTime($this->repoController->getSnapId(), $this->repoController->getTime());
- /**
- * Otherwise we add a new snapshot in the database with today's date
- */
+ // Update advanced params
+ $this->repoSnapshotController->updateAdvancedParams($this->repoController->getSnapId(), $this->repoController->getAdvancedParams());
+
+ // Otherwise we add a new snapshot in the database with today's date
} else {
- /**
- * Add snapshot in database
- */
- $this->repoSnapshotController->add($this->repoController->getDate(), $this->repoController->getTime(), $this->repoController->getGpgSign(), $this->repoController->getArch(), array(), $this->repoController->getPackagesToInclude(), $this->repoController->getPackagesToExclude(), $this->repoController->getType(), 'active', $this->repoController->getRepoId());
-
- /**
- * Retrieve the last insert row Id
- * And we can set snapId = this Id
- */
+ // Add snapshot in database
+ $this->repoSnapshotController->add($this->repoController->getDate(), $this->repoController->getTime(), $this->repoController->getGpgSign(), $this->repoController->getArch(), $this->repoController->getAdvancedParams(), $this->repoController->getType(), 'active', $this->repoController->getRepoId());
+
+ // Retrieve the last insert row Id, so we can set snapId = this Id
$this->repoController->setSnapId($this->repoSnapshotController->getLastInsertRowID());
}
}
@@ -157,17 +116,13 @@ protected function finalize()
$this->taskLogSubStepController->completed();
- /**
- * If the user has specified an environment to point to the created snapshot
- */
+ // If the user has specified an environment to point to the created snapshot
if (in_array($this->action, ['create', 'update'])) {
if (!empty($this->repoController->getEnv())) {
$this->taskLogSubStepController->new('adding-env', 'ADDING ENVIRONMENT');
foreach ($this->repoController->getEnv() as $env) {
- /**
- * If the user has not specified any description, then we retrieve the one currently in place on the environment of the same name (if the environment exists and if it has a description)
- */
+ // If the user has not specified any description, then we retrieve the one currently in place on the environment of the same name (if the environment exists and if it has a description)
if (empty($this->repoController->getDescription())) {
if ($this->repoController->getPackageType() == 'rpm') {
$actualDescription = $this->rpmRepoController->getDescriptionByName($this->repoController->getName(), $this->repoController->getReleasever(), $env);
@@ -176,9 +131,7 @@ protected function finalize()
$actualDescription = $this->debRepoController->getDescriptionByName($this->repoController->getName(), $this->repoController->getDist(), $this->repoController->getSection(), $env);
}
- /**
- * If the retrieved description is empty then the description will remain empty
- */
+ // If the retrieved description is empty then the description will remain empty
if (!empty($actualDescription)) {
$this->repoController->setDescription(htmlspecialchars_decode($actualDescription));
} else {
@@ -186,9 +139,7 @@ protected function finalize()
}
}
- /**
- * Retrieve the Id of the environment currently in place (if there is one)
- */
+ // Retrieve the Id of the environment currently in place (if there is one)
if ($this->repoController->getPackageType() == 'rpm') {
$actualEnvIds = $this->rpmRepoController->getEnvIdFromRepoName($this->repoController->getName(), $this->repoController->getReleasever(), $env);
}
@@ -196,28 +147,22 @@ protected function finalize()
$actualEnvIds = $this->debRepoController->getEnvIdFromRepoName($this->repoController->getName(), $this->repoController->getDist(), $this->repoController->getSection(), $env);
}
- /**
- * Delete the possible environment of the same name pointing to a snapshot of this repo (if there is one)
- */
+ // Delete the possible environment of the same name pointing to a snapshot of this repo (if there is one)
if (!empty($actualEnvIds)) {
foreach ($actualEnvIds as $actualEnvId) {
$this->repoEnvController->remove($actualEnvId);
}
}
- /**
- * Then we declare the new environment and make it point to the previously created snapshot
- */
- $this->repoEnvController->add($env, $this->repoController->getDescription(), $this->repoController->getSnapId());
+ // Then we declare the new environment and make it point to the previously created snapshot
+ $this->repoEnvController->add($this->repoController->getSnapId(), $env, $this->repoController->getDescription());
}
$this->taskLogSubStepController->completed();
}
}
- /**
- * Apply permissions on the snapshot
- */
+ // Apply permissions on the snapshot
$this->taskLogSubStepController->new('applying-permissions', 'APPLYING PERMISSIONS');
if ($this->repoController->getPackageType() == 'rpm') {
diff --git a/www/controllers/Repo/Task/Rebuild.php b/www/controllers/Repo/Task/Rebuild.php
index 26be36086..c74aa37bf 100644
--- a/www/controllers/Repo/Task/Rebuild.php
+++ b/www/controllers/Repo/Task/Rebuild.php
@@ -10,6 +10,8 @@ class Rebuild extends \Controllers\Task\Execution
use \Controllers\Repo\Metadata\Create;
use \Controllers\Repo\Task\Finalize;
+ private $packagesToSign = 'all';
+
public function __construct(string $taskId)
{
parent::__construct($taskId, 'rebuild');
diff --git a/www/controllers/Service/Unit/ScheduledTask.php b/www/controllers/Service/Unit/ScheduledTask.php
index 578079546..fb7d61d46 100644
--- a/www/controllers/Service/Unit/ScheduledTask.php
+++ b/www/controllers/Service/Unit/ScheduledTask.php
@@ -8,6 +8,7 @@
class ScheduledTask extends \Controllers\Service\Service
{
private $taskController;
+ private $taskListingController;
private $taskNotifyController;
public function __construct(string $unit)
@@ -15,6 +16,7 @@ public function __construct(string $unit)
parent::__construct($unit);
$this->taskController = new \Controllers\Task\Task();
+ $this->taskListingController = new \Controllers\Task\Listing();
$this->taskNotifyController = new \Controllers\Task\Notify();
}
@@ -41,7 +43,7 @@ public function execute() : void
/**
* Get scheduled tasks
*/
- $scheduledTasks = $this->taskController->listScheduled();
+ $scheduledTasks = $this->taskListingController->getScheduled();
/**
* Quit if there is no task to execute
@@ -193,7 +195,7 @@ public function sendReminders() : void
/**
* Get scheduled tasks
*/
- $scheduledTasks = $this->taskController->listScheduled();
+ $scheduledTasks = $this->taskListingController->getScheduled();
/**
* Quit if there is no task to execute
diff --git a/www/controllers/Settings.php b/www/controllers/Settings.php
index 7a50ea250..c9dbf6429 100644
--- a/www/controllers/Settings.php
+++ b/www/controllers/Settings.php
@@ -249,19 +249,6 @@ public function apply(array $sendSettings) : void
$settingsToApply['DEB_DEFAULT_ARCH'] = $debDefaultArch;
}
- if (!empty($sendSettings['debDefaultTranslation'])) {
- /**
- * Convert array to a string with values separated by a comma
- */
- $debDefaultTranslation = Validate::string(implode(',', $sendSettings['debDefaultTranslation']));
-
- if (!Validate::alphaNumeric($debDefaultTranslation, [','])) {
- throw new Exception('Invalid translation value for ' . $debDefaultTranslation);
- }
-
- $settingsToApply['DEB_DEFAULT_TRANSLATION'] = $debDefaultTranslation;
- }
-
if (!empty($sendSettings['deb-allow-empty-repo'])) {
if ($sendSettings['deb-allow-empty-repo'] == 'true') {
$settingsToApply['DEB_ALLOW_EMPTY_REPO'] = 'true';
diff --git a/www/controllers/Task/Execution.php b/www/controllers/Task/Execution.php
index 5a280e557..3f114bbf1 100644
--- a/www/controllers/Task/Execution.php
+++ b/www/controllers/Task/Execution.php
@@ -229,9 +229,7 @@ public function paramsCheck($requiredParams)
*/
public function paramsSet($requiredParams = [], $optionalParams = [])
{
- /**
- * Repo controller setter functions depending on parameters
- */
+ // Repo controller setter functions depending on parameters
$setters = [
'repo-id' => 'setRepoId',
'snap-id' => 'setSnapId',
@@ -250,13 +248,10 @@ public function paramsSet($requiredParams = [], $optionalParams = [])
'env' => 'setEnv',
'description' => 'setDescription',
'group' => 'setGroup',
- 'package-include' => 'setPackagesToInclude',
- 'package-exclude' => 'setPackagesToExclude'
+ 'advanced-params' => 'setAdvancedParams'
];
- /**
- * Set required parameters, using the appropriate setter function
- */
+ // Set required parameters, using the appropriate setter function
if (!empty($requiredParams)) {
foreach ($requiredParams as $param) {
$setterFunction = $setters[$param];
@@ -264,9 +259,7 @@ public function paramsSet($requiredParams = [], $optionalParams = [])
}
}
- /**
- * Set optional parameters if defined, using the appropriate setter function
- */
+ // Set optional parameters if defined, using the appropriate setter function
if (!empty($optionalParams)) {
foreach ($optionalParams as $param) {
if (isset($this->params[$param])) {
@@ -358,8 +351,6 @@ public function end() : void
$this->taskController->updateTime($newTaskId, '');
$this->taskController->updateStatus($newTaskId, 'scheduled');
}
-
- unset($myTaskNotify);
}
// Clean unused repos from profiles
diff --git a/www/controllers/Task/Form/Create.php b/www/controllers/Task/Form/Create.php
index 7db70e22f..9ceb5faa2 100644
--- a/www/controllers/Task/Form/Create.php
+++ b/www/controllers/Task/Form/Create.php
@@ -3,128 +3,93 @@
namespace Controllers\Task\Form;
use Exception;
+use Controllers\Repo\Rpm;
+use Controllers\Repo\Deb;
use Controllers\History\Save as History;
class Create
{
- public function validate(array $formParams)
+ public function validate(array $formParams): void
{
- $rpmRepoController = new \Controllers\Repo\Rpm();
- $debRepoController = new \Controllers\Repo\Deb();
+ $rpmRepoController = new Rpm();
+ $debRepoController = new Deb();
- /**
- * Check package type
- */
+ // Check package type
Param\PackageType::check($formParams['package-type']);
- /**
- * Check repo type
- */
+ // Check repo type
Param\RepoType::check($formParams['repo-type']);
- /**
- * Case package type is 'rpm'
- */
+ // Case package type is 'rpm'
if ($formParams['package-type'] == 'rpm') {
- /**
- * Check releasever
- */
+ // Check releasever
Param\Releasever::check($formParams['releasever']);
}
- /**
- * Case package type is 'deb'
- */
+ // Case package type is 'deb'
if ($formParams['package-type'] == 'deb') {
- /**
- * Check distribution
- */
+ // Check distribution
Param\Dist::check($formParams['dist']);
- /**
- * Check section
- */
+ // Check section
Param\Section::check($formParams['section']);
+
+ // Check metadata custom fields
+ Param\Metadata::checkOrigin($formParams['advanced-params']['metadata-custom-fields']['origin']);
+ Param\Metadata::checkLabel($formParams['advanced-params']['metadata-custom-fields']['label']);
+ Param\Metadata::checkDescription($formParams['advanced-params']['metadata-custom-fields']['description']);
}
- /**
- * Check env
- */
+ // Check env
if (!empty($formParams['env'])) {
Param\Environment::check($formParams['env']);
}
- /**
- * Check description
- */
+ // Check description
Param\Description::check($formParams['description']);
- /**
- * Check group
- */
+ // Check group
if (!empty($formParams['group'])) {
Param\Group::check($formParams['group']);
}
- /**
- * If the selected repo type is 'local' we will have to check that a name has been provided (can be left empty in the case of a mirror)
- */
+ // If the selected repo type is 'local' we will have to check that a name has been provided (can be left empty in the case of a mirror)
if ($formParams['repo-type'] == 'local') {
$targetName = $formParams['alias'];
}
- /**
- * If the selected repo type is 'mirror' then we check additional parameters
- */
+ // If the selected repo type is 'mirror' then we check additional parameters
if ($formParams['repo-type'] == 'mirror') {
- /**
- * Check source
- */
+ // Check source
Param\Source::check($formParams['source'], $formParams['package-type']);
- /**
- * If no alias has been given, we use the source name as alias
- */
+ // If no alias has been given, we use the source name as alias
if (!empty($formParams['alias'])) {
$targetName = $formParams['alias'];
} else {
$targetName = $formParams['source'];
}
- /**
- * Check gpg check
- */
+ // Check gpg check
Param\GpgCheck::check($formParams['gpg-check']);
- /**
- * Check gpg sign
- */
+ // Check gpg sign
Param\GpgSign::check($formParams['gpg-sign']);
- /**
- * Check package(s) to include
- */
- Param\PackageInclude::check($formParams['package-include']);
+ // Check package(s) to include
+ Param\PackageInclude::check($formParams['advanced-params']['packages']['include']);
- /**
- * Check package(s) to exclude
- */
- Param\PackageExclude::check($formParams['package-exclude']);
+ // Check package(s) to exclude
+ Param\PackageExclude::check($formParams['advanced-params']['packages']['exclude']);
}
- /**
- * Check name
- */
+ // Check name
Param\Name::check($targetName);
- /**
- * Check architecture
- */
+ // Check architecture
Param\Arch::check($formParams['arch']);
- /**
- * Check if a repository with the same name is already active with snapshots
- */
+ // Check if a repository with the same name is already active with snapshots
if ($formParams['package-type'] == 'rpm') {
foreach ($formParams['releasever'] as $releasever) {
if ($rpmRepoController->isActive($targetName, $releasever)) {
@@ -134,9 +99,7 @@ public function validate(array $formParams)
}
if ($formParams['package-type'] == 'deb') {
- /**
- * For deb repo, we check that no repo/dist/section with the same name is already active
- */
+ // For deb repo, we check that no repo/dist/section with the same name is already active
foreach ($formParams['dist'] as $distribution) {
foreach ($formParams['section'] as $section) {
if ($debRepoController->isActive($targetName, $distribution, $section)) {
@@ -146,14 +109,10 @@ public function validate(array $formParams)
}
}
- /**
- * Check scheduling parameters
- */
+ // Check scheduling parameters
Param\Schedule::check($formParams['schedule']);
- /**
- * Add history
- */
+ // Add history
if ($formParams['package-type'] == 'rpm') {
History::set('Running task: New repository ' . $targetName . ' (' . $formParams['repo-type'] . ')');
}
diff --git a/www/controllers/Task/Form/Delete.php b/www/controllers/Task/Form/Delete.php
index 4f34a8c2b..646bfa385 100644
--- a/www/controllers/Task/Form/Delete.php
+++ b/www/controllers/Task/Form/Delete.php
@@ -3,47 +3,38 @@
namespace Controllers\Task\Form;
use Exception;
+use Controllers\Repo\Repo;
use Controllers\History\Save as History;
class Delete
{
- public function validate(array $formParams)
+ public function validate(array $formParams): void
{
- $myrepo = new \Controllers\Repo\Repo();
+ $repoController = new Repo();
- /**
- * Check that the snapshot id is valid
- */
+ // Check that the snapshot id is valid
Param\Snapshot::checkId($formParams['snap-id']);
- /**
- * Retrieve all repo data from the Ids,
- */
- $myrepo->setSnapId($formParams['snap-id']);
- $myrepo->getAllById('', $formParams['snap-id'], '');
+ // Retrieve all repo data from the Id
+ $repoController->setSnapId($formParams['snap-id']);
+ $repoController->getAllById('', $formParams['snap-id'], '');
- /**
- * Check that the repo exists
- */
- if ($myrepo->existsSnapId($formParams['snap-id']) === false) {
+ // Check that the repo exists
+ if ($repoController->existsSnapId($formParams['snap-id']) === false) {
throw new Exception('Snapshot Id ' . $formParams['snap-id'] . ' does not exist');
}
- /**
- * Check scheduling parameters
- */
+ // Check scheduling parameters
Param\Schedule::check($formParams['schedule']);
- /**
- * Add history
- */
- if ($myrepo->getPackageType() == 'rpm') {
- History::set('Running task: delete repository snapshot ' . $myrepo->getName() . '⸺' . $myrepo->getDateFormatted() . '');
+ // Add history
+ if ($repoController->getPackageType() == 'rpm') {
+ History::set('Running task: delete repository snapshot ' . $repoController->getName() . '⸺' . $repoController->getDateFormatted() . '');
}
- if ($myrepo->getPackageType() == 'deb') {
- History::set('Running task: delete repository snapshot ' . $myrepo->getName() . ' ❯ ' . $myrepo->getDist() . ' ❯ ' . $myrepo->getSection() . '⸺' . $myrepo->getDateFormatted() . '');
+ if ($repoController->getPackageType() == 'deb') {
+ History::set('Running task: delete repository snapshot ' . $repoController->getName() . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection() . '⸺' . $repoController->getDateFormatted() . '');
}
- unset($myrepo);
+ unset($repoController);
}
}
diff --git a/www/controllers/Task/Form/Duplicate.php b/www/controllers/Task/Form/Duplicate.php
index aa600aa4c..463d5f135 100644
--- a/www/controllers/Task/Form/Duplicate.php
+++ b/www/controllers/Task/Form/Duplicate.php
@@ -3,82 +3,65 @@
namespace Controllers\Task\Form;
use Exception;
+use Controllers\Repo\Repo;
use Controllers\History\Save as History;
class Duplicate
{
- public function validate(array $formParams)
+ public function validate(array $formParams): void
{
- $myrepo = new \Controllers\Repo\Repo();
+ $repoController = new Repo();
$rpmRepoController = new \Controllers\Repo\Rpm();
$debRepoController = new \Controllers\Repo\Deb();
- /**
- * Check that the snapshot id is valid
- */
+ // Check that the snapshot id is valid
Param\Snapshot::checkId($formParams['snap-id']);
- /**
- * Retrieve all repo data from the Ids,
- */
- $myrepo->setSnapId($formParams['snap-id']);
- $myrepo->getAllById('', $formParams['snap-id'], '');
+ // Retrieve all repo data from the Id
+ $repoController->setSnapId($formParams['snap-id']);
+ $repoController->getAllById('', $formParams['snap-id'], '');
- /**
- * Check name
- */
+ // Check name
Param\Name::check($formParams['name']);
- /**
- * Check env
- */
+ // Check env
if (!empty($formParams['env'])) {
Param\Environment::check($formParams['env']);
}
- /**
- * Check description
- */
+ // Check description
if (!empty($formParams['description'])) {
Param\Description::check($formParams['description']);
}
- /**
- * Check group
- */
+ // Check group
if (!empty($formParams['group'])) {
Param\Group::check($formParams['group']);
}
- /**
- * Check that a repo with the same name does not already exist
- */
- if ($myrepo->getPackageType() == 'rpm') {
- if ($rpmRepoController->isActive($formParams['name'], $myrepo->getReleasever())) {
- throw new Exception('' . $formParams['name'] . ' ❯ ' . $myrepo->getReleasever() . ' repository already exists');
+ // Check that a repo with the same name does not already exist
+ if ($repoController->getPackageType() == 'rpm') {
+ if ($rpmRepoController->isActive($formParams['name'], $repoController->getReleasever())) {
+ throw new Exception('' . $formParams['name'] . ' ❯ ' . $repoController->getReleasever() . ' repository already exists');
}
}
- if ($myrepo->getPackageType() == 'deb') {
- if ($debRepoController->isActive($formParams['name'], $myrepo->getDist(), $myrepo->getSection())) {
- throw new Exception('' . $formParams['name'] . ' ❯ ' . $myrepo->getDist() . ' ❯ ' . $myrepo->getSection() . ' repo already exists');
+ if ($repoController->getPackageType() == 'deb') {
+ if ($debRepoController->isActive($formParams['name'], $repoController->getDist(), $repoController->getSection())) {
+ throw new Exception('' . $formParams['name'] . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection() . ' repo already exists');
}
}
- /**
- * Check scheduling parameters
- */
+ // Check scheduling parameters
Param\Schedule::check($formParams['schedule']);
- /**
- * Add history
- */
- if ($myrepo->getPackageType() == 'rpm') {
- History::set('Running task: duplicate repository ' . $myrepo->getName() . '⸺' . $myrepo->getDateFormatted() . ' ➡ ' . $formParams['name'] . '');
+ // Add history
+ if ($repoController->getPackageType() == 'rpm') {
+ History::set('Running task: duplicate repository ' . $repoController->getName() . '⸺' . $repoController->getDateFormatted() . ' ➡ ' . $formParams['name'] . '');
}
- if ($myrepo->getPackageType() == 'deb') {
- History::set('Running task: duplicate repository ' . $myrepo->getName() . ' ❯ ' . $myrepo->getDist() . ' ❯ ' . $myrepo->getSection() . '⸺' . $myrepo->getDateFormatted() . ' ➡ ' . $formParams['name'] . ' ❯ ' . $myrepo->getDist() . ' ❯ ' . $myrepo->getSection() . '');
+ if ($repoController->getPackageType() == 'deb') {
+ History::set('Running task: duplicate repository ' . $repoController->getName() . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection() . '⸺' . $repoController->getDateFormatted() . ' ➡ ' . $formParams['name'] . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection() . '');
}
- unset($myrepo, $rpmRepoController, $debRepoController);
+ unset($repoController, $rpmRepoController, $debRepoController);
}
}
diff --git a/www/controllers/Task/Form/Env.php b/www/controllers/Task/Form/Env.php
index 0315854ee..486fbb4b7 100644
--- a/www/controllers/Task/Form/Env.php
+++ b/www/controllers/Task/Form/Env.php
@@ -2,56 +2,45 @@
namespace Controllers\Task\Form;
+use Controllers\Repo\Repo;
use Controllers\Utils\Generate\Html\Label;
use Controllers\History\Save as History;
class Env
{
- public function validate(array $formParams)
+ public function validate(array $formParams): void
{
- $myrepo = new \Controllers\Repo\Repo();
+ $repoController = new Repo();
- /**
- * Check that the snapshot id is valid
- */
+ // Check that the snapshot id is valid
Param\Snapshot::checkId($formParams['snap-id']);
- /**
- * Retrieve all repo data from the Ids,
- */
- $myrepo->setSnapId($formParams['snap-id']);
- $myrepo->getAllById('', $formParams['snap-id'], '');
+ // Retrieve all repo data from the Id
+ $repoController->setSnapId($formParams['snap-id']);
+ $repoController->getAllById('', $formParams['snap-id'], '');
- /**
- * Check environment
- */
+ // Check environment
Param\Environment::check($formParams['env']);
- /**
- * Check description
- */
+ // Check description
Param\Description::check($formParams['description']);
- /**
- * Check scheduling parameters
- */
+ // Check scheduling parameters
Param\Schedule::check($formParams['schedule']);
- /**
- * Add history
- */
+ // Add history
$content = '';
foreach ($formParams['env'] as $env) {
$content .= Label::envtag($env) . ' ';
}
- if ($myrepo->getPackageType() == 'rpm') {
- History::set('Running task: point environment(s) ' . trim($content) . ' to repository ' . $myrepo->getName() . '⸺' . $myrepo->getDateFormatted() . '');
+ if ($repoController->getPackageType() == 'rpm') {
+ History::set('Running task: point environment(s) ' . trim($content) . ' to repository ' . $repoController->getName() . '⸺' . $repoController->getDateFormatted() . '');
}
- if ($myrepo->getPackageType() == 'deb') {
- History::set('Running task: point environment(s) ' . trim($content) . ' to repository ' . $myrepo->getName() . ' ❯ ' . $myrepo->getDist() . ' ❯ ' . $myrepo->getSection() . '⸺' . $myrepo->getDateFormatted() . '');
+ if ($repoController->getPackageType() == 'deb') {
+ History::set('Running task: point environment(s) ' . trim($content) . ' to repository ' . $repoController->getName() . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection() . '⸺' . $repoController->getDateFormatted() . '');
}
- unset($myrepo);
+ unset($repoController);
}
}
diff --git a/www/controllers/Task/Form/Form.php b/www/controllers/Task/Form/Form.php
index 110ecf275..fab2243f5 100644
--- a/www/controllers/Task/Form/Form.php
+++ b/www/controllers/Task/Form/Form.php
@@ -3,7 +3,10 @@
namespace Controllers\Task\Form;
use Exception;
+use Controllers\Repo\Repo;
use Controllers\Utils\Validate;
+use Controllers\Repo\Environment;
+use Controllers\Task\Scheduled as ScheduledTask;
use Controllers\User\Permission\Repo as RepoPermission;
class Form
@@ -21,74 +24,56 @@ public function get(string $action, array $repos) : string
$content = '
';
return $content;
@@ -119,9 +103,7 @@ public function get(string $action, array $repos) : string
public function validate(array $tasksParams) : void
{
foreach ($tasksParams as $task) {
- /**
- * Retrieve action
- */
+ // Retrieve action
if (empty($task['action'])) {
throw new Exception('No action has been specified');
}
@@ -130,28 +112,20 @@ public function validate(array $tasksParams) : void
throw new Exception('Invalid action: ' . $task['action']);
}
- /**
- * If the user does not have permission to perform the specified action, prevent execution of the task.
- */
+ // If the user does not have permission to perform the specified action, prevent execution of the task.
if (!RepoPermission::allowedAction($task['action'])) {
throw new Exception('You are not allowed to execute this action');
}
- /**
- * Generate controller name
- */
+ // Generate controller name
$controllerPath = '\Controllers\Task\Form\\' . ucfirst($task['action']);
- /**
- * Check if class exists, otherwise the action might be invalid
- */
+ // Check if class exists, otherwise the action might be invalid
if (!class_exists($controllerPath)) {
throw new Exception('Invalid action: ' . $task['action']);
}
- /**
- * Validate form by calling the controller
- */
+ // Validate form by calling the controller
$controller = new $controllerPath();
$controller->validate($task);
}
diff --git a/www/controllers/Task/Form/Param/Dist.php b/www/controllers/Task/Form/Param/Dist.php
index 1e77993fe..b8f47bdf2 100644
--- a/www/controllers/Task/Form/Param/Dist.php
+++ b/www/controllers/Task/Form/Param/Dist.php
@@ -15,7 +15,7 @@ public static function check(array $dists) : void
foreach ($dists as $dist) {
if (!Validate::alphaNumeric($dist, ['-', '_', '.', '/'])) {
- throw new Exception('Distribution name cannot contain special characters except hyphen');
+ throw new Exception('Distribution name cannot contain special characters except hyphen, underscore, dot, slash');
}
}
}
diff --git a/www/controllers/Task/Form/Param/Group.php b/www/controllers/Task/Form/Param/Group.php
index 88d1fcd4c..84948d241 100644
--- a/www/controllers/Task/Form/Param/Group.php
+++ b/www/controllers/Task/Form/Param/Group.php
@@ -3,18 +3,20 @@
namespace Controllers\Task\Form\Param;
use Exception;
-use Controllers\Utils\Validate;
+use Controllers\Group\Repo as RepoGroup;
class Group
{
public static function check(string $group) : void
{
+ $groupController = new RepoGroup();
+
if (empty($group)) {
return;
}
- if (!Validate::alphaNumericHyphen($group)) {
- throw new Exception('Group contains invalid characters');
+ if (!$groupController->exists($group)) {
+ throw new Exception('Group ' . $group . ' does not exist');
}
}
}
diff --git a/www/controllers/Task/Form/Param/Metadata.php b/www/controllers/Task/Form/Param/Metadata.php
new file mode 100644
index 000000000..6796f0539
--- /dev/null
+++ b/www/controllers/Task/Form/Param/Metadata.php
@@ -0,0 +1,42 @@
+', '<', '+', ' '])) {
+ throw new Exception('Metadata Origin field cannot contains invalid characters.');
+ }
+ }
+
+ public static function checkLabel(string $label): void
+ {
+ if (empty($label)) {
+ return;
+ }
+
+ if (!Validate::alphaNumeric($label, ['-', '_', '.', '>', '<', '+', ' '])) {
+ throw new Exception('Metadata Label field cannot contains invalid characters.');
+ }
+ }
+
+ public static function checkDescription(string $description): void
+ {
+ if (empty($description)) {
+ return;
+ }
+
+ if (!Validate::alphaNumeric($description, ['-', '_', '.', '>', '<', '+', ' '])) {
+ throw new Exception('Metadata Description field cannot contains invalid characters.');
+ }
+ }
+}
diff --git a/www/controllers/Task/Form/Param/TranslationInc.php b/www/controllers/Task/Form/Param/TranslationInc.php
deleted file mode 100644
index 765345afb..000000000
--- a/www/controllers/Task/Form/Param/TranslationInc.php
+++ /dev/null
@@ -1,22 +0,0 @@
-setSnapId($formParams['snap-id']);
- $myrepo->getAllById('', $formParams['snap-id'], '');
+ // Retrieve all repo data from the Id
+ $repoController->setSnapId($formParams['snap-id']);
+ $repoController->getAllById('', $formParams['snap-id'], '');
- /**
- * Check gpg sign
- */
+ // Check gpg sign
Param\GpgSign::check($formParams['gpg-sign']);
- /**
- * Check scheduling parameters
- */
+ // Check scheduling parameters
Param\Schedule::check($formParams['schedule']);
- /**
- * Add history
- */
- if ($myrepo->getPackageType() == 'rpm') {
- History::set('Running task: rebuild repository metadata files of ' . $myrepo->getName() . '⸺' . $myrepo->getDateFormatted() . '');
+ // Add history
+ if ($repoController->getPackageType() == 'rpm') {
+ History::set('Running task: rebuild repository metadata files of ' . $repoController->getName() . '⸺' . $repoController->getDateFormatted() . '');
}
- if ($myrepo->getPackageType() == 'deb') {
- History::set('Running task: rebuild repository metadata files of ' . $myrepo->getName() . ' ❯ ' . $myrepo->getDist() . ' ❯ ' . $myrepo->getSection() . '⸺' . $myrepo->getDateFormatted() . '');
+ if ($repoController->getPackageType() == 'deb') {
+ History::set('Running task: rebuild repository metadata files of ' . $repoController->getName() . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection() . '⸺' . $repoController->getDateFormatted() . '');
}
- unset($myrepo);
+ unset($repoController);
}
}
diff --git a/www/controllers/Task/Form/RemoveEnv.php b/www/controllers/Task/Form/RemoveEnv.php
index 68b0719db..37a0c6303 100644
--- a/www/controllers/Task/Form/RemoveEnv.php
+++ b/www/controllers/Task/Form/RemoveEnv.php
@@ -2,34 +2,29 @@
namespace Controllers\Task\Form;
+use Controllers\Repo\Repo;
use Controllers\History\Save as History;
class RemoveEnv
{
- public function validate(array $formParams)
+ public function validate(array $formParams): void
{
- $myrepo = new \Controllers\Repo\Repo();
+ $repoController = new Repo();
- /**
- * Check that the snapshot id is valid
- */
+ // Check that the snapshot id is valid
Param\Snapshot::checkId($formParams['snap-id']);
- /**
- * Retrieve all repo data from the Ids,
- */
- $myrepo->getAllById($formParams['repo-id'], $formParams['snap-id'], $formParams['env-id']);
+ // Retrieve all repo data from the Id
+ $repoController->getAllById($formParams['repo-id'], $formParams['snap-id'], $formParams['env-id']);
- /**
- * Add history
- */
- if ($myrepo->getPackageType() == 'rpm') {
- History::set('Running task: remove ' . $myrepo->getEnv() . ' environment from ' . $myrepo->getName() . '⸺' . $myrepo->getDateFormatted() . '');
+ // Add history
+ if ($repoController->getPackageType() == 'rpm') {
+ History::set('Running task: remove ' . $repoController->getEnv() . ' environment from ' . $repoController->getName() . '⸺' . $repoController->getDateFormatted() . '');
}
- if ($myrepo->getPackageType() == 'deb') {
- History::set('Running task: remove ' . $myrepo->getEnv() . ' environment from ' . $myrepo->getName() . ' ❯ ' . $myrepo->getDist() . ' ❯ ' . $myrepo->getSection() . '⸺' . $myrepo->getDateFormatted() . '');
+ if ($repoController->getPackageType() == 'deb') {
+ History::set('Running task: remove ' . $repoController->getEnv() . ' environment from ' . $repoController->getName() . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection() . '⸺' . $repoController->getDateFormatted() . '');
}
- unset($myrepo);
+ unset($repoController);
}
}
diff --git a/www/controllers/Task/Form/Rename.php b/www/controllers/Task/Form/Rename.php
index 63ba3c5d2..ba5b560df 100644
--- a/www/controllers/Task/Form/Rename.php
+++ b/www/controllers/Task/Form/Rename.php
@@ -3,16 +3,19 @@
namespace Controllers\Task\Form;
use Exception;
+use Controllers\Repo\Rpm;
+use Controllers\Repo\Deb;
+use Controllers\Repo\Repo;
use Controllers\History\Save as History;
use Controllers\Utils\Generate\Html\Label;
class Rename
{
- public function validate(array $formParams)
+ public function validate(array $formParams): void
{
- $repoController = new \Controllers\Repo\Repo();
- $rpmRepoController = new \Controllers\Repo\Rpm();
- $debRepoController = new \Controllers\Repo\Deb();
+ $repoController = new Repo();
+ $rpmRepoController = new Rpm();
+ $debRepoController = new Deb();
// Check that the snapshot id is valid
Param\Snapshot::checkId($formParams['snap-id']);
diff --git a/www/controllers/Task/Form/Update.php b/www/controllers/Task/Form/Update.php
index 82ee02e48..d57402e91 100644
--- a/www/controllers/Task/Form/Update.php
+++ b/www/controllers/Task/Form/Update.php
@@ -2,78 +2,63 @@
namespace Controllers\Task\Form;
-use Exception;
+use Controllers\Repo\Repo;
use Controllers\History\Save as History;
class Update
{
- public function validate(array $formParams)
+ public function validate(array $formParams): void
{
- $myrepo = new \Controllers\Repo\Repo();
+ $repoController = new Repo();
- /**
- * Check that the snapshot id is valid
- */
+ // Check that the snapshot id is valid
Param\Snapshot::checkId($formParams['snap-id']);
- /**
- * Retrieve all repo data from the Ids,
- */
- $myrepo->setSnapId($formParams['snap-id']);
- $myrepo->getAllById('', $formParams['snap-id'], '');
+ // Retrieve all repo data from the Id
+ $repoController->setSnapId($formParams['snap-id']);
+ $repoController->getAllById('', $formParams['snap-id'], '');
- /**
- * Check env
- */
+ // Check env
if (!empty($formParams['env'])) {
Param\Environment::check($formParams['env']);
}
- /**
- * Check architecture
- */
+ // Check architecture
Param\Arch::check($formParams['arch']);
- /**
- * Case of a mirror repository, check additional parameters
- */
- if ($myrepo->getType() == 'mirror') {
- /**
- * Check package(s) to include
- */
- Param\PackageInclude::check($formParams['package-include']);
+ // Case of a mirror repository, check additional parameters
+ if ($repoController->getType() == 'mirror') {
+ // Check package(s) to include
+ Param\PackageInclude::check($formParams['advanced-params']['packages']['include']);
- /**
- * Check package(s) to exclude
- */
- Param\PackageExclude::check($formParams['package-exclude']);
+ // Check package(s) to exclude
+ Param\PackageExclude::check($formParams['advanced-params']['packages']['exclude']);
- /**
- * Check gpg check
- */
+ // Check gpg check
Param\GpgCheck::check($formParams['gpg-check']);
+
+ if ($repoController->getPackageType() == 'deb') {
+ // Check metadata custom fields
+ Param\Metadata::checkOrigin($formParams['advanced-params']['metadata-custom-fields']['origin']);
+ Param\Metadata::checkLabel($formParams['advanced-params']['metadata-custom-fields']['label']);
+ Param\Metadata::checkDescription($formParams['advanced-params']['metadata-custom-fields']['description']);
+ }
}
- /**
- * Check gpg sign
- */
+ // Check gpg sign
Param\GpgSign::check($formParams['gpg-sign']);
- /**
- * Check scheduling parameters
- */
+ // Check scheduling parameters
Param\Schedule::check($formParams['schedule']);
- /**
- * Add history
- */
- if ($myrepo->getPackageType() == 'rpm') {
- History::set('Running task: update ' . $myrepo->getType() . ' repository ' . $myrepo->getName() . '');
+ // Add history
+ if ($repoController->getPackageType() == 'rpm') {
+ History::set('Running task: update ' . $repoController->getType() . ' repository ' . $repoController->getName() . '');
}
- if ($myrepo->getPackageType() == 'deb') {
- History::set('Running task: update ' . $myrepo->getType() . ' repository ' . $myrepo->getName() . ' ❯ ' . $myrepo->getDist() . ' ❯ ' . $myrepo->getSection() . '');
+ if ($repoController->getPackageType() == 'deb') {
+ History::set('Running task: update ' . $repoController->getType() . ' repository ' . $repoController->getName() . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection() . '');
}
- unset($myrepo);
+ unset($repoController);
}
}
diff --git a/www/controllers/Task/Listing.php b/www/controllers/Task/Listing.php
new file mode 100644
index 000000000..acfcb013c
--- /dev/null
+++ b/www/controllers/Task/Listing.php
@@ -0,0 +1,62 @@
+model = new \Models\Task\Listing();
+ }
+
+ /**
+ * Get all tasks
+ */
+ public function get(): array
+ {
+ return $this->model->get();
+ }
+
+ /**
+ * Get all queued tasks
+ * It is possible to filter the type of task ('immediate' or 'scheduled')
+ * It is possible to add an offset to the request
+ */
+ public function getQueued(string $type = '', bool $withOffset = false, int $offset = 0): array
+ {
+ return $this->model->getQueued($type, $withOffset, $offset);
+ }
+
+ /**
+ * Get all running tasks
+ * It is possible to filter the type of task ('immediate' or 'scheduled')
+ * It is possible to add an offset to the request
+ */
+ public function getRunning(string $type = '', bool $withOffset = false, int $offset = 0): array
+ {
+ return $this->model->getRunning($type, $withOffset, $offset);
+ }
+
+ /**
+ * Get all scheduled tasks
+ * It is possible to add an offset to the request
+ */
+ public function getScheduled(bool $withOffset = false, int $offset = 0): array
+ {
+ return $this->model->getScheduled($withOffset, $offset);
+ }
+
+ /**
+ * Get all done tasks (with or without errors)
+ * It is possible to filter the type of task ('immediate' or 'scheduled')
+ * It is possible to add an offset to the request
+ */
+ public function getDone(string $type = 'immediate', bool $withOffset = false, int $offset = 0): array
+ {
+ return $this->model->getDone($type, $withOffset, $offset);
+ }
+}
diff --git a/www/controllers/Task/Scheduled.php b/www/controllers/Task/Scheduled.php
index fc00a86c1..982648fc2 100644
--- a/www/controllers/Task/Scheduled.php
+++ b/www/controllers/Task/Scheduled.php
@@ -5,12 +5,17 @@
use Exception;
use JsonException;
use Controllers\Task\Form\Param\Schedule;
+use Controllers\Task\Listing as TaskListing;
class Scheduled extends Task
{
+ private $taskListingController;
+
public function __construct()
{
parent::__construct();
+
+ $this->taskListingController = new TaskListing();
}
/**
@@ -22,7 +27,7 @@ public function getBySnapId(int $id) : array
$tasksUsingSnapId = [];
// Get scheduled tasks
- $scheduledTasks = $this->listScheduled();
+ $scheduledTasks = $this->taskListingController->getScheduled();
if (!empty($scheduledTasks)) {
foreach ($scheduledTasks as $scheduledTask) {
@@ -48,7 +53,7 @@ public function getBySnapId(int $id) : array
public function deleteBySnapId(int $id) : void
{
// Get scheduled tasks
- $scheduledTasks = $this->listScheduled();
+ $scheduledTasks = $this->taskListingController->getScheduled();
// Quit if there is no scheduled task
if (empty($scheduledTasks)) {
diff --git a/www/controllers/Task/Task.php b/www/controllers/Task/Task.php
index a302b267f..a0deb4abc 100644
--- a/www/controllers/Task/Task.php
+++ b/www/controllers/Task/Task.php
@@ -3,9 +3,12 @@
namespace Controllers\Task;
use Controllers\Task\Form\Param\Schedule;
+use Controllers\Task\Log\SubStep;
+use Controllers\Task\Log\Step;
use Controllers\Utils\Cron;
-use Exception;
+use Controllers\Process;
use JsonException;
+use Exception;
use DateTime;
class Task
@@ -96,7 +99,7 @@ public function setError(string $error)
/**
* Get task details by Id
*/
- public function getById(int $id)
+ public function getById(int $id): array
{
return $this->model->getById($id);
}
@@ -141,45 +144,6 @@ public function updateDuration(int $id, string $duration) : void
$this->model->updateDuration($id, $duration);
}
- /**
- * List all queued tasks
- * It is possible to filter the type of task ('immediate' or 'scheduled')
- * It is possible to add an offset to the request
- */
- public function listQueued(string $type = '', bool $withOffset = false, int $offset = 0)
- {
- return $this->model->listQueued($type, $withOffset, $offset);
- }
-
- /**
- * List all running tasks
- * It is possible to filter the type of task ('immediate' or 'scheduled')
- * It is possible to add an offset to the request
- */
- public function listRunning(string $type = '', bool $withOffset = false, int $offset = 0)
- {
- return $this->model->listRunning($type, $withOffset, $offset);
- }
-
- /**
- * List all scheduled tasks
- * It is possible to add an offset to the request
- */
- public function listScheduled(bool $withOffset = false, int $offset = 0)
- {
- return $this->model->listScheduled($withOffset, $offset);
- }
-
- /**
- * List all done tasks (with or without errors)
- * It is possible to filter the type of task ('immediate' or 'scheduled')
- * It is possible to add an offset to the request
- */
- public function listDone(string $type = 'immediate', bool $withOffset = false, int $offset = 0)
- {
- return $this->model->listDone($type, $withOffset, $offset);
- }
-
/**
* Return last done task Id
* Can return null if no task is found (e.g. brand new installation with no task)
@@ -360,89 +324,83 @@ private function new(array $params) : int
/**
* Execute one or more tasks
*/
- public function execute(array $tasksParams) : void
+ public function execute(array $tasksParams): int|array
{
+ $tasks = [];
+
/**
* $tasksParams can contain one or more tasks
* Each task is an array containing all the parameters needed to execute the task
*/
foreach ($tasksParams as $taskParams) {
- /**
- * If the task is a new repo, we need to loop through all the releasever (rpm) or dist/section (deb) and create a dedicated task for each of them
- */
+ // If the task is a new repo, we need to loop through all the releasever (rpm) or dist/section (deb) and create a dedicated task for each of them
if ($taskParams['action'] == 'create') {
if ($taskParams['package-type'] == 'rpm') {
foreach ($taskParams['releasever'] as $releasever) {
- /**
- * Create a new array with the same parameters as the original array, but with only one releasever
- */
+ // Create a new array with the same parameters as the original array, but with only one releasever
$params = $taskParams;
- /**
- * Replace the releasever array with a single releasever
- */
+ // Replace the releasever array with a single releasever
$params['releasever'] = $releasever;
- /**
- * Generate a new task containing all the parameters needed to execute the task retrieve its Id
- */
+ // Generate a new task containing all the parameters needed to execute the task retrieve its Id
$taskId = $this->new($params);
- /**
- * Execute the task now if it is not scheduled
- */
+ // Execute the task now if it is not scheduled
if ($params['schedule']['scheduled'] != 'true') {
$this->executeId($taskId);
}
+
+ // Add task Id to the list of executed tasks
+ $tasks[] = $taskId;
}
}
if ($taskParams['package-type'] == 'deb') {
foreach ($taskParams['dist'] as $dist) {
foreach ($taskParams['section'] as $section) {
- /**
- * Create a new array with the same parameters as the original array, but with only one dist and one section
- */
+ // Create a new array with the same parameters as the original array, but with only one dist and one section
$params = $taskParams;
- /**
- * Replace the dist and section arrays with a single dist and a single section
- */
+ // Replace the dist and section arrays with a single dist and a single section
$params['dist'] = $dist;
$params['section'] = $section;
- /**
- * Generate a new task containing all the parameters needed to execute the task retrieve its Id
- */
+ // Generate a new task containing all the parameters needed to execute the task retrieve its Id
$taskId = $this->new($params);
- /**
- * Execute the task now if it is not scheduled
- */
+ // Execute the task now if it is not scheduled
if ($params['schedule']['scheduled'] != 'true') {
$this->executeId($taskId);
}
+
+ // Add task Id to the list of executed tasks
+ $tasks[] = $taskId;
}
}
}
- /**
- * Every other task can be executed directly
- */
+ // Every other task can be executed directly
} else {
- /**
- * Generate a new task containing all the parameters needed to execute the task retrieve its Id
- */
+ // Generate a new task containing all the parameters needed to execute the task retrieve its Id
$taskId = $this->new($taskParams);
- /**
- * Execute the task now if it is not scheduled
- */
+ // Execute the task now if it is not scheduled
if ($taskParams['schedule']['scheduled'] != 'true') {
$this->executeId($taskId);
}
+
+ // Add task Id to the list of executed tasks
+ $tasks[] = $taskId;
}
}
+
+ // Return the Id of the executed task or an array with all the executed tasks Id
+ if (count($tasks) == 1) {
+ return $tasks[0];
+ } else {
+ return $tasks;
+ }
}
/**
@@ -450,7 +408,7 @@ public function execute(array $tasksParams) : void
*/
public function executeId(int $id) : void
{
- $myprocess = new \Controllers\Process('/usr/bin/php ' . ROOT . '/tasks/execute.php --id="' . $id . '" > ' . MAIN_LOGS_DIR . '/repomanager-task-' . $id . '-log.process 2>&1 &');
+ $myprocess = new Process('/usr/bin/php ' . ROOT . '/tasks/execute.php --id="' . $id . '" > ' . MAIN_LOGS_DIR . '/repomanager-task-' . $id . '-log.process 2>&1 &');
$myprocess->execute();
$myprocess->close();
}
@@ -504,38 +462,43 @@ public function duplicate(int $id) : int
}
/**
- * Stop a task based on the specified PID
+ * Stop a task based on the specified task Id
*/
- public function kill(string $taskId) : void
+ public function stop(int $taskId): void
{
if (!IS_ADMIN and !in_array('stop', USER_PERMISSIONS['tasks']['allowed-actions'])) {
throw new Exception('You are not allowed to stop a task');
}
+ // Check if task exists
+ if (!$this->exists($taskId)) {
+ throw new Exception('Task #' . $taskId . ' does not exist');
+ }
+
+ // Get task details
+ $taskInfo = $this->getById($taskId);
+
+ // Check if task is running
+ if ($taskInfo['Status'] != 'running') {
+ throw new Exception('Task #' . $taskId . ' is not running');
+ }
+
if (file_exists(PID_DIR . '/' . $taskId . '.pid')) {
- /**
- * Getting PID file content
- */
+ // Getting PID file content
$content = file_get_contents(PID_DIR . '/' . $taskId . '.pid');
- /**
- * Getting sub PIDs
- */
+ // Getting sub PIDs
preg_match_all('/(?<=SUBPID=).*/', $content, $subpids);
- /**
- * Killing sub PIDs
- */
+ // Killing sub PIDs
if (!empty($subpids[0])) {
$killError = '';
foreach ($subpids[0] as $subpid) {
$subpid = trim(str_replace('"', '', $subpid));
- /**
- * Check if the PID is still running
- */
- $myprocess = new \Controllers\Process('/usr/bin/ps --pid ' . $subpid);
+ // Check if the PID is still running
+ $myprocess = new Process('/usr/bin/ps --pid ' . $subpid);
$myprocess->execute();
$content = $myprocess->getOutput();
$myprocess->close();
@@ -544,10 +507,8 @@ public function kill(string $taskId) : void
continue;
}
- /**
- * Kill the process
- */
- $myprocess = new \Controllers\Process('/usr/bin/kill -9 ' . $subpid);
+ // Kill the process
+ $myprocess = new Process('/usr/bin/kill -9 ' . $subpid);
$myprocess->execute();
$content = $myprocess->getOutput();
$myprocess->close();
@@ -558,31 +519,23 @@ public function kill(string $taskId) : void
}
}
- /**
- * Delete PID file
- */
+ // Delete PID file
if (!unlink(PID_DIR . '/' . $taskId . '.pid')) {
throw new Exception('Error while deleting PID file');
}
}
- /**
- * Update task in database, set status to 'stopped'
- */
+ // Update task in database, set status to 'stopped'
$this->updateStatus($taskId, 'stopped');
- $taskLogStepController = new \Controllers\Task\Log\Step($taskId);
- $taskLogSubStepController = new \Controllers\Task\Log\SubStep($taskId);
+ $taskLogStepController = new Step($taskId);
+ $taskLogSubStepController = new SubStep($taskId);
- /**
- * Set latest step and substep as stopped
- */
+ // Set latest step and substep as stopped
$taskLogStepController->stopped();
$taskLogSubStepController->stopped();
- /**
- * Update layout containers states
- */
+ // Update layout containers states
$this->layoutContainerReloadController->reload('header/menu');
$this->layoutContainerReloadController->reload('repos/list');
$this->layoutContainerReloadController->reload('tasks/list');
@@ -636,7 +589,7 @@ public function addsubpid(int $pid) : void
public static function getChildrenPid(int $pid) : array|bool
{
// Specified PID could have children PID, we need to get them all
- $processController = new \Controllers\Process('/usr/bin/pgrep -P ' . $pid);
+ $processController = new Process('/usr/bin/pgrep -P ' . $pid);
$processController->execute();
// If exit code is 0, then the PID has children
@@ -657,16 +610,35 @@ public static function getChildrenPid(int $pid) : array|bool
/**
* Enable a recurrent task
*/
- public function enable(array $tasksId) : void
+ public function enable(array $tasksId): void
{
if (!IS_ADMIN and !in_array('enable', USER_PERMISSIONS['tasks']['allowed-actions'])) {
- throw new Exception('You are not allowed to enable a task.');
+ throw new Exception('You are not allowed to enable a task');
}
foreach ($tasksId as $id) {
// Check if task exists
if (!$this->exists($id)) {
- throw new Exception('Task #' . $id . ' does not exist.');
+ throw new Exception('Task #' . $id . ' does not exist');
+ }
+
+ // Get task details
+ $task = $this->getById($id);
+
+ try {
+ $taskRawParams = json_decode($task['Raw_params'], true, 512, JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ throw new Exception('Could not decode task #' . $id . ' JSON parameters: ' . $e->getMessage());
+ }
+
+ // Check if task is a scheduled task
+ if ($taskRawParams['schedule']['scheduled'] != 'true') {
+ throw new Exception('Task #' . $id . ' is not a scheduled task and cannot be enabled');
+ }
+
+ // Do nothing if task is already enabled
+ if ($task['Status'] == 'scheduled') {
+ return;
}
// Enable task
@@ -680,13 +652,32 @@ public function enable(array $tasksId) : void
public function disable(array $tasksId) : void
{
if (!IS_ADMIN and !in_array('disable', USER_PERMISSIONS['tasks']['allowed-actions'])) {
- throw new Exception('You are not allowed to disable a task.');
+ throw new Exception('You are not allowed to disable a task');
}
foreach ($tasksId as $id) {
// Check if task exists
if (!$this->exists($id)) {
- throw new Exception('Task #' . $id . ' does not exist.');
+ throw new Exception('Task #' . $id . ' does not exist');
+ }
+
+ // Get task details
+ $task = $this->getById($id);
+
+ try {
+ $taskRawParams = json_decode($task['Raw_params'], true, 512, JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ throw new Exception('Could not decode task #' . $id . ' JSON parameters: ' . $e->getMessage());
+ }
+
+ // Check if task is a scheduled task
+ if ($taskRawParams['schedule']['scheduled'] != 'true') {
+ throw new Exception('Task #' . $id . ' is not a scheduled task and cannot be disabled');
+ }
+
+ // Do nothing if task is already disabled
+ if ($task['Status'] == 'disabled') {
+ return;
}
// Disable task
@@ -700,13 +691,28 @@ public function disable(array $tasksId) : void
public function delete(array $tasksId) : void
{
if (!IS_ADMIN and !in_array('delete', USER_PERMISSIONS['tasks']['allowed-actions'])) {
- throw new Exception('You are not allowed to delete a task.');
+ throw new Exception('You are not allowed to delete a task');
}
foreach ($tasksId as $id) {
// Check if task exists
if (!$this->exists($id)) {
- throw new Exception('Task #' . $id . ' does not exist.');
+ throw new Exception('Task #' . $id . ' does not exist');
+ }
+
+ // Get task details
+ $task = $this->getById($id);
+
+ try {
+ $taskRawParams = json_decode($task['Raw_params'], true, 512, JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ throw new Exception('Could not decode task #' . $id . ' JSON parameters: ' . $e->getMessage());
+ }
+
+
+ // Check if task is a scheduled task
+ if ($taskRawParams['schedule']['scheduled'] != 'true') {
+ throw new Exception('Task #' . $id . ' is not a scheduled task and cannot be deleted');
}
// Delete task
@@ -937,7 +943,7 @@ public function getDayTimeLeft(int $taskId) : array
$schedule['left']['time'] = $taskTime->diff($timeNow)->format('%hh%im');
}
- unset($task, $taskRawParams, $dateNow, $timeNow, $taskDate, $taskTime, $nextScheduledTaskTime, $nextScheduledTaskDate, $daysLeft, $timeLeft);
+ unset($task, $taskRawParams, $dateNow, $timeNow, $taskDate, $taskTime, $nextScheduledTaskTime, $nextScheduledTaskDate);
return $schedule;
}
diff --git a/www/controllers/User/Create.php b/www/controllers/User/Create.php
index 11b3b1ca6..2883c5f5d 100644
--- a/www/controllers/User/Create.php
+++ b/www/controllers/User/Create.php
@@ -91,7 +91,7 @@ public function create(string $username, string $role) : string
/**
* Add a new SSO user in database
*/
- public function createSSO(string $username, string $firstName = '', string $lastName = '', string $email = '', ?string $role): void
+ public function createSSO(string $username, string $firstName = '', string $lastName = '', string $email = '', string $role = ''): void
{
$userEditController = new \Controllers\User\Edit();
$username = Validate::string($username);
diff --git a/www/controllers/Utils/Validate.php b/www/controllers/Utils/Validate.php
index 22cd87831..5dce7e35b 100644
--- a/www/controllers/Utils/Validate.php
+++ b/www/controllers/Utils/Validate.php
@@ -6,6 +6,11 @@
class Validate
{
+ public static function int(int $int) : int
+ {
+ return filter_var($int, FILTER_VALIDATE_INT);
+ }
+
/**
* Validate a string and return it
*/
diff --git a/www/controllers/Websocket/BrowserClient/Process.php b/www/controllers/Websocket/BrowserClient/Process.php
index 674e677fb..3586a2cf6 100644
--- a/www/controllers/Websocket/BrowserClient/Process.php
+++ b/www/controllers/Websocket/BrowserClient/Process.php
@@ -2,8 +2,6 @@
namespace Controllers\Websocket\BrowserClient;
-use Exception;
-
/**
* Class Process extends WebsocketServer to gain access to its methods
*/
diff --git a/www/controllers/ajax/task.php b/www/controllers/ajax/task.php
index 88dbf16d8..7a1347773 100644
--- a/www/controllers/ajax/task.php
+++ b/www/controllers/ajax/task.php
@@ -105,7 +105,7 @@
*/
if ($_POST['action'] == 'stop' and !empty($_POST['id'])) {
try {
- $myTask->kill($_POST['id']);
+ $myTask->stop($_POST['id']);
} catch (Exception $e) {
response(HTTP_BAD_REQUEST, $e->getMessage());
}
diff --git a/www/libs/composer.json b/www/libs/composer.json
index f7d70ad4d..3df92f693 100644
--- a/www/libs/composer.json
+++ b/www/libs/composer.json
@@ -1,6 +1,10 @@
{
"require": {
"cboden/ratchet": "^0.4.4",
- "jumbojett/openid-connect-php": "^1.0"
+ "jumbojett/openid-connect-php": "^1.0",
+ "phpmailer/phpmailer": "^7.1"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^2.2"
}
}
diff --git a/www/libs/composer.lock b/www/libs/composer.lock
index a7fb1e11b..589e3b433 100644
--- a/www/libs/composer.lock
+++ b/www/libs/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "6f8968896baf95b7f7216f24f352ba52",
+ "content-hash": "4d981f4606bf02ef56eaf5ebb1ac928a",
"packages": [
{
"name": "cboden/ratchet",
@@ -118,16 +118,16 @@
},
{
"name": "guzzlehttp/psr7",
- "version": "2.9.0",
+ "version": "2.10.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884"
+ "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884",
- "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/7c1472269227dc6f18930bd903d7a88fe6c52130",
+ "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130",
"shasum": ""
},
"require": {
@@ -142,9 +142,9 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
- "http-interop/http-factory-tests": "0.9.0",
+ "http-interop/http-factory-tests": "1.1.0",
"jshttp/mime-db": "1.54.0.1",
- "phpunit/phpunit": "^8.5.44 || ^9.6.25"
+ "phpunit/phpunit": "^8.5.52 || ^9.6.34"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
@@ -215,7 +215,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/2.9.0"
+ "source": "https://github.com/guzzle/psr7/tree/2.10.3"
},
"funding": [
{
@@ -231,7 +231,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-10T16:41:02+00:00"
+ "time": "2026-05-27T11:48:20+00:00"
},
{
"name": "jumbojett/openid-connect-php",
@@ -394,6 +394,88 @@
},
"time": "2020-10-15T08:29:30+00:00"
},
+ {
+ "name": "phpmailer/phpmailer",
+ "version": "v7.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPMailer/PHPMailer.git",
+ "reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/1bc1716a507a65e039d4ac9d9adebbbd0d346e15",
+ "reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-filter": "*",
+ "ext-hash": "*",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "doctrine/annotations": "^1.2.6 || ^1.13.3",
+ "php-parallel-lint/php-console-highlighter": "^1.0.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "phpcompatibility/php-compatibility": "^10.0.0@dev",
+ "squizlabs/php_codesniffer": "^3.13.5",
+ "yoast/phpunit-polyfills": "^1.0.4"
+ },
+ "suggest": {
+ "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
+ "directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example",
+ "ext-imap": "Needed to support advanced email address parsing according to RFC822",
+ "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
+ "ext-openssl": "Needed for secure SMTP sending and DKIM signing",
+ "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
+ "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+ "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+ "psr/log": "For optional PSR-3 debug logging",
+ "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
+ "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPMailer\\PHPMailer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-2.1-only"
+ ],
+ "authors": [
+ {
+ "name": "Marcus Bointon",
+ "email": "phpmailer@synchromedia.co.uk"
+ },
+ {
+ "name": "Jim Jagielski",
+ "email": "jimjag@gmail.com"
+ },
+ {
+ "name": "Andy Prevost",
+ "email": "codeworxtech@users.sourceforge.net"
+ },
+ {
+ "name": "Brent R. Matzelle"
+ }
+ ],
+ "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+ "support": {
+ "issues": "https://github.com/PHPMailer/PHPMailer/issues",
+ "source": "https://github.com/PHPMailer/PHPMailer/tree/v7.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Synchro",
+ "type": "github"
+ }
+ ],
+ "time": "2026-05-18T08:06:14+00:00"
+ },
{
"name": "phpseclib/phpseclib",
"version": "3.0.52",
@@ -1237,16 +1319,16 @@
},
{
"name": "symfony/http-foundation",
- "version": "v6.4.35",
+ "version": "v6.4.41",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "cffffd0a2c037117b742b4f8b379a22a2a33f6d2"
+ "reference": "48d76c29a67a301e0f7779a512bf76417395ffef"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cffffd0a2c037117b742b4f8b379a22a2a33f6d2",
- "reference": "cffffd0a2c037117b742b4f8b379a22a2a33f6d2",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/48d76c29a67a301e0f7779a512bf76417395ffef",
+ "reference": "48d76c29a67a301e0f7779a512bf76417395ffef",
"shasum": ""
},
"require": {
@@ -1294,7 +1376,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v6.4.35"
+ "source": "https://github.com/symfony/http-foundation/tree/v6.4.41"
},
"funding": [
{
@@ -1314,20 +1396,20 @@
"type": "tidelift"
}
],
- "time": "2026-03-06T11:15:58+00:00"
+ "time": "2026-05-24T10:54:17+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.37.0",
+ "version": "v1.38.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
+ "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
- "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92",
+ "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92",
"shasum": ""
},
"require": {
@@ -1379,7 +1461,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1"
},
"funding": [
{
@@ -1399,20 +1481,20 @@
"type": "tidelift"
}
],
- "time": "2026-04-10T17:25:58+00:00"
+ "time": "2026-05-26T12:51:13+00:00"
},
{
"name": "symfony/polyfill-php83",
- "version": "v1.37.0",
+ "version": "v1.38.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php83.git",
- "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149"
+ "reference": "8339098cae28673c15cce00d80734af0453054e2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149",
- "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149",
+ "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2",
+ "reference": "8339098cae28673c15cce00d80734af0453054e2",
"shasum": ""
},
"require": {
@@ -1459,7 +1541,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0"
+ "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1"
},
"funding": [
{
@@ -1479,20 +1561,20 @@
"type": "tidelift"
}
],
- "time": "2026-04-10T17:25:58+00:00"
+ "time": "2026-05-26T12:51:13+00:00"
},
{
"name": "symfony/routing",
- "version": "v6.4.37",
+ "version": "v6.4.41",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "48035d186798d27d375d95aad37db8fe097e4048"
+ "reference": "af04c79671fd8df0805a44c83fa2b0ba56c8329e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/48035d186798d27d375d95aad37db8fe097e4048",
- "reference": "48035d186798d27d375d95aad37db8fe097e4048",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/af04c79671fd8df0805a44c83fa2b0ba56c8329e",
+ "reference": "af04c79671fd8df0805a44c83fa2b0ba56c8329e",
"shasum": ""
},
"require": {
@@ -1546,7 +1628,7 @@
"url"
],
"support": {
- "source": "https://github.com/symfony/routing/tree/v6.4.37"
+ "source": "https://github.com/symfony/routing/tree/v6.4.41"
},
"funding": [
{
@@ -1566,10 +1648,75 @@
"type": "tidelift"
}
],
- "time": "2026-04-18T13:45:55+00:00"
+ "time": "2026-05-24T11:18:16+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "phpstan/phpstan",
+ "version": "2.2.0",
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4cd98348c809924f62bb9cc8c047f5e73bc9a58",
+ "reference": "b4cd98348c809924f62bb9cc8c047f5e73bc9a58",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ondřej Mirtes"
+ },
+ {
+ "name": "Markus Staab"
+ },
+ {
+ "name": "Vincent Langlet"
+ }
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "time": "2026-05-28T08:22:43+00:00"
}
],
- "packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
diff --git a/www/libs/vendor/bin/phpstan b/www/libs/vendor/bin/phpstan
new file mode 100755
index 000000000..d76c0be76
--- /dev/null
+++ b/www/libs/vendor/bin/phpstan
@@ -0,0 +1,119 @@
+#!/usr/bin/env php
+realpath = realpath($opened_path) ?: $opened_path;
+ $opened_path = $this->realpath;
+ $this->handle = fopen($this->realpath, $mode);
+ $this->position = 0;
+
+ return (bool) $this->handle;
+ }
+
+ public function stream_read($count)
+ {
+ $data = fread($this->handle, $count);
+
+ if ($this->position === 0) {
+ $data = preg_replace('{^#!.*\r?\n}', '', $data);
+ }
+
+ $this->position += strlen($data);
+
+ return $data;
+ }
+
+ public function stream_cast($castAs)
+ {
+ return $this->handle;
+ }
+
+ public function stream_close()
+ {
+ fclose($this->handle);
+ }
+
+ public function stream_lock($operation)
+ {
+ return $operation ? flock($this->handle, $operation) : true;
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ if (0 === fseek($this->handle, $offset, $whence)) {
+ $this->position = ftell($this->handle);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function stream_tell()
+ {
+ return $this->position;
+ }
+
+ public function stream_eof()
+ {
+ return feof($this->handle);
+ }
+
+ public function stream_stat()
+ {
+ return array();
+ }
+
+ public function stream_set_option($option, $arg1, $arg2)
+ {
+ return true;
+ }
+
+ public function url_stat($path, $flags)
+ {
+ $path = substr($path, 17);
+ if (file_exists($path)) {
+ return stat($path);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ if (
+ (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
+ || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
+ ) {
+ return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan');
+ }
+}
+
+return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan';
diff --git a/www/libs/vendor/bin/phpstan.phar b/www/libs/vendor/bin/phpstan.phar
new file mode 100755
index 000000000..fecf96f69
--- /dev/null
+++ b/www/libs/vendor/bin/phpstan.phar
@@ -0,0 +1,119 @@
+#!/usr/bin/env php
+realpath = realpath($opened_path) ?: $opened_path;
+ $opened_path = $this->realpath;
+ $this->handle = fopen($this->realpath, $mode);
+ $this->position = 0;
+
+ return (bool) $this->handle;
+ }
+
+ public function stream_read($count)
+ {
+ $data = fread($this->handle, $count);
+
+ if ($this->position === 0) {
+ $data = preg_replace('{^#!.*\r?\n}', '', $data);
+ }
+
+ $this->position += strlen($data);
+
+ return $data;
+ }
+
+ public function stream_cast($castAs)
+ {
+ return $this->handle;
+ }
+
+ public function stream_close()
+ {
+ fclose($this->handle);
+ }
+
+ public function stream_lock($operation)
+ {
+ return $operation ? flock($this->handle, $operation) : true;
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ if (0 === fseek($this->handle, $offset, $whence)) {
+ $this->position = ftell($this->handle);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function stream_tell()
+ {
+ return $this->position;
+ }
+
+ public function stream_eof()
+ {
+ return feof($this->handle);
+ }
+
+ public function stream_stat()
+ {
+ return array();
+ }
+
+ public function stream_set_option($option, $arg1, $arg2)
+ {
+ return true;
+ }
+
+ public function url_stat($path, $flags)
+ {
+ $path = substr($path, 17);
+ if (file_exists($path)) {
+ return stat($path);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ if (
+ (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
+ || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
+ ) {
+ return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar');
+ }
+}
+
+return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar';
diff --git a/www/libs/vendor/composer/autoload_files.php b/www/libs/vendor/composer/autoload_files.php
index f3d9a3e5d..d043b55b2 100644
--- a/www/libs/vendor/composer/autoload_files.php
+++ b/www/libs/vendor/composer/autoload_files.php
@@ -12,4 +12,5 @@
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
+ '9b38cf48e83f5d8f60375221cd213eee' => $vendorDir . '/phpstan/phpstan/bootstrap.php',
);
diff --git a/www/libs/vendor/composer/autoload_psr4.php b/www/libs/vendor/composer/autoload_psr4.php
index b94150c14..410d8bdf7 100644
--- a/www/libs/vendor/composer/autoload_psr4.php
+++ b/www/libs/vendor/composer/autoload_psr4.php
@@ -21,6 +21,7 @@
'Ratchet\\' => array($vendorDir . '/cboden/ratchet/src/Ratchet'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
+ 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'Evenement\\' => array($vendorDir . '/evenement/evenement/src'),
);
diff --git a/www/libs/vendor/composer/autoload_static.php b/www/libs/vendor/composer/autoload_static.php
index 902e1ec7e..cc7447ca3 100644
--- a/www/libs/vendor/composer/autoload_static.php
+++ b/www/libs/vendor/composer/autoload_static.php
@@ -13,6 +13,7 @@ class ComposerStaticInitce559b9fbd0e097bccee77e6c3cc8be1
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php',
'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
+ '9b38cf48e83f5d8f60375221cd213eee' => __DIR__ . '/..' . '/phpstan/phpstan/bootstrap.php',
);
public static $prefixLengthsPsr4 = array (
@@ -42,6 +43,7 @@ class ComposerStaticInitce559b9fbd0e097bccee77e6c3cc8be1
array (
'Psr\\Http\\Message\\' => 17,
'ParagonIE\\ConstantTime\\' => 23,
+ 'PHPMailer\\PHPMailer\\' => 20,
),
'G' =>
array (
@@ -115,6 +117,10 @@ class ComposerStaticInitce559b9fbd0e097bccee77e6c3cc8be1
array (
0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src',
),
+ 'PHPMailer\\PHPMailer\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
+ ),
'GuzzleHttp\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
diff --git a/www/libs/vendor/composer/installed.json b/www/libs/vendor/composer/installed.json
index 3f6970cea..c34319ee0 100644
--- a/www/libs/vendor/composer/installed.json
+++ b/www/libs/vendor/composer/installed.json
@@ -118,17 +118,17 @@
},
{
"name": "guzzlehttp/psr7",
- "version": "2.9.0",
- "version_normalized": "2.9.0.0",
+ "version": "2.10.3",
+ "version_normalized": "2.10.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884"
+ "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884",
- "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/7c1472269227dc6f18930bd903d7a88fe6c52130",
+ "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130",
"shasum": ""
},
"require": {
@@ -143,14 +143,14 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
- "http-interop/http-factory-tests": "0.9.0",
+ "http-interop/http-factory-tests": "1.1.0",
"jshttp/mime-db": "1.54.0.1",
- "phpunit/phpunit": "^8.5.44 || ^9.6.25"
+ "phpunit/phpunit": "^8.5.52 || ^9.6.34"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
- "time": "2026-03-10T16:41:02+00:00",
+ "time": "2026-05-27T11:48:20+00:00",
"type": "library",
"extra": {
"bamarni-bin": {
@@ -218,7 +218,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/2.9.0"
+ "source": "https://github.com/guzzle/psr7/tree/2.10.3"
},
"funding": [
{
@@ -406,6 +406,91 @@
},
"install-path": "../paragonie/random_compat"
},
+ {
+ "name": "phpmailer/phpmailer",
+ "version": "v7.1.1",
+ "version_normalized": "7.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPMailer/PHPMailer.git",
+ "reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/1bc1716a507a65e039d4ac9d9adebbbd0d346e15",
+ "reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-filter": "*",
+ "ext-hash": "*",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "doctrine/annotations": "^1.2.6 || ^1.13.3",
+ "php-parallel-lint/php-console-highlighter": "^1.0.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "phpcompatibility/php-compatibility": "^10.0.0@dev",
+ "squizlabs/php_codesniffer": "^3.13.5",
+ "yoast/phpunit-polyfills": "^1.0.4"
+ },
+ "suggest": {
+ "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
+ "directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example",
+ "ext-imap": "Needed to support advanced email address parsing according to RFC822",
+ "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
+ "ext-openssl": "Needed for secure SMTP sending and DKIM signing",
+ "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
+ "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+ "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+ "psr/log": "For optional PSR-3 debug logging",
+ "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
+ "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
+ },
+ "time": "2026-05-18T08:06:14+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "PHPMailer\\PHPMailer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-2.1-only"
+ ],
+ "authors": [
+ {
+ "name": "Marcus Bointon",
+ "email": "phpmailer@synchromedia.co.uk"
+ },
+ {
+ "name": "Jim Jagielski",
+ "email": "jimjag@gmail.com"
+ },
+ {
+ "name": "Andy Prevost",
+ "email": "codeworxtech@users.sourceforge.net"
+ },
+ {
+ "name": "Brent R. Matzelle"
+ }
+ ],
+ "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+ "support": {
+ "issues": "https://github.com/PHPMailer/PHPMailer/issues",
+ "source": "https://github.com/PHPMailer/PHPMailer/tree/v7.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Synchro",
+ "type": "github"
+ }
+ ],
+ "install-path": "../phpmailer/phpmailer"
+ },
{
"name": "phpseclib/phpseclib",
"version": "3.0.52",
@@ -519,6 +604,73 @@
],
"install-path": "../phpseclib/phpseclib"
},
+ {
+ "name": "phpstan/phpstan",
+ "version": "2.2.0",
+ "version_normalized": "2.2.0.0",
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4cd98348c809924f62bb9cc8c047f5e73bc9a58",
+ "reference": "b4cd98348c809924f62bb9cc8c047f5e73bc9a58",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "time": "2026-05-28T08:22:43+00:00",
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ondřej Mirtes"
+ },
+ {
+ "name": "Markus Staab"
+ },
+ {
+ "name": "Vincent Langlet"
+ }
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "install-path": "../phpstan/phpstan"
+ },
{
"name": "psr/http-factory",
"version": "1.1.0",
@@ -1285,17 +1437,17 @@
},
{
"name": "symfony/http-foundation",
- "version": "v6.4.35",
- "version_normalized": "6.4.35.0",
+ "version": "v6.4.41",
+ "version_normalized": "6.4.41.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "cffffd0a2c037117b742b4f8b379a22a2a33f6d2"
+ "reference": "48d76c29a67a301e0f7779a512bf76417395ffef"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cffffd0a2c037117b742b4f8b379a22a2a33f6d2",
- "reference": "cffffd0a2c037117b742b4f8b379a22a2a33f6d2",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/48d76c29a67a301e0f7779a512bf76417395ffef",
+ "reference": "48d76c29a67a301e0f7779a512bf76417395ffef",
"shasum": ""
},
"require": {
@@ -1317,7 +1469,7 @@
"symfony/mime": "^5.4|^6.0|^7.0",
"symfony/rate-limiter": "^5.4|^6.0|^7.0"
},
- "time": "2026-03-06T11:15:58+00:00",
+ "time": "2026-05-24T10:54:17+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1345,7 +1497,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v6.4.35"
+ "source": "https://github.com/symfony/http-foundation/tree/v6.4.41"
},
"funding": [
{
@@ -1369,17 +1521,17 @@
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.37.0",
- "version_normalized": "1.37.0.0",
+ "version": "v1.38.1",
+ "version_normalized": "1.38.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
+ "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
- "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92",
+ "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92",
"shasum": ""
},
"require": {
@@ -1392,7 +1544,7 @@
"suggest": {
"ext-mbstring": "For best performance"
},
- "time": "2026-04-10T17:25:58+00:00",
+ "time": "2026-05-26T12:51:13+00:00",
"type": "library",
"extra": {
"thanks": {
@@ -1433,7 +1585,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1"
},
"funding": [
{
@@ -1457,23 +1609,23 @@
},
{
"name": "symfony/polyfill-php83",
- "version": "v1.37.0",
- "version_normalized": "1.37.0.0",
+ "version": "v1.38.1",
+ "version_normalized": "1.38.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php83.git",
- "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149"
+ "reference": "8339098cae28673c15cce00d80734af0453054e2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149",
- "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149",
+ "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2",
+ "reference": "8339098cae28673c15cce00d80734af0453054e2",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
- "time": "2026-04-10T17:25:58+00:00",
+ "time": "2026-05-26T12:51:13+00:00",
"type": "library",
"extra": {
"thanks": {
@@ -1516,7 +1668,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0"
+ "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1"
},
"funding": [
{
@@ -1540,17 +1692,17 @@
},
{
"name": "symfony/routing",
- "version": "v6.4.37",
- "version_normalized": "6.4.37.0",
+ "version": "v6.4.41",
+ "version_normalized": "6.4.41.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "48035d186798d27d375d95aad37db8fe097e4048"
+ "reference": "af04c79671fd8df0805a44c83fa2b0ba56c8329e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/48035d186798d27d375d95aad37db8fe097e4048",
- "reference": "48035d186798d27d375d95aad37db8fe097e4048",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/af04c79671fd8df0805a44c83fa2b0ba56c8329e",
+ "reference": "af04c79671fd8df0805a44c83fa2b0ba56c8329e",
"shasum": ""
},
"require": {
@@ -1572,7 +1724,7 @@
"symfony/http-foundation": "^5.4|^6.0|^7.0",
"symfony/yaml": "^5.4|^6.0|^7.0"
},
- "time": "2026-04-18T13:45:55+00:00",
+ "time": "2026-05-24T11:18:16+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1606,7 +1758,7 @@
"url"
],
"support": {
- "source": "https://github.com/symfony/routing/tree/v6.4.37"
+ "source": "https://github.com/symfony/routing/tree/v6.4.41"
},
"funding": [
{
@@ -1630,5 +1782,7 @@
}
],
"dev": true,
- "dev-package-names": []
+ "dev-package-names": [
+ "phpstan/phpstan"
+ ]
}
diff --git a/www/libs/vendor/composer/installed.php b/www/libs/vendor/composer/installed.php
index 3b8da387f..f0106788e 100644
--- a/www/libs/vendor/composer/installed.php
+++ b/www/libs/vendor/composer/installed.php
@@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
- 'reference' => '7ba1d3ba7fb36c6146a4e66c124843fad28cfca7',
+ 'reference' => '5688fa676349006f6cba36028c3a8ea72aecd320',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
- 'reference' => '7ba1d3ba7fb36c6146a4e66c124843fad28cfca7',
+ 'reference' => '5688fa676349006f6cba36028c3a8ea72aecd320',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -38,9 +38,9 @@
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
- 'pretty_version' => '2.9.0',
- 'version' => '2.9.0.0',
- 'reference' => '7d0ed42f28e42d61352a7a79de682e5e67fec884',
+ 'pretty_version' => '2.10.3',
+ 'version' => '2.10.3.0',
+ 'reference' => '7c1472269227dc6f18930bd903d7a88fe6c52130',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
@@ -73,6 +73,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'phpmailer/phpmailer' => array(
+ 'pretty_version' => 'v7.1.1',
+ 'version' => '7.1.1.0',
+ 'reference' => '1bc1716a507a65e039d4ac9d9adebbbd0d346e15',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../phpmailer/phpmailer',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'phpseclib/phpseclib' => array(
'pretty_version' => '3.0.52',
'version' => '3.0.52.0',
@@ -82,6 +91,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'phpstan/phpstan' => array(
+ 'pretty_version' => '2.2.0',
+ 'version' => '2.2.0.0',
+ 'reference' => 'b4cd98348c809924f62bb9cc8c047f5e73bc9a58',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../phpstan/phpstan',
+ 'aliases' => array(),
+ 'dev_requirement' => true,
+ ),
'psr/http-factory' => array(
'pretty_version' => '1.1.0',
'version' => '1.1.0.0',
@@ -194,36 +212,36 @@
'dev_requirement' => false,
),
'symfony/http-foundation' => array(
- 'pretty_version' => 'v6.4.35',
- 'version' => '6.4.35.0',
- 'reference' => 'cffffd0a2c037117b742b4f8b379a22a2a33f6d2',
+ 'pretty_version' => 'v6.4.41',
+ 'version' => '6.4.41.0',
+ 'reference' => '48d76c29a67a301e0f7779a512bf76417395ffef',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-foundation',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
- 'pretty_version' => 'v1.37.0',
- 'version' => '1.37.0.0',
- 'reference' => '6a21eb99c6973357967f6ce3708cd55a6bec6315',
+ 'pretty_version' => 'v1.38.1',
+ 'version' => '1.38.1.0',
+ 'reference' => '14c5439eec4ccff081ac14eca2dc57feb2a66d92',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php83' => array(
- 'pretty_version' => 'v1.37.0',
- 'version' => '1.37.0.0',
- 'reference' => '3600c2cb22399e25bb226e4a135ce91eeb2a6149',
+ 'pretty_version' => 'v1.38.1',
+ 'version' => '1.38.1.0',
+ 'reference' => '8339098cae28673c15cce00d80734af0453054e2',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php83',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/routing' => array(
- 'pretty_version' => 'v6.4.37',
- 'version' => '6.4.37.0',
- 'reference' => '48035d186798d27d375d95aad37db8fe097e4048',
+ 'pretty_version' => 'v6.4.41',
+ 'version' => '6.4.41.0',
+ 'reference' => 'af04c79671fd8df0805a44c83fa2b0ba56c8329e',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/routing',
'aliases' => array(),
diff --git a/www/libs/vendor/guzzlehttp/psr7/CHANGELOG.md b/www/libs/vendor/guzzlehttp/psr7/CHANGELOG.md
index 6aca386e3..b167ae6ec 100644
--- a/www/libs/vendor/guzzlehttp/psr7/CHANGELOG.md
+++ b/www/libs/vendor/guzzlehttp/psr7/CHANGELOG.md
@@ -5,6 +5,49 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## 2.10.3 - 2026-05-27
+
+### Fixed
+
+- Fixed URI parsing for IPv6 literals containing embedded IPv4 addresses
+- Fixed malformed UTF-8 URI strings being parsed as empty URIs
+
+## 2.10.2 - 2026-05-25
+
+### Security
+
+- Reject control and whitespace characters in URI host components (GHSA-hq7v-mx3g-29hw)
+- Reject malformed Host values when constructing request URIs (GHSA-34xg-wgjx-8xph)
+
+### Fixed
+
+- Make `ServerRequest::fromGlobals()` robust against unexpected HTTP header value types in `$_SERVER`
+
+## 2.10.1 - 2026-05-20
+
+### Fixed
+
+- Fix `Utils::modifyRequest()` with numeric header names
+
+## 2.10.0 - 2026-05-19
+
+### Changed
+
+- Harden `ServerRequest::fromGlobals()` against malformed `$_SERVER` values
+- Prevent custom stream metadata from affecting internal size handling
+- Throw when `StreamWrapper::getResource()` cannot create a resource
+- Preserve custom request implementations in `Utils::modifyRequest()`
+- Preserve custom URI implementations in `UriResolver::resolve()`
+- Make `Uri::__toString()` side-effect-free
+
+## 2.9.1 - 2026-05-19
+
+### Fixed
+
+- Fix parsing of relative path references containing a colon in a non-initial path segment
+- Fix `CachingStream::detach()` returning an incomplete resource before the decorated stream has been fully read
+- Fix `Message::bodySummary()` returning `null` when truncating printable UTF-8 bodies inside a multibyte character
+
## 2.9.0 - 2026-03-10
### Added
diff --git a/www/libs/vendor/guzzlehttp/psr7/README.md b/www/libs/vendor/guzzlehttp/psr7/README.md
index 24aad8605..a58538318 100644
--- a/www/libs/vendor/guzzlehttp/psr7/README.md
+++ b/www/libs/vendor/guzzlehttp/psr7/README.md
@@ -27,6 +27,8 @@ composer require guzzlehttp/psr7
| 1.x | EOL (2024-06-30) | >=5.4,<8.2 |
| 2.x | Latest | >=7.2.5,<8.6 |
+See [UPGRADING.md](UPGRADING.md) for notes on upgrading from 1.x to 2.0.
+
## AppendStream
@@ -62,7 +64,7 @@ preferred size of the buffer.
use GuzzleHttp\Psr7;
// When more than 1024 bytes are in the buffer, it will begin returning
-// false to writes. This is an indication that writers should slow down.
+// 0 to writes. This is an indication that writers should slow down.
$buffer = new Psr7\BufferStream(1024);
```
@@ -604,36 +606,6 @@ Determines the mimetype of a file by looking at its extension.
Maps a file extensions to a mimetype.
-## Upgrading from Function API
-
-The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience:
-
-| Original Function | Replacement Method |
-|----------------|----------------|
-| `str` | `Message::toString` |
-| `uri_for` | `Utils::uriFor` |
-| `stream_for` | `Utils::streamFor` |
-| `parse_header` | `Header::parse` |
-| `normalize_header` | `Header::normalize` |
-| `modify_request` | `Utils::modifyRequest` |
-| `rewind_body` | `Message::rewindBody` |
-| `try_fopen` | `Utils::tryFopen` |
-| `copy_to_string` | `Utils::copyToString` |
-| `copy_to_stream` | `Utils::copyToStream` |
-| `hash` | `Utils::hash` |
-| `readline` | `Utils::readLine` |
-| `parse_request` | `Message::parseRequest` |
-| `parse_response` | `Message::parseResponse` |
-| `parse_query` | `Query::parse` |
-| `build_query` | `Query::build` |
-| `mimetype_from_filename` | `MimeType::fromFilename` |
-| `mimetype_from_extension` | `MimeType::fromExtension` |
-| `_parse_message` | `Message::parseMessage` |
-| `_parse_request_uri` | `Message::parseRequestUri` |
-| `get_message_body_summary` | `Message::bodySummary` |
-| `_caseless_remove` | `Utils::caselessRemove` |
-
-
# Additional URI Methods
Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
diff --git a/www/libs/vendor/guzzlehttp/psr7/UPGRADING.md b/www/libs/vendor/guzzlehttp/psr7/UPGRADING.md
new file mode 100644
index 000000000..5608bf9cc
--- /dev/null
+++ b/www/libs/vendor/guzzlehttp/psr7/UPGRADING.md
@@ -0,0 +1,198 @@
+Guzzle PSR-7 Upgrade Guide
+==========================
+
+1.x to 2.0
+----------
+
+Guzzle PSR-7 2.0 is a major release that removes deprecated APIs, raises the
+minimum PHP version, and adds PHP 7 parameter and return types. Applications that
+only depend on PSR-7 interfaces should usually need small changes. Applications
+that call helper functions, extend package classes, or pass invalid argument
+types need closer review.
+
+#### PHP Version and Dependencies
+
+Guzzle PSR-7 2.0 requires PHP `^7.2.5 || ^8.0`. Guzzle PSR-7 1.x supported PHP
+`>=5.4.0`.
+
+Composer dependency changes that can affect upgrades:
+
+- `ralouphie/getallheaders` v2 support was dropped; 2.0 requires `^3.0`.
+- `psr/http-factory:^1.0` is required because 2.0 ships PSR-17 factories through `GuzzleHttp\Psr7\HttpFactory`.
+
+#### PHP 7 Type Hints and Return Types
+
+Type hints and return types were added wherever possible. Please make sure:
+
+- You pass values of the documented type when calling methods and functions.
+- Classes that extend Guzzle PSR-7 classes update any overridden method signatures to remain compatible.
+- Code that expected package-specific `InvalidArgumentException` exceptions for invalid argument types may now receive PHP `TypeError` exceptions instead.
+
+Common examples include passing a real integer status code to `Response::__construct()` and passing a string method to `Request::__construct()`.
+
+#### Removed Function API
+
+The static API was introduced in 1.7.0 to mitigate problems with functions
+conflicting between global and local copies of the package. The function API was
+removed in 2.0.0, along with the Composer `files` autoload entry that loaded
+`src/functions_include.php`.
+
+Replace namespaced function calls with the corresponding static methods in the
+`GuzzleHttp\Psr7` namespace:
+
+```php
+// Before:
+use function GuzzleHttp\Psr7\stream_for;
+
+$stream = stream_for('body');
+
+// After:
+use GuzzleHttp\Psr7\Utils;
+
+$stream = Utils::streamFor('body');
+```
+
+| Original Function | Replacement Method |
+|-------------------|--------------------|
+| `str` | `Message::toString` |
+| `uri_for` | `Utils::uriFor` |
+| `stream_for` | `Utils::streamFor` |
+| `parse_header` | `Header::parse` |
+| `normalize_header` | `Header::normalize` |
+| `modify_request` | `Utils::modifyRequest` |
+| `rewind_body` | `Message::rewindBody` |
+| `try_fopen` | `Utils::tryFopen` |
+| `copy_to_string` | `Utils::copyToString` |
+| `copy_to_stream` | `Utils::copyToStream` |
+| `hash` | `Utils::hash` |
+| `readline` | `Utils::readLine` |
+| `parse_request` | `Message::parseRequest` |
+| `parse_response` | `Message::parseResponse` |
+| `parse_query` | `Query::parse` |
+| `build_query` | `Query::build` |
+| `mimetype_from_filename` | `MimeType::fromFilename` |
+| `mimetype_from_extension` | `MimeType::fromExtension` |
+| `_parse_message` | `Message::parseMessage` |
+| `_parse_request_uri` | `Message::parseRequestUri` |
+| `get_message_body_summary` | `Message::bodySummary` |
+| `_caseless_remove` | `Utils::caselessRemove` |
+
+`Header::normalize()` remains the direct 2.0 replacement for
+`normalize_header()`. In newer 2.x versions, prefer `Header::splitList()` for
+new code.
+
+#### Deprecated URI Methods Removed
+
+The deprecated `Uri::resolve()` and `Uri::removeDotSegments()` methods were
+removed. Use `UriResolver` instead.
+
+```php
+// Before:
+$resolved = Uri::resolve($base, '../path');
+$path = Uri::removeDotSegments('/a/../b');
+
+// After:
+use GuzzleHttp\Psr7\UriResolver;
+use GuzzleHttp\Psr7\Utils;
+
+$resolved = UriResolver::resolve($base, Utils::uriFor('../path'));
+$path = UriResolver::removeDotSegments('/a/../b');
+```
+
+#### Stricter URI Validation
+
+Guzzle PSR-7 1.x automatically fixed a URI that combined an authority with a
+relative path by prepending `/` to the path. That deprecated behavior was removed
+in 2.0. Such URIs now throw `InvalidArgumentException`.
+
+```php
+// Before: automatically converted to //example.com/foo.
+$uri = (new Uri())->withHost('example.com')->withPath('foo');
+
+// After: make the absolute path explicit.
+$uri = (new Uri())->withHost('example.com')->withPath('/foo');
+```
+
+#### Header Validation
+
+Header names are validated more strictly according to RFC 7230 token syntax.
+Names containing whitespace, `/`, `(`, `)`, `\\`, or other invalid characters are
+rejected.
+
+If you construct messages from untrusted or non-standard input, normalize or
+reject invalid header names before constructing `Request`, `Response`, or
+`ServerRequest` instances.
+
+#### Query String Boolean Serialization
+
+`Query::build()` now serializes booleans as `1` and `0`, matching
+`http_build_query()` behavior.
+
+```php
+Query::build(['enabled' => true, 'disabled' => false]);
+// enabled=1&disabled=0
+```
+
+In current 2.x versions, pass `false` as the third argument if you need textual
+boolean values:
+
+```php
+Query::build(['enabled' => true, 'disabled' => false], PHP_QUERY_RFC3986, false);
+// enabled=true&disabled=false
+```
+
+#### Final Stream and Decorator Classes
+
+Several classes that were annotated with `@final` in 1.x are declared `final` in
+2.0:
+
+- `AppendStream`
+- `BufferStream`
+- `CachingStream`
+- `DroppingStream`
+- `FnStream`
+- `InflateStream`
+- `LazyOpenStream`
+- `LimitStream`
+- `MultipartStream`
+- `NoSeekStream`
+- `PumpStream`
+- `StreamWrapper`
+
+If your code extends one of these classes, replace inheritance with composition.
+For custom streams, implement `Psr\Http\Message\StreamInterface` directly or use
+`GuzzleHttp\Psr7\StreamDecoratorTrait` in your own class.
+
+`Request`, `Response`, `ServerRequest`, `Stream`, `UploadedFile`, and `Uri` remain
+extendable in 2.0, but overridden methods must have compatible signatures.
+
+#### Public Constants and Internal Details
+
+Some constants that were public in 1.x are implementation details in 2.0:
+
+- `Stream::READABLE_MODES`
+- `Stream::WRITABLE_MODES`
+- `Uri::HTTP_DEFAULT_HOST`
+
+If your code used these constants, define application-specific constants instead
+of depending on package internals.
+
+#### Stream Behavior Changes
+
+`BufferStream::write()` returns `0` instead of `false` when the buffer exceeds
+its high-water mark. This keeps the method compatible with the `int` return type
+from `StreamInterface::write()`.
+
+Several stream `__toString()` implementations now catch `Throwable`. On PHP 7.4
+and newer, exceptions thrown during stringification are rethrown. Avoid relying
+on `(string) $stream` to hide read failures; call `getContents()` or `read()` and
+handle exceptions when failures are possible.
+
+#### PSR-17 Factories
+
+Guzzle PSR-7 2.0 adds `GuzzleHttp\Psr7\HttpFactory`, an implementation of the
+PSR-17 factory interfaces from `psr/http-factory`. This is additive, but it is
+the reason for the new required dependency.
+
+For the full 2.0 diff, see
+https://github.com/guzzle/psr7/compare/1.8.1...2.0.0.
diff --git a/www/libs/vendor/guzzlehttp/psr7/composer.json b/www/libs/vendor/guzzlehttp/psr7/composer.json
index 56a320b9a..55bd7c711 100644
--- a/www/libs/vendor/guzzlehttp/psr7/composer.json
+++ b/www/libs/vendor/guzzlehttp/psr7/composer.json
@@ -1,6 +1,7 @@
{
"name": "guzzlehttp/psr7",
"description": "PSR-7 message implementation that also provides common utility methods",
+ "license": "MIT",
"keywords": [
"request",
"response",
@@ -11,7 +12,6 @@
"url",
"psr-7"
],
- "license": "MIT",
"authors": [
{
"name": "Graham Campbell",
@@ -49,38 +49,38 @@
"homepage": "https://sagikazarmark.hu"
}
],
- "repositories": [
- {
- "type": "package",
- "package": {
- "name": "jshttp/mime-db",
- "version": "1.54.0.1",
- "dist": {
- "url": "https://codeload.github.com/jshttp/mime-db/zip/0a9fd0bfbc87a725ff638495839114e7807b7177",
- "type": "zip"
- }
- }
- }
- ],
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0",
"ralouphie/getallheaders": "^3.0"
},
- "provide": {
- "psr/http-factory-implementation": "1.0",
- "psr/http-message-implementation": "1.0"
- },
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
- "http-interop/http-factory-tests": "0.9.0",
+ "http-interop/http-factory-tests": "1.1.0",
"jshttp/mime-db": "1.54.0.1",
- "phpunit/phpunit": "^8.5.44 || ^9.6.25"
+ "phpunit/phpunit": "^8.5.52 || ^9.6.34"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
+ "repositories": [
+ {
+ "type": "package",
+ "package": {
+ "name": "jshttp/mime-db",
+ "version": "1.54.0.1",
+ "dist": {
+ "type": "zip",
+ "url": "https://codeload.github.com/jshttp/mime-db/zip/0a9fd0bfbc87a725ff638495839114e7807b7177"
+ }
+ }
+ }
+ ],
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
@@ -91,17 +91,17 @@
"GuzzleHttp\\Tests\\Psr7\\": "tests/"
}
},
- "extra": {
- "bamarni-bin": {
- "bin-links": true,
- "forward-command": false
- }
- },
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
},
"preferred-install": "dist",
"sort-packages": true
+ },
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
}
}
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/CachingStream.php b/www/libs/vendor/guzzlehttp/psr7/src/CachingStream.php
index 7e4554d5c..c0e3b8c2b 100644
--- a/www/libs/vendor/guzzlehttp/psr7/src/CachingStream.php
+++ b/www/libs/vendor/guzzlehttp/psr7/src/CachingStream.php
@@ -25,6 +25,9 @@ final class CachingStream implements StreamInterface
*/
private $stream;
+ /** @var bool */
+ private $detached = false;
+
/**
* We will treat the buffer object as the body of the stream
*
@@ -41,6 +44,10 @@ public function __construct(
public function getSize(): ?int
{
+ if ($this->detached) {
+ return null;
+ }
+
$remoteSize = $this->remoteStream->getSize();
if (null === $remoteSize) {
@@ -134,6 +141,23 @@ public function eof(): bool
return $this->stream->eof() && $this->remoteStream->eof();
}
+ public function detach()
+ {
+ if ($this->detached) {
+ return null;
+ }
+
+ $position = $this->tell();
+
+ $this->cacheEntireStream();
+ $this->stream->seek($position);
+
+ $resource = $this->stream->detach();
+ $this->detached = true;
+
+ return $resource;
+ }
+
/**
* Close both the remote stream and buffer stream
*/
@@ -141,6 +165,7 @@ public function close(): void
{
$this->remoteStream->close();
$this->stream->close();
+ $this->detached = true;
}
private function cacheEntireStream(): int
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/Message.php b/www/libs/vendor/guzzlehttp/psr7/src/Message.php
index 5561a5130..1e83d440c 100644
--- a/www/libs/vendor/guzzlehttp/psr7/src/Message.php
+++ b/www/libs/vendor/guzzlehttp/psr7/src/Message.php
@@ -69,12 +69,17 @@ public static function bodySummary(MessageInterface $message, int $truncateAt =
$body->rewind();
$summary = $body->read($truncateAt);
- $body->rewind();
if ($size > $truncateAt) {
+ if (preg_match('//u', $summary) !== 1) {
+ $summary = self::trimTrailingIncompleteUtf8Character($summary, $body->read(3));
+ }
+
$summary .= ' (truncated...)';
}
+ $body->rewind();
+
// Matches any printable character, including unicode characters:
// letters, marks, numbers, punctuation, spacing, and separators.
if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) {
@@ -84,6 +89,60 @@ public static function bodySummary(MessageInterface $message, int $truncateAt =
return $summary;
}
+ /**
+ * Trims a partial UTF-8 character from the end of a truncated string.
+ */
+ private static function trimTrailingIncompleteUtf8Character(string $summary, string $lookahead): string
+ {
+ $length = strlen($summary);
+
+ if ($length === 0) {
+ return $summary;
+ }
+
+ $start = $length - 1;
+
+ while ($start >= 0) {
+ $byte = ord($summary[$start]);
+
+ if ($byte < 0x80 || $byte > 0xBF) {
+ break;
+ }
+
+ --$start;
+ }
+
+ if ($start < 0) {
+ return $summary;
+ }
+
+ $lead = ord($summary[$start]);
+
+ if ($lead >= 0xC2 && $lead <= 0xDF) {
+ $expectedLength = 2;
+ } elseif ($lead >= 0xE0 && $lead <= 0xEF) {
+ $expectedLength = 3;
+ } elseif ($lead >= 0xF0 && $lead <= 0xF4) {
+ $expectedLength = 4;
+ } else {
+ return $summary;
+ }
+
+ $availableLength = $length - $start;
+
+ if ($availableLength >= $expectedLength) {
+ return $summary;
+ }
+
+ $sequence = substr($summary, $start).substr($lookahead, 0, $expectedLength - $availableLength);
+
+ if (strlen($sequence) !== $expectedLength || preg_match('//u', $sequence) !== 1) {
+ return $summary;
+ }
+
+ return substr($summary, 0, $start);
+ }
+
/**
* Attempts to rewind a message body and throws an exception on failure.
*
@@ -174,6 +233,23 @@ public static function parseMessage(string $message): array
* @param array $headers Array of headers (each value an array).
*/
public static function parseRequestUri(string $path, array $headers): string
+ {
+ $host = self::getHostFromHeaders($headers);
+
+ // If no host is found, then a full URI cannot be constructed.
+ if ($host === null) {
+ return $path;
+ }
+
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme.'://'.$host.'/'.ltrim($path, '/');
+ }
+
+ /**
+ * @param array $headers Array of headers (each value an array).
+ */
+ private static function getHostFromHeaders(array $headers): ?string
{
$hostKey = array_filter(array_keys($headers), function ($k) {
// Numeric array keys are converted to int by PHP.
@@ -182,15 +258,16 @@ public static function parseRequestUri(string $path, array $headers): string
return strtolower($k) === 'host';
});
- // If no host is found, then a full URI cannot be constructed.
if (!$hostKey) {
- return $path;
+ return null;
}
$host = $headers[reset($hostKey)][0];
- $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+ if (!is_string($host) || Rfc7230::parseHostHeader($host) === null) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
- return $scheme.'://'.$host.'/'.ltrim($path, '/');
+ return $host;
}
/**
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/Request.php b/www/libs/vendor/guzzlehttp/psr7/src/Request.php
index b63bcac56..b042514ab 100644
--- a/www/libs/vendor/guzzlehttp/psr7/src/Request.php
+++ b/www/libs/vendor/guzzlehttp/psr7/src/Request.php
@@ -132,10 +132,14 @@ private function updateHostFromUri(): void
return;
}
+ Uri::assertValidHost($host);
+
if (($port = $this->uri->getPort()) !== null) {
$host .= ':'.$port;
}
+ $this->assertValue($host);
+
if (isset($this->headerNames['host'])) {
$header = $this->headerNames['host'];
} else {
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/Rfc3986.php b/www/libs/vendor/guzzlehttp/psr7/src/Rfc3986.php
new file mode 100644
index 000000000..6cc2ec082
--- /dev/null
+++ b/www/libs/vendor/guzzlehttp/psr7/src/Rfc3986.php
@@ -0,0 +1,25 @@
+@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
+
+ /**
+ * @return array{0: string, 1: int|null}|null
+ */
+ public static function parseHostHeader(string $authority): ?array
+ {
+ if ($authority === '') {
+ return null;
+ }
+
+ $host = $authority;
+ $port = null;
+
+ if ($authority[0] === '[') {
+ $closingBracket = strpos($authority, ']');
+ if ($closingBracket === false) {
+ return null;
+ }
+
+ $host = substr($authority, 0, $closingBracket + 1);
+ $remainder = substr($authority, $closingBracket + 1);
+ if ($remainder !== '') {
+ if ($remainder[0] !== ':') {
+ return null;
+ }
+
+ $port = self::parseAuthorityPort(substr($remainder, 1));
+ if ($port === null) {
+ return null;
+ }
+ }
+ } elseif (false !== ($colon = strpos($authority, ':'))) {
+ $host = substr($authority, 0, $colon);
+ $port = self::parseAuthorityPort(substr($authority, $colon + 1));
+ if ($port === null) {
+ return null;
+ }
+ }
+
+ if ($host === '' || !self::isValidHostHeaderHost($host)) {
+ return null;
+ }
+
+ return [$host, $port];
+ }
+
+ private static function isValidHostHeaderHost(string $host): bool
+ {
+ if (preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host)) {
+ return false;
+ }
+
+ if (strpos($host, '[') !== false || strpos($host, ']') !== false) {
+ if ($host[0] !== '[' || substr($host, -1) !== ']') {
+ return false;
+ }
+
+ $address = substr($host, 1, -1);
+
+ return filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== false
+ || preg_match('/^v[0-9a-f]+\.['.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.':]+$/iD', $address) === 1;
+ }
+
+ return strpos($host, ':') === false;
+ }
+
+ private static function parseAuthorityPort(string $port): ?int
+ {
+ if ($port === '' || !ctype_digit($port)) {
+ return null;
+ }
+
+ $normalized = ltrim($port, '0');
+ if ($normalized === '') {
+ return 0;
+ }
+
+ if (strlen($normalized) > 5 || (int) $normalized > 0xFFFF) {
+ return null;
+ }
+
+ return (int) $normalized;
+ }
}
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/ServerRequest.php b/www/libs/vendor/guzzlehttp/psr7/src/ServerRequest.php
index 3cc953453..3f87f9680 100644
--- a/www/libs/vendor/guzzlehttp/psr7/src/ServerRequest.php
+++ b/www/libs/vendor/guzzlehttp/psr7/src/ServerRequest.php
@@ -165,11 +165,12 @@ private static function normalizeNestedFileSpec(array $files = []): array
*/
public static function fromGlobals(): ServerRequestInterface
{
- $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
- $headers = getallheaders();
+ $method = self::getServerParam('REQUEST_METHOD') ?? 'GET';
+ $headers = self::removeInvalidHostHeader(self::getAllHeaders());
$uri = self::getUriFromGlobals();
$body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
- $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
+ $serverProtocol = self::getServerParam('SERVER_PROTOCOL');
+ $protocol = $serverProtocol !== null ? str_replace('HTTP/', '', $serverProtocol) : '1.1';
$serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
@@ -180,18 +181,63 @@ public static function fromGlobals(): ServerRequestInterface
->withUploadedFiles(self::normalizeFiles($_FILES));
}
- private static function extractHostAndPortFromAuthority(string $authority): array
+ /**
+ * @return array
+ */
+ private static function getAllHeaders(): array
+ {
+ return self::normalizeHeaderValues(getallheaders());
+ }
+
+ /**
+ * @param array $headers
+ *
+ * @return array
+ */
+ private static function normalizeHeaderValues(array $headers): array
{
- $uri = 'http://'.$authority;
- $parts = parse_url($uri);
- if (false === $parts) {
- return [null, null];
+ $normalized = [];
+
+ foreach ($headers as $name => $value) {
+ if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
+ $normalized[$name] = (string) $value;
+ }
}
- $host = $parts['host'] ?? null;
- $port = $parts['port'] ?? null;
+ return $normalized;
+ }
+
+ private static function getServerParam(string $key): ?string
+ {
+ return isset($_SERVER[$key]) && is_string($_SERVER[$key]) ? $_SERVER[$key] : null;
+ }
- return [$host, $port];
+ /**
+ * @param array $headers
+ *
+ * @return array
+ */
+ private static function removeInvalidHostHeader(array $headers): array
+ {
+ foreach ($headers as $name => $value) {
+ if (strtolower((string) $name) !== 'host') {
+ continue;
+ }
+
+ if (Rfc7230::parseHostHeader($value) === null) {
+ unset($headers[$name]);
+ }
+ }
+
+ return $headers;
+ }
+
+ /**
+ * @return array{0: string|null, 1: int|null}
+ */
+ private static function extractHostAndPortFromAuthority(string $authority): array
+ {
+ return Rfc7230::parseHostHeader($authority) ?? [null, null];
}
/**
@@ -201,11 +247,13 @@ public static function getUriFromGlobals(): UriInterface
{
$uri = new Uri('');
- $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
+ $https = self::getServerParam('HTTPS');
+ $uri = $uri->withScheme(!empty($https) && $https !== 'off' ? 'https' : 'http');
$hasPort = false;
- if (isset($_SERVER['HTTP_HOST'])) {
- [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
+ $authority = self::getServerParam('HTTP_HOST');
+ if ($authority !== null) {
+ [$host, $port] = self::extractHostAndPortFromAuthority($authority);
if ($host !== null) {
$uri = $uri->withHost($host);
}
@@ -214,19 +262,21 @@ public static function getUriFromGlobals(): UriInterface
$hasPort = true;
$uri = $uri->withPort($port);
}
- } elseif (isset($_SERVER['SERVER_NAME'])) {
- $uri = $uri->withHost($_SERVER['SERVER_NAME']);
- } elseif (isset($_SERVER['SERVER_ADDR'])) {
- $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
+ } elseif (($serverName = self::getServerParam('SERVER_NAME')) !== null) {
+ $uri = $uri->withHost($serverName);
+ } elseif (($serverAddr = self::getServerParam('SERVER_ADDR')) !== null) {
+ $uri = $uri->withHost($serverAddr);
}
- if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
- $uri = $uri->withPort($_SERVER['SERVER_PORT']);
+ $serverPort = self::getServerParam('SERVER_PORT');
+ if (!$hasPort && $serverPort !== null && preg_match('/^[+-]?\d+$/', $serverPort) === 1) {
+ $uri = $uri->withPort((int) $serverPort);
}
$hasQuery = false;
- if (isset($_SERVER['REQUEST_URI'])) {
- $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
+ $requestUri = self::getServerParam('REQUEST_URI');
+ if ($requestUri !== null) {
+ $requestUriParts = explode('?', $requestUri, 2);
$uri = $uri->withPath($requestUriParts[0]);
if (isset($requestUriParts[1])) {
$hasQuery = true;
@@ -234,8 +284,9 @@ public static function getUriFromGlobals(): UriInterface
}
}
- if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
- $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
+ $queryString = self::getServerParam('QUERY_STRING');
+ if (!$hasQuery && $queryString !== null) {
+ $uri = $uri->withQuery($queryString);
}
return $uri;
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/Stream.php b/www/libs/vendor/guzzlehttp/psr7/src/Stream.php
index 0aff9b2b7..c7cd0c453 100644
--- a/www/libs/vendor/guzzlehttp/psr7/src/Stream.php
+++ b/www/libs/vendor/guzzlehttp/psr7/src/Stream.php
@@ -63,7 +63,7 @@ public function __construct($stream, array $options = [])
$this->seekable = $meta['seekable'];
$this->readable = (bool) preg_match(self::READABLE_MODES, $meta['mode']);
$this->writable = (bool) preg_match(self::WRITABLE_MODES, $meta['mode']);
- $this->uri = $this->getMetadata('uri');
+ $this->uri = $meta['uri'] ?? null;
}
/**
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/www/libs/vendor/guzzlehttp/psr7/src/StreamWrapper.php
index 77b04d747..cd20764f6 100644
--- a/www/libs/vendor/guzzlehttp/psr7/src/StreamWrapper.php
+++ b/www/libs/vendor/guzzlehttp/psr7/src/StreamWrapper.php
@@ -44,7 +44,13 @@ public static function getResource(StreamInterface $stream)
.'writable, or both.');
}
- return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream));
+ $resource = @fopen('guzzle://stream', $mode, false, self::createStreamContext($stream));
+
+ if ($resource === false) {
+ throw new \RuntimeException('Unable to create stream resource');
+ }
+
+ return $resource;
}
/**
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/Uri.php b/www/libs/vendor/guzzlehttp/psr7/src/Uri.php
index bef82e29d..07a44aefd 100644
--- a/www/libs/vendor/guzzlehttp/psr7/src/Uri.php
+++ b/www/libs/vendor/guzzlehttp/psr7/src/Uri.php
@@ -38,19 +38,6 @@ class Uri implements UriInterface, \JsonSerializable
'ldap' => 389,
];
- /**
- * Unreserved characters for use in a regex.
- *
- * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
- */
- private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
-
- /**
- * Sub-delims for use in a regex.
- *
- * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
- */
- private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B'];
/** @var string Uri scheme. */
@@ -74,9 +61,6 @@ class Uri implements UriInterface, \JsonSerializable
/** @var string Uri fragment. */
private $fragment = '';
- /** @var string|null String representation */
- private $composedComponents;
-
public function __construct(string $uri = '')
{
if ($uri !== '') {
@@ -84,7 +68,13 @@ public function __construct(string $uri = '')
if ($parts === false) {
throw new MalformedUriException("Unable to parse URI: $uri");
}
- $this->applyParts($parts);
+ try {
+ $this->applyParts($parts);
+ } catch (MalformedUriException $e) {
+ throw $e;
+ } catch (\InvalidArgumentException $e) {
+ throw new MalformedUriException($e->getMessage(), 0, $e);
+ }
}
}
@@ -105,15 +95,19 @@ public function __construct(string $uri = '')
*/
private static function parse(string $url)
{
- // If IPv6
+ if (self::isPathNoSchemeReference($url)) {
+ return self::parsePathNoSchemeReference($url);
+ }
+
+ // Preserve bracketed IPv6 literals before encoding, including dotted IPv4 tails.
$prefix = '';
- if (preg_match('%^(.*://\[[0-9:a-fA-F]+\])(.*?)$%', $url, $matches)) {
+ if (preg_match('%^([0-9A-Za-z+.-]+://\[[0-9:.a-fA-F]+\])(.*?)$%', $url, $matches)) {
/** @var array{0:string, 1:string, 2:string} $matches */
$prefix = $matches[1];
$url = $matches[2];
}
- /** @var string */
+ /** @var string|null */
$encodedUrl = preg_replace_callback(
'%[^:/@?&=#]+%usD',
static function ($matches) {
@@ -122,6 +116,10 @@ static function ($matches) {
$url
);
+ if ($encodedUrl === null) {
+ return false;
+ }
+
$result = parse_url($prefix.$encodedUrl);
if ($result === false) {
@@ -131,19 +129,48 @@ static function ($matches) {
return array_map('urldecode', $result);
}
- public function __toString(): string
+ private static function isPathNoSchemeReference(string $url): bool
{
- if ($this->composedComponents === null) {
- $this->composedComponents = self::composeComponents(
- $this->scheme,
- $this->getAuthority(),
- $this->path,
- $this->query,
- $this->fragment
- );
+ if ($url === '' || $url[0] === '/' || $url[0] === '?' || $url[0] === '#') {
+ return false;
+ }
+
+ $firstSegment = substr($url, 0, strcspn($url, '/?#'));
+
+ return strpos($firstSegment, ':') === false;
+ }
+
+ /**
+ * @return array{path: string, query?: string, fragment?: string}
+ */
+ private static function parsePathNoSchemeReference(string $url): array
+ {
+ $parts = [];
+
+ if (false !== ($fragmentPosition = strpos($url, '#'))) {
+ $parts['fragment'] = substr($url, $fragmentPosition + 1);
+ $url = substr($url, 0, $fragmentPosition);
}
- return $this->composedComponents;
+ if (false !== ($queryPosition = strpos($url, '?'))) {
+ $parts['query'] = substr($url, $queryPosition + 1);
+ $url = substr($url, 0, $queryPosition);
+ }
+
+ $parts['path'] = $url;
+
+ return $parts;
+ }
+
+ public function __toString(): string
+ {
+ return self::composeComponents(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->path,
+ $this->query,
+ $this->fragment
+ );
}
/**
@@ -360,12 +387,34 @@ public static function withQueryValues(UriInterface $uri, array $keyValueArray):
public static function fromParts(array $parts): UriInterface
{
$uri = new self();
- $uri->applyParts($parts);
- $uri->validateState();
+ try {
+ $uri->applyParts($parts);
+ $uri->validateState();
+ } catch (MalformedUriException $e) {
+ throw $e;
+ } catch (\InvalidArgumentException $e) {
+ throw new MalformedUriException($e->getMessage(), 0, $e);
+ }
return $uri;
}
+ /**
+ * @throws \InvalidArgumentException If the host is invalid.
+ *
+ * @internal
+ */
+ public static function assertValidHost(string $host): void
+ {
+ if ($host === '') {
+ return;
+ }
+
+ if (preg_match('/[\x00-\x20\x7F]/', $host)) {
+ throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host));
+ }
+ }
+
public function getScheme(): string
{
return $this->scheme;
@@ -425,7 +474,6 @@ public function withScheme($scheme): UriInterface
$new = clone $this;
$new->scheme = $scheme;
- $new->composedComponents = null;
$new->removeDefaultPort();
$new->validateState();
@@ -445,7 +493,6 @@ public function withUserInfo($user, $password = null): UriInterface
$new = clone $this;
$new->userInfo = $info;
- $new->composedComponents = null;
$new->validateState();
return $new;
@@ -461,7 +508,6 @@ public function withHost($host): UriInterface
$new = clone $this;
$new->host = $host;
- $new->composedComponents = null;
$new->validateState();
return $new;
@@ -477,7 +523,6 @@ public function withPort($port): UriInterface
$new = clone $this;
$new->port = $port;
- $new->composedComponents = null;
$new->removeDefaultPort();
$new->validateState();
@@ -494,7 +539,6 @@ public function withPath($path): UriInterface
$new = clone $this;
$new->path = $path;
- $new->composedComponents = null;
$new->validateState();
return $new;
@@ -510,7 +554,6 @@ public function withQuery($query): UriInterface
$new = clone $this;
$new->query = $query;
- $new->composedComponents = null;
return $new;
}
@@ -525,7 +568,6 @@ public function withFragment($fragment): UriInterface
$new = clone $this;
$new->fragment = $fragment;
- $new->composedComponents = null;
return $new;
}
@@ -596,7 +638,7 @@ private function filterUserInfoComponent($component): string
}
return preg_replace_callback(
- '/(?:[^%'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
+ '/(?:[^%'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$component
);
@@ -613,7 +655,10 @@ private function filterHost($host): string
throw new \InvalidArgumentException('Host must be a string');
}
- return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
+ $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
+ self::assertValidHost($host);
+
+ return $host;
}
/**
@@ -695,7 +740,7 @@ private function filterPath($path): string
}
return preg_replace_callback(
- '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+ '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$path
);
@@ -715,7 +760,7 @@ private function filterQueryAndFragment($str): string
}
return preg_replace_callback(
- '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+ '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$str
);
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/UriResolver.php b/www/libs/vendor/guzzlehttp/psr7/src/UriResolver.php
index 3737be1e5..80b4b2058 100644
--- a/www/libs/vendor/guzzlehttp/psr7/src/UriResolver.php
+++ b/www/libs/vendor/guzzlehttp/psr7/src/UriResolver.php
@@ -67,41 +67,37 @@ public static function resolve(UriInterface $base, UriInterface $rel): UriInterf
}
if ($rel->getAuthority() != '') {
- $targetAuthority = $rel->getAuthority();
- $targetPath = self::removeDotSegments($rel->getPath());
- $targetQuery = $rel->getQuery();
+ return $rel
+ ->withScheme($base->getScheme())
+ ->withPath(self::removeDotSegments($rel->getPath()));
+ }
+
+ if ($rel->getPath() === '') {
+ $targetPath = $base->getPath();
+ $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
} else {
- $targetAuthority = $base->getAuthority();
- if ($rel->getPath() === '') {
- $targetPath = $base->getPath();
- $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
+ if ($rel->getPath()[0] === '/') {
+ $targetPath = $rel->getPath();
} else {
- if ($rel->getPath()[0] === '/') {
- $targetPath = $rel->getPath();
+ if ($base->getAuthority() != '' && $base->getPath() === '') {
+ $targetPath = '/'.$rel->getPath();
} else {
- if ($targetAuthority != '' && $base->getPath() === '') {
- $targetPath = '/'.$rel->getPath();
+ $lastSlashPos = strrpos($base->getPath(), '/');
+ if ($lastSlashPos === false) {
+ $targetPath = $rel->getPath();
} else {
- $lastSlashPos = strrpos($base->getPath(), '/');
- if ($lastSlashPos === false) {
- $targetPath = $rel->getPath();
- } else {
- $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath();
- }
+ $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath();
}
}
- $targetPath = self::removeDotSegments($targetPath);
- $targetQuery = $rel->getQuery();
}
+ $targetPath = self::removeDotSegments($targetPath);
+ $targetQuery = $rel->getQuery();
}
- return new Uri(Uri::composeComponents(
- $base->getScheme(),
- $targetAuthority,
- $targetPath,
- $targetQuery,
- $rel->getFragment()
- ));
+ return $base
+ ->withPath($targetPath)
+ ->withQuery($targetQuery)
+ ->withFragment($rel->getFragment());
}
/**
diff --git a/www/libs/vendor/guzzlehttp/psr7/src/Utils.php b/www/libs/vendor/guzzlehttp/psr7/src/Utils.php
index 5451e3dcd..a7cb8c2bc 100644
--- a/www/libs/vendor/guzzlehttp/psr7/src/Utils.php
+++ b/www/libs/vendor/guzzlehttp/psr7/src/Utils.php
@@ -5,7 +5,6 @@
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
@@ -195,34 +194,68 @@ public static function modifyRequest(RequestInterface $request, array $changes):
$uri = $uri->withQuery($changes['query']);
}
- if ($request instanceof ServerRequestInterface) {
- $new = (new ServerRequest(
- $changes['method'] ?? $request->getMethod(),
- $uri,
- $headers,
- $changes['body'] ?? $request->getBody(),
- $changes['version'] ?? $request->getProtocolVersion(),
- $request->getServerParams()
- ))
- ->withParsedBody($request->getParsedBody())
- ->withQueryParams($request->getQueryParams())
- ->withCookieParams($request->getCookieParams())
- ->withUploadedFiles($request->getUploadedFiles());
-
- foreach ($request->getAttributes() as $key => $value) {
- $new = $new->withAttribute($key, $value);
+ $hasHost = false;
+ foreach (array_keys($headers) as $header) {
+ if (strtolower((string) $header) === 'host') {
+ $hasHost = true;
+ break;
+ }
+ }
+
+ // Match Request::__construct() by adding a Host header when one is not provided.
+ if (!$hasHost && $uri->getHost() !== '') {
+ $host = $uri->getHost();
+
+ if (($port = $uri->getPort()) !== null) {
+ $host .= ':'.$port;
+ }
+
+ $headers = ['Host' => [$host]] + $headers;
+ }
+
+ $new = $request;
+
+ if (isset($changes['method'])) {
+ $new = $new->withMethod($changes['method']);
+ }
+
+ if (isset($changes['uri']) || isset($changes['query'])) {
+ $new = $new->withUri($uri, true);
+ }
+
+ if ($headers !== $new->getHeaders()) {
+ foreach (array_keys($new->getHeaders()) as $header) {
+ /** @var RequestInterface */
+ $new = $new->withoutHeader((string) $header);
+ }
+
+ $addedHeaders = [];
+ foreach ($headers as $header => $value) {
+ $header = (string) $header;
+ $normalized = strtolower($header);
+
+ if (isset($addedHeaders[$normalized])) {
+ /** @var RequestInterface */
+ $new = $new->withAddedHeader($addedHeaders[$normalized], $value);
+ } else {
+ /** @var RequestInterface */
+ $new = $new->withHeader($header, $value);
+ $addedHeaders[$normalized] = $header;
+ }
}
+ }
+
+ if (isset($changes['body'])) {
+ /** @var RequestInterface */
+ $new = $new->withBody(self::streamFor($changes['body']));
+ }
- return $new;
+ if (isset($changes['version'])) {
+ /** @var RequestInterface */
+ $new = $new->withProtocolVersion($changes['version']);
}
- return new Request(
- $changes['method'] ?? $request->getMethod(),
- $uri,
- $headers,
- $changes['body'] ?? $request->getBody(),
- $changes['version'] ?? $request->getProtocolVersion()
- );
+ return $new;
}
/**
diff --git a/www/libs/vendor/phpmailer/phpmailer/COMMITMENT b/www/libs/vendor/phpmailer/phpmailer/COMMITMENT
new file mode 100644
index 000000000..a687e0ddb
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/COMMITMENT
@@ -0,0 +1,46 @@
+GPL Cooperation Commitment
+Version 1.0
+
+Before filing or continuing to prosecute any legal proceeding or claim
+(other than a Defensive Action) arising from termination of a Covered
+License, we commit to extend to the person or entity ('you') accused
+of violating the Covered License the following provisions regarding
+cure and reinstatement, taken from GPL version 3. As used here, the
+term 'this License' refers to the specific Covered License being
+enforced.
+
+ However, if you cease all violation of this License, then your
+ license from a particular copyright holder is reinstated (a)
+ provisionally, unless and until the copyright holder explicitly
+ and finally terminates your license, and (b) permanently, if the
+ copyright holder fails to notify you of the violation by some
+ reasonable means prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+ reinstated permanently if the copyright holder notifies you of the
+ violation by some reasonable means, this is the first time you
+ have received notice of violation of this License (for any work)
+ from that copyright holder, and you cure the violation prior to 30
+ days after your receipt of the notice.
+
+We intend this Commitment to be irrevocable, and binding and
+enforceable against us and assignees of or successors to our
+copyrights.
+
+Definitions
+
+'Covered License' means the GNU General Public License, version 2
+(GPLv2), the GNU Lesser General Public License, version 2.1
+(LGPLv2.1), or the GNU Library General Public License, version 2
+(LGPLv2), all as published by the Free Software Foundation.
+
+'Defensive Action' means a legal proceeding or claim that We bring
+against you in response to a prior proceeding or claim initiated by
+you or your affiliate.
+
+'We' means each contributor to this repository as of the date of
+inclusion of this file, including subsidiaries of a corporate
+contributor.
+
+This work is available under a Creative Commons Attribution-ShareAlike
+4.0 International license (https://creativecommons.org/licenses/by-sa/4.0/).
diff --git a/www/libs/vendor/phpmailer/phpmailer/LICENSE b/www/libs/vendor/phpmailer/phpmailer/LICENSE
new file mode 100644
index 000000000..f166cc57b
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/LICENSE
@@ -0,0 +1,502 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ , 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
\ No newline at end of file
diff --git a/www/libs/vendor/phpmailer/phpmailer/README.md b/www/libs/vendor/phpmailer/phpmailer/README.md
new file mode 100644
index 000000000..bf0421f24
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/README.md
@@ -0,0 +1,232 @@
+[](https://supportukrainenow.org/)
+
+
+
+# PHPMailer – A full-featured email creation and transfer class for PHP
+
+[](https://github.com/PHPMailer/PHPMailer/actions)
+[](https://codecov.io/gh/PHPMailer/PHPMailer)
+[](https://packagist.org/packages/phpmailer/phpmailer)
+[](https://packagist.org/packages/phpmailer/phpmailer)
+[](https://packagist.org/packages/phpmailer/phpmailer)
+[](https://phpmailer.github.io/PHPMailer/)
+[](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer)
+
+## Features
+- Probably the world's most popular code for sending email from PHP!
+- Used by many open-source projects: WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla! and many more
+- Integrated SMTP support – send without a local mail server
+- Send emails with multiple To, CC, BCC, and Reply-to addresses
+- Multipart/alternative emails for mail clients that do not read HTML email
+- Add attachments, including inline
+- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings
+- Full UTF-8 support when using servers that support `SMTPUTF8`.
+- Support for iCal events in multiparts and attachments
+- SMTP authentication with `LOGIN`, `PLAIN`, `CRAM-MD5`, and `XOAUTH2` mechanisms over SMTPS and SMTP+STARTTLS transports
+- Validates email addresses automatically
+- Protects against header injection attacks
+- Error messages in over 50 languages!
+- DKIM and S/MIME signing support
+- Compatible with PHP 5.5 and later, including PHP 8.5
+- Namespaced to prevent name clashes
+- Much more!
+
+## Why you might need it
+Many PHP developers need to send email from their code. The only PHP function that supports this directly is [`mail()`](https://www.php.net/manual/en/function.mail.php). However, it does not provide any assistance for making use of popular features such as authentication, HTML messages, and attachments.
+
+Formatting email correctly is surprisingly difficult. There are myriad overlapping (and conflicting) standards, requiring tight adherence to horribly complicated formatting and encoding rules – the vast majority of code that you'll find online that uses the `mail()` function directly is just plain wrong, if not unsafe!
+
+The PHP `mail()` function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD, and macOS platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP client allows email sending on all platforms without needing a local mail server. Be aware though, that the `mail()` function should be avoided when possible; it's both faster and [safer](https://exploitbox.io/paper/Pwning-PHP-Mail-Function-For-Fun-And-RCE.html) to use SMTP to localhost.
+
+*Please* don't be tempted to do it yourself – if you don't use PHPMailer, there are many other excellent libraries that
+you should look at before rolling your own. Try [Symfony Mailer](https://symfony.com/doc/current/mailer.html), [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail), etc.
+
+## License
+This software is distributed under the [LGPL 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution.
+
+## Installation & loading
+PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file:
+
+```json
+"phpmailer/phpmailer": "^7.0.0"
+```
+
+or run
+
+```sh
+composer require phpmailer/phpmailer
+```
+
+Note that the `vendor` folder and the `vendor/autoload.php` script are generated by Composer; they are not part of PHPMailer.
+
+If you want to use XOAUTH2 authentication, you will also need to add a dependency on the `league/oauth2-client` and appropriate service adapters package in your `composer.json`, or take a look at
+by @decomplexity's [SendOauth2 wrapper](https://github.com/decomplexity/SendOauth2), especially if you're using Microsoft services.
+
+Alternatively, if you're not using Composer, you
+can [download PHPMailer as a zip file](https://github.com/PHPMailer/PHPMailer/archive/master.zip), (note that docs and examples are not included in the zip file), then copy the contents of the PHPMailer folder into one of the `include_path` directories specified in your PHP configuration and load each class file manually:
+
+```php
+SMTPDebug = SMTP::DEBUG_SERVER; //Enable verbose debug output
+ $mail->isSMTP(); //Send using SMTP
+ $mail->Host = 'smtp.example.com'; //Set the SMTP server to send through
+ $mail->SMTPAuth = true; //Enable SMTP authentication
+ $mail->Username = 'user@example.com'; //SMTP username
+ $mail->Password = 'secret'; //SMTP password
+ $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption
+ $mail->Port = 465; //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`
+
+ //Recipients
+ $mail->setFrom('from@example.com', 'Mailer');
+ $mail->addAddress('joe@example.net', 'Joe User'); //Add a recipient
+ $mail->addAddress('ellen@example.com'); //Name is optional
+ $mail->addReplyTo('info@example.com', 'Information');
+ $mail->addCC('cc@example.com');
+ $mail->addBCC('bcc@example.com');
+
+ //Attachments
+ $mail->addAttachment('/var/tmp/file.tar.gz'); //Add attachments
+ $mail->addAttachment('/tmp/image.jpg', 'new.jpg'); //Optional name
+
+ //Content
+ $mail->isHTML(true); //Set email format to HTML
+ $mail->Subject = 'Here is the subject';
+ $mail->Body = 'This is the HTML message body in bold!';
+ $mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
+
+ $mail->send();
+ echo 'Message has been sent';
+} catch (Exception $e) {
+ echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
+}
+```
+
+You'll find plenty to play with in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder, which covers many common scenarios including sending through Gmail, building contact forms, sending to mailing lists, and more.
+
+If you are re-using the instance (e.g. when sending to a mailing list), you may need to clear the recipient list to avoid sending duplicate messages. See [the mailing list example](https://github.com/PHPMailer/PHPMailer/blob/master/examples/mailing_list.phps) for further guidance.
+
+That's it. You should now be ready to use PHPMailer!
+
+## Localization
+PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder, you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this:
+
+```php
+//To load the French version
+$mail->setLanguage('fr', '/optional/path/to/language/directory/');
+```
+
+We welcome corrections and new languages – if you're looking for corrections, run the [Language/TranslationCompletenessTest.php](https://github.com/PHPMailer/PHPMailer/blob/master/test/Language/TranslationCompletenessTest.php) script in the tests folder and it will show any missing translations.
+
+## Documentation
+Start reading at the [GitHub wiki](https://github.com/PHPMailer/PHPMailer/wiki). If you're having trouble, head for [the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting) as it's frequently updated.
+
+Examples of how to use PHPMailer for common scenarios can be found in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder. If you're looking for a good starting point, we recommend you start with [the Gmail example](https://github.com/PHPMailer/PHPMailer/tree/master/examples/gmail.phps).
+
+To reduce PHPMailer's deployed code footprint, examples are not included if you load PHPMailer via Composer or via [GitHub's zip file download](https://github.com/PHPMailer/PHPMailer/archive/master.zip), so you'll need to either clone the git repository or use the above links to get to the examples directly.
+
+Complete generated API documentation is [available online](https://phpmailer.github.io/PHPMailer/).
+
+You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](https://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailer/PHPMailerTest.php) a good reference for how to do various operations such as encryption.
+
+If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](https://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting).
+
+## Tests
+[PHPMailer tests](https://github.com/PHPMailer/PHPMailer/tree/master/test/) use PHPUnit 9, with [a polyfill](https://github.com/Yoast/PHPUnit-Polyfills) to let 9-style tests run on older PHPUnit and PHP versions.
+
+[](https://github.com/PHPMailer/PHPMailer/actions)
+
+If this isn't passing, is there something you can do to help?
+
+## Security
+Please disclose any vulnerabilities found responsibly – report security issues to the maintainers privately.
+
+See [SECURITY](https://github.com/PHPMailer/PHPMailer/tree/master/SECURITY.md) and [PHPMailer's security advisories on GitHub](https://github.com/PHPMailer/PHPMailer/security).
+
+## Contributing
+Please submit bug reports, suggestions, and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues).
+
+We're particularly interested in fixing edge cases, expanding test coverage, and updating translations.
+
+If you found a mistake in the docs, or want to add something, go ahead and amend the wiki – anyone can edit it.
+
+If you have git clones from prior to the move to the PHPMailer GitHub organisation, you'll need to update any remote URLs referencing the old GitHub location with a command like this from within your clone:
+
+```sh
+git remote set-url upstream https://github.com/PHPMailer/PHPMailer.git
+```
+
+Please *don't* use the SourceForge or Google Code projects any more; they are obsolete and no longer maintained.
+
+## Sponsorship
+Development time and resources for PHPMailer are provided by [Smartmessages.net](https://info.smartmessages.net/), the world's only privacy-first email marketing system.
+
+
+
+Donations are very welcome, whether in beer 🍺, T-shirts 👕, or cold, hard cash 💰. Sponsorship through GitHub is a simple and convenient way to say "thank you" to PHPMailer's maintainers and contributors – just click the "Sponsor" button [on the project page](https://github.com/PHPMailer/PHPMailer). If your company uses PHPMailer, consider taking part in Tidelift's enterprise support programme.
+
+## PHPMailer For Enterprise
+
+Available as part of the Tidelift Subscription.
+
+The maintainers of PHPMailer and thousands of other packages are working with Tidelift to deliver commercial
+support and maintenance for the open-source packages you use to build your applications. Save time, reduce risk, and
+improve code health, while paying the maintainers of the exact packages you
+use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-phpmailer-phpmailer?utm_source=packagist-phpmailer-phpmailer&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
+
+## Changelog
+See [changelog](changelog.md).
+
+## History
+- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](https://sourceforge.net/projects/phpmailer/).
+- [Marcus Bointon](https://github.com/Synchro) (`coolbru` on SF) and Andy Prevost (`codeworxtech`) took over the project in 2004.
+- Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski.
+- Marcus created [his fork on GitHub](https://github.com/Synchro/PHPMailer) in 2008.
+- Jim and Marcus decide to join forces and use GitHub as the canonical and official repo for PHPMailer in 2013.
+- PHPMailer moves to [the PHPMailer organisation](https://github.com/PHPMailer) on GitHub in 2013.
+
+### What's changed since moving from SourceForge?
+- Official successor to the SourceForge and Google Code projects.
+- Test suite.
+- Continuous integration with GitHub Actions.
+- Composer support.
+- Public development.
+- Additional languages and language strings.
+- CRAM-MD5 authentication support.
+- Preserves full repo history of authors, commits, and branches from the original SourceForge project.
diff --git a/www/libs/vendor/phpmailer/phpmailer/SECURITY.md b/www/libs/vendor/phpmailer/phpmailer/SECURITY.md
new file mode 100644
index 000000000..4f34026df
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/SECURITY.md
@@ -0,0 +1,37 @@
+# Security notices relating to PHPMailer
+
+Please disclose any security issues or vulnerabilities found through [Tidelift's coordinated disclosure system](https://tidelift.com/security) or to the maintainers privately.
+
+PHPMailer 6.4.1 and earlier contain a vulnerability that can result in untrusted code being called (if such code is injected into the host project's scope by other means). If the `$patternselect` parameter to `validateAddress()` is set to `'php'` (the default, defined by `PHPMailer::$validator`), and the global namespace contains a function called `php`, it will be called in preference to the built-in validator of the same name. Mitigated in PHPMailer 6.5.0 by denying the use of simple strings as validator function names. Recorded as [CVE-2021-3603](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-3603). Reported by [Vikrant Singh Chauhan](mailto:vi@hackberry.xyz) via [huntr.dev](https://www.huntr.dev/).
+
+PHPMailer versions 6.4.1 and earlier contain a possible remote code execution vulnerability through the `$lang_path` parameter of the `setLanguage()` method. If the `$lang_path` parameter is passed unfiltered from user input, it can be set to [a UNC path](https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths), and if an attacker is also able to persuade the server to load a file from that UNC path, a script file under their control may be executed. This vulnerability only applies to systems that resolve UNC paths, typically only Microsoft Windows.
+PHPMailer 6.5.0 mitigates this by no longer treating translation files as PHP code, but by parsing their text content directly. This approach avoids the possibility of executing unknown code while retaining backward compatibility. This isn't ideal, so the current translation format is deprecated and will be replaced in the next major release. Recorded as [CVE-2021-34551](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-34551). Reported by [Jilin Diting Information Technology Co., Ltd](https://listensec.com) via Tidelift.
+
+PHPMailer versions between 6.1.8 and 6.4.0 contain a regression of the earlier CVE-2018-19296 object injection vulnerability as a result of [a fix for Windows UNC paths in 6.1.8](https://github.com/PHPMailer/PHPMailer/commit/e2e07a355ee8ff36aba21d0242c5950c56e4c6f9). Recorded as [CVE-2020-36326](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-36326). Reported by Fariskhi Vidyan via Tidelift. 6.4.1 fixes this issue, and also enforces stricter checks for URL schemes in local path contexts.
+
+PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs in `Content-Type` and `Content-Disposition` when filenames passed into `addAttachment` and other methods that accept attachment names contain double quote characters, in contravention of RFC822 3.4.1. No specific vulnerability has been found relating to this, but it could allow file attachments to bypass attachment filters that are based on matching filename extensions. Recorded as [CVE-2020-13625](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-13625). Reported by Elar Lang of Clarified Security.
+
+PHPMailer versions prior to 6.0.6 and 5.2.27 are vulnerable to an object injection attack by passing `phar://` paths into `addAttachment()` and other functions that may receive unfiltered local paths, possibly leading to RCE. Recorded as [CVE-2018-19296](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2018-19296). See [this article](https://knasmueller.net/5-answers-about-php-phar-exploitation) for more info on this type of vulnerability. Mitigated by blocking the use of paths containing URL-protocol style prefixes such as `phar://`. Reported by Sehun Oh of cyberone.kr.
+
+PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it is not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project.
+
+PHPMailer versions prior to 5.2.22 (released January 9th 2017) have a local file disclosure vulnerability, [CVE-2017-5223](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-5223). If content passed into `msgHTML()` is sourced from unfiltered user input, relative paths can map to absolute local file paths and added as attachments. Also note that `addAttachment` (just like `file_get_contents`, `passthru`, `unlink`, etc) should not be passed user-sourced params either! Reported by Yongxiang Li of Asiasecurity.
+
+PHPMailer versions prior to 5.2.20 (released December 28th 2016) are vulnerable to [CVE-2016-10045](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10045) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass.html), and patched by Paul Buonopane (@Zenexer).
+
+PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html).
+
+PHPMailer versions prior to 5.2.14 (released November 2015) are vulnerable to [CVE-2015-8476](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-8476) an SMTP CRLF injection bug permitting arbitrary message sending.
+
+PHPMailer versions prior to 5.2.10 (released May 2015) are vulnerable to [CVE-2008-5619](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2008-5619), a remote code execution vulnerability in the bundled html2text library. This file was removed in 5.2.10, so if you are using a version prior to that and make use of the html2text function, it's vitally important that you upgrade and remove this file.
+
+PHPMailer versions prior to 2.0.7 and 2.2.1 are vulnerable to [CVE-2012-0796](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2012-0796), an email header injection attack.
+
+Joomla 1.6.0 uses PHPMailer in an unsafe way, allowing it to reveal local file paths, reported in [CVE-2011-3747](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-3747).
+
+PHPMailer didn't sanitise the `$lang_path` parameter in `SetLanguage`. This wasn't a problem in itself, but some apps (PHPClassifieds, ATutor) also failed to sanitise user-provided parameters passed to it, permitting semi-arbitrary local file inclusion, reported in [CVE-2010-4914](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2010-4914), [CVE-2007-2021](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-2021) and [CVE-2006-5734](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2006-5734).
+
+PHPMailer 1.7.2 and earlier contained a possible DDoS vulnerability reported in [CVE-2005-1807](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2005-1807).
+
+PHPMailer 1.7 and earlier (June 2003) have a possible vulnerability in the `SendmailSend` method where shell commands may not be sanitised. Reported in [CVE-2007-3215](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-3215).
+
diff --git a/www/libs/vendor/phpmailer/phpmailer/SMTPUTF8.md b/www/libs/vendor/phpmailer/phpmailer/SMTPUTF8.md
new file mode 100644
index 000000000..ca284ee21
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/SMTPUTF8.md
@@ -0,0 +1,48 @@
+# A short history of UTF-8 in email
+
+## Background
+
+For most of its existence, SMTP has been a 7-bit channel, only supporting US-ASCII characters. This has been a problem for many languages, especially those that use non-Latin scripts, and has led to the development of various workarounds.
+
+The first major improvement, introduced in 1994 in [RFC 1652](https://www.rfc-editor.org/rfc/rfc1652) and extended in 2011 in [RFC 6152](https://www.rfc-editor.org/rfc/rfc6152), was the addition of the `8BITMIME` SMTP extension, which allowed raw 8-bit data to be included in message bodies sent over SMTP.
+This allowed the message *contents* to contain 8-bit data, including things like UTF-8 text, even though the SMTP protocol itself was still firmly 7-bit. This worked by having the server switch to 8-bit after the headers, and then back to 7-bit after the completion of a `DATA` command.
+
+From 1996, messages could support [RFC 2047 encoding](https://www.rfc-editor.org/rfc/rfc2047), which permitted inserting characters from any character set into header *values* (but not names), but only by encoding them in somewhat unreadable ways to allow them to survive passage through a 7-bit channel. An example with a subject of "Schrödinger's cat" would be:
+
+```
+Subject: =?utf-8?Q=Schr=C3=B6dinger=92s_Cat?=
+```
+
+Here the accented `ö` is encoded as `=C3=B6`, which is the UTF-8 encoding of the 2-byte character, and the whole thing is wrapped in `=?utf-8?Q?` to indicate that it uses the UTF-8 charset and `quoted-printable` encoding. This is a bit of a hack, and not very human-friendly, but it works.
+
+Similarly, 8-bit message bodies could be encoded using the same `quoted-printable` and `base64` content transfer encoding (CTE) schemes, which preserved the 8-bit content while encoding it in a format that could survive transmission through a 7-bit channel.
+
+Domain names were originally also stuck in a 7-bit world, actually even more constrained to only a subset of the US-ASCII character set. But of course, many people want to have domains in their own language/script. Internationalized domain name (IDN) permitted this, using yet another complex encoding scheme called punycode, defined for domain names in 2003 in [RFC 3492](https://www.rfc-editor.org/rfc/rfc3492). This finally allowed the domain part (after the `@`) of email addresses to contain UTF-8, though it was actually an illusion preserved by email client applications. For example, an address of
+`user@café.example.com` translates to
+`user@xn--caf-dma.example.com` in punycode, rendering it mostly unreadable, but 7-bit friendly, and remaining compatible with email clients that don't know about IDN.
+
+The one remaining part of email that could not handle UTF-8 is the local part of email addresses (the part before the `@`).
+
+I've only mentioned UTF-8 here, but most of these approaches also allowed other character sets that were popular, such as [the ISO-8859 family](https://en.wikipedia.org/wiki/ISO/IEC_8859). However, UTF-8 solves so many problems that these other character sets are gradually falling out of favour, as UTF-8 can support all languages.
+
+This patchwork of overlapping approaches has served us well, but we have to admit that it's a mess.
+
+## SMTPUTF8
+
+`SMTPUTF8` is another SMTP extension, defined in [RFC 6531](https://www.rfc-editor.org/rfc/rfc6531) in 2012. This essentially solves the whole problem, allowing the entire SMTP conversation — commands, headers, and message bodies — to be sent in raw, unencoded UTF-8.
+
+But there's a problem with this approach: adoption. If you send a UTF-8 message to a recipient whose mail server doesn't support this format, the sender has to somehow downgrade the message to make it survive a transition to 7-bit. This is a hard problem to solve, especially since there is no way to make a 7-bit system support UTF-8 in the local parts of addresses. This downgrade problem is what held up the adoption of `SMTPUTF8` in PHPMailer for many years, but in that time the *de facto* approach has become to simply fail in that situation, and tell the recipient it's time they upgraded their mail server 😅.
+
+The vast majority of large email providers (gmail, Yahoo, Microsoft, etc), mail servers (postfix, exim, IIS, etc), and mail clients (Apple Mail, Outlook, Thunderbird, etc) now all support SMTPUTF8, so the need for backward compatibility is no longer what it was.
+
+## SMTPUTF8 in PHPMailer
+
+Several other PHP email libraries have implemented a halfway solution to `SMTPUTF8`, adding only the ability to support UTF-8 in email addresses, not elsewhere in the protocol. I wanted PHPMailer to do it "the right way", and this has taken much longer. PHPMailer now supports UTF-8 everywhere, and does not need to use transfer or header encodings for UTF-8 text when connecting to an `SMTPUTF8`-capable mail server.
+
+This support is handled automatically: if you add an email address that requires UTF-8, PHPMailer will use UTF-8 for everything. If not, it will fall back to 7-bit and encode the message as necessary.
+
+The one place you will need to be careful is in the selection of the address validator. By default, PHPMailer uses PHP's built-in `filter_var` validator, which does not allow UTF-8 email addresses. When PHPMailer spots that you have submitted a UTF-8 address, but have not altered the default validator, it will automatically switch to using a UTF-8-compatible validator. As soon as you do this, any SMTP connection you make will *require* that the server you connect to supports `SMTPUTF8`. You can select this validator explicitly by setting `PHPMailer::$validator = 'eai'` (an acronym for Email Address Internationalization).
+
+### Postfix gotcha
+
+Postfix has supported `SMTPUTF8` for a long time, but it has a peculiarity that it does not always advertise that it does so. However, rather surprisingly, if you use UTF-8 in the conversation, it will work anyway.
diff --git a/www/libs/vendor/phpmailer/phpmailer/VERSION b/www/libs/vendor/phpmailer/phpmailer/VERSION
new file mode 100644
index 000000000..21c8c7b46
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/VERSION
@@ -0,0 +1 @@
+7.1.1
diff --git a/www/libs/vendor/phpmailer/phpmailer/composer.json b/www/libs/vendor/phpmailer/phpmailer/composer.json
new file mode 100644
index 000000000..63d0ffd67
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/composer.json
@@ -0,0 +1,84 @@
+{
+ "name": "phpmailer/phpmailer",
+ "type": "library",
+ "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+ "authors": [
+ {
+ "name": "Marcus Bointon",
+ "email": "phpmailer@synchromedia.co.uk"
+ },
+ {
+ "name": "Jim Jagielski",
+ "email": "jimjag@gmail.com"
+ },
+ {
+ "name": "Andy Prevost",
+ "email": "codeworxtech@users.sourceforge.net"
+ },
+ {
+ "name": "Brent R. Matzelle"
+ }
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/Synchro",
+ "type": "github"
+ }
+ ],
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ },
+ "lock": false
+ },
+ "require": {
+ "php": ">=5.5.0",
+ "ext-ctype": "*",
+ "ext-filter": "*",
+ "ext-hash": "*"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "doctrine/annotations": "^1.2.6 || ^1.13.3",
+ "php-parallel-lint/php-console-highlighter": "^1.0.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "phpcompatibility/php-compatibility": "^10.0.0@dev",
+ "squizlabs/php_codesniffer": "^3.13.5",
+ "yoast/phpunit-polyfills": "^1.0.4"
+ },
+ "suggest": {
+ "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
+ "ext-imap": "Needed to support advanced email address parsing according to RFC822",
+ "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
+ "ext-openssl": "Needed for secure SMTP sending and DKIM signing",
+ "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
+ "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+ "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+ "psr/log": "For optional PSR-3 debug logging",
+ "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
+ "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication",
+ "directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example"
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "autoload": {
+ "psr-4": {
+ "PHPMailer\\PHPMailer\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "PHPMailer\\Test\\": "test/"
+ }
+ },
+ "license": "LGPL-2.1-only",
+ "scripts": {
+ "check": "./vendor/bin/phpcs",
+ "style": "./vendor/bin/phpcbf",
+ "test": "./vendor/bin/phpunit --no-coverage",
+ "coverage": "./vendor/bin/phpunit",
+ "lint": [
+ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . --show-deprecated -e php,phps --exclude vendor --exclude .git --exclude build"
+ ]
+ }
+}
diff --git a/www/libs/vendor/phpmailer/phpmailer/get_oauth_token.php b/www/libs/vendor/phpmailer/phpmailer/get_oauth_token.php
new file mode 100644
index 000000000..9342b9c75
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/get_oauth_token.php
@@ -0,0 +1,182 @@
+
+ * @author Jim Jagielski (jimjag)
+ * @author Andy Prevost (codeworxtech)
+ * @author Brent R. Matzelle (original founder)
+ * @copyright 2012 - 2020 Marcus Bointon
+ * @copyright 2010 - 2012 Jim Jagielski
+ * @copyright 2004 - 2009 Andy Prevost
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
+ * @note This program is distributed in the hope that it will be useful - WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/**
+ * Get an OAuth2 token from an OAuth2 provider.
+ * * Install this script on your server so that it's accessible
+ * as [https/http]:////get_oauth_token.php
+ * e.g.: http://localhost/phpmailer/get_oauth_token.php
+ * * Ensure dependencies are installed with 'composer install'
+ * * Set up an app in your Google/Yahoo/Microsoft account
+ * * Set the script address as the app's redirect URL
+ * If no refresh token is obtained when running this file,
+ * revoke access to your app and run the script again.
+ */
+
+namespace PHPMailer\PHPMailer;
+
+/**
+ * Aliases for League Provider Classes
+ * Make sure you have added these to your composer.json and run `composer install`
+ * Plenty to choose from here:
+ * @see https://oauth2-client.thephpleague.com/providers/thirdparty/
+ */
+//@see https://github.com/thephpleague/oauth2-google
+use League\OAuth2\Client\Provider\Google;
+//@see https://packagist.org/packages/hayageek/oauth2-yahoo
+use Hayageek\OAuth2\Client\Provider\Yahoo;
+//@see https://github.com/stevenmaguire/oauth2-microsoft
+use Stevenmaguire\OAuth2\Client\Provider\Microsoft;
+//@see https://github.com/greew/oauth2-azure-provider
+use Greew\OAuth2\Client\Provider\Azure;
+
+if (!isset($_GET['code']) && !isset($_POST['provider'])) {
+ ?>
+
+
+
+
+
+ $clientId,
+ 'clientSecret' => $clientSecret,
+ 'redirectUri' => $redirectUri,
+ 'accessType' => 'offline'
+];
+
+$options = [];
+$provider = null;
+
+switch ($providerName) {
+ case 'Google':
+ $provider = new Google($params);
+ $options = [
+ 'scope' => [
+ 'https://mail.google.com/'
+ ]
+ ];
+ break;
+ case 'Yahoo':
+ $provider = new Yahoo($params);
+ break;
+ case 'Microsoft':
+ $provider = new Microsoft($params);
+ $options = [
+ 'scope' => [
+ 'wl.imap',
+ 'wl.offline_access'
+ ]
+ ];
+ break;
+ case 'Azure':
+ $params['tenantId'] = $tenantId;
+
+ $provider = new Azure($params);
+ $options = [
+ 'scope' => [
+ 'https://outlook.office.com/SMTP.Send',
+ 'offline_access'
+ ]
+ ];
+ break;
+}
+
+if (null === $provider) {
+ exit('Provider missing');
+}
+
+if (!isset($_GET['code'])) {
+ //If we don't have an authorization code then get one
+ $authUrl = $provider->getAuthorizationUrl($options);
+ $_SESSION['oauth2state'] = $provider->getState();
+ header('Location: ' . $authUrl);
+ exit;
+ //Check given state against previously stored one to mitigate CSRF attack
+} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
+ unset($_SESSION['oauth2state']);
+ unset($_SESSION['provider']);
+ exit('Invalid state');
+} else {
+ unset($_SESSION['provider']);
+ //Try to get an access token (using the authorization code grant)
+ $token = $provider->getAccessToken(
+ 'authorization_code',
+ [
+ 'code' => $_GET['code']
+ ]
+ );
+ //Use this to interact with an API on the users behalf
+ //Use this to get a new access token if the old one expires
+ echo 'Refresh Token: ', htmlspecialchars($token->getRefreshToken(), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401);
+}
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php
new file mode 100644
index 000000000..0b2a72d52
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php
@@ -0,0 +1,26 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'خطأ SMTP : لا يمكن تأكيد الهوية.';
+$PHPMAILER_LANG['connect_host'] = 'خطأ SMTP: لا يمكن الاتصال بالخادم SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'خطأ SMTP: لم يتم قبول المعلومات .';
+$PHPMAILER_LANG['empty_message'] = 'نص الرسالة فارغ';
+$PHPMAILER_LANG['encoding'] = 'ترميز غير معروف: ';
+$PHPMAILER_LANG['execute'] = 'لا يمكن تنفيذ : ';
+$PHPMAILER_LANG['file_access'] = 'لا يمكن الوصول للملف: ';
+$PHPMAILER_LANG['file_open'] = 'خطأ في الملف: لا يمكن فتحه: ';
+$PHPMAILER_LANG['from_failed'] = 'خطأ على مستوى عنوان المرسل : ';
+$PHPMAILER_LANG['instantiate'] = 'لا يمكن توفير خدمة البريد.';
+$PHPMAILER_LANG['invalid_address'] = 'الإرسال غير ممكن لأن عنوان البريد الإلكتروني غير صالح: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' برنامج الإرسال غير مدعوم.';
+$PHPMAILER_LANG['provide_address'] = 'يجب توفير عنوان البريد الإلكتروني لمستلم واحد على الأقل.';
+$PHPMAILER_LANG['recipients_failed'] = 'خطأ SMTP: الأخطاء التالية فشل في الارسال لكل من : ';
+$PHPMAILER_LANG['signing'] = 'خطأ في التوقيع: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() غير ممكن.';
+$PHPMAILER_LANG['smtp_error'] = 'خطأ على مستوى الخادم SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'لا يمكن تعيين أو إعادة تعيين متغير: ';
+$PHPMAILER_LANG['extension_missing'] = 'الإضافة غير موجودة: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php
new file mode 100644
index 000000000..327dfbafa
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php
@@ -0,0 +1,35 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP ত্ৰুটি: প্ৰমাণীকৰণ কৰিব নোৱাৰি';
+$PHPMAILER_LANG['buggy_php'] = 'আপোনাৰ PHP সংস্কৰণ এটা বাগৰ দ্বাৰা প্ৰভাৱিত হয় যাৰ ফলত নষ্ট বাৰ্তা হব পাৰে । ইয়াক সমাধান কৰিবলে, প্ৰেৰণ কৰিবলে SMTP ব্যৱহাৰ কৰক, আপোনাৰ php.ini ত mail.add_x_header বিকল্প নিষ্ক্ৰিয় কৰক, MacOS বা Linux লৈ সলনি কৰক, বা আপোনাৰ PHP সংস্কৰণ 7.0.17+ বা 7.1.3+ লৈ সলনি কৰক ।';
+$PHPMAILER_LANG['connect_host'] = 'SMTP ত্ৰুটি: SMTP চাৰ্ভাৰৰ সৈতে সংযোগ কৰিবলে অক্ষম';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্ৰুটি: তথ্য গ্ৰহণ কৰা হোৱা নাই';
+$PHPMAILER_LANG['empty_message'] = 'বাৰ্তাৰ মূখ্য অংশ খালী।';
+$PHPMAILER_LANG['encoding'] = 'অজ্ঞাত এনকোডিং: ';
+$PHPMAILER_LANG['execute'] = 'এক্সিকিউট কৰিব নোৱাৰি: ';
+$PHPMAILER_LANG['extension_missing'] = 'সম্প্ৰসাৰণ নোহোৱা হৈছে: ';
+$PHPMAILER_LANG['file_access'] = 'ফাইল অভিগম কৰিবলে অক্ষম: ';
+$PHPMAILER_LANG['file_open'] = 'ফাইল ত্ৰুটি: ফাইল খোলিবলৈ অক্ষম: ';
+$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্ৰেৰকৰ ঠিকনা(সমূহ) ব্যৰ্থ: ';
+$PHPMAILER_LANG['instantiate'] = 'মেইল ফাংচনৰ এটা উদাহৰণ সৃষ্টি কৰিবলে অক্ষম';
+$PHPMAILER_LANG['invalid_address'] = 'প্ৰেৰণ কৰিব নোৱাৰি: অবৈধ ইমেইল ঠিকনা: ';
+$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডাৰৰ নাম বা মান';
+$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোষ্টেন্ট্ৰি: ';
+$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হস্ট:';
+$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলাৰ সমৰ্থিত নহয়।';
+$PHPMAILER_LANG['provide_address'] = 'আপুনি অন্ততঃ এটা গন্তব্য ইমেইল ঠিকনা দিব লাগিব';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্ৰুটি: নিম্নলিখিত গন্তব্যস্থানসমূহ ব্যৰ্থ: ';
+$PHPMAILER_LANG['signing'] = 'স্বাক্ষৰ কৰাত ব্যৰ্থ: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP কড: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'অতিৰিক্ত SMTP তথ্য: ';
+$PHPMAILER_LANG['smtp_detail'] = 'বিৱৰণ:';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যৰ্থ';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP চাৰ্ভাৰৰ ত্ৰুটি: ';
+$PHPMAILER_LANG['variable_set'] = 'চলক নিৰ্ধাৰণ কৰিব পৰা নগল: ';
+$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত সম্প্ৰসাৰণ: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php
new file mode 100644
index 000000000..552167ef6
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela prijava.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Nije moguće spojiti se sa SMTP serverom.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.';
+$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.';
+$PHPMAILER_LANG['encoding'] = 'Nepoznata kriptografija: ';
+$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: ';
+$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: ';
+$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: ';
+$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje sa navedenih e-mail adresa nije uspjelo: ';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedene e-mail adrese nije uspjelo: ';
+$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.';
+$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.';
+$PHPMAILER_LANG['provide_address'] = 'Definišite barem jednu adresu primaoca.';
+$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP server nije uspjelo.';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP greška: ';
+$PHPMAILER_LANG['variable_set'] = 'Nije moguće postaviti varijablu ili je vratiti nazad: ';
+$PHPMAILER_LANG['extension_missing'] = 'Nedostaje ekstenzija: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php
new file mode 100644
index 000000000..9e92ddaaf
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Памылка SMTP: памылка ідэнтыфікацыі.';
+$PHPMAILER_LANG['connect_host'] = 'Памылка SMTP: нельга ўстанавіць сувязь з SMTP-серверам.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Памылка SMTP: звесткі непрынятыя.';
+$PHPMAILER_LANG['empty_message'] = 'Пустое паведамленне.';
+$PHPMAILER_LANG['encoding'] = 'Невядомая кадыроўка тэксту: ';
+$PHPMAILER_LANG['execute'] = 'Нельга выканаць каманду: ';
+$PHPMAILER_LANG['file_access'] = 'Няма доступу да файла: ';
+$PHPMAILER_LANG['file_open'] = 'Нельга адкрыць файл: ';
+$PHPMAILER_LANG['from_failed'] = 'Няправільны адрас адпраўніка: ';
+$PHPMAILER_LANG['instantiate'] = 'Нельга прымяніць функцыю mail().';
+$PHPMAILER_LANG['invalid_address'] = 'Нельга даслаць паведамленне, няправільны email атрымальніка: ';
+$PHPMAILER_LANG['provide_address'] = 'Запоўніце, калі ласка, правільны email атрымальніка.';
+$PHPMAILER_LANG['mailer_not_supported'] = ' - паштовы сервер не падтрымліваецца.';
+$PHPMAILER_LANG['recipients_failed'] = 'Памылка SMTP: няправільныя атрымальнікі: ';
+$PHPMAILER_LANG['signing'] = 'Памылка подпісу паведамлення: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Памылка сувязі з SMTP-серверам.';
+$PHPMAILER_LANG['smtp_error'] = 'Памылка SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'Нельга ўстанавіць або перамяніць значэнне пераменнай: ';
+//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php
new file mode 100644
index 000000000..c41f675df
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: Не може да се удостовери пред сървъра.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: Не може да се свърже с SMTP хоста.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: данните не са приети.';
+$PHPMAILER_LANG['empty_message'] = 'Съдържанието на съобщението е празно';
+$PHPMAILER_LANG['encoding'] = 'Неизвестно кодиране: ';
+$PHPMAILER_LANG['execute'] = 'Не може да се изпълни: ';
+$PHPMAILER_LANG['file_access'] = 'Няма достъп до файл: ';
+$PHPMAILER_LANG['file_open'] = 'Файлова грешка: Не може да се отвори файл: ';
+$PHPMAILER_LANG['from_failed'] = 'Следните адреси за подател са невалидни: ';
+$PHPMAILER_LANG['instantiate'] = 'Не може да се инстанцира функцията mail.';
+$PHPMAILER_LANG['invalid_address'] = 'Невалиден адрес: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' - пощенски сървър не се поддържа.';
+$PHPMAILER_LANG['provide_address'] = 'Трябва да предоставите поне един email адрес за получател.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: Следните адреси за Получател са невалидни: ';
+$PHPMAILER_LANG['signing'] = 'Грешка при подписване: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP провален connect().';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP сървърна грешка: ';
+$PHPMAILER_LANG['variable_set'] = 'Не може да се установи или възстанови променлива: ';
+$PHPMAILER_LANG['extension_missing'] = 'Липсва разширение: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php
new file mode 100644
index 000000000..473651080
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php
@@ -0,0 +1,35 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP ত্রুটি: প্রমাণীকরণ করতে অক্ষম৷';
+$PHPMAILER_LANG['buggy_php'] = 'আপনার PHP সংস্করণ একটি বাগ দ্বারা প্রভাবিত হয় যার ফলে দূষিত বার্তা হতে পারে। এটি ঠিক করতে, পাঠাতে SMTP ব্যবহার করুন, আপনার php.ini এ mail.add_x_header বিকল্পটি নিষ্ক্রিয় করুন, MacOS বা Linux-এ স্যুইচ করুন, অথবা আপনার PHP সংস্করণকে 7.0.17+ বা 7.1.3+ এ পরিবর্তন করুন।';
+$PHPMAILER_LANG['connect_host'] = 'SMTP ত্রুটি: SMTP সার্ভারের সাথে সংযোগ করতে অক্ষম৷';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্রুটি: ডেটা গ্রহণ করা হয়নি৷';
+$PHPMAILER_LANG['empty_message'] = 'বার্তার অংশটি খালি।';
+$PHPMAILER_LANG['encoding'] = 'অজানা এনকোডিং: ';
+$PHPMAILER_LANG['execute'] = 'নির্বাহ করতে অক্ষম: ';
+$PHPMAILER_LANG['extension_missing'] = 'এক্সটেনশন অনুপস্থিত:';
+$PHPMAILER_LANG['file_access'] = 'ফাইল অ্যাক্সেস করতে অক্ষম: ';
+$PHPMAILER_LANG['file_open'] = 'ফাইল ত্রুটি: ফাইল খুলতে অক্ষম: ';
+$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্রেরকের ঠিকানা(গুলি) ব্যর্থ হয়েছে: ';
+$PHPMAILER_LANG['instantiate'] = 'মেল ফাংশনের একটি উদাহরণ তৈরি করতে অক্ষম৷';
+$PHPMAILER_LANG['invalid_address'] = 'পাঠাতে অক্ষম: অবৈধ ইমেল ঠিকানা: ';
+$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডার নাম বা মান';
+$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোস্টেন্ট্রি: ';
+$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হোস্ট:';
+$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলার সমর্থিত নয়।';
+$PHPMAILER_LANG['provide_address'] = 'আপনাকে অবশ্যই অন্তত একটি গন্তব্য ইমেল ঠিকানা প্রদান করতে হবে৷';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্রুটি: নিম্নলিখিত গন্তব্যগুলি ব্যর্থ হয়েছে: ';
+$PHPMAILER_LANG['signing'] = 'স্বাক্ষর করতে ব্যর্থ হয়েছে: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP কোড: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'অতিরিক্ত SMTP তথ্য:';
+$PHPMAILER_LANG['smtp_detail'] = 'বর্ণনা: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যর্থ হয়েছে৷';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP সার্ভার ত্রুটি: ';
+$PHPMAILER_LANG['variable_set'] = 'পরিবর্তনশীল সেট করা যায়নি: ';
+$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত এক্সটেনশন: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php
new file mode 100644
index 000000000..34684855a
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Error SMTP: No s’ha pogut autenticar.';
+$PHPMAILER_LANG['connect_host'] = 'Error SMTP: No es pot connectar al servidor SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Dades no acceptades.';
+$PHPMAILER_LANG['empty_message'] = 'El cos del missatge està buit.';
+$PHPMAILER_LANG['encoding'] = 'Codificació desconeguda: ';
+$PHPMAILER_LANG['execute'] = 'No es pot executar: ';
+$PHPMAILER_LANG['file_access'] = 'No es pot accedir a l’arxiu: ';
+$PHPMAILER_LANG['file_open'] = 'Error d’Arxiu: No es pot obrir l’arxiu: ';
+$PHPMAILER_LANG['from_failed'] = 'La(s) següent(s) adreces de remitent han fallat: ';
+$PHPMAILER_LANG['instantiate'] = 'No s’ha pogut crear una instància de la funció Mail.';
+$PHPMAILER_LANG['invalid_address'] = 'Adreça d’email invalida: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no està suportat';
+$PHPMAILER_LANG['provide_address'] = 'S’ha de proveir almenys una adreça d’email com a destinatari.';
+$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Els següents destinataris han fallat: ';
+$PHPMAILER_LANG['signing'] = 'Error al signar: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Ha fallat el SMTP Connect().';
+$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'No s’ha pogut establir o restablir la variable: ';
+//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php
new file mode 100644
index 000000000..e770a1a26
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php
@@ -0,0 +1,28 @@
+
+ * Rewrite and extension of the work by Mikael Stokkebro
+ *
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP fejl: Login mislykkedes.';
+$PHPMAILER_LANG['buggy_php'] = 'Din version af PHP er berørt af en fejl, som gør at dine beskeder muligvis vises forkert. For at rette dette kan du skifte til SMTP, slå mail.add_x_header headeren i din php.ini fil fra, skifte til MacOS eller Linux eller opgradere din version af PHP til 7.0.17+ eller 7.1.3+.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP fejl: Forbindelse til SMTP serveren kunne ikke oprettes.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fejl: Data blev ikke accepteret.';
+$PHPMAILER_LANG['empty_message'] = 'Meddelelsen er uden indhold';
+$PHPMAILER_LANG['encoding'] = 'Ukendt encode-format: ';
+$PHPMAILER_LANG['execute'] = 'Kunne ikke afvikle: ';
+$PHPMAILER_LANG['extension_missing'] = 'Udvidelse mangler: ';
+$PHPMAILER_LANG['file_access'] = 'Kunne ikke tilgå filen: ';
+$PHPMAILER_LANG['file_open'] = 'Fil fejl: Kunne ikke åbne filen: ';
+$PHPMAILER_LANG['from_failed'] = 'Følgende afsenderadresse er forkert: ';
+$PHPMAILER_LANG['instantiate'] = 'Email funktionen kunne ikke initialiseres.';
+$PHPMAILER_LANG['invalid_address'] = 'Udgyldig adresse: ';
+$PHPMAILER_LANG['invalid_header'] = 'Ugyldig header navn eller værdi';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Ugyldig hostentry: ';
+$PHPMAILER_LANG['invalid_host'] = 'Ugyldig vært: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer understøttes ikke.';
+$PHPMAILER_LANG['provide_address'] = 'Indtast mindst en modtagers email adresse.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP fejl: Følgende modtagere fejlede: ';
+$PHPMAILER_LANG['signing'] = 'Signeringsfejl: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP kode: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Yderligere SMTP info: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fejlede.';
+$PHPMAILER_LANG['smtp_detail'] = 'Detalje: ';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP server fejl: ';
+$PHPMAILER_LANG['variable_set'] = 'Kunne ikke definere eller nulstille variablen: ';
+$PHPMAILER_LANG['no_smtputf8'] = 'Serveren understøtter ikke SMTPUTF8 som påkrævet for at sende til Unicode adresser';
+$PHPMAILER_LANG['imap_recommended'] = 'Brug af forenklet adresseparser anbefales ikke. Installer PHP IMAP udvidelsen for fuld RFC822 parsing.';
+$PHPMAILER_LANG['deprecated_argument'] = 'Udfaset argument: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php
new file mode 100644
index 000000000..e7e59d2b6
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php
@@ -0,0 +1,28 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP-eraro: Ne eblis aŭtentigi.';
+$PHPMAILER_LANG['buggy_php'] = 'Via versio de PHP estas trafita de cimo, kiu povas kaŭzi difektitajn mesaĝojn. Por ripari tion, ŝanĝu al sendado per SMTP, malŝaltu la opcion mail.add_x_header en via php.ini, ŝanĝu al MacOS aŭ Linux, aŭ ĝisdatigu vian PHP al versio 7.0.17+ aŭ 7.1.3+.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP-eraro: Ne eblis konektiĝi al la SMTP-gastiganto.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-eraro: Datumoj ne akceptitaj.';
+$PHPMAILER_LANG['empty_message'] = 'Mesaĝokorpo malplena';
+$PHPMAILER_LANG['encoding'] = 'Nekonata kodoprezento: ';
+$PHPMAILER_LANG['execute'] = 'Ne eblis plenumi: ';
+$PHPMAILER_LANG['extension_missing'] = 'Kromprogramo mankas: ';
+$PHPMAILER_LANG['file_access'] = 'Ne eblis aliri la dosieron: ';
+$PHPMAILER_LANG['file_open'] = 'Dosiera eraro: Ne eblis malfermi la dosieron: ';
+$PHPMAILER_LANG['from_failed'] = 'La sekva(j) sendinto(j) malsukcesis: ';
+$PHPMAILER_LANG['instantiate'] = 'Ne eblis funkciigi la retpoŝtan funkcion.';
+$PHPMAILER_LANG['invalid_address'] = 'Nevalida adreso: ';
+$PHPMAILER_LANG['invalid_header'] = 'Nevalida kaplinia nomo aŭ valoro';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Nevalida enigo de gastiganto: ';
+$PHPMAILER_LANG['invalid_host'] = 'Nevalida gastiganto: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' retpoŝtilo ne estas subtenata.';
+$PHPMAILER_LANG['provide_address'] = 'Vi devas provizi almenaŭ unu retpoŝtadreson de ricevonto.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP-eraro: La sekva(j) ricevonto(j) malsukcesis: ';
+$PHPMAILER_LANG['signing'] = 'Subskriba eraro: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP-kodo: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Pliaj SMTP-informoj: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'La SMTP-konektiĝo malsukcesis.';
+$PHPMAILER_LANG['smtp_detail'] = 'Informoj: ';
+$PHPMAILER_LANG['smtp_error'] = 'Eraro de SMTP-servilo: ';
+$PHPMAILER_LANG['variable_set'] = 'Ne eblas agordi aŭ reagordi la variablon: ';
+$PHPMAILER_LANG['no_smtputf8'] = 'La servilo ne subtenas SMTPUTF8, kiu estas bezonata por sendi al Unicode-adresoj.';
+$PHPMAILER_LANG['imap_recommended'] = 'Uzado de la simpligita adresanalizilo ne estas rekomendita. Instalu la IMAP-kromprogramon por PHP por plena RFC822-analizado.';
+$PHPMAILER_LANG['deprecated_argument'] = 'Malrekomendita argumento: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php
new file mode 100644
index 000000000..e6a3d85ef
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php
@@ -0,0 +1,38 @@
+
+ * @author Crystopher Glodzienski Cardoso
+ * @author Daniel Cruz
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.';
+$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP ha sido afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.';
+$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.';
+$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.';
+$PHPMAILER_LANG['encoding'] = 'Codificación desconocida: ';
+$PHPMAILER_LANG['execute'] = 'Imposible ejecutar: ';
+$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: ';
+$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: ';
+$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: ';
+$PHPMAILER_LANG['from_failed'] = 'La siguiente dirección de remitente falló: ';
+$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.';
+$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: ';
+$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Hostentry inválido: ';
+$PHPMAILER_LANG['invalid_host'] = 'Host inválido: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no está soportado.';
+$PHPMAILER_LANG['provide_address'] = 'Debe proporcionar al menos una dirección de email de destino.';
+$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Los siguientes destinos fallaron: ';
+$PHPMAILER_LANG['signing'] = 'Error al firmar: ';
+$PHPMAILER_LANG['smtp_code'] = 'Código del servidor SMTP: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Información adicional del servidor SMTP: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.';
+$PHPMAILER_LANG['smtp_detail'] = 'Detalle: ';
+$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: ';
+$PHPMAILER_LANG['imap_recommended'] = 'No se recomienda usar el analizador de direcciones simplificado. Instala la extensión IMAP de PHP para un análisis RFC822 más completo.';
+$PHPMAILER_LANG['deprecated_argument'] = 'Argumento obsoleto: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php
new file mode 100644
index 000000000..93addc9e3
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php
@@ -0,0 +1,28 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP Viga: Autoriseerimise viga.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP Viga: Ei õnnestunud luua ühendust SMTP serveriga.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Viga: Vigased andmed.';
+$PHPMAILER_LANG['empty_message'] = 'Tühi kirja sisu';
+$PHPMAILER_LANG["encoding"] = 'Tundmatu kodeering: ';
+$PHPMAILER_LANG['execute'] = 'Tegevus ebaõnnestus: ';
+$PHPMAILER_LANG['file_access'] = 'Pole piisavalt õiguseid järgneva faili avamiseks: ';
+$PHPMAILER_LANG['file_open'] = 'Faili Viga: Faili avamine ebaõnnestus: ';
+$PHPMAILER_LANG['from_failed'] = 'Järgnev saatja e-posti aadress on vigane: ';
+$PHPMAILER_LANG['instantiate'] = 'mail funktiooni käivitamine ebaõnnestus.';
+$PHPMAILER_LANG['invalid_address'] = 'Saatmine peatatud, e-posti address vigane: ';
+$PHPMAILER_LANG['provide_address'] = 'Te peate määrama vähemalt ühe saaja e-posti aadressi.';
+$PHPMAILER_LANG['mailer_not_supported'] = ' maileri tugi puudub.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP Viga: Järgnevate saajate e-posti aadressid on vigased: ';
+$PHPMAILER_LANG["signing"] = 'Viga allkirjastamisel: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() ebaõnnestus.';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP serveri viga: ';
+$PHPMAILER_LANG['variable_set'] = 'Ei õnnestunud määrata või lähtestada muutujat: ';
+$PHPMAILER_LANG['extension_missing'] = 'Nõutud laiendus on puudu: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php
new file mode 100644
index 000000000..295a47f95
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php
@@ -0,0 +1,28 @@
+
+ * @author Mohammad Hossein Mojtahedi
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'خطای SMTP: احراز هویت با شکست مواجه شد.';
+$PHPMAILER_LANG['connect_host'] = 'خطای SMTP: اتصال به سرور SMTP برقرار نشد.';
+$PHPMAILER_LANG['data_not_accepted'] = 'خطای SMTP: دادهها نادرست هستند.';
+$PHPMAILER_LANG['empty_message'] = 'بخش متن پیام خالی است.';
+$PHPMAILER_LANG['encoding'] = 'کدگذاری ناشناخته: ';
+$PHPMAILER_LANG['execute'] = 'امکان اجرا وجود ندارد: ';
+$PHPMAILER_LANG['file_access'] = 'امکان دسترسی به فایل وجود ندارد: ';
+$PHPMAILER_LANG['file_open'] = 'خطای File: امکان بازکردن فایل وجود ندارد: ';
+$PHPMAILER_LANG['from_failed'] = 'آدرس فرستنده اشتباه است: ';
+$PHPMAILER_LANG['instantiate'] = 'امکان معرفی تابع ایمیل وجود ندارد.';
+$PHPMAILER_LANG['invalid_address'] = 'آدرس ایمیل معتبر نیست: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer پشتیبانی نمیشود.';
+$PHPMAILER_LANG['provide_address'] = 'باید حداقل یک آدرس گیرنده وارد کنید.';
+$PHPMAILER_LANG['recipients_failed'] = 'خطای SMTP: ارسال به آدرس گیرنده با خطا مواجه شد: ';
+$PHPMAILER_LANG['signing'] = 'خطا در امضا: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'خطا در اتصال به SMTP.';
+$PHPMAILER_LANG['smtp_error'] = 'خطا در SMTP Server: ';
+$PHPMAILER_LANG['variable_set'] = 'امکان ارسال یا ارسال مجدد متغیرها وجود ندارد: ';
+$PHPMAILER_LANG['extension_missing'] = 'افزونه موجود نیست: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php
new file mode 100644
index 000000000..6d1e63739
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP feilur: Kundi ikki góðkenna.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP feilur: Kundi ikki knýta samband við SMTP vert.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP feilur: Data ikki góðkent.';
+//$PHPMAILER_LANG['empty_message'] = 'Message body empty';
+$PHPMAILER_LANG['encoding'] = 'Ókend encoding: ';
+$PHPMAILER_LANG['execute'] = 'Kundi ikki útføra: ';
+$PHPMAILER_LANG['file_access'] = 'Kundi ikki tilganga fílu: ';
+$PHPMAILER_LANG['file_open'] = 'Fílu feilur: Kundi ikki opna fílu: ';
+$PHPMAILER_LANG['from_failed'] = 'fylgjandi Frá/From adressa miseydnaðist: ';
+$PHPMAILER_LANG['instantiate'] = 'Kuni ikki instantiera mail funktión.';
+//$PHPMAILER_LANG['invalid_address'] = 'Invalid address: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' er ikki supporterað.';
+$PHPMAILER_LANG['provide_address'] = 'Tú skal uppgeva minst móttakara-emailadressu(r).';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP Feilur: Fylgjandi móttakarar miseydnaðust: ';
+//$PHPMAILER_LANG['signing'] = 'Signing Error: ';
+//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.';
+//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: ';
+//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: ';
+//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php
new file mode 100644
index 000000000..a6d582d83
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php
@@ -0,0 +1,36 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Erro SMTP: Non puido ser autentificado.';
+$PHPMAILER_LANG['connect_host'] = 'Erro SMTP: Non puido conectar co servidor SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Erro SMTP: Datos non aceptados.';
+$PHPMAILER_LANG['empty_message'] = 'Corpo da mensaxe vacía';
+$PHPMAILER_LANG['encoding'] = 'Codificación descoñecida: ';
+$PHPMAILER_LANG['execute'] = 'Non puido ser executado: ';
+$PHPMAILER_LANG['file_access'] = 'Nob puido acceder ó arquivo: ';
+$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: No puido abrir o arquivo: ';
+$PHPMAILER_LANG['from_failed'] = 'A(s) seguinte(s) dirección(s) de remitente(s) deron erro: ';
+$PHPMAILER_LANG['instantiate'] = 'Non puido crear unha instancia da función Mail.';
+$PHPMAILER_LANG['invalid_address'] = 'Non puido envia-lo correo: dirección de email inválida: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer non está soportado.';
+$PHPMAILER_LANG['provide_address'] = 'Debe engadir polo menos unha dirección de email coma destino.';
+$PHPMAILER_LANG['recipients_failed'] = 'Erro SMTP: Os seguintes destinos fallaron: ';
+$PHPMAILER_LANG['signing'] = 'Erro ó firmar: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallou.';
+$PHPMAILER_LANG['smtp_error'] = 'Erro do servidor SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'Non puidemos axustar ou reaxustar a variábel: ';
+//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php
new file mode 100644
index 000000000..b123aa5fc
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'שגיאת SMTP: פעולת האימות נכשלה.';
+$PHPMAILER_LANG['connect_host'] = 'שגיאת SMTP: לא הצלחתי להתחבר לשרת SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'שגיאת SMTP: מידע לא התקבל.';
+$PHPMAILER_LANG['empty_message'] = 'גוף ההודעה ריק';
+$PHPMAILER_LANG['invalid_address'] = 'כתובת שגויה: ';
+$PHPMAILER_LANG['encoding'] = 'קידוד לא מוכר: ';
+$PHPMAILER_LANG['execute'] = 'לא הצלחתי להפעיל את: ';
+$PHPMAILER_LANG['file_access'] = 'לא ניתן לגשת לקובץ: ';
+$PHPMAILER_LANG['file_open'] = 'שגיאת קובץ: לא ניתן לגשת לקובץ: ';
+$PHPMAILER_LANG['from_failed'] = 'כתובות הנמענים הבאות נכשלו: ';
+$PHPMAILER_LANG['instantiate'] = 'לא הצלחתי להפעיל את פונקציית המייל.';
+$PHPMAILER_LANG['mailer_not_supported'] = ' אינה נתמכת.';
+$PHPMAILER_LANG['provide_address'] = 'חובה לספק לפחות כתובת אחת של מקבל המייל.';
+$PHPMAILER_LANG['recipients_failed'] = 'שגיאת SMTP: הנמענים הבאים נכשלו: ';
+$PHPMAILER_LANG['signing'] = 'שגיאת חתימה: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.';
+$PHPMAILER_LANG['smtp_error'] = 'שגיאת שרת SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'לא ניתן לקבוע או לשנות את המשתנה: ';
+//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php
new file mode 100644
index 000000000..d2856e057
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php
@@ -0,0 +1,35 @@
+
+ * Rewrite and extension of the work by Jayanti Suthar
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP त्रुटि: प्रामाणिकता की जांच नहीं हो सका। ';
+$PHPMAILER_LANG['buggy_php'] = 'PHP का आपका संस्करण एक बग से प्रभावित है जिसके परिणामस्वरूप संदेश दूषित हो सकते हैं. इसे ठीक करने हेतु, भेजने के लिए SMTP का उपयोग करे, अपने php.ini में mail.add_x_header विकल्प को अक्षम करें, MacOS या Linux पर जाए, या अपने PHP संस्करण को 7.0.17+ या 7.1.3+ बदले.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP त्रुटि: SMTP सर्वर से कनेक्ट नहीं हो सका। ';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP त्रुटि: डेटा स्वीकार नहीं किया जाता है। ';
+$PHPMAILER_LANG['empty_message'] = 'संदेश खाली है। ';
+$PHPMAILER_LANG['encoding'] = 'अज्ञात एन्कोडिंग प्रकार। ';
+$PHPMAILER_LANG['execute'] = 'आदेश को निष्पादित करने में विफल। ';
+$PHPMAILER_LANG['extension_missing'] = 'एक्सटेन्षन गायब है: ';
+$PHPMAILER_LANG['file_access'] = 'फ़ाइल उपलब्ध नहीं है। ';
+$PHPMAILER_LANG['file_open'] = 'फ़ाइल त्रुटि: फाइल को खोला नहीं जा सका। ';
+$PHPMAILER_LANG['from_failed'] = 'प्रेषक का पता गलत है। ';
+$PHPMAILER_LANG['instantiate'] = 'मेल फ़ंक्शन कॉल नहीं कर सकता है।';
+$PHPMAILER_LANG['invalid_address'] = 'पता गलत है। ';
+$PHPMAILER_LANG['invalid_header'] = 'अमान्य हेडर नाम या मान';
+$PHPMAILER_LANG['invalid_hostentry'] = 'अमान्य hostentry: ';
+$PHPMAILER_LANG['invalid_host'] = 'अमान्य होस्ट: ';
+$PHPMAILER_LANG['mailer_not_supported'] = 'मेल सर्वर के साथ काम नहीं करता है। ';
+$PHPMAILER_LANG['provide_address'] = 'आपको कम से कम एक प्राप्तकर्ता का ई-मेल पता प्रदान करना होगा।';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP त्रुटि: निम्न प्राप्तकर्ताओं को पते भेजने में विफल। ';
+$PHPMAILER_LANG['signing'] = 'साइनअप त्रुटि: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP कोड: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'अतिरिक्त SMTP जानकारी: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP का connect () फ़ंक्शन विफल हुआ। ';
+$PHPMAILER_LANG['smtp_detail'] = 'विवरण: ';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP सर्वर त्रुटि। ';
+$PHPMAILER_LANG['variable_set'] = 'चर को बना या संशोधित नहीं किया जा सकता। ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php
new file mode 100644
index 000000000..cacb6c37e
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela autentikacija.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Ne mogu se spojiti na SMTP poslužitelj.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.';
+$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.';
+$PHPMAILER_LANG['encoding'] = 'Nepoznati encoding: ';
+$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: ';
+$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: ';
+$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: ';
+$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje s navedenih e-mail adresa nije uspjelo: ';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedenih e-mail adresa nije uspjelo: ';
+$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.';
+$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.';
+$PHPMAILER_LANG['provide_address'] = 'Definirajte barem jednu adresu primatelja.';
+$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP poslužitelj nije uspjelo.';
+$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP poslužitelja: ';
+$PHPMAILER_LANG['variable_set'] = 'Ne mogu postaviti varijablu niti ju vratiti nazad: ';
+$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php
new file mode 100644
index 000000000..e6b58b0db
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP -ի սխալ: չհաջողվեց ստուգել իսկությունը.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP -ի սխալ: չհաջողվեց կապ հաստատել SMTP սերվերի հետ.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP -ի սխալ: տվյալները ընդունված չեն.';
+$PHPMAILER_LANG['empty_message'] = 'Հաղորդագրությունը դատարկ է';
+$PHPMAILER_LANG['encoding'] = 'Կոդավորման անհայտ տեսակ: ';
+$PHPMAILER_LANG['execute'] = 'Չհաջողվեց իրականացնել հրամանը: ';
+$PHPMAILER_LANG['file_access'] = 'Ֆայլը հասանելի չէ: ';
+$PHPMAILER_LANG['file_open'] = 'Ֆայլի սխալ: ֆայլը չհաջողվեց բացել: ';
+$PHPMAILER_LANG['from_failed'] = 'Ուղարկողի հետևյալ հասցեն սխալ է: ';
+$PHPMAILER_LANG['instantiate'] = 'Հնարավոր չէ կանչել mail ֆունկցիան.';
+$PHPMAILER_LANG['invalid_address'] = 'Հասցեն սխալ է: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' փոստային սերվերի հետ չի աշխատում.';
+$PHPMAILER_LANG['provide_address'] = 'Անհրաժեշտ է տրամադրել գոնե մեկ ստացողի e-mail հասցե.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP -ի սխալ: չի հաջողվել ուղարկել հետևյալ ստացողների հասցեներին: ';
+$PHPMAILER_LANG['signing'] = 'Ստորագրման սխալ: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP -ի connect() ֆունկցիան չի հաջողվել';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP սերվերի սխալ: ';
+$PHPMAILER_LANG['variable_set'] = 'Չի հաջողվում ստեղծել կամ վերափոխել փոփոխականը: ';
+$PHPMAILER_LANG['extension_missing'] = 'Հավելվածը բացակայում է: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php
new file mode 100644
index 000000000..212a11f13
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php
@@ -0,0 +1,31 @@
+
+ * @author @januridp
+ * @author Ian Mustafa
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Kesalahan SMTP: Tidak dapat mengotentikasi.';
+$PHPMAILER_LANG['connect_host'] = 'Kesalahan SMTP: Tidak dapat terhubung ke host SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Kesalahan SMTP: Data tidak diterima.';
+$PHPMAILER_LANG['empty_message'] = 'Isi pesan kosong';
+$PHPMAILER_LANG['encoding'] = 'Pengkodean karakter tidak dikenali: ';
+$PHPMAILER_LANG['execute'] = 'Tidak dapat menjalankan proses: ';
+$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses berkas: ';
+$PHPMAILER_LANG['file_open'] = 'Kesalahan Berkas: Berkas tidak dapat dibuka: ';
+$PHPMAILER_LANG['from_failed'] = 'Alamat pengirim berikut mengakibatkan kesalahan: ';
+$PHPMAILER_LANG['instantiate'] = 'Tidak dapat menginisialisasi fungsi surel.';
+$PHPMAILER_LANG['invalid_address'] = 'Gagal terkirim, alamat surel tidak sesuai: ';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Gagal terkirim, entri host tidak sesuai: ';
+$PHPMAILER_LANG['invalid_host'] = 'Gagal terkirim, host tidak sesuai: ';
+$PHPMAILER_LANG['provide_address'] = 'Harus tersedia minimal satu alamat tujuan';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tidak didukung';
+$PHPMAILER_LANG['recipients_failed'] = 'Kesalahan SMTP: Alamat tujuan berikut menyebabkan kesalahan: ';
+$PHPMAILER_LANG['signing'] = 'Kesalahan dalam penandatangan SSL: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() gagal.';
+$PHPMAILER_LANG['smtp_error'] = 'Kesalahan pada pelayan SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'Tidak dapat mengatur atau mengatur ulang variabel: ';
+$PHPMAILER_LANG['extension_missing'] = 'Ekstensi PHP tidak tersedia: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php
new file mode 100644
index 000000000..08a6b7333
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php
@@ -0,0 +1,28 @@
+
+ * @author Stefano Sabatini
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Impossibile autenticarsi.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Impossibile connettersi all\'host SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dati non accettati dal server.';
+$PHPMAILER_LANG['empty_message'] = 'Il corpo del messaggio è vuoto';
+$PHPMAILER_LANG['encoding'] = 'Codifica dei caratteri sconosciuta: ';
+$PHPMAILER_LANG['execute'] = 'Impossibile eseguire l\'operazione: ';
+$PHPMAILER_LANG['file_access'] = 'Impossibile accedere al file: ';
+$PHPMAILER_LANG['file_open'] = 'File Error: Impossibile aprire il file: ';
+$PHPMAILER_LANG['from_failed'] = 'I seguenti indirizzi mittenti hanno generato errore: ';
+$PHPMAILER_LANG['instantiate'] = 'Impossibile istanziare la funzione mail';
+$PHPMAILER_LANG['invalid_address'] = 'Impossibile inviare, l\'indirizzo email non è valido: ';
+$PHPMAILER_LANG['provide_address'] = 'Deve essere fornito almeno un indirizzo ricevente';
+$PHPMAILER_LANG['mailer_not_supported'] = 'Mailer non supportato';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: I seguenti indirizzi destinatari hanno generato un errore: ';
+$PHPMAILER_LANG['signing'] = 'Errore nella firma: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallita.';
+$PHPMAILER_LANG['smtp_error'] = 'Errore del server SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'Impossibile impostare o resettare la variabile: ';
+$PHPMAILER_LANG['extension_missing'] = 'Estensione mancante: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php
new file mode 100644
index 000000000..d01869cec
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php
@@ -0,0 +1,37 @@
+
+ * @author Yoshi Sakai
+ * @author Arisophy
+ * @author ARAKI Musashi
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTPエラー: 認証できませんでした。';
+$PHPMAILER_LANG['buggy_php'] = 'ご利用のバージョンのPHPには不具合があり、メッセージが破損するおそれがあります。問題の解決は以下のいずれかを行ってください。SMTPでの送信に切り替える。php.iniのmail.add_x_headerをoffにする。MacOSまたはLinuxに切り替える。PHPバージョン7.0.17以降または7.1.3以降にアップグレードする。';
+$PHPMAILER_LANG['connect_host'] = 'SMTPエラー: SMTPホストに接続できませんでした。';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTPエラー: データが受け付けられませんでした。';
+$PHPMAILER_LANG['empty_message'] = 'メール本文が空です。';
+$PHPMAILER_LANG['encoding'] = '不明なエンコーディング: ';
+$PHPMAILER_LANG['execute'] = '実行できませんでした: ';
+$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: ';
+$PHPMAILER_LANG['file_access'] = 'ファイルにアクセスできません: ';
+$PHPMAILER_LANG['file_open'] = 'ファイルエラー: ファイルを開けません: ';
+$PHPMAILER_LANG['from_failed'] = 'Fromアドレスを登録する際にエラーが発生しました: ';
+$PHPMAILER_LANG['instantiate'] = 'メール関数が正常に動作しませんでした。';
+$PHPMAILER_LANG['invalid_address'] = '不正なメールアドレス: ';
+$PHPMAILER_LANG['invalid_header'] = '不正なヘッダー名またはその内容';
+$PHPMAILER_LANG['invalid_hostentry'] = '不正なホストエントリー: ';
+$PHPMAILER_LANG['invalid_host'] = '不正なホスト: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' メーラーがサポートされていません。';
+$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTPエラー: 次の受信者アドレスに 間違いがあります: ';
+$PHPMAILER_LANG['signing'] = '署名エラー: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTPコード: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'SMTP追加情報: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP接続に失敗しました。';
+$PHPMAILER_LANG['smtp_detail'] = '詳細: ';
+$PHPMAILER_LANG['smtp_error'] = 'SMTPサーバーエラー: ';
+$PHPMAILER_LANG['variable_set'] = '変数が存在しません: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php
new file mode 100644
index 000000000..51fe403b4
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP შეცდომა: ავტორიზაცია შეუძლებელია.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP შეცდომა: SMTP სერვერთან დაკავშირება შეუძლებელია.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP შეცდომა: მონაცემები არ იქნა მიღებული.';
+$PHPMAILER_LANG['encoding'] = 'კოდირების უცნობი ტიპი: ';
+$PHPMAILER_LANG['execute'] = 'შეუძლებელია შემდეგი ბრძანების შესრულება: ';
+$PHPMAILER_LANG['file_access'] = 'შეუძლებელია წვდომა ფაილთან: ';
+$PHPMAILER_LANG['file_open'] = 'ფაილური სისტემის შეცდომა: არ იხსნება ფაილი: ';
+$PHPMAILER_LANG['from_failed'] = 'გამგზავნის არასწორი მისამართი: ';
+$PHPMAILER_LANG['instantiate'] = 'mail ფუნქციის გაშვება ვერ ხერხდება.';
+$PHPMAILER_LANG['provide_address'] = 'გთხოვთ მიუთითოთ ერთი ადრესატის e-mail მისამართი მაინც.';
+$PHPMAILER_LANG['mailer_not_supported'] = ' - საფოსტო სერვერის მხარდაჭერა არ არის.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP შეცდომა: შემდეგ მისამართებზე გაგზავნა ვერ მოხერხდა: ';
+$PHPMAILER_LANG['empty_message'] = 'შეტყობინება ცარიელია';
+$PHPMAILER_LANG['invalid_address'] = 'არ გაიგზავნა, e-mail მისამართის არასწორი ფორმატი: ';
+$PHPMAILER_LANG['signing'] = 'ხელმოწერის შეცდომა: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'შეცდომა SMTP სერვერთან დაკავშირებისას';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP სერვერის შეცდომა: ';
+$PHPMAILER_LANG['variable_set'] = 'შეუძლებელია შემდეგი ცვლადის შექმნა ან შეცვლა: ';
+$PHPMAILER_LANG['extension_missing'] = 'ბიბლიოთეკა არ არსებობს: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php
new file mode 100644
index 000000000..8c97dd947
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP 오류: 인증할 수 없습니다.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP 오류: SMTP 호스트에 접속할 수 없습니다.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 오류: 데이터가 받아들여지지 않았습니다.';
+$PHPMAILER_LANG['empty_message'] = '메세지 내용이 없습니다';
+$PHPMAILER_LANG['encoding'] = '알 수 없는 인코딩: ';
+$PHPMAILER_LANG['execute'] = '실행 불가: ';
+$PHPMAILER_LANG['file_access'] = '파일 접근 불가: ';
+$PHPMAILER_LANG['file_open'] = '파일 오류: 파일을 열 수 없습니다: ';
+$PHPMAILER_LANG['from_failed'] = '다음 From 주소에서 오류가 발생했습니다: ';
+$PHPMAILER_LANG['instantiate'] = 'mail 함수를 인스턴스화할 수 없습니다';
+$PHPMAILER_LANG['invalid_address'] = '잘못된 주소: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' 메일러는 지원되지 않습니다.';
+$PHPMAILER_LANG['provide_address'] = '적어도 한 개 이상의 수신자 메일 주소를 제공해야 합니다.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP 오류: 다음 수신자에서 오류가 발생했습니다: ';
+$PHPMAILER_LANG['signing'] = '서명 오류: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 연결을 실패하였습니다.';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP 서버 오류: ';
+$PHPMAILER_LANG['variable_set'] = '변수 설정 및 초기화 불가: ';
+$PHPMAILER_LANG['extension_missing'] = '확장자 없음: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php
new file mode 100644
index 000000000..cf3bda69f
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'هەڵەی SMTP : نەتوانرا کۆدەکە پشتڕاست بکرێتەوە ';
+$PHPMAILER_LANG['connect_host'] = 'هەڵەی SMTP: نەتوانرا پەیوەندی بە سێرڤەرەوە بکات SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'هەڵەی SMTP: ئەو زانیاریانە قبوڵ نەکرا.';
+$PHPMAILER_LANG['empty_message'] = 'پەیامەکە بەتاڵە';
+$PHPMAILER_LANG['encoding'] = 'کۆدکردنی نەزانراو : ';
+$PHPMAILER_LANG['execute'] = 'ناتوانرێت جێبەجێ بکرێت: ';
+$PHPMAILER_LANG['file_access'] = 'ناتوانرێت دەستت بگات بە فایلەکە: ';
+$PHPMAILER_LANG['file_open'] = 'هەڵەی پەڕگە(فایل): ناتوانرێت بکرێتەوە: ';
+$PHPMAILER_LANG['from_failed'] = 'هەڵە لە ئاستی ناونیشانی نێرەر: ';
+$PHPMAILER_LANG['instantiate'] = 'ناتوانرێت خزمەتگوزاری پۆستە پێشکەش بکرێت.';
+$PHPMAILER_LANG['invalid_address'] = 'نەتوانرا بنێردرێت ، چونکە ناونیشانی ئیمەیڵەکە نادروستە: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' مەیلەر پشتگیری ناکات';
+$PHPMAILER_LANG['provide_address'] = 'دەبێت ناونیشانی ئیمەیڵی لانیکەم یەک وەرگر دابین بکرێت.';
+$PHPMAILER_LANG['recipients_failed'] = ' هەڵەی SMTP: ئەم هەڵانەی خوارەوەشکستی هێنا لە ناردن بۆ هەردووکیان: ';
+$PHPMAILER_LANG['signing'] = 'هەڵەی واژۆ: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect()پەیوەندی شکستی هێنا .';
+$PHPMAILER_LANG['smtp_error'] = 'هەڵەی ئاستی سێرڤەری SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'ناتوانرێت بیگۆڕیت یان دوبارە بینێریتەوە: ';
+$PHPMAILER_LANG['extension_missing'] = 'درێژکراوە نەماوە: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php
new file mode 100644
index 000000000..4f115b1c5
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP klaida: autentifikacija nepavyko.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP klaida: nepavyksta prisijungti prie SMTP stoties.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP klaida: duomenys nepriimti.';
+$PHPMAILER_LANG['empty_message'] = 'Laiško turinys tuščias';
+$PHPMAILER_LANG['encoding'] = 'Neatpažinta koduotė: ';
+$PHPMAILER_LANG['execute'] = 'Nepavyko įvykdyti komandos: ';
+$PHPMAILER_LANG['file_access'] = 'Byla nepasiekiama: ';
+$PHPMAILER_LANG['file_open'] = 'Bylos klaida: Nepavyksta atidaryti: ';
+$PHPMAILER_LANG['from_failed'] = 'Neteisingas siuntėjo adresas: ';
+$PHPMAILER_LANG['instantiate'] = 'Nepavyko paleisti mail funkcijos.';
+$PHPMAILER_LANG['invalid_address'] = 'Neteisingas adresas: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' pašto stotis nepalaikoma.';
+$PHPMAILER_LANG['provide_address'] = 'Nurodykite bent vieną gavėjo adresą.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP klaida: nepavyko išsiųsti šiems gavėjams: ';
+$PHPMAILER_LANG['signing'] = 'Prisijungimo klaida: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP susijungimo klaida';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP stoties klaida: ';
+$PHPMAILER_LANG['variable_set'] = 'Nepavyko priskirti reikšmės kintamajam: ';
+//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php
new file mode 100644
index 000000000..679b18cf9
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP kļūda: Autorizācija neizdevās.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP Kļūda: Nevar izveidot savienojumu ar SMTP serveri.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Kļūda: Nepieņem informāciju.';
+$PHPMAILER_LANG['empty_message'] = 'Ziņojuma teksts ir tukšs';
+$PHPMAILER_LANG['encoding'] = 'Neatpazīts kodējums: ';
+$PHPMAILER_LANG['execute'] = 'Neizdevās izpildīt komandu: ';
+$PHPMAILER_LANG['file_access'] = 'Fails nav pieejams: ';
+$PHPMAILER_LANG['file_open'] = 'Faila kļūda: Nevar atvērt failu: ';
+$PHPMAILER_LANG['from_failed'] = 'Nepareiza sūtītāja adrese: ';
+$PHPMAILER_LANG['instantiate'] = 'Nevar palaist sūtīšanas funkciju.';
+$PHPMAILER_LANG['invalid_address'] = 'Nepareiza adrese: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' sūtītājs netiek atbalstīts.';
+$PHPMAILER_LANG['provide_address'] = 'Lūdzu, norādiet vismaz vienu adresātu.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP kļūda: neizdevās nosūtīt šādiem saņēmējiem: ';
+$PHPMAILER_LANG['signing'] = 'Autorizācijas kļūda: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP savienojuma kļūda';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP servera kļūda: ';
+$PHPMAILER_LANG['variable_set'] = 'Nevar piešķirt mainīgā vērtību: ';
+//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php
new file mode 100644
index 000000000..8a94f6a04
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Hadisoana SMTP: Tsy nahomby ny fanamarinana.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Tsy afaka mampifandray amin\'ny mpampiantrano SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP diso: tsy voarakitra ny angona.';
+$PHPMAILER_LANG['empty_message'] = 'Tsy misy ny votoaty mailaka.';
+$PHPMAILER_LANG['encoding'] = 'Tsy fantatra encoding: ';
+$PHPMAILER_LANG['execute'] = 'Tsy afaka manatanteraka ity baiko manaraka ity: ';
+$PHPMAILER_LANG['file_access'] = 'Tsy nahomby ny fidirana amin\'ity rakitra ity: ';
+$PHPMAILER_LANG['file_open'] = 'Hadisoana diso: Tsy afaka nanokatra ity file manaraka ity: ';
+$PHPMAILER_LANG['from_failed'] = 'Ny adiresy iraka manaraka dia diso: ';
+$PHPMAILER_LANG['instantiate'] = 'Tsy afaka nanomboka ny hetsika mail.';
+$PHPMAILER_LANG['invalid_address'] = 'Tsy mety ny adiresy: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tsy manohana.';
+$PHPMAILER_LANG['provide_address'] = 'Alefaso azafady iray adiresy iray farafahakeliny.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Tsy mety ireo mpanaraka ireto: ';
+$PHPMAILER_LANG['signing'] = 'Error nandritra ny sonia:';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Tsy nahomby ny fifandraisana tamin\'ny server SMTP.';
+$PHPMAILER_LANG['smtp_error'] = 'Fahadisoana tamin\'ny server SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'Tsy azo atao ny mametraka na mamerina ny variable: ';
+$PHPMAILER_LANG['extension_missing'] = 'Tsy hita ny ampahany: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php
new file mode 100644
index 000000000..04d262c72
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Ralat SMTP: Tidak dapat pengesahan.';
+$PHPMAILER_LANG['connect_host'] = 'Ralat SMTP: Tidak dapat menghubungi hos pelayan SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Ralat SMTP: Data tidak diterima oleh pelayan.';
+$PHPMAILER_LANG['empty_message'] = 'Tiada isi untuk mesej';
+$PHPMAILER_LANG['encoding'] = 'Pengekodan tidak diketahui: ';
+$PHPMAILER_LANG['execute'] = 'Tidak dapat melaksanakan: ';
+$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses fail: ';
+$PHPMAILER_LANG['file_open'] = 'Ralat Fail: Tidak dapat membuka fail: ';
+$PHPMAILER_LANG['from_failed'] = 'Berikut merupakan ralat dari alamat e-mel: ';
+$PHPMAILER_LANG['instantiate'] = 'Tidak dapat memberi contoh fungsi e-mel.';
+$PHPMAILER_LANG['invalid_address'] = 'Alamat emel tidak sah: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' jenis penghantar emel tidak disokong.';
+$PHPMAILER_LANG['provide_address'] = 'Anda perlu menyediakan sekurang-kurangnya satu alamat e-mel penerima.';
+$PHPMAILER_LANG['recipients_failed'] = 'Ralat SMTP: Penerima e-mel berikut telah gagal: ';
+$PHPMAILER_LANG['signing'] = 'Ralat pada tanda tangan: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() telah gagal.';
+$PHPMAILER_LANG['smtp_error'] = 'Ralat pada pelayan SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'Tidak boleh menetapkan atau menetapkan semula pembolehubah: ';
+$PHPMAILER_LANG['extension_missing'] = 'Sambungan hilang: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php
new file mode 100644
index 000000000..621d2e74f
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php
@@ -0,0 +1,37 @@
+
+ */
+
+ $PHPMAILER_LANG['authenticate'] = 'SMTP-feil: Kunne ikke autentisere.';
+ $PHPMAILER_LANG['buggy_php'] = 'Din versjon av PHP er påvirket av en feil som kan føre til ødelagte meldinger. For å løse problemet kan du bytte til sending via SMTP, deaktivere mail.add_x_header-alternativet i php.ini, bytte til MacOS eller Linux, eller oppgradere PHP til versjon 7.0.17+ eller 7.1.3+.';
+ $PHPMAILER_LANG['connect_host'] = 'SMTP-feil: Kunne ikke koble til SMTP-vert.';
+ $PHPMAILER_LANG['data_not_accepted'] = 'SMTP-feil: data ikke akseptert.';
+ $PHPMAILER_LANG['empty_message'] = 'Meldingsinnholdet er tomt';
+ $PHPMAILER_LANG['encoding'] = 'Ukjent koding: ';
+ $PHPMAILER_LANG['execute'] = 'Kunne ikke utføres: ';
+ $PHPMAILER_LANG['extension_missing'] = 'Utvidelse mangler: ';
+ $PHPMAILER_LANG['file_access'] = 'Kunne ikke få tilgang til filen: ';
+ $PHPMAILER_LANG['file_open'] = 'Feil i fil: Kunne ikke åpne filen: ';
+ $PHPMAILER_LANG['from_failed'] = 'Følgende avsenderadresse mislyktes: ';
+ $PHPMAILER_LANG['instantiate'] = 'Kunne ikke starte e-postfunksjonen.';
+ $PHPMAILER_LANG['invalid_address'] = 'Ugyldig adresse: ';
+ $PHPMAILER_LANG['invalid_header'] = 'Ugyldig headernavn eller verdi';
+ $PHPMAILER_LANG['invalid_hostentry'] = 'Ugyldig vertsinngang: ';
+ $PHPMAILER_LANG['invalid_host'] = 'Ugyldig vert: ';
+ $PHPMAILER_LANG['mailer_not_supported'] = ' sender er ikke støttet.';
+ $PHPMAILER_LANG['provide_address'] = 'Du må oppgi minst én mottaker-e-postadresse.';
+ $PHPMAILER_LANG['recipients_failed'] = 'SMTP-feil: Følgende mottakeradresser feilet: ';
+ $PHPMAILER_LANG['signing'] = 'Signeringsfeil: ';
+ $PHPMAILER_LANG['smtp_code'] = 'SMTP-kode: ';
+ $PHPMAILER_LANG['smtp_code_ex'] = 'Ytterligere SMTP-info: ';
+ $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP connect() mislyktes.';
+ $PHPMAILER_LANG['smtp_detail'] = 'Detaljer: ';
+ $PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfeil: ';
+ $PHPMAILER_LANG['variable_set'] = 'Kan ikke angi eller tilbakestille variabel: ';
+ $PHPMAILER_LANG['no_smtputf8'] = 'Serveren støtter ikke SMTPUTF8, som er nødvendig for å sende til Unicode-adresser.';
+ $PHPMAILER_LANG['imap_recommended'] = 'Det anbefales ikke å bruke forenklet adresseanalyse. Installer PHP IMAP-utvidelsen for full RFC822-analyse.';
+ $PHPMAILER_LANG['deprecated_argument'] = 'Avviklet argument: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php
new file mode 100644
index 000000000..cbb3622a0
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php
@@ -0,0 +1,38 @@
+
+ * @author Robin van der Vliet
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP-fout: authenticatie mislukt.';
+$PHPMAILER_LANG['buggy_php'] = 'PHP-versie gedetecteerd die onderhevig is aan een bug die kan resulteren in gecorrumpeerde berichten. Om dit te voorkomen, gebruik SMTP voor het verzenden van berichten, zet de optie mail.add_x_header in uw php.ini uit, gebruik MacOS of Linux, of pas de gebruikte PHP-versie aan naar versie 7.0.17+ or 7.1.3+.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP-fout: kon niet verbinden met SMTP-host.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-fout: data niet geaccepteerd.';
+$PHPMAILER_LANG['empty_message'] = 'Berichttekst is leeg';
+$PHPMAILER_LANG['encoding'] = 'Onbekende codering: ';
+$PHPMAILER_LANG['execute'] = 'Kon niet uitvoeren: ';
+$PHPMAILER_LANG['extension_missing'] = 'Extensie afwezig: ';
+$PHPMAILER_LANG['file_access'] = 'Kreeg geen toegang tot bestand: ';
+$PHPMAILER_LANG['file_open'] = 'Bestandsfout: kon bestand niet openen: ';
+$PHPMAILER_LANG['from_failed'] = 'Het volgende afzenderadres is mislukt: ';
+$PHPMAILER_LANG['instantiate'] = 'Kon mailfunctie niet initialiseren.';
+$PHPMAILER_LANG['invalid_address'] = 'Ongeldig adres: ';
+$PHPMAILER_LANG['invalid_header'] = 'Ongeldige headernaam of -waarde';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Ongeldige hostentry: ';
+$PHPMAILER_LANG['invalid_host'] = 'Ongeldige host: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer wordt niet ondersteund.';
+$PHPMAILER_LANG['provide_address'] = 'Er moet minstens één ontvanger worden opgegeven.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP-fout: de volgende ontvangers zijn mislukt: ';
+$PHPMAILER_LANG['signing'] = 'Signeerfout: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP-code: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Aanvullende SMTP-informatie: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP-verbinding mislukt.';
+$PHPMAILER_LANG['smtp_detail'] = 'Detail: ';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfout: ';
+$PHPMAILER_LANG['variable_set'] = 'Kan de volgende variabele niet instellen of herstellen: ';
+$PHPMAILER_LANG['no_smtputf8'] = 'De server ondersteunt geen SMTPUTF8 dat nodig is om naar Unicode-adressen te sturen.';
+$PHPMAILER_LANG['imap_recommended'] = 'Het gebruik van de vereenvoudigde adresparser is niet aanbevolen. Installeer de IMAP-extensie voor PHP voor volledige RFC822-ondersteuning.';
+$PHPMAILER_LANG['deprecated_argument'] = 'Verouderd argument: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php
new file mode 100644
index 000000000..35b3320a1
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php
@@ -0,0 +1,33 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Erro SMTP: Falha na autenticação.';
+$PHPMAILER_LANG['buggy_php'] = 'A sua versão do PHP tem um bug que pode causar mensagens corrompidas. Para resolver, utilize o envio por SMTP, desative a opção mail.add_x_header no ficheiro php.ini, mude para MacOS ou Linux, ou atualize o PHP para a versão 7.0.17+ ou 7.1.3+.';
+$PHPMAILER_LANG['connect_host'] = 'Erro SMTP: Não foi possível ligar ao servidor SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Erro SMTP: Dados não aceites.';
+$PHPMAILER_LANG['empty_message'] = 'A mensagem de e-mail está vazia.';
+$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: ';
+$PHPMAILER_LANG['execute'] = 'Não foi possível executar: ';
+$PHPMAILER_LANG['extension_missing'] = 'Extensão em falta: ';
+$PHPMAILER_LANG['file_access'] = 'Não foi possível aceder ao ficheiro: ';
+$PHPMAILER_LANG['file_open'] = 'Erro ao abrir o ficheiro: ';
+$PHPMAILER_LANG['from_failed'] = 'O envio falhou para o seguinte endereço do remetente: ';
+$PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.';
+$PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: ';
+$PHPMAILER_LANG['invalid_header'] = 'Nome ou valor do cabeçalho inválido.';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Entrada de host inválida: ';
+$PHPMAILER_LANG['invalid_host'] = 'Host inválido: ';
+$PHPMAILER_LANG['mailer_not_supported'] = 'O cliente de e-mail não é suportado.';
+$PHPMAILER_LANG['provide_address'] = 'Deve fornecer pelo menos um endereço de destinatário.';
+$PHPMAILER_LANG['recipients_failed'] = 'Erro SMTP: Falha no envio para os seguintes destinatários: ';
+$PHPMAILER_LANG['signing'] = 'Erro ao assinar: ';
+$PHPMAILER_LANG['smtp_code'] = 'Código SMTP: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Informações adicionais SMTP: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Falha na função SMTP connect().';
+$PHPMAILER_LANG['smtp_detail'] = 'Detalhes: ';
+$PHPMAILER_LANG['smtp_error'] = 'Erro do servidor SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php
new file mode 100644
index 000000000..5239865a6
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php
@@ -0,0 +1,38 @@
+
+ * @author Lucas Guimarães
+ * @author Phelipe Alves
+ * @author Fabio Beneditto
+ * @author Geidson Benício Coelho
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Erro de SMTP: Não foi possível autenticar.';
+$PHPMAILER_LANG['buggy_php'] = 'Sua versão do PHP é afetada por um bug que por resultar em messagens corrompidas. Para corrigir, mude para enviar usando SMTP, desative a opção mail.add_x_header em seu php.ini, mude para MacOS ou Linux, ou atualize seu PHP para versão 7.0.17+ ou 7.1.3+ ';
+$PHPMAILER_LANG['connect_host'] = 'Erro de SMTP: Não foi possível conectar ao servidor SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Erro de SMTP: Dados rejeitados.';
+$PHPMAILER_LANG['empty_message'] = 'Mensagem vazia';
+$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: ';
+$PHPMAILER_LANG['execute'] = 'Não foi possível executar: ';
+$PHPMAILER_LANG['extension_missing'] = 'Extensão não existe: ';
+$PHPMAILER_LANG['file_access'] = 'Não foi possível acessar o arquivo: ';
+$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: Não foi possível abrir o arquivo: ';
+$PHPMAILER_LANG['from_failed'] = 'Os seguintes remetentes falharam: ';
+$PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.';
+$PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: ';
+$PHPMAILER_LANG['invalid_header'] = 'Nome ou valor de cabeçalho inválido';
+$PHPMAILER_LANG['invalid_hostentry'] = 'hostentry inválido: ';
+$PHPMAILER_LANG['invalid_host'] = 'host inválido: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.';
+$PHPMAILER_LANG['provide_address'] = 'Você deve informar pelo menos um destinatário.';
+$PHPMAILER_LANG['recipients_failed'] = 'Erro de SMTP: Os seguintes destinatários falharam: ';
+$PHPMAILER_LANG['signing'] = 'Erro de Assinatura: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.';
+$PHPMAILER_LANG['smtp_code'] = 'Código do servidor SMTP: ';
+$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Informações adicionais do servidor SMTP: ';
+$PHPMAILER_LANG['smtp_detail'] = 'Detalhes do servidor SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php
new file mode 100644
index 000000000..45bef9155
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php
@@ -0,0 +1,33 @@
+
+ * @author Foster Snowhill
+ * @author ProjectSoft
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: не удалось пройти аутентификацию.';
+$PHPMAILER_LANG['buggy_php'] = 'В вашей версии PHP есть ошибка, которая может привести к повреждению сообщений. Чтобы исправить, переключитесь на отправку по SMTP, отключите опцию mail.add_x_header в ваш php.ini, переключитесь на MacOS или Linux или обновите PHP до версии 7.0.17+ или 7.1.3+.';
+$PHPMAILER_LANG['connect_host'] = 'Ошибка SMTP: не удается подключиться к SMTP-серверу.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Ошибка SMTP: данные не приняты.';
+$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение';
+$PHPMAILER_LANG['encoding'] = 'Неизвестная кодировка: ';
+$PHPMAILER_LANG['execute'] = 'Невозможно выполнить команду: ';
+$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: ';
+$PHPMAILER_LANG['file_access'] = 'Нет доступа к файлу: ';
+$PHPMAILER_LANG['file_open'] = 'Файловая ошибка: не удаётся открыть файл: ';
+$PHPMAILER_LANG['from_failed'] = 'Неверный адрес отправителя: ';
+$PHPMAILER_LANG['instantiate'] = 'Невозможно запустить функцию mail().';
+$PHPMAILER_LANG['invalid_address'] = 'Не отправлено из-за неправильного формата email-адреса: ';
+$PHPMAILER_LANG['invalid_header'] = 'Неверное имя или значение заголовка';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Неверная запись хоста: ';
+$PHPMAILER_LANG['invalid_host'] = 'Неверный хост: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.';
+$PHPMAILER_LANG['provide_address'] = 'Вы должны указать хотя бы один адрес электронной почты получателя.';
+$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: Ошибка следующих получателей: ';
+$PHPMAILER_LANG['signing'] = 'Ошибка подписи: ';
+$PHPMAILER_LANG['smtp_code'] = 'Код SMTP: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Дополнительная информация SMTP: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером.';
+$PHPMAILER_LANG['smtp_detail'] = 'Детали: ';
+$PHPMAILER_LANG['smtp_error'] = 'Ошибка SMTP-сервера: ';
+$PHPMAILER_LANG['variable_set'] = 'Невозможно установить или сбросить переменную: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php
new file mode 100644
index 000000000..dce502aa0
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php
@@ -0,0 +1,34 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP දෝෂය: සත්යාපනය අසාර්ථක විය.';
+$PHPMAILER_LANG['buggy_php'] = 'ඔබගේ PHP version එකෙහි පවතින දෝෂයක් නිසා email පණිවිඩ දෝෂ සහගත වීමේ හැකියාවක් ඇත. මෙය විසදීම සදහා SMTP භාවිතා කිරීම, mail.add_x_header INI setting එක අක්රීය කිරීම, MacOS හෝ Linux වලට මාරු වීම, හෝ ඔබගේ PHP version එක 7.0.17+ හෝ 7.1.3+ වලට අලුත් කිරීම කරගන්න.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP දෝෂය: සම්බන්ධ වීමට නොහැකි විය.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP දෝෂය: දත්ත පිළිගනු නොලැබේ.';
+$PHPMAILER_LANG['empty_message'] = 'පණිවිඩ අන්තර්ගතය හිස්';
+$PHPMAILER_LANG['encoding'] = 'නොදන්නා කේතනය: ';
+$PHPMAILER_LANG['execute'] = 'ක්රියාත්මක කළ නොහැකි විය: ';
+$PHPMAILER_LANG['extension_missing'] = 'Extension එක නොමැත: ';
+$PHPMAILER_LANG['file_access'] = 'File එකට ප්රවේශ විය නොහැකි විය: ';
+$PHPMAILER_LANG['file_open'] = 'File දෝෂය: File එක විවෘත කළ නොහැක: ';
+$PHPMAILER_LANG['from_failed'] = 'පහත From ලිපිනයන් අසාර්ථක විය: ';
+$PHPMAILER_LANG['instantiate'] = 'mail function එක ක්රියාත්මක කළ නොහැක.';
+$PHPMAILER_LANG['invalid_address'] = 'වලංගු නොවන ලිපිනය: ';
+$PHPMAILER_LANG['invalid_header'] = 'වලංගු නොවන header නාමයක් හෝ අගයක්';
+$PHPMAILER_LANG['invalid_hostentry'] = 'වලංගු නොවන hostentry එකක්: ';
+$PHPMAILER_LANG['invalid_host'] = 'වලංගු නොවන host එකක්: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer සහාය නොදක්වයි.';
+$PHPMAILER_LANG['provide_address'] = 'ඔබ අවම වශයෙන් එක් ලබන්නෙකුගේ ඊමේල් ලිපිනයක් සැපයිය යුතුය.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP දෝෂය: පහත ලබන්නන් අසමත් විය: ';
+$PHPMAILER_LANG['signing'] = 'Sign කිරීමේ දෝෂය: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP කේතය: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'අමතර SMTP තොරතුරු: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP සම්බන්ධය අසාර්ථක විය.';
+$PHPMAILER_LANG['smtp_detail'] = 'තොරතුරු: ';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP දෝෂය: ';
+$PHPMAILER_LANG['variable_set'] = 'Variable එක සැකසීමට හෝ නැවත සැකසීමට නොහැක: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php
new file mode 100644
index 000000000..028f5bc49
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php
@@ -0,0 +1,30 @@
+
+ * @author Peter Orlický
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Chyba autentifikácie.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Nebolo možné nadviazať spojenie so SMTP serverom.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dáta neboli prijaté';
+$PHPMAILER_LANG['empty_message'] = 'Prázdne telo správy.';
+$PHPMAILER_LANG['encoding'] = 'Neznáme kódovanie: ';
+$PHPMAILER_LANG['execute'] = 'Nedá sa vykonať: ';
+$PHPMAILER_LANG['file_access'] = 'Súbor nebol nájdený: ';
+$PHPMAILER_LANG['file_open'] = 'File Error: Súbor sa otvoriť pre čítanie: ';
+$PHPMAILER_LANG['from_failed'] = 'Následujúca adresa From je nesprávna: ';
+$PHPMAILER_LANG['instantiate'] = 'Nedá sa vytvoriť inštancia emailovej funkcie.';
+$PHPMAILER_LANG['invalid_address'] = 'Neodoslané, emailová adresa je nesprávna: ';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Záznam hostiteľa je nesprávny: ';
+$PHPMAILER_LANG['invalid_host'] = 'Hostiteľ je nesprávny: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' emailový klient nieje podporovaný.';
+$PHPMAILER_LANG['provide_address'] = 'Musíte zadať aspoň jednu emailovú adresu príjemcu.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Adresy príjemcov niesu správne ';
+$PHPMAILER_LANG['signing'] = 'Chyba prihlasovania: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() zlyhalo.';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP chyba serveru: ';
+$PHPMAILER_LANG['variable_set'] = 'Nemožno nastaviť alebo resetovať premennú: ';
+$PHPMAILER_LANG['extension_missing'] = 'Chýba rozšírenie: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php
new file mode 100644
index 000000000..3e00c2596
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php
@@ -0,0 +1,36 @@
+
+ * @author Filip Š
+ * @author Blaž Oražem
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP napaka: Avtentikacija ni uspela.';
+$PHPMAILER_LANG['buggy_php'] = 'Na vašo PHP različico vpliva napaka, ki lahko povzroči poškodovana sporočila. Če želite težavo odpraviti, preklopite na pošiljanje prek SMTP, onemogočite možnost mail.add_x_header v vaši php.ini datoteki, preklopite na MacOS ali Linux, ali nadgradite vašo PHP zaličico na 7.0.17+ ali 7.1.3+.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP napaka: Vzpostavljanje povezave s SMTP gostiteljem ni uspelo.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP napaka: Strežnik zavrača podatke.';
+$PHPMAILER_LANG['empty_message'] = 'E-poštno sporočilo nima vsebine.';
+$PHPMAILER_LANG['encoding'] = 'Nepoznan tip kodiranja: ';
+$PHPMAILER_LANG['execute'] = 'Operacija ni uspela: ';
+$PHPMAILER_LANG['extension_missing'] = 'Manjkajoča razširitev: ';
+$PHPMAILER_LANG['file_access'] = 'Nimam dostopa do datoteke: ';
+$PHPMAILER_LANG['file_open'] = 'Ne morem odpreti datoteke: ';
+$PHPMAILER_LANG['from_failed'] = 'Neveljaven e-naslov pošiljatelja: ';
+$PHPMAILER_LANG['instantiate'] = 'Ne morem inicializirati mail funkcije.';
+$PHPMAILER_LANG['invalid_address'] = 'E-poštno sporočilo ni bilo poslano. E-naslov je neveljaven: ';
+$PHPMAILER_LANG['invalid_header'] = 'Neveljavno ime ali vrednost glave';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Neveljaven vnos gostitelja: ';
+$PHPMAILER_LANG['invalid_host'] = 'Neveljaven gostitelj: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer ni podprt.';
+$PHPMAILER_LANG['provide_address'] = 'Prosimo, vnesite vsaj enega naslovnika.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP napaka: Sledeči naslovniki so neveljavni: ';
+$PHPMAILER_LANG['signing'] = 'Napaka pri podpisovanju: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP koda: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Dodatne informacije o SMTP: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Ne morem vzpostaviti povezave s SMTP strežnikom.';
+$PHPMAILER_LANG['smtp_detail'] = 'Podrobnosti: ';
+$PHPMAILER_LANG['smtp_error'] = 'Napaka SMTP strežnika: ';
+$PHPMAILER_LANG['variable_set'] = 'Ne morem nastaviti oz. ponastaviti spremenljivke: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php
new file mode 100644
index 000000000..0b5280f75
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php
@@ -0,0 +1,28 @@
+
+ * @author Miloš Milanović
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: аутентификација није успела.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: повезивање са SMTP сервером није успело.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: подаци нису прихваћени.';
+$PHPMAILER_LANG['empty_message'] = 'Садржај поруке је празан.';
+$PHPMAILER_LANG['encoding'] = 'Непознато кодирање: ';
+$PHPMAILER_LANG['execute'] = 'Није могуће извршити наредбу: ';
+$PHPMAILER_LANG['file_access'] = 'Није могуће приступити датотеци: ';
+$PHPMAILER_LANG['file_open'] = 'Није могуће отворити датотеку: ';
+$PHPMAILER_LANG['from_failed'] = 'SMTP грешка: слање са следећих адреса није успело: ';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: слање на следеће адресе није успело: ';
+$PHPMAILER_LANG['instantiate'] = 'Није могуће покренути mail функцију.';
+$PHPMAILER_LANG['invalid_address'] = 'Порука није послата. Неисправна адреса: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' мејлер није подржан.';
+$PHPMAILER_LANG['provide_address'] = 'Дефинишите бар једну адресу примаоца.';
+$PHPMAILER_LANG['signing'] = 'Грешка приликом пријаве: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Повезивање са SMTP сервером није успело.';
+$PHPMAILER_LANG['smtp_error'] = 'Грешка SMTP сервера: ';
+$PHPMAILER_LANG['variable_set'] = 'Није могуће задати нити ресетовати променљиву: ';
+$PHPMAILER_LANG['extension_missing'] = 'Недостаје проширење: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php
new file mode 100644
index 000000000..62138329a
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php
@@ -0,0 +1,28 @@
+
+ * @author Miloš Milanović
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP greška: autentifikacija nije uspela.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP greška: povezivanje sa SMTP serverom nije uspelo.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP greška: podaci nisu prihvaćeni.';
+$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.';
+$PHPMAILER_LANG['encoding'] = 'Nepoznato kodiranje: ';
+$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: ';
+$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: ';
+$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: ';
+$PHPMAILER_LANG['from_failed'] = 'SMTP greška: slanje sa sledećih adresa nije uspelo: ';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP greška: slanje na sledeće adrese nije uspelo: ';
+$PHPMAILER_LANG['instantiate'] = 'Nije moguće pokrenuti mail funkciju.';
+$PHPMAILER_LANG['invalid_address'] = 'Poruka nije poslata. Neispravna adresa: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' majler nije podržan.';
+$PHPMAILER_LANG['provide_address'] = 'Definišite bar jednu adresu primaoca.';
+$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Povezivanje sa SMTP serverom nije uspelo.';
+$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP servera: ';
+$PHPMAILER_LANG['variable_set'] = 'Nije moguće zadati niti resetovati promenljivu: ';
+$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php
new file mode 100644
index 000000000..9872c1921
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP fel: Kunde inte autentisera.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP fel: Kunde inte ansluta till SMTP-server.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fel: Data accepterades inte.';
+//$PHPMAILER_LANG['empty_message'] = 'Message body empty';
+$PHPMAILER_LANG['encoding'] = 'Okänt encode-format: ';
+$PHPMAILER_LANG['execute'] = 'Kunde inte köra: ';
+$PHPMAILER_LANG['file_access'] = 'Ingen åtkomst till fil: ';
+$PHPMAILER_LANG['file_open'] = 'Fil fel: Kunde inte öppna fil: ';
+$PHPMAILER_LANG['from_failed'] = 'Följande avsändaradress är felaktig: ';
+$PHPMAILER_LANG['instantiate'] = 'Kunde inte initiera e-postfunktion.';
+$PHPMAILER_LANG['invalid_address'] = 'Felaktig adress: ';
+$PHPMAILER_LANG['provide_address'] = 'Du måste ange minst en mottagares e-postadress.';
+$PHPMAILER_LANG['mailer_not_supported'] = ' mailer stöds inte.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP fel: Följande mottagare är felaktig: ';
+$PHPMAILER_LANG['signing'] = 'Signeringsfel: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() misslyckades.';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP serverfel: ';
+$PHPMAILER_LANG['variable_set'] = 'Kunde inte definiera eller återställa variabel: ';
+$PHPMAILER_LANG['extension_missing'] = 'Tillägg ej tillgängligt: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php
new file mode 100644
index 000000000..d15bed1c8
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php
@@ -0,0 +1,28 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Hindi mapatotohanan.';
+$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Hindi makakonekta sa SMTP host.';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Ang datos ay hindi naitanggap.';
+$PHPMAILER_LANG['empty_message'] = 'Walang laman ang mensahe';
+$PHPMAILER_LANG['encoding'] = 'Hindi alam ang encoding: ';
+$PHPMAILER_LANG['execute'] = 'Hindi maisasagawa: ';
+$PHPMAILER_LANG['file_access'] = 'Hindi ma-access ang file: ';
+$PHPMAILER_LANG['file_open'] = 'File Error: Hindi mabuksan ang file: ';
+$PHPMAILER_LANG['from_failed'] = 'Ang sumusunod na address ay nabigo: ';
+$PHPMAILER_LANG['instantiate'] = 'Hindi maisimulan ang instance ng mail function.';
+$PHPMAILER_LANG['invalid_address'] = 'Hindi wasto ang address na naibigay: ';
+$PHPMAILER_LANG['mailer_not_supported'] = 'Ang mailer ay hindi suportado.';
+$PHPMAILER_LANG['provide_address'] = 'Kailangan mong magbigay ng kahit isang email address na tatanggap.';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Ang mga sumusunod na tatanggap ay nabigo: ';
+$PHPMAILER_LANG['signing'] = 'Hindi ma-sign: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Ang SMTP connect() ay nabigo.';
+$PHPMAILER_LANG['smtp_error'] = 'Ang server ng SMTP ay nabigo: ';
+$PHPMAILER_LANG['variable_set'] = 'Hindi matatakda o ma-reset ang mga variables: ';
+$PHPMAILER_LANG['extension_missing'] = 'Nawawala ang extension: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php
new file mode 100644
index 000000000..ab5550168
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php
@@ -0,0 +1,43 @@
+
+ * @fixed by Boris Yurchenko
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Помилка SMTP: помилка авторизації.';
+$PHPMAILER_LANG['connect_host'] = 'Помилка SMTP: не вдається під\'єднатися до SMTP-серверу.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Помилка SMTP: дані не прийнято.';
+$PHPMAILER_LANG['encoding'] = 'Невідоме кодування: ';
+$PHPMAILER_LANG['execute'] = 'Неможливо виконати команду: ';
+$PHPMAILER_LANG['file_access'] = 'Немає доступу до файлу: ';
+$PHPMAILER_LANG['file_open'] = 'Помилка файлової системи: не вдається відкрити файл: ';
+$PHPMAILER_LANG['from_failed'] = 'Невірна адреса відправника: ';
+$PHPMAILER_LANG['instantiate'] = 'Неможливо запустити функцію mail().';
+$PHPMAILER_LANG['provide_address'] = 'Будь ласка, введіть хоча б одну email-адресу отримувача.';
+$PHPMAILER_LANG['mailer_not_supported'] = ' - поштовий сервер не підтримується.';
+$PHPMAILER_LANG['recipients_failed'] = 'Помилка SMTP: не вдалося відправлення для таких отримувачів: ';
+$PHPMAILER_LANG['empty_message'] = 'Пусте повідомлення';
+$PHPMAILER_LANG['invalid_address'] = 'Не відправлено через неправильний формат email-адреси: ';
+$PHPMAILER_LANG['signing'] = 'Помилка підпису: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Помилка з\'єднання з SMTP-сервером';
+$PHPMAILER_LANG['smtp_error'] = 'Помилка SMTP-сервера: ';
+$PHPMAILER_LANG['variable_set'] = 'Неможливо встановити або скинути змінну: ';
+$PHPMAILER_LANG['extension_missing'] = 'Розширення відсутнє: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php
new file mode 100644
index 000000000..0b9de0f12
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php
@@ -0,0 +1,30 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP خرابی: تصدیق کرنے سے قاصر۔';
+$PHPMAILER_LANG['connect_host'] = 'SMTP خرابی: سرور سے منسلک ہونے سے قاصر۔';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP خرابی: ڈیٹا قبول نہیں کیا گیا۔';
+$PHPMAILER_LANG['empty_message'] = 'پیغام کی باڈی خالی ہے۔';
+$PHPMAILER_LANG['encoding'] = 'نامعلوم انکوڈنگ: ';
+$PHPMAILER_LANG['execute'] = 'عمل کرنے کے قابل نہیں ';
+$PHPMAILER_LANG['file_access'] = 'فائل تک رسائی سے قاصر:';
+$PHPMAILER_LANG['file_open'] = 'فائل کی خرابی: فائل کو کھولنے سے قاصر:';
+$PHPMAILER_LANG['from_failed'] = 'درج ذیل بھیجنے والے کا پتہ ناکام ہو گیا:';
+$PHPMAILER_LANG['instantiate'] = 'میل فنکشن کی مثال بنانے سے قاصر۔';
+$PHPMAILER_LANG['invalid_address'] = 'بھیجنے سے قاصر: غلط ای میل پتہ:';
+$PHPMAILER_LANG['mailer_not_supported'] = ' میلر تعاون یافتہ نہیں ہے۔';
+$PHPMAILER_LANG['provide_address'] = 'آپ کو کم از کم ایک منزل کا ای میل پتہ فراہم کرنا چاہیے۔';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP خرابی: درج ذیل پتہ پر نہیں بھیجا جاسکا: ';
+$PHPMAILER_LANG['signing'] = 'دستخط کی خرابی: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP ملنا ناکام ہوا';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP سرور کی خرابی: ';
+$PHPMAILER_LANG['variable_set'] = 'متغیر سیٹ نہیں کیا جا سکا: ';
+$PHPMAILER_LANG['extension_missing'] = 'ایکٹینشن موجود نہیں ہے۔ ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP سرور کوڈ: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'اضافی SMTP سرور کی معلومات:';
+$PHPMAILER_LANG['invalid_header'] = 'غلط ہیڈر کا نام یا قدر';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php
new file mode 100644
index 000000000..d65576e2d
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'Lỗi SMTP: Không thể xác thực.';
+$PHPMAILER_LANG['connect_host'] = 'Lỗi SMTP: Không thể kết nối máy chủ SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'Lỗi SMTP: Dữ liệu không được chấp nhận.';
+$PHPMAILER_LANG['empty_message'] = 'Không có nội dung';
+$PHPMAILER_LANG['encoding'] = 'Mã hóa không xác định: ';
+$PHPMAILER_LANG['execute'] = 'Không thực hiện được: ';
+$PHPMAILER_LANG['file_access'] = 'Không thể truy cập tệp tin ';
+$PHPMAILER_LANG['file_open'] = 'Lỗi Tập tin: Không thể mở tệp tin: ';
+$PHPMAILER_LANG['from_failed'] = 'Lỗi địa chỉ gửi đi: ';
+$PHPMAILER_LANG['instantiate'] = 'Không dùng được các hàm gửi thư.';
+$PHPMAILER_LANG['invalid_address'] = 'Đại chỉ emai không đúng: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' trình gửi thư không được hỗ trợ.';
+$PHPMAILER_LANG['provide_address'] = 'Bạn phải cung cấp ít nhất một địa chỉ người nhận.';
+$PHPMAILER_LANG['recipients_failed'] = 'Lỗi SMTP: lỗi địa chỉ người nhận: ';
+$PHPMAILER_LANG['signing'] = 'Lỗi đăng nhập: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Lỗi kết nối với SMTP';
+$PHPMAILER_LANG['smtp_error'] = 'Lỗi máy chủ smtp ';
+$PHPMAILER_LANG['variable_set'] = 'Không thể thiết lập hoặc thiết lập lại biến: ';
+//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php
new file mode 100644
index 000000000..35e4e7000
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php
@@ -0,0 +1,29 @@
+
+ * @author Peter Dave Hello <@PeterDaveHello/>
+ * @author Jason Chiang
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP 錯誤:登入失敗。';
+$PHPMAILER_LANG['connect_host'] = 'SMTP 錯誤:無法連線到 SMTP 主機。';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 錯誤:無法接受的資料。';
+$PHPMAILER_LANG['empty_message'] = '郵件內容為空';
+$PHPMAILER_LANG['encoding'] = '未知編碼: ';
+$PHPMAILER_LANG['execute'] = '無法執行:';
+$PHPMAILER_LANG['file_access'] = '無法存取檔案:';
+$PHPMAILER_LANG['file_open'] = '檔案錯誤:無法開啟檔案:';
+$PHPMAILER_LANG['from_failed'] = '發送地址錯誤:';
+$PHPMAILER_LANG['instantiate'] = '未知函數呼叫。';
+$PHPMAILER_LANG['invalid_address'] = '因為電子郵件地址無效,無法傳送: ';
+$PHPMAILER_LANG['mailer_not_supported'] = '不支援的發信客戶端。';
+$PHPMAILER_LANG['provide_address'] = '必須提供至少一個收件人地址。';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP 錯誤:以下收件人地址錯誤:';
+$PHPMAILER_LANG['signing'] = '電子簽章錯誤: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 連線失敗';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP 伺服器錯誤: ';
+$PHPMAILER_LANG['variable_set'] = '無法設定或重設變數: ';
+$PHPMAILER_LANG['extension_missing'] = '遺失模組 Extension: ';
diff --git a/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php
new file mode 100644
index 000000000..03d491165
--- /dev/null
+++ b/www/libs/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php
@@ -0,0 +1,36 @@
+
+ * @author young
+ * @author Teddysun
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP 错误:登录失败。';
+$PHPMAILER_LANG['buggy_php'] = '您的 PHP 版本存在漏洞,可能会导致消息损坏。为修复此问题,请切换到使用 SMTP 发送,在您的 php.ini 中禁用 mail.add_x_header 选项。切换到 MacOS 或 Linux,或将您的 PHP 升级到 7.0.17+ 或 7.1.3+ 版本。';
+$PHPMAILER_LANG['connect_host'] = 'SMTP 错误:无法连接到 SMTP 主机。';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误:数据不被接受。';
+$PHPMAILER_LANG['empty_message'] = '邮件正文为空。';
+$PHPMAILER_LANG['encoding'] = '未知编码:';
+$PHPMAILER_LANG['execute'] = '无法执行:';
+$PHPMAILER_LANG['extension_missing'] = '缺少扩展名:';
+$PHPMAILER_LANG['file_access'] = '无法访问文件:';
+$PHPMAILER_LANG['file_open'] = '文件错误:无法打开文件:';
+$PHPMAILER_LANG['from_failed'] = '发送地址错误:';
+$PHPMAILER_LANG['instantiate'] = '未知函数调用。';
+$PHPMAILER_LANG['invalid_address'] = '发送失败,电子邮箱地址是无效的:';
+$PHPMAILER_LANG['mailer_not_supported'] = '发信客户端不被支持。';
+$PHPMAILER_LANG['provide_address'] = '必须提供至少一个收件人地址。';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误:';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP服务器连接失败。';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP服务器出错:';
+$PHPMAILER_LANG['variable_set'] = '无法设置或重置变量:';
+$PHPMAILER_LANG['invalid_header'] = '无效的标题名称或值';
+$PHPMAILER_LANG['invalid_hostentry'] = '无效的hostentry: ';
+$PHPMAILER_LANG['invalid_host'] = '无效的主机:';
+$PHPMAILER_LANG['signing'] = '签名错误:';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP代码: ';
+$PHPMAILER_LANG['smtp_code_ex'] = '附加SMTP信息: ';
+$PHPMAILER_LANG['smtp_detail'] = '详情:';
diff --git a/www/libs/PHPMailer/DSNConfigurator.php b/www/libs/vendor/phpmailer/phpmailer/src/DSNConfigurator.php
similarity index 97%
rename from www/libs/PHPMailer/DSNConfigurator.php
rename to www/libs/vendor/phpmailer/phpmailer/src/DSNConfigurator.php
index ab707d2b4..7058c1f05 100644
--- a/www/libs/PHPMailer/DSNConfigurator.php
+++ b/www/libs/vendor/phpmailer/phpmailer/src/DSNConfigurator.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2023 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -80,9 +80,7 @@ private function parseDSN($dsn)
$config = $this->parseUrl($dsn);
if (false === $config || !isset($config['scheme']) || !isset($config['host'])) {
- throw new Exception(
- sprintf('Malformed DSN: "%s".', $dsn)
- );
+ throw new Exception('Malformed DSN');
}
if (isset($config['query'])) {
diff --git a/www/libs/PHPMailer/Exception.php b/www/libs/vendor/phpmailer/phpmailer/src/Exception.php
similarity index 91%
rename from www/libs/PHPMailer/Exception.php
rename to www/libs/vendor/phpmailer/phpmailer/src/Exception.php
index 52eaf9515..09c1a2cfe 100644
--- a/www/libs/PHPMailer/Exception.php
+++ b/www/libs/vendor/phpmailer/phpmailer/src/Exception.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/www/libs/PHPMailer/OAuth.php b/www/libs/vendor/phpmailer/phpmailer/src/OAuth.php
similarity index 95%
rename from www/libs/PHPMailer/OAuth.php
rename to www/libs/vendor/phpmailer/phpmailer/src/OAuth.php
index c1d5b7762..a7e958860 100644
--- a/www/libs/PHPMailer/OAuth.php
+++ b/www/libs/vendor/phpmailer/phpmailer/src/OAuth.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -29,7 +29,7 @@
* OAuth - OAuth2 authentication wrapper class.
* Uses the oauth2-client package from the League of Extraordinary Packages.
*
- * @see http://oauth2-client.thephpleague.com
+ * @see https://oauth2-client.thephpleague.com
*
* @author Marcus Bointon (Synchro/coolbru)
*/
diff --git a/www/libs/PHPMailer/OAuthTokenProvider.php b/www/libs/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php
similarity index 93%
rename from www/libs/PHPMailer/OAuthTokenProvider.php
rename to www/libs/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php
index 115550743..cbda1a129 100644
--- a/www/libs/PHPMailer/OAuthTokenProvider.php
+++ b/www/libs/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/www/libs/PHPMailer/PHPMailer.php b/www/libs/vendor/phpmailer/phpmailer/src/PHPMailer.php
similarity index 84%
rename from www/libs/PHPMailer/PHPMailer.php
rename to www/libs/vendor/phpmailer/phpmailer/src/PHPMailer.php
index 81a2d667a..4900cbc43 100644
--- a/www/libs/PHPMailer/PHPMailer.php
+++ b/www/libs/vendor/phpmailer/phpmailer/src/PHPMailer.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -59,6 +59,7 @@ class PHPMailer
const ICAL_METHOD_REFRESH = 'REFRESH';
const ICAL_METHOD_COUNTER = 'COUNTER';
const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER';
+ const RFC822_DATE_FORMAT = 'D, j M Y H:i:s O';
/**
* Email priority.
@@ -77,7 +78,7 @@ class PHPMailer
public $CharSet = self::CHARSET_ISO88591;
/**
- * The MIME Content-type of the message.
+ * The MIME Content-Type of the message.
*
* @var string
*/
@@ -152,15 +153,14 @@ class PHPMailer
* Only supported in simple alt or alt_inline message types
* To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator.
*
- * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
- * @see http://kigkonsult.se/iCalcreator/
+ * @see https://kigkonsult.se/iCalcreator/
*
* @var string
*/
public $Ical = '';
/**
- * Value-array of "method" in Contenttype header "text/calendar"
+ * Value-array of "method" in Content-Type header "text/calendar"
*
* @var string[]
*/
@@ -254,7 +254,7 @@ class PHPMailer
* You can set your own, but it must be in the format "",
* as defined in RFC5322 section 3.6.4 or it will be ignored.
*
- * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
+ * @see https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4
*
* @var string
*/
@@ -357,6 +357,13 @@ class PHPMailer
*/
public $AuthType = '';
+ /**
+ * SMTP SMTPXClient command attributes
+ *
+ * @var array
+ */
+ protected $SMTPXClient = [];
+
/**
* An implementation of the PHPMailer OAuthTokenProvider interface.
*
@@ -381,7 +388,7 @@ class PHPMailer
* 'DELAY' will notify you if there is an unusual delay in delivery, but the actual
* delivery's outcome (success or failure) is not yet decided.
*
- * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY
+ * @see https://www.rfc-editor.org/rfc/rfc3461.html#section-4.1 for more information about NOTIFY
*/
public $dsn = '';
@@ -461,7 +468,7 @@ class PHPMailer
* Only applicable when sending via SMTP.
*
* @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
- * @see http://www.postfix.org/VERP_README.html Postfix VERP info
+ * @see https://www.postfix.org/VERP_README.html Postfix VERP info
*
* @var bool
*/
@@ -544,10 +551,10 @@ class PHPMailer
* The function that handles the result of the send email action.
* It is called out by send() for each email sent.
*
- * Value can be any php callable: http://www.php.net/is_callable
+ * Value can be any php callable: https://www.php.net/is_callable
*
* Parameters:
- * bool $result result of the send action
+ * bool $result result of the send action
* array $to email addresses of the recipients
* array $cc cc email addresses
* array $bcc bcc email addresses
@@ -555,9 +562,9 @@ class PHPMailer
* string $body the email body
* string $from email address of sender
* string $extra extra information of possible use
- * "smtp_transaction_id' => last smtp transaction id
+ * 'smtp_transaction_id' => last smtp transaction id
*
- * @var string
+ * @var callable|callable-string
*/
public $action_function = '';
@@ -574,6 +581,10 @@ class PHPMailer
* May be a callable to inject your own validator, but there are several built-in validators.
* The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option.
*
+ * If CharSet is UTF8, the validator is left at the default value,
+ * and you send to addresses that use non-ASCII local parts, then
+ * PHPMailer automatically changes to the 'eai' validator.
+ *
* @see PHPMailer::validateAddress()
*
* @var string|callable
@@ -653,6 +664,14 @@ class PHPMailer
*/
protected $ReplyToQueue = [];
+ /**
+ * Whether the need for SMTPUTF8 has been detected. Set by
+ * preSend() if necessary.
+ *
+ * @var bool
+ */
+ public $UseSMTPUTF8 = false;
+
/**
* The array of attachments.
*
@@ -693,7 +712,7 @@ class PHPMailer
*
* @var array
*/
- protected $language = [];
+ protected static $language = [];
/**
* The number of errors encountered.
@@ -750,7 +769,7 @@ class PHPMailer
*
* @var string
*/
- const VERSION = '6.8.0';
+ const VERSION = '7.1.1';
/**
* Error severity: message only, continue processing.
@@ -858,6 +877,7 @@ public function __destruct()
private function mailPassthru($to, $subject, $body, $header, $params)
{
//Check overloading of mail function to avoid double-encoding
+ // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecatedRemoved
if ((int)ini_get('mbstring.func_overload') & 1) {
$subject = $this->secureHeader($subject);
} else {
@@ -896,7 +916,7 @@ protected function edebug($str)
}
//Is this a PSR-3 logger?
if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
- $this->Debugoutput->debug($str);
+ $this->Debugoutput->debug(rtrim($str, "\r\n"));
return;
}
@@ -969,6 +989,54 @@ public function isMail()
$this->Mailer = 'mail';
}
+ /**
+ * Extract sendmail path and parse to deal with known parameters.
+ *
+ * @param string $sendmailPath The sendmail path as set in php.ini
+ *
+ * @return string The sendmail path without the known parameters
+ */
+ private function parseSendmailPath($sendmailPath)
+ {
+ $sendmailPath = trim((string)$sendmailPath);
+ if ($sendmailPath === '') {
+ return $sendmailPath;
+ }
+
+ $parts = preg_split('/\s+/', $sendmailPath);
+ if (empty($parts)) {
+ return $sendmailPath;
+ }
+
+ $command = array_shift($parts);
+ $remainder = [];
+
+ // Parse only -t, -i, -oi and -f parameters.
+ for ($i = 0; $i < count($parts); ++$i) {
+ $part = $parts[$i];
+ if (preg_match('/^-(i|oi|t)$/', $part, $matches)) {
+ continue;
+ }
+ if (preg_match('/^-f(.*)$/', $part, $matches)) {
+ $address = $matches[1];
+ if ($address === '' && isset($parts[$i + 1]) && strpos($parts[$i + 1], '-') !== 0) {
+ $address = $parts[++$i];
+ }
+ $this->Sender = $address;
+ continue;
+ }
+
+ $remainder[] = $part;
+ }
+
+ // The params that are not parsed are added back to the command.
+ if (!empty($remainder)) {
+ $command .= ' ' . implode(' ', $remainder);
+ }
+
+ return $command;
+ }
+
/**
* Send messages using $Sendmail.
*/
@@ -977,10 +1045,9 @@ public function isSendmail()
$ini_sendmail_path = ini_get('sendmail_path');
if (false === stripos($ini_sendmail_path, 'sendmail')) {
- $this->Sendmail = '/usr/sbin/sendmail';
- } else {
- $this->Sendmail = $ini_sendmail_path;
+ $ini_sendmail_path = '/usr/sbin/sendmail';
}
+ $this->Sendmail = $this->parseSendmailPath($ini_sendmail_path);
$this->Mailer = 'sendmail';
}
@@ -992,10 +1059,9 @@ public function isQmail()
$ini_sendmail_path = ini_get('sendmail_path');
if (false === stripos($ini_sendmail_path, 'qmail')) {
- $this->Sendmail = '/var/qmail/bin/qmail-inject';
- } else {
- $this->Sendmail = $ini_sendmail_path;
+ $ini_sendmail_path = '/var/qmail/bin/qmail-inject';
}
+ $this->Sendmail = $this->parseSendmailPath($ini_sendmail_path);
$this->Mailer = 'qmail';
}
@@ -1065,7 +1131,7 @@ public function addReplyTo($address, $name = '')
* be modified after calling this function), addition of such addresses is delayed until send().
* Addresses that have been added already return false, but do not throw exceptions.
*
- * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
+ * @param string $kind One of 'to', 'cc', 'bcc', or 'Reply-To'
* @param string $address The email address
* @param string $name An optional username associated with the address
*
@@ -1084,7 +1150,7 @@ protected function addOrEnqueueAnAddress($kind, $address, $name)
//At-sign is missing.
$error_message = sprintf(
'%s (%s): %s',
- $this->lang('invalid_address'),
+ self::lang('invalid_address'),
$kind,
$address
);
@@ -1104,19 +1170,22 @@ protected function addOrEnqueueAnAddress($kind, $address, $name)
$params = [$kind, $address, $name];
//Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
//Domain is assumed to be whatever is after the last @ symbol in the address
- if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) {
- if ('Reply-To' !== $kind) {
- if (!array_key_exists($address, $this->RecipientsQueue)) {
- $this->RecipientsQueue[$address] = $params;
+ if ($this->has8bitChars(substr($address, ++$pos))) {
+ if (static::idnSupported()) {
+ if ('Reply-To' !== $kind) {
+ if (!array_key_exists($address, $this->RecipientsQueue)) {
+ $this->RecipientsQueue[$address] = $params;
+
+ return true;
+ }
+ } elseif (!array_key_exists($address, $this->ReplyToQueue)) {
+ $this->ReplyToQueue[$address] = $params;
return true;
}
- } elseif (!array_key_exists($address, $this->ReplyToQueue)) {
- $this->ReplyToQueue[$address] = $params;
-
- return true;
}
-
+ //We have an 8-bit domain, but we are missing the necessary extensions to support it
+ //Or we are already sending to this address
return false;
}
@@ -1154,10 +1223,19 @@ public function setBoundaries()
*/
protected function addAnAddress($kind, $address, $name = '')
{
+ if (
+ self::$validator === 'php' &&
+ ((bool) preg_match('/[\x80-\xFF]/', $address))
+ ) {
+ //The caller has not altered the validator and is sending to an address
+ //with UTF-8, so assume that they want UTF-8 support instead of failing
+ $this->CharSet = self::CHARSET_UTF8;
+ self::$validator = 'eai';
+ }
if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
$error_message = sprintf(
'%s: %s',
- $this->lang('Invalid recipient kind'),
+ self::lang('Invalid recipient kind'),
$kind
);
$this->setError($error_message);
@@ -1171,7 +1249,7 @@ protected function addAnAddress($kind, $address, $name = '')
if (!static::validateAddress($address)) {
$error_message = sprintf(
'%s (%s): %s',
- $this->lang('invalid_address'),
+ self::lang('invalid_address'),
$kind,
$address
);
@@ -1190,56 +1268,61 @@ protected function addAnAddress($kind, $address, $name = '')
return true;
}
- } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
- $this->ReplyTo[strtolower($address)] = [$address, $name];
+ } else {
+ foreach ($this->ReplyTo as $replyTo) {
+ if (0 === strcasecmp($replyTo[0], $address)) {
+ return false;
+ }
+ }
+ $this->ReplyTo[] = [$address, $name];
return true;
}
-
return false;
}
/**
* Parse and validate a string containing one or more RFC822-style comma-separated email addresses
* of the form "display name " into an array of name/address pairs.
- * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
+ * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available and
+ * the deprecated $useimap argument is truthy.
* Note that quotes in the name part are removed.
*
- * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
+ * @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
*
* @param string $addrstr The address list string
- * @param bool $useimap Whether to use the IMAP extension to parse the list
+ * @param bool|null $useimap Deprecated in PHPMailer 6.11.0.
+ * Truthy values request the deprecated IMAP parser
+ * and trigger a deprecation warning.
* @param string $charset The charset to use when decoding the address list string.
*
* @return array
*/
- public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591)
+ public static function parseAddresses($addrstr, $useimap = null, $charset = self::CHARSET_ISO88591)
{
+ if ($useimap == true) {
+ trigger_error(self::lang('deprecated_argument') . '$useimap', E_USER_DEPRECATED);
+ }
$addresses = [];
- if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
+ if ($useimap == true && function_exists('imap_rfc822_parse_adrlist')) {
//Use this built-in parser if it's available
+ // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.imap_rfc822_parse_adrlistRemoved -- wrapped in function_exists()
$list = imap_rfc822_parse_adrlist($addrstr, '');
// Clear any potential IMAP errors to get rid of notices being thrown at end of script.
+ // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.imap_errorsRemoved -- wrapped in function_exists()
imap_errors();
foreach ($list as $address) {
if (
'.SYNTAX-ERROR.' !== $address->host &&
static::validateAddress($address->mailbox . '@' . $address->host)
) {
- //Decode the name part if it's present and encoded
+ //Decode the name part if it's present and maybe encoded
if (
- property_exists($address, 'personal') &&
- //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
- defined('MB_CASE_UPPER') &&
- preg_match('/^=\?.*\?=$/s', $address->personal)
+ property_exists($address, 'personal')
+ && is_string($address->personal)
+ && $address->personal !== ''
) {
- $origCharset = mb_internal_encoding();
- mb_internal_encoding($charset);
- //Undo any RFC2047-encoded spaces-as-underscores
- $address->personal = str_replace('_', '=20', $address->personal);
- //Decode the name
- $address->personal = mb_decode_mimeheader($address->personal);
- mb_internal_encoding($origCharset);
+ $address->personal = static::decodeHeader($address->personal, $charset);
}
$addresses[] = [
@@ -1250,40 +1333,51 @@ public static function parseAddresses($addrstr, $useimap = true, $charset = self
}
} else {
//Use this simpler parser
- $list = explode(',', $addrstr);
- foreach ($list as $address) {
- $address = trim($address);
- //Is there a separate name part?
- if (strpos($address, '<') === false) {
- //No separate name, just use the whole thing
- if (static::validateAddress($address)) {
- $addresses[] = [
- 'name' => '',
- 'address' => $address,
- ];
- }
- } else {
- list($name, $email) = explode('<', $address);
- $email = trim(str_replace('>', '', $email));
- $name = trim($name);
- if (static::validateAddress($email)) {
- //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
- //If this name is encoded, decode it
- if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) {
- $origCharset = mb_internal_encoding();
- mb_internal_encoding($charset);
- //Undo any RFC2047-encoded spaces-as-underscores
- $name = str_replace('_', '=20', $name);
- //Decode the name
- $name = mb_decode_mimeheader($name);
- mb_internal_encoding($origCharset);
- }
- $addresses[] = [
- //Remove any surrounding quotes and spaces from the name
- 'name' => trim($name, '\'" '),
- 'address' => $email,
- ];
- }
+ $addresses = static::parseSimplerAddresses($addrstr, $charset);
+ }
+
+ return $addresses;
+ }
+
+ /**
+ * Parse a string containing one or more RFC822-style comma-separated email addresses
+ * with the form "display name " into an array of name/address pairs.
+ * Uses a simpler parser that does not require the IMAP extension but doesnt support
+ * the full RFC822 spec. For full RFC822 support, use the PHP IMAP extension.
+ *
+ * @param string $addrstr The address list string
+ * @param string $charset The charset to use when decoding the address list string.
+ *
+ * @return array
+ */
+ protected static function parseSimplerAddresses($addrstr, $charset)
+ {
+ // Emit a runtime notice to recommend using the IMAP extension for full RFC822 parsing
+ trigger_error(self::lang('imap_recommended'), E_USER_NOTICE);
+
+ $addresses = [];
+ $list = explode(',', $addrstr);
+ foreach ($list as $address) {
+ $address = trim($address);
+ //Is there a separate name part?
+ if (strpos($address, '<') === false) {
+ //No separate name, just use the whole thing
+ if (static::validateAddress($address)) {
+ $addresses[] = [
+ 'name' => '',
+ 'address' => $address,
+ ];
+ }
+ } else {
+ $parsed = static::parseEmailString($address);
+ $email = $parsed['email'];
+ if (static::validateAddress($email)) {
+ $name = static::decodeHeader($parsed['name'], $charset);
+ $addresses[] = [
+ //Remove any surrounding quotes and spaces from the name
+ 'name' => trim($name, '\'" '),
+ 'address' => $email,
+ ];
}
}
}
@@ -1291,6 +1385,42 @@ public static function parseAddresses($addrstr, $useimap = true, $charset = self
return $addresses;
}
+ /**
+ * Parse a string containing an email address with an optional name
+ * and divide it into a name and email address.
+ *
+ * @param string $input The email with name.
+ *
+ * @return array{name: string, email: string}
+ */
+ private static function parseEmailString($input)
+ {
+ $input = trim((string)$input);
+
+ if ($input === '') {
+ return ['name' => '', 'email' => ''];
+ }
+
+ $pattern = '/^\s*(?:(?:"([^"]*)"|\'([^\']*)\'|([^<]*?))\s*)?<\s*([^>]+)\s*>\s*$/';
+ if (preg_match($pattern, $input, $matches)) {
+ $name = '';
+ // Double quotes including special scenarios.
+ if (isset($matches[1]) && $matches[1] !== '') {
+ $name = $matches[1];
+ // Single quotes including special scenarios.
+ } elseif (isset($matches[2]) && $matches[2] !== '') {
+ $name = $matches[2];
+ // Simplest scenario, name and email are in the format "Name ".
+ } elseif (isset($matches[3])) {
+ $name = trim($matches[3]);
+ }
+
+ return ['name' => $name, 'email' => trim($matches[4])];
+ }
+
+ return ['name' => '', 'email' => $input];
+ }
+
/**
* Set the From and FromName properties.
*
@@ -1304,6 +1434,10 @@ public static function parseAddresses($addrstr, $useimap = true, $charset = self
*/
public function setFrom($address, $name = '', $auto = true)
{
+ if (is_null($name)) {
+ //Helps avoid a deprecation warning in the preg_replace() below
+ $name = '';
+ }
$address = trim((string)$address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
//Don't validate now addresses with IDN. Will be done in send().
@@ -1315,7 +1449,7 @@ public function setFrom($address, $name = '', $auto = true)
) {
$error_message = sprintf(
'%s (From): %s',
- $this->lang('invalid_address'),
+ self::lang('invalid_address'),
$address
);
$this->setError($error_message);
@@ -1356,6 +1490,7 @@ public function getLastMessageID()
* * `pcre` Use old PCRE implementation;
* * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
* * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
+ * * `eai` Use a pattern similar to the HTML5 spec for 'email' and to firefox, extended to support EAI (RFC6530).
* * `noregex` Don't use a regex: super fast, really dumb.
* Alternatively you may pass in a callable to inject your own validator, for example:
*
@@ -1400,7 +1535,6 @@ public static function validateAddress($address, $patternselect = null)
* * IPv6 literals: 'first.last@[IPv6:a1::]'
* Not all of these will necessarily work for sending!
*
- * @see http://squiloople.com/2009/12/20/email-address-validation/
* @copyright 2009-2010 Michael Rushton
* Feel free to use and redistribute this code. But please keep this copyright notice.
*/
@@ -1427,6 +1561,24 @@ public static function validateAddress($address, $patternselect = null)
'[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
$address
);
+ case 'eai':
+ /*
+ * This is the pattern used in the HTML5 spec for validation of 'email' type
+ * form input elements (as above), modified to accept Unicode email addresses.
+ * This is also more lenient than Firefox' html5 spec, in order to make the regex faster.
+ * 'eai' is an acronym for Email Address Internationalization.
+ * This validator is selected automatically if you attempt to use recipient addresses
+ * that contain Unicode characters in the local part.
+ *
+ * @see https://html.spec.whatwg.org/#e-mail-state-(type=email)
+ * @see https://en.wikipedia.org/wiki/International_email
+ */
+ return (bool) preg_match(
+ '/^[-\p{L}\p{N}\p{M}.!#$%&\'*+\/=?^_`{|}~]+@[\p{L}\p{N}\p{M}](?:[\p{L}\p{N}\p{M}-]{0,61}' .
+ '[\p{L}\p{N}\p{M}])?(?:\.[\p{L}\p{N}\p{M}]' .
+ '(?:[-\p{L}\p{N}\p{M}]{0,61}[\p{L}\p{N}\p{M}])?)*$/usD',
+ $address
+ );
case 'php':
default:
return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
@@ -1484,9 +1636,11 @@ public function punyencodeAddress($address)
);
} elseif (defined('INTL_IDNA_VARIANT_2003')) {
//Fall back to this old, deprecated/removed encoding
+ // phpcs:ignore PHPCompatibility.Constants.RemovedConstants.intl_idna_variant_2003DeprecatedRemoved
$punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003);
} else {
//Fall back to a default we don't know about
+ // phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet
$punycode = idn_to_ascii($domain, $errorcode);
}
if (false !== $punycode) {
@@ -1553,24 +1707,45 @@ public function preSend()
&& ini_get('mail.add_x_header') === '1'
&& stripos(PHP_OS, 'WIN') === 0
) {
- trigger_error($this->lang('buggy_php'), E_USER_WARNING);
+ trigger_error(self::lang('buggy_php'), E_USER_WARNING);
}
try {
$this->error_count = 0; //Reset errors
$this->mailHeader = '';
+ //The code below tries to support full use of Unicode,
+ //while remaining compatible with legacy SMTP servers to
+ //the greatest degree possible: If the message uses
+ //Unicode in the local parts of any addresses, it is sent
+ //using SMTPUTF8. If not, it it sent using
+ //punycode-encoded domains and plain SMTP.
+ if (
+ static::CHARSET_UTF8 === strtolower($this->CharSet) &&
+ ($this->anyAddressHasUnicodeLocalPart($this->RecipientsQueue) ||
+ $this->anyAddressHasUnicodeLocalPart(array_keys($this->all_recipients)) ||
+ $this->anyAddressHasUnicodeLocalPart($this->ReplyToQueue) ||
+ $this->addressHasUnicodeLocalPart($this->From))
+ ) {
+ $this->UseSMTPUTF8 = true;
+ }
//Dequeue recipient and Reply-To addresses with IDN
foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
- $params[1] = $this->punyencodeAddress($params[1]);
+ if (!$this->UseSMTPUTF8) {
+ $params[1] = $this->punyencodeAddress($params[1]);
+ }
call_user_func_array([$this, 'addAnAddress'], $params);
}
if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
- throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
+ throw new Exception(self::lang('provide_address'), self::STOP_CRITICAL);
}
//Validate From, Sender, and ConfirmReadingTo addresses
foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
+ if ($this->{$address_kind} === null) {
+ $this->{$address_kind} = '';
+ continue;
+ }
$this->{$address_kind} = trim($this->{$address_kind});
if (empty($this->{$address_kind})) {
continue;
@@ -1579,7 +1754,7 @@ public function preSend()
if (!static::validateAddress($this->{$address_kind})) {
$error_message = sprintf(
'%s (%s): %s',
- $this->lang('invalid_address'),
+ self::lang('invalid_address'),
$address_kind,
$this->{$address_kind}
);
@@ -1601,11 +1776,13 @@ public function preSend()
$this->setMessageType();
//Refuse to send an empty message unless we are specifically allowing it
if (!$this->AllowEmpty && empty($this->Body)) {
- throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
+ throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
}
//Trim subject consistently
$this->Subject = trim($this->Subject);
+
+
//Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
$this->MIMEHeader = '';
$this->MIMEBody = $this->createBody();
@@ -1680,7 +1857,7 @@ public function postSend()
return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
default:
$sendMethod = $this->Mailer . 'Send';
- if (method_exists($this, $sendMethod)) {
+ if (!empty($this->Mailer) && method_exists($this, $sendMethod)) {
return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody);
}
@@ -1723,9 +1900,8 @@ protected function sendmailSend($header, $body)
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
//A space after `-f` is optional, but there is a long history of its presence
//causing problems, so we don't use one
- //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
- //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
- //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
+ //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
+ //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html
//Example problem: https://www.drupal.org/node/1057954
//PHP 5.6 workaround
@@ -1734,23 +1910,27 @@ protected function sendmailSend($header, $body)
//PHP config has a sender address we can use
$this->Sender = ini_get('sendmail_from');
}
- //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
+
+ $sendmailArgs = [];
+
+ // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
+ // Also don't add the -f automatically unless it has been set either via Sender
+ // or sendmail_path. Otherwise, it can introduce new problems.
+ // @see http://github.com/PHPMailer/PHPMailer/issues/2298
if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {
- if ($this->Mailer === 'qmail') {
- $sendmailFmt = '%s -f%s';
- } else {
- $sendmailFmt = '%s -oi -f%s -t';
- }
- } else {
- //allow sendmail to choose a default envelope sender. It may
- //seem preferable to force it to use the From header as with
- //SMTP, but that introduces new problems (see
- //), and
- //it has historically worked this way.
- $sendmailFmt = '%s -oi -t';
+ $sendmailArgs[] = '-f' . $this->Sender;
+ }
+
+ // Qmail doesn't accept all the sendmail parameters
+ // @see https://github.com/PHPMailer/PHPMailer/issues/3189
+ if ($this->Mailer !== 'qmail') {
+ $sendmailArgs[] = '-i';
+ $sendmailArgs[] = '-t';
}
- $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
+ $resultArgs = (empty($sendmailArgs) ? '' : ' ' . implode(' ', $sendmailArgs));
+
+ $sendmail = trim(escapeshellcmd($this->Sendmail) . $resultArgs);
$this->edebug('Sendmail path: ' . $this->Sendmail);
$this->edebug('Sendmail command: ' . $sendmail);
$this->edebug('Envelope sender: ' . $this->Sender);
@@ -1760,33 +1940,35 @@ protected function sendmailSend($header, $body)
foreach ($this->SingleToArray as $toAddr) {
$mail = @popen($sendmail, 'w');
if (!$mail) {
- throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
+ throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
$this->edebug("To: {$toAddr}");
fwrite($mail, 'To: ' . $toAddr . "\n");
fwrite($mail, $header);
fwrite($mail, $body);
$result = pclose($mail);
- $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
- $this->doCallback(
- ($result === 0),
- [[$addrinfo['address'], $addrinfo['name']]],
- $this->cc,
- $this->bcc,
- $this->Subject,
- $body,
- $this->From,
- []
- );
+ $addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
+ foreach ($addrinfo as $addr) {
+ $this->doCallback(
+ ($result === 0),
+ [[$addr['address'], $addr['name']]],
+ $this->cc,
+ $this->bcc,
+ $this->Subject,
+ $body,
+ $this->From,
+ []
+ );
+ }
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) {
- throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
+ throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
} else {
$mail = @popen($sendmail, 'w');
if (!$mail) {
- throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
+ throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
fwrite($mail, $header);
fwrite($mail, $body);
@@ -1803,7 +1985,7 @@ protected function sendmailSend($header, $body)
);
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) {
- throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
+ throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
@@ -1863,7 +2045,7 @@ protected static function isShellSafe($string)
*/
protected static function isPermittedPath($path)
{
- //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1
+ //Matches scheme definition from https://www.rfc-editor.org/rfc/rfc3986#section-3.1
return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path);
}
@@ -1890,7 +2072,7 @@ protected static function fileIsAccessible($path)
/**
* Send mail using the PHP mail() function.
*
- * @see http://www.php.net/manual/en/book.mail.php
+ * @see https://www.php.net/manual/en/book.mail.php
*
* @param string $header The message headers
* @param string $body The message body
@@ -1920,9 +2102,8 @@ protected function mailSend($header, $body)
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
//A space after `-f` is optional, but there is a long history of its presence
//causing problems, so we don't use one
- //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
- //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
- //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
+ //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
+ //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html
//Example problem: https://www.drupal.org/node/1057954
//CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
@@ -1933,7 +2114,8 @@ protected function mailSend($header, $body)
$this->Sender = ini_get('sendmail_from');
}
if (!empty($this->Sender) && static::validateAddress($this->Sender)) {
- if (self::isShellSafe($this->Sender)) {
+ $phpmailer_path = ini_get('sendmail_path');
+ if (self::isShellSafe($this->Sender) && strpos($phpmailer_path, ' -f') === false) {
$params = sprintf('-f%s', $this->Sender);
}
$old_from = ini_get('sendmail_from');
@@ -1943,17 +2125,19 @@ protected function mailSend($header, $body)
if ($this->SingleTo && count($toArr) > 1) {
foreach ($toArr as $toAddr) {
$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
- $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
- $this->doCallback(
- $result,
- [[$addrinfo['address'], $addrinfo['name']]],
- $this->cc,
- $this->bcc,
- $this->Subject,
- $body,
- $this->From,
- []
- );
+ $addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
+ foreach ($addrinfo as $addr) {
+ $this->doCallback(
+ $result,
+ [[$addr['address'], $addr['name']]],
+ $this->cc,
+ $this->bcc,
+ $this->Subject,
+ $body,
+ $this->From,
+ []
+ );
+ }
}
} else {
$result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
@@ -1963,7 +2147,7 @@ protected function mailSend($header, $body)
ini_set('sendmail_from', $old_from);
}
if (!$result) {
- throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
+ throw new Exception(self::lang('instantiate'), self::STOP_CRITICAL);
}
return true;
@@ -1997,6 +2181,38 @@ public function setSMTPInstance(SMTP $smtp)
return $this->smtp;
}
+ /**
+ * Provide SMTP XCLIENT attributes
+ *
+ * @param string $name Attribute name
+ * @param ?string $value Attribute value
+ *
+ * @return bool
+ */
+ public function setSMTPXclientAttribute($name, $value)
+ {
+ if (!in_array($name, SMTP::$xclient_allowed_attributes)) {
+ return false;
+ }
+ if (isset($this->SMTPXClient[$name]) && $value === null) {
+ unset($this->SMTPXClient[$name]);
+ } elseif ($value !== null) {
+ $this->SMTPXClient[$name] = $value;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get SMTP XCLIENT attributes
+ *
+ * @return array
+ */
+ public function getSMTPXclientAttributes()
+ {
+ return $this->SMTPXClient;
+ }
+
/**
* Send mail via SMTP.
* Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
@@ -2017,7 +2233,12 @@ protected function smtpSend($header, $body)
$header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
$bad_rcpt = [];
if (!$this->smtpConnect($this->SMTPOptions)) {
- throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
+ throw new Exception(self::lang('smtp_connect_failed'), self::STOP_CRITICAL);
+ }
+ //If we have recipient addresses that need Unicode support,
+ //but the server doesn't support it, stop here
+ if ($this->UseSMTPUTF8 && !$this->smtp->getServerExt('SMTPUTF8')) {
+ throw new Exception(self::lang('no_smtputf8'), self::STOP_CRITICAL);
}
//Sender already validated in preSend()
if ('' === $this->Sender) {
@@ -2025,8 +2246,11 @@ protected function smtpSend($header, $body)
} else {
$smtp_from = $this->Sender;
}
+ if (count($this->SMTPXClient)) {
+ $this->smtp->xclient($this->SMTPXClient);
+ }
if (!$this->smtp->mail($smtp_from)) {
- $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
+ $this->setError(self::lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
}
@@ -2048,7 +2272,7 @@ protected function smtpSend($header, $body)
//Only send the DATA command if we have viable recipients
if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
- throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
+ throw new Exception(self::lang('data_not_accepted'), self::STOP_CRITICAL);
}
$smtp_transaction_id = $this->smtp->getLastTransactionID();
@@ -2079,7 +2303,7 @@ protected function smtpSend($header, $body)
foreach ($bad_rcpt as $bad) {
$errstr .= $bad['to'] . ': ' . $bad['error'];
}
- throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
+ throw new Exception(self::lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
}
return true;
@@ -2117,6 +2341,7 @@ public function smtpConnect($options = null)
$this->smtp->setDebugLevel($this->SMTPDebug);
$this->smtp->setDebugOutput($this->Debugoutput);
$this->smtp->setVerp($this->do_verp);
+ $this->smtp->setSMTPUTF8($this->UseSMTPUTF8);
if ($this->Host === null) {
$this->Host = 'localhost';
}
@@ -2132,7 +2357,7 @@ public function smtpConnect($options = null)
$hostinfo
)
) {
- $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
+ $this->edebug(self::lang('invalid_hostentry') . ' ' . trim($hostentry));
//Not a valid host entry
continue;
}
@@ -2144,7 +2369,7 @@ public function smtpConnect($options = null)
//Check the host name is a valid name or IP address before trying to use it
if (!static::isValidHost($hostinfo[2])) {
- $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
+ $this->edebug(self::lang('invalid_host') . ' ' . $hostinfo[2]);
continue;
}
$prefix = '';
@@ -2164,7 +2389,7 @@ public function smtpConnect($options = null)
if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
//Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
if (!$sslext) {
- throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
+ throw new Exception(self::lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
}
}
$host = $hostinfo[2];
@@ -2187,10 +2412,17 @@ public function smtpConnect($options = null)
$this->smtp->hello($hello);
//Automatically enable TLS encryption if:
//* it's not disabled
+ //* we are not connecting to localhost
//* we have openssl extension
//* we are not already using SSL
//* the server offers STARTTLS
- if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) {
+ if (
+ $this->SMTPAutoTLS &&
+ $this->Host !== 'localhost' &&
+ $sslext &&
+ $secure !== 'ssl' &&
+ $this->smtp->getServerExt('STARTTLS')
+ ) {
$tls = true;
}
if ($tls) {
@@ -2209,7 +2441,7 @@ public function smtpConnect($options = null)
$this->oauth
)
) {
- throw new Exception($this->lang('authenticate'));
+ throw new Exception(self::lang('authenticate'));
}
return true;
@@ -2259,7 +2491,7 @@ public function smtpClose()
*
* @return bool Returns true if the requested language was loaded, false otherwise.
*/
- public function setLanguage($langcode = 'en', $lang_path = '')
+ public static function setLanguage($langcode = 'en', $lang_path = '')
{
//Backwards compatibility for renamed language codes
$renamed_langcodes = [
@@ -2282,7 +2514,7 @@ public function setLanguage($langcode = 'en', $lang_path = '')
'authenticate' => 'SMTP Error: Could not authenticate.',
'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' .
' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' .
- ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
+ ' your php.ini, switch to macOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
'data_not_accepted' => 'SMTP Error: data not accepted.',
'empty_message' => 'Message body empty',
@@ -2307,6 +2539,10 @@ public function setLanguage($langcode = 'en', $lang_path = '')
'smtp_detail' => 'Detail: ',
'smtp_error' => 'SMTP server error: ',
'variable_set' => 'Cannot set or reset variable: ',
+ 'no_smtputf8' => 'Server does not support SMTPUTF8 needed to send to Unicode addresses',
+ 'imap_recommended' => 'Using simplified address parser is not recommended. ' .
+ 'Install the PHP IMAP extension for full RFC822 parsing.',
+ 'deprecated_argument' => 'Deprecated Argument: ',
];
if (empty($lang_path)) {
//Calculate an absolute path so it can work if CWD is not here
@@ -2373,7 +2609,7 @@ public function setLanguage($langcode = 'en', $lang_path = '')
}
}
}
- $this->language = $PHPMAILER_LANG;
+ self::$language = $PHPMAILER_LANG;
return $foundlang; //Returns false if language not found
}
@@ -2385,11 +2621,11 @@ public function setLanguage($langcode = 'en', $lang_path = '')
*/
public function getTranslations()
{
- if (empty($this->language)) {
- $this->setLanguage(); // Set the default language.
+ if (empty(self::$language)) {
+ self::setLanguage(); // Set the default language.
}
- return $this->language;
+ return self::$language;
}
/**
@@ -2615,7 +2851,10 @@ public function createHeader()
{
$result = '';
- $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);
+ $result .= $this->headerLine(
+ 'Date',
+ self::sanitiseDate($this->MessageDate)
+ );
//The To header is created automatically by mail(), so needs to be omitted here
if ('mail' !== $this->Mailer) {
@@ -2656,7 +2895,7 @@ public function createHeader()
}
//Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
- //https://tools.ietf.org/html/rfc5322#section-3.6.4
+ //https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4
if (
'' !== $this->MessageID &&
preg_match(
@@ -2684,7 +2923,7 @@ public function createHeader()
);
} elseif (is_string($this->XMailer) && trim($this->XMailer) !== '') {
//Some string
- $result .= $this->headerLine('X-Mailer', trim($this->XMailer));
+ $result .= $this->headerLine('X-Mailer', $this->secureHeader(trim($this->XMailer)));
} //Other values result in no X-Mailer header
if ('' !== $this->ConfirmReadingTo) {
@@ -2734,13 +2973,20 @@ public function getMailMIME()
break;
default:
//Catches case 'plain': and case '':
- $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
+ $result .= $this->textLine(
+ 'Content-Type: ' .
+ $this->secureHeader($this->ContentType) .
+ '; charset=' . $this->secureHeader($this->CharSet)
+ );
$ismultipart = false;
break;
}
+ if (!$this->validateEncoding($this->Encoding)) {
+ throw new Exception(self::lang('encoding') . $this->Encoding);
+ }
//RFC1341 part 5 says 7bit is assumed if not specified
if (static::ENCODING_7BIT !== $this->Encoding) {
- //RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
+ //RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit, or binary CTE
if ($ismultipart) {
if (static::ENCODING_8BIT === $this->Encoding) {
$result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
@@ -2780,6 +3026,7 @@ protected function generateId()
$bytes = '';
if (function_exists('random_bytes')) {
try {
+ // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.random_bytesFound -- Wrapped in function_exists.
$bytes = random_bytes($len);
} catch (\Exception $e) {
//Do nothing
@@ -2812,16 +3059,17 @@ public function createBody()
//Create unique IDs and preset boundaries
$this->setBoundaries();
- if ($this->sign_key_file) {
- $body .= $this->getMailMIME() . static::$LE;
- }
-
$this->setWordWrap();
+ if (!$this->validateEncoding($this->Encoding)) {
+ throw new Exception(self::lang('encoding') . $this->Encoding);
+ }
$bodyEncoding = $this->Encoding;
$bodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
- if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
+ if ($this->UseSMTPUTF8) {
+ $bodyEncoding = static::ENCODING_8BIT;
+ } elseif (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
$bodyEncoding = static::ENCODING_7BIT;
//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
$bodyCharSet = static::CHARSET_ASCII;
@@ -2845,6 +3093,12 @@ public function createBody()
if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
$altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
}
+
+ if ($this->sign_key_file) {
+ $this->Encoding = $bodyEncoding;
+ $body .= $this->getMailMIME() . static::$LE;
+ }
+
//Use this as a preamble in all multipart message types
$mimepre = '';
switch ($this->message_type) {
@@ -3026,12 +3280,12 @@ public function createBody()
if ($this->isError()) {
$body = '';
if ($this->exceptions) {
- throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
+ throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
}
} elseif ($this->sign_key_file) {
try {
if (!defined('PKCS7_TEXT')) {
- throw new Exception($this->lang('extension_missing') . 'openssl');
+ throw new Exception(self::lang('extension_missing') . 'openssl');
}
$file = tempnam(sys_get_temp_dir(), 'srcsign');
@@ -3069,7 +3323,7 @@ public function createBody()
$body = $parts[1];
} else {
@unlink($signed);
- throw new Exception($this->lang('signing') . openssl_error_string());
+ throw new Exception(self::lang('signing') . openssl_error_string());
}
} catch (Exception $exc) {
$body = '';
@@ -3214,7 +3468,7 @@ public function addAttachment(
) {
try {
if (!static::fileIsAccessible($path)) {
- throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
+ throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
}
//If a MIME type is not specified, try to work it out from the file name
@@ -3227,7 +3481,7 @@ public function addAttachment(
$name = $filename;
}
if (!$this->validateEncoding($encoding)) {
- throw new Exception($this->lang('encoding') . $encoding);
+ throw new Exception(self::lang('encoding') . $encoding);
}
$this->attachment[] = [
@@ -3388,11 +3642,11 @@ protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
{
try {
if (!static::fileIsAccessible($path)) {
- throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
+ throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
}
$file_buffer = file_get_contents($path);
if (false === $file_buffer) {
- throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
+ throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
}
$file_buffer = $this->encodeString($file_buffer, $encoding);
@@ -3445,9 +3699,9 @@ public function encodeString($str, $encoding = self::ENCODING_BASE64)
$encoded = $this->encodeQP($str);
break;
default:
- $this->setError($this->lang('encoding') . $encoding);
+ $this->setError(self::lang('encoding') . $encoding);
if ($this->exceptions) {
- throw new Exception($this->lang('encoding') . $encoding);
+ throw new Exception(self::lang('encoding') . $encoding);
}
break;
}
@@ -3458,7 +3712,8 @@ public function encodeString($str, $encoding = self::ENCODING_BASE64)
/**
* Encode a header value (not including its label) optimally.
* Picks shortest of Q, B, or none. Result includes folding if needed.
- * See RFC822 definitions for phrase, comment and text positions.
+ * See RFC822 definitions for phrase, comment and text positions,
+ * and RFC2047 for inline encodings.
*
* @param string $str The header value to encode
* @param string $position What context the string will be used in
@@ -3467,6 +3722,11 @@ public function encodeString($str, $encoding = self::ENCODING_BASE64)
*/
public function encodeHeader($str, $position = 'text')
{
+ $position = strtolower($position);
+ if ($this->UseSMTPUTF8 && !("comment" === $position)) {
+ return trim(static::normalizeBreaks($str));
+ }
+
$matchcount = 0;
switch (strtolower($position)) {
case 'phrase':
@@ -3547,6 +3807,42 @@ public function encodeHeader($str, $position = 'text')
return trim(static::normalizeBreaks($encoded));
}
+ /**
+ * Decode an RFC2047-encoded header value
+ * Attempts multiple strategies so it works even when the mbstring extension is disabled.
+ *
+ * @param string $value The header value to decode
+ * @param string $charset The target charset to convert to, defaults to ISO-8859-1 for BC
+ *
+ * @return string The decoded header value
+ */
+ public static function decodeHeader($value, $charset = self::CHARSET_ISO88591)
+ {
+ if (!is_string($value) || $value === '') {
+ return '';
+ }
+ // Detect the presence of any RFC2047 encoded-words
+ $hasEncodedWord = (bool) preg_match('/=\?.*\?=/s', $value);
+ if ($hasEncodedWord && defined('MB_CASE_UPPER')) {
+ $origCharset = mb_internal_encoding();
+ // Always decode to UTF-8 to provide a consistent, modern output encoding.
+ mb_internal_encoding($charset);
+ if (PHP_VERSION_ID < 80300) {
+ // Undo any RFC2047-encoded spaces-as-underscores.
+ $value = str_replace('_', '=20', $value);
+ } else {
+ // PHP 8.3+ already interprets underscores as spaces. Remove additional
+ // linear whitespace between adjacent encoded words to avoid double spacing.
+ $value = preg_replace('/(\?=)\s+(=\?)/', '$1$2', $value);
+ }
+ // Decode the header value
+ $value = mb_decode_mimeheader($value);
+ mb_internal_encoding($origCharset);
+ }
+
+ return $value;
+ }
+
/**
* Check if a string contains multi-byte characters.
*
@@ -3581,7 +3877,7 @@ public function has8bitChars($text)
* without breaking lines within a character.
* Adapted from a function by paravoid.
*
- * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
+ * @see https://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
*
* @param string $str multi-byte text to wrap encode
* @param string $linebreak string to use as linefeed/end-of-line
@@ -3637,7 +3933,7 @@ public function encodeQP($string)
/**
* Encode a string using Q encoding.
*
- * @see http://tools.ietf.org/html/rfc2047#section-4.2
+ * @see https://www.rfc-editor.org/rfc/rfc2047#section-4.2
*
* @param string $str the text to encode
* @param string $position Where the text is going to be used, see the RFC for what that means
@@ -3716,7 +4012,7 @@ public function addStringAttachment(
}
if (!$this->validateEncoding($encoding)) {
- throw new Exception($this->lang('encoding') . $encoding);
+ throw new Exception(self::lang('encoding') . $encoding);
}
//Append to $attachment array
@@ -3775,7 +4071,7 @@ public function addEmbeddedImage(
) {
try {
if (!static::fileIsAccessible($path)) {
- throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
+ throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
}
//If a MIME type is not specified, try to work it out from the file name
@@ -3784,7 +4080,7 @@ public function addEmbeddedImage(
}
if (!$this->validateEncoding($encoding)) {
- throw new Exception($this->lang('encoding') . $encoding);
+ throw new Exception(self::lang('encoding') . $encoding);
}
$filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
@@ -3850,7 +4146,7 @@ public function addStringEmbeddedImage(
}
if (!$this->validateEncoding($encoding)) {
- throw new Exception($this->lang('encoding') . $encoding);
+ throw new Exception(self::lang('encoding') . $encoding);
}
//Append to $attachment array
@@ -3887,7 +4183,7 @@ public function addStringEmbeddedImage(
protected function validateEncoding($encoding)
{
return in_array(
- $encoding,
+ strtolower($encoding),
[
self::ENCODING_7BIT,
self::ENCODING_QUOTED_PRINTABLE,
@@ -4047,6 +4343,79 @@ public function clearCustomHeaders()
$this->CustomHeader = [];
}
+ /**
+ * Clear a specific custom header by name or name and value.
+ * $name value can be overloaded to contain
+ * both header name and value (name:value).
+ *
+ * @param string $name Custom header name
+ * @param string|null $value Header value
+ *
+ * @return bool True if a header was replaced successfully
+ */
+ public function clearCustomHeader($name, $value = null)
+ {
+ if (null === $value && strpos($name, ':') !== false) {
+ //Value passed in as name:value
+ list($name, $value) = explode(':', $name, 2);
+ }
+ $name = trim($name);
+ $value = (null === $value) ? null : trim($value);
+
+ foreach ($this->CustomHeader as $k => $pair) {
+ if ($pair[0] == $name) {
+ // We remove the header if the value is not provided or it matches.
+ if (null === $value || $pair[1] == $value) {
+ unset($this->CustomHeader[$k]);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Replace a custom header.
+ * $name value can be overloaded to contain
+ * both header name and value (name:value).
+ *
+ * @param string $name Custom header name
+ * @param string|null $value Header value
+ *
+ * @return bool True if a header was replaced successfully
+ * @throws Exception
+ */
+ public function replaceCustomHeader($name, $value = null)
+ {
+ if (null === $value && strpos($name, ':') !== false) {
+ //Value passed in as name:value
+ list($name, $value) = explode(':', $name, 2);
+ }
+ $name = trim($name);
+ $value = (null === $value) ? '' : trim($value);
+
+ $replaced = false;
+ foreach ($this->CustomHeader as $k => $pair) {
+ if ($pair[0] == $name) {
+ if ($replaced) {
+ unset($this->CustomHeader[$k]);
+ continue;
+ }
+ if (strpbrk($name . $value, "\r\n") !== false) {
+ if ($this->exceptions) {
+ throw new Exception(self::lang('invalid_header'));
+ }
+
+ return false;
+ }
+ $this->CustomHeader[$k] = [$name, $value];
+ $replaced = true;
+ }
+ }
+
+ return true;
+ }
+
/**
* Add an error message to the error container.
*
@@ -4058,15 +4427,15 @@ protected function setError($msg)
if ('smtp' === $this->Mailer && null !== $this->smtp) {
$lasterror = $this->smtp->getError();
if (!empty($lasterror['error'])) {
- $msg .= $this->lang('smtp_error') . $lasterror['error'];
+ $msg .= ' ' . self::lang('smtp_error') . $lasterror['error'];
if (!empty($lasterror['detail'])) {
- $msg .= ' ' . $this->lang('smtp_detail') . $lasterror['detail'];
+ $msg .= ' ' . self::lang('smtp_detail') . $lasterror['detail'];
}
if (!empty($lasterror['smtp_code'])) {
- $msg .= ' ' . $this->lang('smtp_code') . $lasterror['smtp_code'];
+ $msg .= ' ' . self::lang('smtp_code') . $lasterror['smtp_code'];
}
if (!empty($lasterror['smtp_code_ex'])) {
- $msg .= ' ' . $this->lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
+ $msg .= ' ' . self::lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
}
}
}
@@ -4074,7 +4443,7 @@ protected function setError($msg)
}
/**
- * Return an RFC 822 formatted date.
+ * Return the current date and time as an RFC 822 formatted date.
*
* @return string
*/
@@ -4084,7 +4453,51 @@ public static function rfcDate()
//Will default to UTC if it's not set properly in php.ini
date_default_timezone_set(@date_default_timezone_get());
- return date('D, j M Y H:i:s O');
+ return date(self::RFC822_DATE_FORMAT);
+ }
+
+ /**
+ * Normalise a user-supplied date into a correctly-formatted RFC 5322 date value
+ * string suitable for use in the Date header.
+ *
+ * Accepts:
+ * - A {@see \DateTime} (or \DateTimeImmutable) object
+ * - Any date/time string understood by PHP's DateTime constructor (RFC 5322, ISO 8601,
+ * Unix timestamp with leading "@", natural-language strings, etc.)
+ *
+ * Dates in the future are not permitted for email headers; if the parsed date is later
+ * than "now" the method falls back to the current time via {@see self::rfcDate()}.
+ * An empty value, a non-string/non-DateTime argument, or any value that cannot be
+ * parsed will likewise fall back to {@see self::rfcDate()}.
+ *
+ * @param \DateTime|\DateTimeImmutable|string $date The date to normalise
+ *
+ * @return string An RFC 5322-formatted date string
+ */
+ private static function sanitiseDate($date)
+ {
+ try {
+ //Ensure the default timezone is set properly
+ date_default_timezone_set(@date_default_timezone_get());
+
+ if ($date instanceof \DateTimeInterface) {
+ $dt = $date;
+ } elseif (is_string($date) && $date !== '') {
+ $dt = new \DateTime($date);
+ } else {
+ //Empty string, null, or any unsupported type
+ return self::rfcDate();
+ }
+
+ //Reject future dates — they are invalid for outgoing message headers
+ if ($dt->getTimestamp() > time()) {
+ return self::rfcDate();
+ }
+
+ return $dt->format(self::RFC822_DATE_FORMAT);
+ } catch (\Exception $e) {
+ return self::rfcDate();
+ }
}
/**
@@ -4102,7 +4515,7 @@ protected function serverHostname()
$result = $_SERVER['SERVER_NAME'];
} elseif (function_exists('gethostname') && gethostname() !== false) {
$result = gethostname();
- } elseif (php_uname('n') !== false) {
+ } elseif (php_uname('n') !== '') {
$result = php_uname('n');
}
if (!static::isValidHost($result)) {
@@ -4127,7 +4540,7 @@ public static function isValidHost($host)
empty($host)
|| !is_string($host)
|| strlen($host) > 256
- || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+\])$/', $host)
+ || !preg_match('/^([a-z\d.-]*|\[[a-f\d:]+\])$/i', $host)
) {
return false;
}
@@ -4141,10 +4554,49 @@ public static function isValidHost($host)
//Is it a valid IPv4 address?
return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
}
- //Is it a syntactically valid hostname (when embeded in a URL)?
- return filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false;
+ //Is it a syntactically valid hostname (when embedded in a URL)?
+ return filter_var('https://' . $host, FILTER_VALIDATE_URL) !== false;
+ }
+
+ /**
+ * Check whether the supplied address uses Unicode in the local part.
+ *
+ * @return bool
+ */
+ protected function addressHasUnicodeLocalPart($address)
+ {
+ return (bool) preg_match('/[\x80-\xFF].*@/', $address);
+ }
+
+ /**
+ * Check whether any of the supplied addresses use Unicode in the local part.
+ *
+ * @return bool
+ */
+ protected function anyAddressHasUnicodeLocalPart($addresses)
+ {
+ foreach ($addresses as $address) {
+ if (is_array($address)) {
+ $address = $address[0];
+ }
+ if ($this->addressHasUnicodeLocalPart($address)) {
+ return true;
+ }
+ }
+ return false;
}
+ /**
+ * Check whether the message requires SMTPUTF8 based on what's known so far.
+ *
+ * @return bool
+ */
+ public function needsSMTPUTF8()
+ {
+ return $this->UseSMTPUTF8;
+ }
+
+
/**
* Get an error message in the current language.
*
@@ -4152,21 +4604,21 @@ public static function isValidHost($host)
*
* @return string
*/
- protected function lang($key)
+ protected static function lang($key)
{
- if (count($this->language) < 1) {
- $this->setLanguage(); //Set the default language
+ if (count(self::$language) < 1) {
+ self::setLanguage(); //Set the default language
}
- if (array_key_exists($key, $this->language)) {
+ if (array_key_exists($key, self::$language)) {
if ('smtp_connect_failed' === $key) {
//Include a link to troubleshooting docs on SMTP connection failure.
//This is by far the biggest cause of support questions
//but it's usually not PHPMailer's fault.
- return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
+ return self::$language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
}
- return $this->language[$key];
+ return self::$language[$key];
}
//Return the key as a fallback
@@ -4181,7 +4633,7 @@ protected function lang($key)
*/
private function getSmtpErrorMessage($base_key)
{
- $message = $this->lang($base_key);
+ $message = self::lang($base_key);
$error = $this->smtp->getError();
if (!empty($error['error'])) {
$message .= ' ' . $error['error'];
@@ -4225,7 +4677,7 @@ public function addCustomHeader($name, $value = null)
//Ensure name is not empty, and that neither name nor value contain line breaks
if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
if ($this->exceptions) {
- throw new Exception($this->lang('invalid_header'));
+ throw new Exception(self::lang('invalid_header'));
}
return false;
@@ -4256,10 +4708,10 @@ public function getCustomHeaders()
* Converts data-uri images into embedded attachments.
* If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
*
- * @param string $message HTML message string
- * @param string $basedir Absolute path to a base directory to prepend to relative paths to images
- * @param bool|callable $advanced Whether to use the internal HTML to text converter
- * or your own custom converter
+ * @param string $message HTML message string
+ * @param string $basedir Absolute path to a base directory to prepend to relative paths to images
+ * @param bool|callable $advanced Whether to use the internal HTML to text converter
+ * or your own custom converter
* @return string The transformed message body
*
* @throws Exception
@@ -4268,6 +4720,12 @@ public function getCustomHeaders()
*/
public function msgHTML($message, $basedir = '', $advanced = false)
{
+ $cid_domain = 'phpmailer.0';
+ if (filter_var($this->From, FILTER_VALIDATE_EMAIL)) {
+ //prepend with a character to create valid RFC822 string in order to validate
+ $cid_domain = substr($this->From, strrpos($this->From, '@') + 1);
+ }
+
preg_match_all('/(? 1 && '/' !== substr($basedir, -1)) {
@@ -4289,7 +4747,7 @@ public function msgHTML($message, $basedir = '', $advanced = false)
}
//Hash the decoded data, not the URL, so that the same data-URI image used in multiple places
//will only be embedded once, even if it used a different encoding
- $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; //RFC2392 S 2
+ $cid = substr(hash('sha256', $data), 0, 32) . '@' . $cid_domain; //RFC2392 S 2
if (!$this->cidExists($cid)) {
$this->addStringEmbeddedImage(
@@ -4323,7 +4781,7 @@ public function msgHTML($message, $basedir = '', $advanced = false)
$directory = '';
}
//RFC2392 S 2
- $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0';
+ $cid = substr(hash('sha256', $url), 0, 32) . '@' . $cid_domain;
if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
$basedir .= '/';
}
@@ -4553,7 +5011,7 @@ public static function filenameToType($filename)
* Multi-byte-safe pathinfo replacement.
* Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
*
- * @see http://www.php.net/manual/en/function.pathinfo.php#107461
+ * @see https://www.php.net/manual/en/function.pathinfo.php#107461
*
* @param string $path A filename or path, does not need to exist as a file
* @param int|string $options Either a PATHINFO_* constant,
@@ -4618,7 +5076,7 @@ public function set($name, $value = '')
return true;
}
- $this->setError($this->lang('variable_set') . $name);
+ $this->setError(self::lang('variable_set') . $name);
return false;
}
@@ -4756,7 +5214,7 @@ public function DKIM_Sign($signHeader)
{
if (!defined('PKCS7_TEXT')) {
if ($this->exceptions) {
- throw new Exception($this->lang('extension_missing') . 'openssl');
+ throw new Exception(self::lang('extension_missing') . 'openssl');
}
return '';
@@ -4771,12 +5229,14 @@ public function DKIM_Sign($signHeader)
}
if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
if (\PHP_MAJOR_VERSION < 8) {
+ // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.openssl_pkey_freeDeprecated
openssl_pkey_free($privKey);
}
return base64_encode($signature);
}
if (\PHP_MAJOR_VERSION < 8) {
+ // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.openssl_pkey_freeDeprecated
openssl_pkey_free($privKey);
}
@@ -4788,7 +5248,7 @@ public function DKIM_Sign($signHeader)
* Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
* Canonicalized headers should *always* use CRLF, regardless of mailer setting.
*
- * @see https://tools.ietf.org/html/rfc6376#section-3.4.2
+ * @see https://www.rfc-editor.org/rfc/rfc6376#section-3.4.2
*
* @param string $signHeader Header
*
@@ -4800,7 +5260,7 @@ public function DKIM_HeaderC($signHeader)
$signHeader = static::normalizeBreaks($signHeader, self::CRLF);
//Unfold header lines
//Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
- //@see https://tools.ietf.org/html/rfc5322#section-2.2
+ //@see https://www.rfc-editor.org/rfc/rfc5322#section-2.2
//That means this may break if you do something daft like put vertical tabs in your headers.
$signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
//Break headers out into an array
@@ -4832,7 +5292,7 @@ public function DKIM_HeaderC($signHeader)
* Uses the 'simple' algorithm from RFC6376 section 3.4.3.
* Canonicalized bodies should *always* use CRLF, regardless of mailer setting.
*
- * @see https://tools.ietf.org/html/rfc6376#section-3.4.3
+ * @see https://www.rfc-editor.org/rfc/rfc6376#section-3.4.3
*
* @param string $body Message Body
*
@@ -4868,7 +5328,7 @@ public function DKIM_Add($headers_line, $subject, $body)
$DKIMquery = 'dns/txt'; //Query method
$DKIMtime = time();
//Always sign these headers without being asked
- //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1
+ //Recommended list from https://www.rfc-editor.org/rfc/rfc6376#section-5.4.1
$autoSignHeaders = [
'from',
'to',
@@ -4974,7 +5434,7 @@ public function DKIM_Add($headers_line, $subject, $body)
}
//The DKIM-Signature header is included in the signature *except for* the value of the `b` tag
//which is appended after calculating the signature
- //https://tools.ietf.org/html/rfc6376#section-3.5
+ //https://www.rfc-editor.org/rfc/rfc6376#section-3.5
$dkimSignatureHeader = 'DKIM-Signature: v=1;' .
' d=' . $this->DKIM_domain . ';' .
' s=' . $this->DKIM_selector . ';' . static::$LE .
diff --git a/www/libs/PHPMailer/POP3.php b/www/libs/vendor/phpmailer/phpmailer/src/POP3.php
similarity index 92%
rename from www/libs/PHPMailer/POP3.php
rename to www/libs/vendor/phpmailer/phpmailer/src/POP3.php
index d025689e8..0ba967837 100644
--- a/www/libs/PHPMailer/POP3.php
+++ b/www/libs/vendor/phpmailer/phpmailer/src/POP3.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -45,8 +45,9 @@ class POP3
* The POP3 PHPMailer Version number.
*
* @var string
+ * @deprecated This constant will be removed in PHPMailer 8.0. Use `PHPMailer::VERSION` instead.
*/
- const VERSION = '6.8.0';
+ const VERSION = '7.1.1';
/**
* Default POP3 port number.
@@ -211,9 +212,9 @@ public function authorise($host, $port = false, $timeout = false, $username = ''
} else {
$this->tval = (int) $timeout;
}
- $this->do_debug = $debug_level;
- $this->username = $username;
- $this->password = $password;
+ $this->do_debug = (int) $debug_level;
+ $this->username = self::stripControls($username);
+ $this->password = self::stripControls($password);
//Reset the error log
$this->errors = [];
//Connect
@@ -250,7 +251,9 @@ public function connect($host, $port = false, $tval = 30)
//On Windows this will raise a PHP Warning error if the hostname doesn't exist.
//Rather than suppress it with @fsockopen, capture it cleanly instead
- set_error_handler([$this, 'catchWarning']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'catchWarning'], func_get_args());
+ });
if (false === $port) {
$port = static::DEFAULT_PORT;
@@ -316,7 +319,8 @@ public function login($username = '', $password = '')
if (empty($password)) {
$password = $this->password;
}
-
+ $username = self::stripControls($username);
+ $password = self::stripControls($password);
//Send the Username
$this->sendString("USER $username" . static::LE);
$pop3_response = $this->getResponse();
@@ -404,7 +408,7 @@ protected function sendString($string)
/**
* Checks the POP3 server response.
- * Looks for for +OK or -ERR.
+ * Looks for +OK or -ERR.
*
* @param string $string
*
@@ -464,4 +468,16 @@ protected function catchWarning($errno, $errstr, $errfile, $errline)
"errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline"
);
}
+
+ /**
+ * Strip all control chars from a string.
+ *
+ * @param $string
+ *
+ * @return string
+ */
+ protected static function stripControls($string)
+ {
+ return preg_replace('/[\x00-\x1F\x7F]/u', '', $string);
+ }
}
diff --git a/www/libs/PHPMailer/SMTP.php b/www/libs/vendor/phpmailer/phpmailer/src/SMTP.php
similarity index 84%
rename from www/libs/PHPMailer/SMTP.php
rename to www/libs/vendor/phpmailer/phpmailer/src/SMTP.php
index fc4b781a5..f0957b80a 100644
--- a/www/libs/PHPMailer/SMTP.php
+++ b/www/libs/vendor/phpmailer/phpmailer/src/SMTP.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -34,8 +34,9 @@ class SMTP
* The PHPMailer SMTP version number.
*
* @var string
+ * @deprecated This constant will be removed in PHPMailer 8.0. Use `PHPMailer::VERSION` instead.
*/
- const VERSION = '6.8.0';
+ const VERSION = '7.1.1';
/**
* SMTP line break constant.
@@ -62,7 +63,7 @@ class SMTP
* The maximum line length allowed by RFC 5321 section 4.5.3.1.6,
* *excluding* a trailing CRLF break.
*
- * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6
+ * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.6
*
* @var int
*/
@@ -72,7 +73,7 @@ class SMTP
* The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5,
* *including* a trailing CRLF line break.
*
- * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5
+ * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.5
*
* @var int
*/
@@ -152,19 +153,28 @@ class SMTP
/**
* Whether to use VERP.
*
- * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path
- * @see http://www.postfix.org/VERP_README.html Info on VERP
+ * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
+ * @see https://www.postfix.org/VERP_README.html Info on VERP
*
* @var bool
*/
public $do_verp = false;
+ /**
+ * Whether to use SMTPUTF8.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc6531
+ *
+ * @var bool
+ */
+ public $do_smtputf8 = false;
+
/**
* The timeout value for connection, in seconds.
* Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
* This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
*
- * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2
+ * @see https://www.rfc-editor.org/rfc/rfc2821#section-4.5.3.2
*
* @var int
*/
@@ -187,15 +197,28 @@ class SMTP
*/
protected $smtp_transaction_id_patterns = [
'exim' => '/[\d]{3} OK id=(.*)/',
- 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/',
- 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/',
- 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/',
+ 'sendmail' => '/[\d]{3} 2\.0\.0 (.*) Message/',
+ 'postfix' => '/[\d]{3} 2\.0\.0 Ok: queued as (.*)/',
+ 'Microsoft_ESMTP' => '/[0-9]{3} 2\.[\d]\.0 (.*)@(?:.*) Queued mail for delivery/',
'Amazon_SES' => '/[\d]{3} Ok (.*)/',
'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
- 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
+ 'CampaignMonitor' => '/[\d]{3} 2\.0\.0 OK:([a-zA-Z\d]{48})/',
'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
'Mailjet' => '/[\d]{3} OK queued as (.*)/',
+ 'Gsmtp' => '/[\d]{3} 2\.0\.0 OK (.*) - gsmtp/',
+ ];
+
+ /**
+ * Allowed SMTP XCLIENT attributes.
+ * Must be allowed by the SMTP server. EHLO response is not checked.
+ *
+ * @see https://www.postfix.org/XCLIENT_README.html
+ *
+ * @var array
+ */
+ public static $xclient_allowed_attributes = [
+ 'NAME', 'ADDR', 'PORT', 'PROTO', 'HELO', 'LOGIN', 'DESTADDR', 'DESTPORT'
];
/**
@@ -268,7 +291,8 @@ protected function edebug($str, $level = 0)
}
//Is this a PSR-3 logger?
if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
- $this->Debugoutput->debug($str);
+ //Remove trailing line breaks potentially added by calls to SMTP::client_send()
+ $this->Debugoutput->debug(rtrim($str, "\r\n"));
return;
}
@@ -281,6 +305,7 @@ protected function edebug($str, $level = 0)
switch ($this->Debugoutput) {
case 'error_log':
//Don't output, just log
+ /** @noinspection ForgottenDebugOutputInspection */
error_log($str);
break;
case 'html':
@@ -359,7 +384,7 @@ public function connect($host, $port = null, $timeout = 30, $options = [])
}
//Anything other than a 220 response means something went wrong
//RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error
- //https://tools.ietf.org/html/rfc5321#section-3.1
+ //https://www.rfc-editor.org/rfc/rfc5321#section-3.1
if ($responseCode === 554) {
$this->quit();
}
@@ -392,7 +417,9 @@ protected function getSMTPConnection($host, $port = null, $timeout = 30, $option
$errstr = '';
if ($streamok) {
$socket_context = stream_context_create($options);
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$connection = stream_socket_client(
$host . ':' . $port,
$errno,
@@ -407,7 +434,9 @@ protected function getSMTPConnection($host, $port = null, $timeout = 30, $option
'Connection: stream_socket_client not available, falling back to fsockopen',
self::DEBUG_CONNECTION
);
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$connection = fsockopen(
$host,
$port,
@@ -466,12 +495,16 @@ public function startTLS()
//PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
//so add them back in manually if we can
if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
+ // phpcs:ignore PHPCompatibility.Constants.NewConstants.stream_crypto_method_tlsv1_2_clientFound
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+ // phpcs:ignore PHPCompatibility.Constants.NewConstants.stream_crypto_method_tlsv1_1_clientFound
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
}
//Begin encrypted connection
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$crypto_ok = stream_socket_enable_crypto(
$this->smtp_conn,
true,
@@ -562,7 +595,7 @@ public function authenticate(
}
//Send encoded username and password
if (
- //Format from https://tools.ietf.org/html/rfc4616#section-2
+ //Format from https://www.rfc-editor.org/rfc/rfc4616#section-2
//We skip the first field (it's forgery), so the string starts with a null byte
!$this->sendCommand(
'User & Password',
@@ -603,11 +636,48 @@ public function authenticate(
if (null === $OAuth) {
return false;
}
- $oauth = $OAuth->getOauth64();
-
- //Start authentication
- if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
- return false;
+ try {
+ $oauth = $OAuth->getOauth64();
+ } catch (\Exception $e) {
+ // We catch all exceptions and convert them to PHPMailer exceptions to be able to
+ // handle them correctly later
+ throw new Exception("SMTP authentication error", 0, $e);
+ }
+ /*
+ * An SMTP command line can have a maximum length of 512 bytes, including the command name,
+ * so the base64-encoded OAUTH token has a maximum length of:
+ * 512 - 13 (AUTH XOAUTH2) - 2 (CRLF) = 497 bytes
+ * If the token is longer than that, the command and the token must be sent separately as described in
+ * https://www.rfc-editor.org/rfc/rfc4954#section-4
+ */
+ if ($oauth === '') {
+ //Sending an empty auth token is legitimate, but it must be encoded as '='
+ //to indicate it's not a 2-part command
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 =', 235)) {
+ return false;
+ }
+ } elseif (strlen($oauth) <= 497) {
+ //Authenticate using a token in the initial-response part
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
+ return false;
+ }
+ } else {
+ //The token is too long, so we need to send it in two parts.
+ //Send the auth command without a token and expect a 334
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2', 334)) {
+ return false;
+ }
+ //Send the token
+ if (!$this->sendCommand('OAuth TOKEN', $oauth, [235, 334])) {
+ return false;
+ }
+ //If the server answers with 334, send an empty line and wait for a 235
+ if (
+ substr($this->last_reply, 0, 3) === '334'
+ && $this->sendCommand('AUTH End', '', 235)
+ ) {
+ return false;
+ }
}
break;
default:
@@ -636,7 +706,7 @@ protected function hmac($data, $key)
}
//The following borrowed from
- //http://php.net/manual/en/function.mhash.php#27225
+ //https://www.php.net/manual/en/function.mhash.php#27225
//RFC 2104 HMAC implementation for php.
//Creates an md5 HMAC.
@@ -700,11 +770,30 @@ public function close()
}
}
+ private function iterateLines($s)
+ {
+ $start = 0;
+ $length = strlen($s);
+
+ for ($i = 0; $i < $length; $i++) {
+ $c = $s[$i];
+ if ($c === "\n" || $c === "\r") {
+ yield substr($s, $start, $i - $start);
+ if ($c === "\r" && $i + 1 < $length && $s[$i + 1] === "\n") {
+ $i++;
+ }
+ $start = $i + 1;
+ }
+ }
+
+ yield substr($s, $start);
+ }
+
/**
* Send an SMTP DATA command.
* Issues a data command and sends the msg_data to the server,
* finalizing the mail transaction. $msg_data is the message
- * that is to be send with the headers. Each header needs to be
+ * that is to be sent with the headers. Each header needs to be
* on a single line followed by a with the message headers
* and the message body being separated by an additional .
* Implements RFC 821: DATA .
@@ -728,15 +817,16 @@ public function data($msg_data)
* NOTE: this does not count towards line-length limit.
*/
- //Normalize line breaks before exploding
- $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
+ //Iterate over lines with normalized line breaks
+ $lines = $this->iterateLines($msg_data);
/* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
- * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
+ * of the first line (':' separated) does not contain a space then it _should_ be a header, and we will
* process all lines before a blank line as headers.
*/
- $field = substr($lines[0], 0, strpos($lines[0], ':'));
+ $first_line = $lines->current();
+ $field = substr($first_line, 0, strpos($first_line, ':'));
$in_headers = false;
if (!empty($field) && strpos($field, ' ') === false) {
$in_headers = true;
@@ -775,7 +865,7 @@ public function data($msg_data)
//Send the lines to the server
foreach ($lines_out as $line_out) {
//Dot-stuffing as per RFC5321 section 4.5.2
- //https://tools.ietf.org/html/rfc5321#section-4.5.2
+ //https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2
if (!empty($line_out) && $line_out[0] === '.') {
$line_out = '.' . $line_out;
}
@@ -893,7 +983,15 @@ protected function parseHelloFields($type)
* $from. Returns true if successful or false otherwise. If True
* the mail transaction is started and then one or more recipient
* commands may be called followed by a data command.
- * Implements RFC 821: MAIL FROM:.
+ * Implements RFC 821: MAIL FROM: and
+ * two extensions, namely XVERP and SMTPUTF8.
+ *
+ * The server's EHLO response is not checked. If use of either
+ * extensions is enabled even though the server does not support
+ * that, mail submission will fail.
+ *
+ * XVERP is documented at https://www.postfix.org/VERP_README.html
+ * and SMTPUTF8 is specified in RFC 6531.
*
* @param string $from Source address of this message
*
@@ -902,10 +1000,11 @@ protected function parseHelloFields($type)
public function mail($from)
{
$useVerp = ($this->do_verp ? ' XVERP' : '');
+ $useSmtputf8 = ($this->do_smtputf8 ? ' SMTPUTF8' : '');
return $this->sendCommand(
'MAIL FROM',
- 'MAIL FROM:<' . $from . '>' . $useVerp,
+ 'MAIL FROM:<' . $from . '>' . $useSmtputf8 . $useVerp,
250
);
}
@@ -971,6 +1070,25 @@ public function recipient($address, $dsn = '')
);
}
+ /**
+ * Send SMTP XCLIENT command to server and check its return code.
+ *
+ * @return bool True on success
+ */
+ public function xclient(array $vars)
+ {
+ $xclient_options = "";
+ foreach ($vars as $key => $value) {
+ if (in_array($key, SMTP::$xclient_allowed_attributes)) {
+ $xclient_options .= " {$key}={$value}";
+ }
+ }
+ if (!$xclient_options) {
+ return true;
+ }
+ return $this->sendCommand('XCLIENT', 'XCLIENT' . $xclient_options, 250);
+ }
+
/**
* Send an SMTP RSET command.
* Abort any transaction that is currently in progress.
@@ -1131,7 +1249,9 @@ public function client_send($data, $command = '')
} else {
$this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
}
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$result = fwrite($this->smtp_conn, $data);
restore_error_handler();
@@ -1169,7 +1289,7 @@ public function getServerExtList()
* 3. EHLO has been sent -
* $name == 'HELO'|'EHLO': returns the server name
* $name == any other string: if extension $name exists, returns True
- * or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
+ * or its options (e.g. AUTH mechanisms supported). Otherwise, returns False.
*
* @param string $name Name of SMTP extension or 'HELO'|'EHLO'
*
@@ -1234,7 +1354,9 @@ protected function get_lines()
while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
//Must pass vars in here as params are by reference
//solution for signals inspired by https://github.com/symfony/symfony/pull/6540
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$n = stream_select($selR, $selW, $selW, $this->Timelimit);
restore_error_handler();
@@ -1248,7 +1370,16 @@ protected function get_lines()
//stream_select returns false when the `select` system call is interrupted
//by an incoming signal, try the select again
- if (stripos($message, 'interrupted system call') !== false) {
+ if (
+ stripos($message, 'interrupted system call') !== false ||
+ (
+ // on applications with a different locale than english, the message above is not found because
+ // it's translated. So we also check for the SOCKET_EINTR constant which is defined under
+ // Windows and UNIX-like platforms (if available on the platform).
+ defined('SOCKET_EINTR') &&
+ stripos($message, 'stream_select(): Unable to select [' . SOCKET_EINTR . ']') !== false
+ )
+ ) {
$this->edebug(
'SMTP -> get_lines(): retrying stream_select',
self::DEBUG_LOWLEVEL
@@ -1321,6 +1452,26 @@ public function getVerp()
return $this->do_verp;
}
+ /**
+ * Enable or disable use of SMTPUTF8.
+ *
+ * @param bool $enabled
+ */
+ public function setSMTPUTF8($enabled = false)
+ {
+ $this->do_smtputf8 = $enabled;
+ }
+
+ /**
+ * Get SMTPUTF8 use.
+ *
+ * @return bool
+ */
+ public function getSMTPUTF8()
+ {
+ return $this->do_smtputf8;
+ }
+
/**
* Set error messages and codes.
*
diff --git a/www/libs/vendor/phpstan/phpstan/CLAUDE.md b/www/libs/vendor/phpstan/phpstan/CLAUDE.md
new file mode 100644
index 000000000..bed0f2088
--- /dev/null
+++ b/www/libs/vendor/phpstan/phpstan/CLAUDE.md
@@ -0,0 +1,115 @@
+# PHPStan - PHP Static Analysis Tool
+
+## Project Overview
+
+PHPStan finds errors in PHP code without running it. It catches bugs before tests are written, moving PHP closer to compiled languages. This is the **distribution repository** — the compiled PHAR and supporting infrastructure. The actual source code lives at [phpstan/phpstan-src](https://github.com/phpstan/phpstan-src).
+
+- **Website:** https://phpstan.org/
+- **Documentation:** https://phpstan.org/user-guide/getting-started
+- **API Reference:** https://apiref.phpstan.org/
+
+## Repository Structure
+
+```
+├── phpstan # CLI entry point (shell script loading the PHAR)
+├── phpstan.phar # Compiled PHAR archive (~26 MB)
+├── phpstan.phar.asc # GPG signature for the PHAR
+├── bootstrap.php # PHAR autoloader with PHP version polyfills
+├── composer.json # Package definition (requires PHP ^7.4|^8.0)
+├── .phar-checksum # MD5 + SHA1 checksums for reproducible builds
+├── conf/
+│ └── bleedingEdge.neon # Bleeding edge configuration profile
+├── e2e/ # End-to-end tests (~67 test scenarios)
+├── docker/ # Dockerfiles for PHP 8.0–8.4
+├── identifier-extractor/ # Tool to extract error identifiers from rule source code
+├── playground-api/ # AWS Lambda API for the online playground (TypeScript)
+├── playground-runner/ # AWS Lambda runner for the playground (PHP/Bref)
+├── website/ # phpstan.org static site (Eleventy + Vite + TailwindCSS)
+└── .github/workflows/ # CI/CD workflows
+```
+
+## Key Concepts
+
+### Distribution Model
+
+This repository distributes a pre-built PHAR archive. The source code is in [phpstan/phpstan-src](https://github.com/phpstan/phpstan-src), which is on PHP 8.1+ internally but the PHAR build is downgraded to support PHP 7.4+. The `phpstan` script loads the PHAR and delegates to the bundled binary.
+
+### PHP Version Support
+
+- **This distribution package:** PHP ^7.4|^8.0 (defined in composer.json)
+- **phpstan-src internally:** PHP 8.1+ (downgraded during PHAR build)
+- **Other extension repositories** (phpstan-strict-rules, phpstan-doctrine, phpstan-symfony, etc.): still support PHP 7.4+
+- **E2E tests run on:** PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5 (Linux + Windows)
+
+### Bleeding Edge
+
+The `conf/bleedingEdge.neon` configuration enables experimental/strict checks before they become defaults. Users opt in by including this config.
+
+### Error Identifiers
+
+Every PHPStan error has a unique identifier (e.g., `argument.type`, `deadCode.unreachable`). The `identifier-extractor/` tool scans all PHPStan repositories to extract these identifiers, producing JSON used to generate documentation on the website.
+
+## Development
+
+
+### CI Workflows
+
+Key workflows in `.github/workflows/`:
+
+- **`tests.yml`** — Runs e2e tests on PHP 7.4–8.5, Linux + Windows. Triggered on changes to `e2e/**`, `phpstan`, `.phar-checksum`, `bootstrap.php`.
+- **`other-tests.yml`** — Additional integration tests (PHP-Parser, React Promise, etc.)
+- **`integration-tests.yml`** — Tests against major projects (Rector, Larastan, Carbon, etc.)
+- **`extension-tests.yml`** — Tests 1st-party PHPStan extensions (PHPUnit, Doctrine, Symfony, etc.)
+- **`release.yml`** — Creates GitHub release on tag push, uploads `phpstan.phar` and signature
+- **`docker-stable.yml`** / **`docker-nightly.yml`** — Builds multi-arch Docker images (arm64 + amd64) pushed to ghcr.io
+- **`extract-identifiers.yml`** — Extracts error identifiers from all PHPStan repos
+- **`website.yml`** — Builds and deploys phpstan.org
+- **`generate-error-docs.lock.yml`** — Generates error documentation using Claude
+
+### Current Branch
+
+The main development branch for this repository is `2.2.x`.
+
+## Website
+
+The website (phpstan.org) lives in `website/` and has its own `website/CLAUDE.md` with detailed instructions. Key points:
+
+- Built with Eleventy (11ty) + Vite + TailwindCSS
+- Two-stage build: `npm run build:11ty` then `npm run build:vite`
+- Deployed to AWS S3 + CloudFront
+- Error documentation in `website/errors/` is auto-generated (see `website/errors/CLAUDE.md`)
+
+## Playground
+
+The online playground at https://phpstan.org/try consists of two AWS Lambda services:
+
+- **playground-api/** — TypeScript API that orchestrates analysis across multiple PHP versions
+- **playground-runner/** — PHP Lambda (via Bref) that executes PHPStan against submitted code
+
+Both are deployed via the Serverless Framework to eu-west-1.
+
+## Docker
+
+Dockerfiles in `docker/` build images for PHP 8.0–8.4, based on `php:*-cli-alpine`. Images are published to `ghcr.io` as multi-architecture (arm64 + amd64). The images install PHPStan via Composer and use `phpstan` as the entrypoint.
+
+## Related Repositories
+
+- [phpstan/phpstan-src](https://github.com/phpstan/phpstan-src) — Main source code (PHP 8.1+, downgraded for PHAR)
+- [phpstan/phpstan-strict-rules](https://github.com/phpstan/phpstan-strict-rules) — Additional strict rules
+- [phpstan/phpstan-deprecation-rules](https://github.com/phpstan/phpstan-deprecation-rules) — Deprecation detection
+- [phpstan/phpstan-doctrine](https://github.com/phpstan/phpstan-doctrine) — Doctrine integration
+- [phpstan/phpstan-symfony](https://github.com/phpstan/phpstan-symfony) — Symfony integration
+- [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) — PHPUnit integration
+- [phpstan/phpstan-nette](https://github.com/phpstan/phpstan-nette) — Nette integration
+- [phpstan/phpstan-webmozart-assert](https://github.com/phpstan/phpstan-webmozart-assert) — Webmozart Assert integration
+- [phpstan/phpdoc-parser](https://github.com/phpstan/phpdoc-parser) — PHPDoc parser library
+
+Extension repositories support PHP 7.4+ and some support multiple versions of the libraries they analyse.
+
+## Making Changes
+
+- **Source code changes** belong in [phpstan/phpstan-src](https://github.com/phpstan/phpstan-src), not here
+- **E2E tests** can be added or modified in `e2e/`
+- **Website content** is in `website/src/` (see `website/CLAUDE.md`)
+- **Error documentation** is in `website/errors/` (see `website/errors/CLAUDE.md`)
+- Configuration uses NEON format (Nette Object Notation), similar to YAML
diff --git a/www/libs/vendor/phpstan/phpstan/LICENSE b/www/libs/vendor/phpstan/phpstan/LICENSE
new file mode 100644
index 000000000..e5f34e607
--- /dev/null
+++ b/www/libs/vendor/phpstan/phpstan/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2016 Ondřej Mirtes
+Copyright (c) 2025 PHPStan s.r.o.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/www/libs/vendor/phpstan/phpstan/README.md b/www/libs/vendor/phpstan/phpstan/README.md
new file mode 100644
index 000000000..443d1d13d
--- /dev/null
+++ b/www/libs/vendor/phpstan/phpstan/README.md
@@ -0,0 +1,119 @@
+
PHPStan - PHP Static Analysis Tool
+
+
+
+
+
+
+
+
+
+
+
+
+
+------
+
+PHPStan focuses on finding errors in your code without actually running it. It catches whole classes of bugs
+even before you write tests for the code. It moves PHP closer to compiled languages in the sense that the correctness of each line of the code
+can be checked before you run the actual line.
+
+**[Read more about PHPStan »](https://phpstan.org/)**
+
+**[Try out PHPStan on the on-line playground! »](https://phpstan.org/try)**
+
+## Sponsors
+
+Want your logo here? [Learn more »](https://phpstan.org/sponsor)
+
+### Gold Sponsors
+
+
+
+
+
+
+
+
+
+### Silver Sponsors
+
+
+
+
+
+
+### Bronze Sponsors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[**You can sponsor my open-source work on PHPStan through GitHub Sponsors and also directly.**](https://phpstan.org/sponsor)
+
+One-time donations [through Revolut.me](https://revolut.me/ondrejmirtes) are also accepted. To request an invoice, [contact me](mailto:ondrej@mirtes.cz) through e-mail.
+
+## Documentation
+
+All the documentation lives on the [phpstan.org website](https://phpstan.org/):
+
+* [Getting Started & User Guide](https://phpstan.org/user-guide/getting-started)
+* [Config Reference](https://phpstan.org/config-reference)
+* [PHPDocs Basics](https://phpstan.org/writing-php-code/phpdocs-basics) & [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types)
+* [Extension Library](https://phpstan.org/user-guide/extension-library)
+* [Developing Extensions](https://phpstan.org/developing-extensions/extension-types)
+* [API Reference](https://apiref.phpstan.org/)
+
+## PHPStan Pro
+
+PHPStan Pro is a paid add-on on top of open-source PHPStan Static Analysis Tool with these premium features:
+
+* Web UI for browsing found errors, you can click and open your editor of choice on the offending line.
+* Continuous analysis (watch mode): scans changed files in the background, refreshes the UI automatically.
+
+Try it on PHPStan 0.12.45 or later by running it with the `--pro` option. You can create an account either by following the on-screen instructions, or by visiting [account.phpstan.com](https://account.phpstan.com/).
+
+After 30-day free trial period it costs 7 EUR for individuals monthly, 70 EUR for teams (up to 25 members). By paying for PHPStan Pro, you're supporting the development of open-source PHPStan.
+
+You can read more about it on [PHPStan's website](https://phpstan.org/blog/introducing-phpstan-pro).
+
+## Code of Conduct
+
+This project adheres to a [Contributor Code of Conduct](https://github.com/phpstan/phpstan/blob/master/CODE_OF_CONDUCT.md). By participating in this project and its community, you are expected to uphold this code.
+
+## Contributing
+
+Any contributions are welcome. PHPStan's source code open to pull requests lives at [`phpstan/phpstan-src`](https://github.com/phpstan/phpstan-src).
diff --git a/www/libs/vendor/phpstan/phpstan/UPGRADING.md b/www/libs/vendor/phpstan/phpstan/UPGRADING.md
new file mode 100644
index 000000000..522949096
--- /dev/null
+++ b/www/libs/vendor/phpstan/phpstan/UPGRADING.md
@@ -0,0 +1,338 @@
+Upgrading from PHPStan 1.x to 2.0
+=================================
+
+## PHP version requirements
+
+PHPStan now requires PHP 7.4 or newer to run.
+
+## Upgrading guide for end users
+
+The best way to get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release**
+and enable [**Bleeding Edge**](https://phpstan.org/blog/what-is-bleeding-edge). This will enable the new rules and behaviours that 2.0 turns on for all users.
+
+Also make sure to install and enable [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules).
+
+Once you get to a green build with no deprecations showed on latest PHPStan 1.12.x with Bleeding Edge enabled, you can update all your related PHPStan dependencies to 2.0 in `composer.json`:
+
+```json
+"require-dev": {
+ "phpstan/phpstan": "^2.0",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-doctrine": "^2.0",
+ "phpstan/phpstan-nette": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpstan/phpstan-symfony": "^2.0",
+ "phpstan/phpstan-webmozart-assert": "^2.0",
+ ...
+}
+```
+
+Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-guide/extension-library) as well.
+
+After changing your `composer.json`, run `composer update 'phpstan/*' -W`.
+
+It's up to you whether you go through the new reported errors or if you just put them all to the [baseline](https://phpstan.org/user-guide/baseline) ;) Everyone who's on PHPStan 1.12 should be able to upgrade to PHPStan 2.0.
+
+### Noteworthy changes to code analysis
+
+* [**Enhancements in handling parameters passed by reference**](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference)
+* [**Validate inline PHPDoc `@var` tag type**](https://phpstan.org/blog/phpstan-1-10-comes-with-lie-detector#validate-inline-phpdoc-%40var-tag-type)
+* [**List type enforced**](https://phpstan.org/blog/phpstan-1-9-0-with-phpdoc-asserts-list-type#list-type)
+* **Always `true` conditions always reported**: previously reported only with phpstan-strict-rules, this is now always reported.
+
+### Removed option `checkMissingIterableValueType`
+
+It's strongly recommended to add the missing array typehints.
+
+If you want to continue ignoring missing typehints from arrays, add `missingType.iterableValue` error identifier to your `ignoreErrors`:
+
+```neon
+parameters:
+ ignoreErrors:
+ -
+ identifier: missingType.iterableValue
+```
+
+### Removed option `checkGenericClassInNonGenericObjectType`
+
+It's strongly recommended to add the missing generic typehints.
+
+If you want to continue ignoring missing typehints from generics, add `missingType.generics` error identifier to your `ignoreErrors`:
+
+```neon
+parameters:
+ ignoreErrors:
+ -
+ identifier: missingType.generics
+```
+
+### Removed `checkAlwaysTrue*` options
+
+These options have been removed because PHPStan now always behaves as if these were set to `true`:
+
+* `checkAlwaysTrueCheckTypeFunctionCall`
+* `checkAlwaysTrueInstanceof`
+* `checkAlwaysTrueStrictComparison`
+* `checkAlwaysTrueLooseComparison`
+
+### Removed option `excludes_analyse`
+
+It has been replaced with [`excludePaths`](https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files).
+
+### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern
+
+If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`:
+
+```neon
+parameters:
+ excludePaths:
+ - tests/*/data/*
+ - src/broken
+ - node_modules (?) # optional path, might not exist
+```
+
+If you have the same situation in `ignoreErrors` (ignoring an error in a path that might not exist), use `reportUnmatchedIgnoredErrors: false`.
+
+```neon
+parameters:
+ reportUnmatchedIgnoredErrors: false
+```
+
+Appending `(?)` in `ignoreErrors` is not supported.
+
+### Changes in 1st party PHPStan extensions
+
+* [phpstan-doctrine](https://github.com/phpstan/phpstan-doctrine)
+ * Removed config parameter `searchOtherMethodsForQueryBuilderBeginning` (extension now behaves as when this was set to `true`)
+ * Removed config parameter `queryBuilderFastAlgorithm` (extension now behaves as when this was set to `false`)
+* [phpstan-symfony](https://github.com/phpstan/phpstan-symfony)
+ * Removed legacy options with `_` in the name
+ * `container_xml_path` -> use `containerXmlPath`
+ * `constant_hassers` -> use `constantHassers`
+ * `console_application_loader` -> use `consoleApplicationLoader`
+
+### Minor backward compatibility breaks
+
+* Removed unused config parameter `cache.nodesByFileCountMax`
+* Removed unused config parameter `memoryLimitFile`
+* Removed unused feature toggle `disableRuntimeReflectionProvider`
+* Removed unused config parameter `staticReflectionClassNamePatterns`
+* Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead
+* Remove `tempResultCachePath` config parameter, use `resultCachePath` instead
+* `additionalConfigFiles` config parameter must be a list
+
+## Upgrading guide for extension developers
+
+> [!NOTE]
+> Please switch to PHPStan 2.0 in a new major version of your extension. It's not feasible to try to support both PHPStan 1.x and PHPStan 2.x with the same extension code.
+>
+> You can definitely get closer to supporting PHPStan 2.0 without increasing major version by solving reported deprecations and other issues by analysing your extension code with PHPStan & phpstan-deprecation-rules & Bleeding Edge, but the final leap and solving backward incompatibilities should be done by requiring `"phpstan/phpstan": "^2.0"` in your `composer.json`, and releasing a new major version.
+
+### PHPStan now uses nikic/php-parser v5
+
+See [UPGRADING](https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md) guide for PHP-Parser.
+
+The most notable change is how `throw` statement is represented. Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class, while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class.
+
+Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The
+`Stmt\Throw_` class has been removed.
+
+### PHPStan now uses phpstan/phpdoc-parser v2
+
+See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser.
+
+### Returning plain strings as errors no longer supported, use RuleErrorBuilder
+
+Identifiers are also required in custom rules.
+
+Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder)
+
+**Before**:
+
+```php
+return ['My error'];
+```
+
+**After**:
+
+```php
+return [
+ RuleErrorBuilder::message('My error')
+ ->identifier('my.error')
+ ->build(),
+];
+```
+
+### Deprecate various `instanceof *Type` in favour of new methods on `Type` interface
+
+Learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated)
+
+### Removed deprecated `ParametersAcceptorSelector::selectSingle()`
+
+Use [`ParametersAcceptorSelector::selectFromArgs()`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ParametersAcceptorSelector.html#_selectFromArgs) instead. It should be used in most places where `selectSingle()` was previously used, like dynamic return type extensions.
+
+**Before**:
+
+```php
+$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
+```
+
+**After**:
+
+```php
+$defaultReturnType = ParametersAcceptorSelector::selectFromArgs(
+ $scope,
+ $functionCall->getArgs(),
+ $functionReflection->getVariants()
+)->getReturnType();
+```
+
+If you're analysing function or method body itself and you're using one of the following methods, ask for `getParameters()` and `getReturnType()` directly on the reflection object:
+
+* [InClassMethodNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InClassMethodNode.html)
+* [InFunctionNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InFunctionNode.html)
+* [FunctionReturnStatementsNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.FunctionReturnStatementsNode.html)
+* [MethodReturnStatementsNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.MethodReturnStatementsNode.html)
+* [Scope::getFunction()](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.Scope.html#_getFunction)
+
+**Before**:
+
+```php
+$function = $node->getFunctionReflection();
+$returnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType();
+```
+
+**After**:
+
+```php
+$returnType = $node->getFunctionReflection()->getReturnType();
+```
+
+### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters
+
+[`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required):
+
+* `Expr $expr`
+* `Type $type`
+* `TypeSpecifierContext $context`
+* `Scope $scope`
+
+If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by this method), call `setAlwaysOverwriteTypes()` and `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::create()`). These methods return a new object (SpecifiedTypes is immutable).
+
+[`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) constructor now accepts:
+
+* `array $sureTypes`
+* `array $sureNotTypes`
+
+If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by the constructor), call `setAlwaysOverwriteTypes()` and `setRootExpr()`. These methods return a new object (SpecifiedTypes is immutable).
+
+### `ConstantArrayType` no longer extends `ArrayType`
+
+`Type::getArrays()` now returns `list`.
+
+Using `$type instanceof ArrayType` is [being deprecated anyway](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) so the impact of this change should be minimal.
+
+### Changed `TypeSpecifier::specifyTypesInCondition()`
+
+This method no longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable).
+
+### Node attributes `parent`, `previous`, `next` are no longer available
+
+Learn more: https://phpstan.org/blog/preprocessing-ast-for-custom-rules
+
+### Removed config parameter `scopeClass`
+
+As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtension`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.ExpressionTypeResolverExtension.html) interface instead and register it as a service.
+
+### Removed `PHPStan\Broker\Broker`
+
+Use [`PHPStan\Reflection\ReflectionProvider`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ReflectionProvider.html) instead.
+
+`BrokerAwareExtension` was also removed. Ask for `ReflectionProvider` in the extension constructor instead.
+
+Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createReflectionProvider()`.
+
+### List type is enabled for everyone
+
+Removed static methods from `AccessoryArrayListType` class:
+
+* `isListTypeEnabled()`
+* `setListTypeEnabled()`
+* `intersectWith()`
+
+Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::intersect($type, new AccessoryArrayListType())`.
+
+### Minor backward compatibility breaks
+
+* Classes that were previously `@final` were made `final`
+* Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required
+* Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required
+* ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties)
+* `Type::getSmallerType()`, `Type::getSmallerOrEqualType()`, `Type::getGreaterType()`, `Type::getGreaterOrEqualType()`, `Type::isSmallerThan()`, `Type::isSmallerThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument.
+* `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument.
+* Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes)
+* Remove `ArrayType::generalizeKeys()`
+* Remove `ArrayType::count()`, use `Type::getArraySize()` instead
+* Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead
+* Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead
+* Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead
+* Remove unused `PHPStanTestCase::$useStaticReflectionProvider`
+* Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead
+* Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead
+* Remove `AnalysisResult::getInternalErrors()`, use `getInternalErrorObjects()` instead
+* Remove `ConstantReflection::getValue()`, use `getValueExpr()` instead. To get `Type` from `Expr`, use `Scope::getType()` or `InitializerExprTypeResolver::getType()`
+* Remove `PropertyTag::getType()`, use `getReadableType()` / `getWritableType()` instead
+* Remove `GenericTypeVariableResolver`, use [`Type::getTemplateType()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType) instead
+* Rename `Type::isClassStringType()` to `Type::isClassString()`
+* Remove `Scope::isSpecified()`, use `hasExpressionType()` instead
+* Remove `ConstantArrayType::isEmpty()`, use `isIterableAtLeastOnce()->no()` instead
+* Remove `ConstantArrayType::getNextAutoIndex()`
+* Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type`
+ * Use `getFirstIterable*Type` and `getLastIterable*Type` instead
+* Remove `ConstantArrayType::generalizeToArray()`
+* Remove `ConstantArrayType::findTypeAndMethodName()`, use `findTypeAndMethodNames()` instead
+* Remove `ConstantArrayType::removeLast()`, use [`Type::popArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray) instead
+* Remove `ConstantArrayType::removeFirst()`, use [`Type::shiftArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray) instead
+* Remove `ConstantArrayType::reverse()`, use [`Type::reverseArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_reverseArray) instead
+* Remove `ConstantArrayType::chunk()`, use [`Type::chunkArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_chunkArray) instead
+* Remove `ConstantArrayType::slice()`, use [`Type::sliceArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_sliceArray) instead
+* Made `TypeUtils` thinner by removing methods:
+ * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead
+ * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead
+ * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead
+ * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize)
+ * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead
+ * Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead
+ * Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead
+ * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead
+ * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead
+* Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead
+* Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer `bool`
+* Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer `int`
+* Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead
+* `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool`
+* Remove `FunctionReflection::isFinal()`
+* [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html)
+* Remove `__set_state()` on objects that should not be serialized in cache
+* Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts `string`
+* `LevelsTestCase::dataTopics()` data provider made static
+* `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint
+* Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html)
+* Remove `CompoundType::isAcceptedWithReasonBy()`, `CompoundType::isAcceptedBy()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html)
+Remove `Type::isSuperTypeOfWithReason()`, `Type:isSuperTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html)
+* Remove `CompoundType::isSubTypeOfWithReasonBy()`, `CompoundType::isSubTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html)
+* Remove `TemplateType::isValidVarianceWithReason()`, changed `TemplateType::isValidVariance()` return type to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html)
+* `RuleLevelHelper::accepts()` return type changed from `bool` to [`RuleLevelHelperAcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html)
+* Changes around `ClassConstantReflection`
+ * Class `ClassConstantReflection` removed from BC promise, renamed to `RealClassConstantReflection`
+ * Interface `ConstantReflection` renamed to `ClassConstantReflection`
+ * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection`
+ * Interface `GlobalConstantReflection` renamed to `ConstantReflection`
+* Renamed interfaces and classes from `*WithPhpDocs` to `Extended*`
+ * `ParametersAcceptorWithPhpDocs` -> `ExtendedParametersAcceptor`
+ * `ParameterReflectionWithPhpDocs` -> `ExtendedParameterReflection`
+ * `FunctionVariantWithPhpDocs` -> `ExtendedFunctionVariant`
+* `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null`
+* Class `PHPStan\Node\ClassMethod` (accessible from `ClassMethodsNode`) is no longer an AST node
+ * Call `PHPStan\Node\ClassMethod::getNode()` to access the original AST node
diff --git a/www/libs/vendor/phpstan/phpstan/bootstrap.php b/www/libs/vendor/phpstan/phpstan/bootstrap.php
new file mode 100644
index 000000000..53370b560
--- /dev/null
+++ b/www/libs/vendor/phpstan/phpstan/bootstrap.php
@@ -0,0 +1,145 @@
+loadClass($class);
+
+ return;
+ }
+ if (strpos($class, 'PHPStan\\') !== 0 || strpos($class, 'PHPStan\\PhpDocParser\\') === 0) {
+ return;
+ }
+
+ if (!in_array('phar', stream_get_wrappers(), true)) {
+ throw new \Exception('Phar wrapper is not registered. Please review your php.ini settings.');
+ }
+
+ if (!self::$polyfillsLoaded) {
+ self::$polyfillsLoaded = true;
+
+ if (
+ PHP_VERSION_ID < 80000
+ && empty($GLOBALS['__composer_autoload_files']['a4a119a56e50fbb293281d9a48007e0e'])
+ && !class_exists(\Symfony\Polyfill\Php80\Php80::class, false)
+ ) {
+ $GLOBALS['__composer_autoload_files']['a4a119a56e50fbb293281d9a48007e0e'] = true;
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php80/Php80.php';
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php80/bootstrap.php';
+ }
+
+ if (
+ empty($GLOBALS['__composer_autoload_files']['0e6d7bf4a5811bfa5cf40c5ccd6fae6a'])
+ && !class_exists(\Symfony\Polyfill\Mbstring\Mbstring::class, false)
+ ) {
+ $GLOBALS['__composer_autoload_files']['0e6d7bf4a5811bfa5cf40c5ccd6fae6a'] = true;
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-mbstring/Mbstring.php';
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-mbstring/bootstrap.php';
+ }
+
+ if (
+ empty($GLOBALS['__composer_autoload_files']['e69f7f6ee287b969198c3c9d6777bd38'])
+ && !class_exists(\Symfony\Polyfill\Intl\Normalizer\Normalizer::class, false)
+ ) {
+ $GLOBALS['__composer_autoload_files']['e69f7f6ee287b969198c3c9d6777bd38'] = true;
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-normalizer/Normalizer.php';
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-normalizer/bootstrap.php';
+ }
+
+ if (
+ !extension_loaded('intl')
+ && empty($GLOBALS['__composer_autoload_files']['8825ede83f2f289127722d4e842cf7e8'])
+ && !class_exists(\Symfony\Polyfill\Intl\Grapheme\Grapheme::class, false)
+ ) {
+ $GLOBALS['__composer_autoload_files']['8825ede83f2f289127722d4e842cf7e8'] = true;
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-grapheme/Grapheme.php';
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-grapheme/bootstrap.php';
+ }
+
+ if (
+ PHP_VERSION_ID < 80100
+ && empty ($GLOBALS['__composer_autoload_files']['23c18046f52bef3eea034657bafda50f'])
+ && !class_exists(\Symfony\Polyfill\Php81\Php81::class, false)
+ ) {
+ $GLOBALS['__composer_autoload_files']['23c18046f52bef3eea034657bafda50f'] = true;
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php81/Php81.php';
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php81/bootstrap.php';
+ }
+
+ if (
+ PHP_VERSION_ID < 80300
+ && empty ($GLOBALS['__composer_autoload_files']['662a729f963d39afe703c9d9b7ab4a8c'])
+ && !class_exists(\Symfony\Polyfill\Php83\Php83::class, false)
+ ) {
+ $GLOBALS['__composer_autoload_files']['662a729f963d39afe703c9d9b7ab4a8c'] = true;
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php83/Php83.php';
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php83/bootstrap.php';
+ }
+
+ if (
+ PHP_VERSION_ID < 80400
+ && empty ($GLOBALS['__composer_autoload_files']['9d2b9fc6db0f153a0a149fefb182415e'])
+ && !class_exists(\Symfony\Polyfill\Php84\Php84::class, false)
+ ) {
+ $GLOBALS['__composer_autoload_files']['9d2b9fc6db0f153a0a149fefb182415e'] = true;
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php84/Php84.php';
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php84/bootstrap.php';
+ }
+
+ if (
+ PHP_VERSION_ID < 80500
+ && empty ($GLOBALS['__composer_autoload_files']['606a39d89246991a373564698c2d8383'])
+ && !class_exists(\Symfony\Polyfill\Php85\Php85::class, false)
+ ) {
+ $GLOBALS['__composer_autoload_files']['606a39d89246991a373564698c2d8383'] = true;
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php85/Php85.php';
+ require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php85/bootstrap.php';
+ }
+ }
+
+ $filename = str_replace('\\', DIRECTORY_SEPARATOR, $class);
+ if (strpos($class, 'PHPStan\\BetterReflection\\') === 0) {
+ $filename = substr($filename, strlen('PHPStan\\BetterReflection\\'));
+ $filepath = 'phar://' . __DIR__ . '/phpstan.phar/vendor/ondrejmirtes/better-reflection/src/' . $filename . '.php';
+ } else {
+ $filename = substr($filename, strlen('PHPStan\\'));
+ $filepath = 'phar://' . __DIR__ . '/phpstan.phar/src/' . $filename . '.php';
+ }
+
+ if (!file_exists($filepath)) {
+ return;
+ }
+
+ require $filepath;
+ }
+}
+
+spl_autoload_register([PharAutoloader::class, 'loadClass']);
diff --git a/www/libs/vendor/phpstan/phpstan/composer.json b/www/libs/vendor/phpstan/phpstan/composer.json
new file mode 100644
index 000000000..a267d4c50
--- /dev/null
+++ b/www/libs/vendor/phpstan/phpstan/composer.json
@@ -0,0 +1,42 @@
+{
+ "name": "phpstan/phpstan",
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "license": ["MIT"],
+ "authors": [
+ {
+ "name": "Ondřej Mirtes"
+ },
+ {
+ "name": "Markus Staab"
+ },
+ {
+ "name": "Vincent Langlet"
+ }
+ ],
+ "keywords": ["dev", "static analysis"],
+ "require": {
+ "php": "^7.4|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "autoload": {
+ "files": ["bootstrap.php"]
+ },
+ "source": {
+ "type": "",
+ "url": "",
+ "reference": ""
+ },
+ "support": {
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "source": "https://github.com/phpstan/phpstan-src",
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "security": "https://github.com/phpstan/phpstan/security/policy"
+ }
+}
diff --git a/www/libs/vendor/phpstan/phpstan/conf/bleedingEdge.neon b/www/libs/vendor/phpstan/phpstan/conf/bleedingEdge.neon
new file mode 100644
index 000000000..01fee972d
--- /dev/null
+++ b/www/libs/vendor/phpstan/phpstan/conf/bleedingEdge.neon
@@ -0,0 +1,2 @@
+includes:
+ - phar://phpstan.phar/conf/bleedingEdge.neon
diff --git a/www/libs/vendor/phpstan/phpstan/phpstan b/www/libs/vendor/phpstan/phpstan/phpstan
new file mode 100755
index 000000000..7a08ef485
--- /dev/null
+++ b/www/libs/vendor/phpstan/phpstan/phpstan
@@ -0,0 +1,8 @@
+#!/usr/bin/env php
+getTargetFile($directory, $name);
- set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+ set_error_handler(static function ($type, $msg) use (&$error) { $error = $msg; });
try {
$renamed = rename($this->getPathname(), $target);
} finally {
@@ -96,7 +96,7 @@ public function move(string $directory, ?string $name = null): self
throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
}
- @chmod($target, 0666 & ~umask());
+ @chmod($target, 0o666 & ~umask());
return $target;
}
@@ -115,7 +115,7 @@ public function getContent(): string
protected function getTargetFile(string $directory, ?string $name = null): self
{
if (!is_dir($directory)) {
- if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
+ if (false === @mkdir($directory, 0o777, true) && !is_dir($directory)) {
throw new FileException(\sprintf('Unable to create the "%s" directory.', $directory));
}
} elseif (!is_writable($directory)) {
diff --git a/www/libs/vendor/symfony/http-foundation/File/UploadedFile.php b/www/libs/vendor/symfony/http-foundation/File/UploadedFile.php
index dcbf19458..0b6a15416 100644
--- a/www/libs/vendor/symfony/http-foundation/File/UploadedFile.php
+++ b/www/libs/vendor/symfony/http-foundation/File/UploadedFile.php
@@ -167,7 +167,7 @@ public function move(string $directory, ?string $name = null): File
$target = $this->getTargetFile($directory, $name);
- set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+ set_error_handler(static function ($type, $msg) use (&$error) { $error = $msg; });
try {
$moved = move_uploaded_file($this->getPathname(), $target);
} finally {
@@ -177,7 +177,7 @@ public function move(string $directory, ?string $name = null): File
throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
}
- @chmod($target, 0666 & ~umask());
+ @chmod($target, 0o666 & ~umask());
return $target;
}
diff --git a/www/libs/vendor/symfony/http-foundation/IpUtils.php b/www/libs/vendor/symfony/http-foundation/IpUtils.php
index 8b52d2a9d..2c097443d 100644
--- a/www/libs/vendor/symfony/http-foundation/IpUtils.php
+++ b/www/libs/vendor/symfony/http-foundation/IpUtils.php
@@ -29,8 +29,13 @@ class IpUtils
'::1/128', // Loopback
'fc00::/7', // Unique Local Address
'fe80::/10', // Link Local Address
- '::ffff:0:0/96', // IPv4 translations
+ '::ffff:0:0/96', // IPv4-mapped IPv6 addresses (RFC 4291 section 2.5.5.2)
'::/128', // Unspecified address
+ '::/96', // IPv4-compatible IPv6 addresses (RFC 4291 section 2.5.5.1)
+ '2002::/16', // 6to4 (RFC 3056)
+ '2001::/32', // Teredo tunneling (RFC 4380)
+ '64:ff9b::/96', // NAT64 well-known prefix (RFC 6052)
+ '64:ff9b:1::/48', // NAT64 local-use prefix (RFC 8215)
];
private static array $checkedIps = [];
diff --git a/www/libs/vendor/symfony/http-foundation/Request.php b/www/libs/vendor/symfony/http-foundation/Request.php
index 0bc7edb2e..117e0c8f1 100644
--- a/www/libs/vendor/symfony/http-foundation/Request.php
+++ b/www/libs/vendor/symfony/http-foundation/Request.php
@@ -614,7 +614,7 @@ public function overrideGlobals()
*/
public static function setTrustedProxies(array $proxies, int $trustedHeaderSet)
{
- self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) {
+ self::$trustedProxies = array_reduce($proxies, static function ($proxies, $proxy) {
if ('REMOTE_ADDR' !== $proxy) {
$proxies[] = $proxy;
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
@@ -657,7 +657,7 @@ public static function getTrustedHeaderSet(): int
*/
public static function setTrustedHosts(array $hostPatterns)
{
- self::$trustedHostPatterns = array_map(fn ($hostPattern) => \sprintf('{%s}i', $hostPattern), $hostPatterns);
+ self::$trustedHostPatterns = array_map(static fn ($hostPattern) => \sprintf('{%s}i', $hostPattern), $hostPatterns);
// we need to reset trusted hosts on trusted host patterns change
self::$trustedHosts = [];
}
@@ -836,10 +836,6 @@ public function getClientIps(): array
* being the original client, and each successive proxy that passed the request
* adding the IP address where it received the request from.
*
- * If your reverse proxy uses a different header name than "X-Forwarded-For",
- * ("Client-Ip" for instance), configure it via the $trustedHeaderSet
- * argument of the Request::setTrustedProxies() method instead.
- *
* @see getClientIps()
* @see https://wikipedia.org/wiki/X-Forwarded-For
*/
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
index 284cd869d..81e97be94 100644
--- a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
@@ -41,7 +41,7 @@ public function __construct(?string $savePath = null)
$baseDir = ltrim(strrchr($savePath, ';'), ';');
}
- if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
+ if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0o777, true) && !is_dir($baseDir)) {
throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $baseDir));
}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
index 84c2c4363..f5470f769 100644
--- a/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
@@ -34,7 +34,7 @@ public function __construct(?string $savePath = null, string $name = 'MOCKSESSID
{
$savePath ??= sys_get_temp_dir();
- if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) {
+ if (!is_dir($savePath) && !@mkdir($savePath, 0o777, true) && !is_dir($savePath)) {
throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $savePath));
}
diff --git a/www/libs/vendor/symfony/http-foundation/StreamedJsonResponse.php b/www/libs/vendor/symfony/http-foundation/StreamedJsonResponse.php
index 5b20ce910..bb8c00996 100644
--- a/www/libs/vendor/symfony/http-foundation/StreamedJsonResponse.php
+++ b/www/libs/vendor/symfony/http-foundation/StreamedJsonResponse.php
@@ -94,7 +94,7 @@ private function streamArray(array $data, int $jsonEncodingOptions, int $keyEnco
{
$generators = [];
- array_walk_recursive($data, function (&$item, $key) use (&$generators) {
+ array_walk_recursive($data, static function (&$item, $key) use (&$generators) {
if (self::PLACEHOLDER === $key) {
// if the placeholder is already in the structure it should be replaced with a new one that explode
// works like expected for the structure
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/Mbstring.php b/www/libs/vendor/symfony/polyfill-mbstring/Mbstring.php
index 7f256360b..982b711b5 100644
--- a/www/libs/vendor/symfony/polyfill-mbstring/Mbstring.php
+++ b/www/libs/vendor/symfony/polyfill-mbstring/Mbstring.php
@@ -82,6 +82,7 @@ final class Mbstring
private static $encodingList = ['ASCII', 'UTF-8'];
private static $language = 'neutral';
private static $internalEncoding = 'UTF-8';
+ private static $iconvSupportsIgnore;
public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
{
@@ -116,18 +117,51 @@ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null
$fromEncoding = 'Windows-1252';
}
if ('UTF-8' !== $fromEncoding) {
- $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
+ $s = self::iconv($fromEncoding, 'UTF-8', $s);
}
return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s);
}
if ('HTML-ENTITIES' === $fromEncoding) {
- $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8');
+ $decodeControlChars = static function ($m) {
+ $code = '' !== ($m[2] ?? '') ? hexdec($m[2]) : (int) $m[1];
+
+ if ($code < 32 || 127 === $code) {
+ return \chr($code);
+ }
+ if (128 <= $code && $code <= 159) {
+ return "\xC2".\chr(0x80 | ($code & 0x3F));
+ }
+
+ return $m[0];
+ };
+
+ if (\PHP_VERSION_ID >= 70400) {
+ $s = html_entity_decode($s, \ENT_QUOTES, 'UTF-8');
+ // html_entity_decode() leaves numeric entities for C0/C1 control
+ // characters as-is (HTML spec), but mb_convert_encoding() decodes
+ // them. Catch what html_entity_decode() missed.
+ if (false !== strpos($s, '')) {
+ $s = preg_replace_callback('/(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s);
+ }
+ } else {
+ // PHP < 7.4: html_entity_decode() truncates strings at NUL bytes,
+ // so decode the control character entities first then call
+ // html_entity_decode() on each NUL-delimited chunk independently.
+ $s = preg_replace_callback('/(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s);
+ $s = implode("\0", array_map(static function ($chunk) {
+ return html_entity_decode($chunk, \ENT_QUOTES, 'UTF-8');
+ }, explode("\0", $s)));
+ }
$fromEncoding = 'UTF-8';
}
- return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
+ if ($fromEncoding === $toEncoding) {
+ return $s;
+ }
+
+ return self::iconv($fromEncoding, $toEncoding, $s);
}
public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
@@ -180,10 +214,10 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null)
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
- $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ $s = @self::iconv('UTF-8', 'UTF-8', $s);
}
} else {
- $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ $s = self::iconv($encoding, 'UTF-8', $s);
}
$cnt = floor(\count($convmap) / 4) * 4;
@@ -209,7 +243,7 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null)
return $s;
}
- return iconv('UTF-8', $encoding.'//IGNORE', $s);
+ return self::iconv('UTF-8', $encoding, $s);
}
public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
@@ -246,10 +280,10 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
- $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ $s = @self::iconv('UTF-8', 'UTF-8', $s);
}
} else {
- $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ $s = self::iconv($encoding, 'UTF-8', $s);
}
static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
@@ -279,7 +313,7 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $
return $result;
}
- return iconv('UTF-8', $encoding.'//IGNORE', $result);
+ return self::iconv('UTF-8', $encoding, $result);
}
public static function mb_convert_case($s, $mode, $encoding = null)
@@ -294,10 +328,10 @@ public static function mb_convert_case($s, $mode, $encoding = null)
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
- $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ $s = @self::iconv('UTF-8', 'UTF-8', $s);
}
} else {
- $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ $s = self::iconv($encoding, 'UTF-8', $s);
}
if (\MB_CASE_TITLE == $mode) {
@@ -361,7 +395,7 @@ public static function mb_convert_case($s, $mode, $encoding = null)
return $s;
}
- return iconv('UTF-8', $encoding.'//IGNORE', $s);
+ return self::iconv('UTF-8', $encoding, $s);
}
public static function mb_internal_encoding($encoding = null)
@@ -519,7 +553,15 @@ public static function mb_strlen($s, $encoding = null)
return \strlen($s);
}
- return @iconv_strlen($s, $encoding);
+ if (false !== $len = @iconv_strlen($s, $encoding)) {
+ return $len;
+ }
+
+ if ('UTF-8' !== $encoding) {
+ return $len;
+ }
+
+ return preg_match_all('/[\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]?|[\xE0-\xEF][\x80-\xBF]{0,2}|[\xF0-\xF7][\x80-\xBF]{0,3}|[\xF8-\xFB][\x80-\xBF]{0,4}|[\xFC-\xFD][\x80-\xBF]{0,5}|[\x80-\xBF\xFE\xFF]/s', $s);
}
public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
@@ -773,7 +815,7 @@ public static function mb_strwidth($s, $encoding = null)
$encoding = self::getEncoding($encoding);
if ('UTF-8' !== $encoding) {
- $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ $s = self::iconv($encoding, 'UTF-8', $s);
}
$s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
@@ -912,6 +954,24 @@ public static function mb_lcfirst(string $string, ?string $encoding = null)
return $firstChar.mb_substr($string, 1, null, $encoding);
}
+ /** @return string|false */
+ public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null)
+ {
+ return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
+ }
+
+ /** @return string|false */
+ public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null)
+ {
+ return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
+ }
+
+ /** @return string|false */
+ public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null)
+ {
+ return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
+ }
+
private static function getSubpart($pos, $part, $haystack, $encoding)
{
if (false === $pos) {
@@ -994,22 +1054,15 @@ private static function getEncoding($encoding)
return $encoding;
}
- /** @return string|false */
- public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null)
- {
- return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
- }
-
- /** @return string|false */
- public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null)
+ private static function iconv($fromEncoding, $toEncoding, $s)
{
- return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
- }
+ if (null === self::$iconvSupportsIgnore) {
+ self::$iconvSupportsIgnore = false !== @iconv('UTF-8', 'UTF-8//IGNORE', '');
+ }
- /** @return string|false */
- public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null)
- {
- return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
+ return self::$iconvSupportsIgnore
+ ? iconv($fromEncoding, $toEncoding.'//IGNORE', $s)
+ : iconv($fromEncoding, $toEncoding, $s);
}
/** @return string|false */
@@ -1028,16 +1081,16 @@ private static function mb_internal_trim(string $regex, string $string, ?string
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $string)) {
- $string = @iconv('UTF-8', 'UTF-8//IGNORE', $string);
+ $string = @self::iconv('UTF-8', 'UTF-8', $string);
}
if (null !== $characters && !preg_match('//u', $characters)) {
- $characters = @iconv('UTF-8', 'UTF-8//IGNORE', $characters);
+ $characters = @self::iconv('UTF-8', 'UTF-8', $characters);
}
} else {
- $string = iconv($encoding, 'UTF-8//IGNORE', $string);
+ $string = self::iconv($encoding, 'UTF-8', $string);
if (null !== $characters) {
- $characters = iconv($encoding, 'UTF-8//IGNORE', $characters);
+ $characters = self::iconv($encoding, 'UTF-8', $characters);
}
}
@@ -1053,7 +1106,7 @@ private static function mb_internal_trim(string $regex, string $string, ?string
return $string;
}
- return iconv('UTF-8', $encoding.'//IGNORE', $string);
+ return self::iconv('UTF-8', $encoding, $string);
}
private static function assertEncoding(string $encoding, string $errorFormat): bool
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/bootstrap.php b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap.php
index df3d9f3d4..0e934d7af 100644
--- a/www/libs/vendor/symfony/polyfill-mbstring/bootstrap.php
+++ b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap.php
@@ -9,163 +9,8 @@
* file that was distributed with this source code.
*/
-use Symfony\Polyfill\Mbstring as p;
-
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
-if (!function_exists('mb_convert_encoding')) {
- function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
-}
-if (!function_exists('mb_decode_mimeheader')) {
- function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
-}
-if (!function_exists('mb_encode_mimeheader')) {
- function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); }
-}
-if (!function_exists('mb_decode_numericentity')) {
- function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
-}
-if (!function_exists('mb_encode_numericentity')) {
- function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
-}
-if (!function_exists('mb_convert_case')) {
- function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
-}
-if (!function_exists('mb_internal_encoding')) {
- function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
-}
-if (!function_exists('mb_language')) {
- function mb_language($language = null) { return p\Mbstring::mb_language($language); }
-}
-if (!function_exists('mb_list_encodings')) {
- function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
-}
-if (!function_exists('mb_encoding_aliases')) {
- function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
-}
-if (!function_exists('mb_check_encoding')) {
- function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
-}
-if (!function_exists('mb_detect_encoding')) {
- function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
-}
-if (!function_exists('mb_detect_order')) {
- function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
-}
-if (!function_exists('mb_parse_str')) {
- function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; }
-}
-if (!function_exists('mb_strlen')) {
- function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
-}
-if (!function_exists('mb_strpos')) {
- function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
-}
-if (!function_exists('mb_strtolower')) {
- function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
-}
-if (!function_exists('mb_strtoupper')) {
- function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
-}
-if (!function_exists('mb_substitute_character')) {
- function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
-}
-if (!function_exists('mb_substr')) {
- function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
-}
-if (!function_exists('mb_stripos')) {
- function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
-}
-if (!function_exists('mb_stristr')) {
- function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
-}
-if (!function_exists('mb_strrchr')) {
- function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
-}
-if (!function_exists('mb_strrichr')) {
- function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
-}
-if (!function_exists('mb_strripos')) {
- function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
-}
-if (!function_exists('mb_strrpos')) {
- function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
-}
-if (!function_exists('mb_strstr')) {
- function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
-}
-if (!function_exists('mb_get_info')) {
- function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
-}
-if (!function_exists('mb_http_output')) {
- function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
-}
-if (!function_exists('mb_strwidth')) {
- function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
-}
-if (!function_exists('mb_substr_count')) {
- function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
-}
-if (!function_exists('mb_output_handler')) {
- function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
-}
-if (!function_exists('mb_http_input')) {
- function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); }
-}
-
-if (!function_exists('mb_convert_variables')) {
- function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
-}
-
-if (!function_exists('mb_ord')) {
- function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
-}
-if (!function_exists('mb_chr')) {
- function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
-}
-if (!function_exists('mb_scrub')) {
- function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
-}
-if (!function_exists('mb_str_split')) {
- function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
-}
-
-if (!function_exists('mb_str_pad')) {
- function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
-}
-
-if (!function_exists('mb_ucfirst')) {
- function mb_ucfirst(string $string, ?string $encoding = null) { return p\Mbstring::mb_ucfirst($string, $encoding); }
-}
-
-if (!function_exists('mb_lcfirst')) {
- function mb_lcfirst(string $string, ?string $encoding = null) { return p\Mbstring::mb_lcfirst($string, $encoding); }
-}
-
-if (!function_exists('mb_trim')) {
- function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_trim($string, $characters, $encoding); }
-}
-
-if (!function_exists('mb_ltrim')) {
- function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
-}
-
-if (!function_exists('mb_rtrim')) {
- function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
-}
-
-if (extension_loaded('mbstring')) {
- return;
-}
-
-if (!defined('MB_CASE_UPPER')) {
- define('MB_CASE_UPPER', 0);
-}
-if (!defined('MB_CASE_LOWER')) {
- define('MB_CASE_LOWER', 1);
-}
-if (!defined('MB_CASE_TITLE')) {
- define('MB_CASE_TITLE', 2);
-}
+return require __DIR__.'/bootstrap72.php';
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/bootstrap72.php b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap72.php
new file mode 100644
index 000000000..8efc00131
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap72.php
@@ -0,0 +1,173 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Mbstring as p;
+
+if (!function_exists('mb_convert_encoding')) {
+ function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
+}
+if (!function_exists('mb_decode_mimeheader')) {
+ function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
+}
+if (!function_exists('mb_encode_mimeheader')) {
+ function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); }
+}
+if (!function_exists('mb_decode_numericentity')) {
+ function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
+}
+if (!function_exists('mb_encode_numericentity')) {
+ function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
+}
+if (!function_exists('mb_convert_case')) {
+ function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
+}
+if (!function_exists('mb_internal_encoding')) {
+ function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
+}
+if (!function_exists('mb_language')) {
+ function mb_language($language = null) { return p\Mbstring::mb_language($language); }
+}
+if (!function_exists('mb_list_encodings')) {
+ function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
+}
+if (!function_exists('mb_encoding_aliases')) {
+ function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
+}
+if (!function_exists('mb_check_encoding')) {
+ function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
+}
+if (!function_exists('mb_detect_encoding')) {
+ function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
+}
+if (!function_exists('mb_detect_order')) {
+ function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
+}
+if (!function_exists('mb_parse_str')) {
+ function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; }
+}
+if (!function_exists('mb_strlen')) {
+ function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
+}
+if (!function_exists('mb_strpos')) {
+ function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_strtolower')) {
+ function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
+}
+if (!function_exists('mb_strtoupper')) {
+ function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
+}
+if (!function_exists('mb_substitute_character')) {
+ function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
+}
+if (!function_exists('mb_substr')) {
+ function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
+}
+if (!function_exists('mb_stripos')) {
+ function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_stristr')) {
+ function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrchr')) {
+ function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrichr')) {
+ function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_strripos')) {
+ function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_strrpos')) {
+ function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_strstr')) {
+ function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_get_info')) {
+ function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
+}
+if (!function_exists('mb_http_output')) {
+ function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
+}
+if (!function_exists('mb_strwidth')) {
+ function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
+}
+if (!function_exists('mb_substr_count')) {
+ function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
+}
+if (!function_exists('mb_output_handler')) {
+ function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
+}
+if (!function_exists('mb_http_input')) {
+ function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); }
+}
+
+if (!function_exists('mb_convert_variables')) {
+ function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
+}
+
+if (!function_exists('mb_ord')) {
+ function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
+}
+if (!function_exists('mb_chr')) {
+ function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
+}
+if (!function_exists('mb_scrub')) {
+ function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
+}
+if (!function_exists('mb_str_split')) {
+ function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
+}
+
+if (!function_exists('mb_str_pad')) {
+ /** @return string|false */
+ function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Mbstring::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); }
+}
+
+if (!function_exists('mb_ucfirst')) {
+ /** @return string|false */
+ function mb_ucfirst(?string $string, ?string $encoding = null) { return p\Mbstring::mb_ucfirst((string) $string, $encoding); }
+}
+
+if (!function_exists('mb_lcfirst')) {
+ /** @return string|false */
+ function mb_lcfirst(?string $string, ?string $encoding = null) { return p\Mbstring::mb_lcfirst((string) $string, $encoding); }
+}
+
+if (!function_exists('mb_trim')) {
+ /** @return string|false */
+ function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_trim((string) $string, $characters, $encoding); }
+}
+
+if (!function_exists('mb_ltrim')) {
+ /** @return string|false */
+ function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_ltrim((string) $string, $characters, $encoding); }
+}
+
+if (!function_exists('mb_rtrim')) {
+ /** @return string|false */
+ function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_rtrim((string) $string, $characters, $encoding); }
+}
+
+if (extension_loaded('mbstring')) {
+ return;
+}
+
+if (!defined('MB_CASE_UPPER')) {
+ define('MB_CASE_UPPER', 0);
+}
+if (!defined('MB_CASE_LOWER')) {
+ define('MB_CASE_LOWER', 1);
+}
+if (!defined('MB_CASE_TITLE')) {
+ define('MB_CASE_TITLE', 2);
+}
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/bootstrap80.php b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap80.php
index 5236e6dcc..4a08f984c 100644
--- a/www/libs/vendor/symfony/polyfill-mbstring/bootstrap80.php
+++ b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap80.php
@@ -129,27 +129,27 @@ function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = nul
}
if (!function_exists('mb_str_pad')) {
- function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
+ function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); }
}
if (!function_exists('mb_ucfirst')) {
- function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
+ function mb_ucfirst(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst((string) $string, $encoding); }
}
if (!function_exists('mb_lcfirst')) {
- function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
+ function mb_lcfirst(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst((string) $string, $encoding); }
}
if (!function_exists('mb_trim')) {
- function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); }
+ function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim((string) $string, $characters, $encoding); }
}
if (!function_exists('mb_ltrim')) {
- function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
+ function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim((string) $string, $characters, $encoding); }
}
if (!function_exists('mb_rtrim')) {
- function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
+ function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim((string) $string, $characters, $encoding); }
}
if (extension_loaded('mbstring')) {
diff --git a/www/libs/vendor/symfony/polyfill-php83/Php83.php b/www/libs/vendor/symfony/polyfill-php83/Php83.php
index 03409fb0c..8cb104097 100644
--- a/www/libs/vendor/symfony/polyfill-php83/Php83.php
+++ b/www/libs/vendor/symfony/polyfill-php83/Php83.php
@@ -60,7 +60,7 @@ public static function mb_str_pad(string $string, int $length, string $pad_strin
$errorToTrigger = \sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding);
}
- if (mb_strlen($pad_string, $encoding) <= 0) {
+ if (null === $errorToTrigger && mb_strlen($pad_string, $encoding) <= 0) {
$errorToTrigger = 'mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string';
}
@@ -103,34 +103,33 @@ public static function str_increment(string $string): string
throw new \ValueError('str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters');
}
- if (is_numeric($string)) {
- $offset = stripos($string, 'e');
- if (false !== $offset) {
- $char = $string[$offset];
- ++$char;
- $string[$offset] = $char;
- ++$string;
-
- switch ($string[$offset]) {
- case 'f':
- $string[$offset] = 'e';
- break;
- case 'F':
- $string[$offset] = 'E';
- break;
- case 'g':
- $string[$offset] = 'f';
- break;
- case 'G':
- $string[$offset] = 'F';
- break;
- }
-
- return $string;
+ for ($i = \strlen($string) - 1; $i >= 0; --$i) {
+ $char = $string[$i];
+
+ if ('z' === $char) {
+ $string[$i] = 'a';
+ continue;
+ }
+ if ('Z' === $char) {
+ $string[$i] = 'A';
+ continue;
+ }
+ if ('9' === $char) {
+ $string[$i] = '0';
+ continue;
}
+
+ $string[$i] = \chr(\ord($char) + 1);
+
+ return $string;
+ }
+
+ switch ($string[0]) {
+ case 'a': return 'a'.$string;
+ case 'A': return 'A'.$string;
}
- return ++$string;
+ return '1'.$string;
}
public static function str_decrement(string $string): string
diff --git a/www/libs/vendor/symfony/polyfill-php83/bootstrap.php b/www/libs/vendor/symfony/polyfill-php83/bootstrap.php
index 8d721a29c..95cf7830d 100644
--- a/www/libs/vendor/symfony/polyfill-php83/bootstrap.php
+++ b/www/libs/vendor/symfony/polyfill-php83/bootstrap.php
@@ -31,18 +31,22 @@ function str_increment(string $string): string { return p\Php83::str_increment($
function str_decrement(string $string): string { return p\Php83::str_decrement($string); }
}
-if (\PHP_VERSION_ID >= 80000) {
- return require __DIR__.'/bootstrap80.php';
+if (\PHP_VERSION_ID < 80000) {
+ require __DIR__.'/bootstrap72.php';
}
if (extension_loaded('mbstring')) {
if (!function_exists('mb_str_pad')) {
- function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
+ function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); }
}
}
+if (\PHP_VERSION_ID >= 80100) {
+ return require __DIR__.'/bootstrap81.php';
+}
+
if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
- function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
+ function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $response_data, $response_oid); }
}
if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
diff --git a/www/libs/vendor/symfony/polyfill-php83/bootstrap72.php b/www/libs/vendor/symfony/polyfill-php83/bootstrap72.php
new file mode 100644
index 000000000..94a941747
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/bootstrap72.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php83 as p;
+
+if (extension_loaded('mbstring')) {
+ if (!function_exists('mb_str_pad')) {
+ /** @return string|false */
+ function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Php83::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); }
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/bootstrap80.php b/www/libs/vendor/symfony/polyfill-php83/bootstrap80.php
deleted file mode 100644
index 9b812d024..000000000
--- a/www/libs/vendor/symfony/polyfill-php83/bootstrap80.php
+++ /dev/null
@@ -1,30 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-use Symfony\Polyfill\Php83 as p;
-
-if (extension_loaded('mbstring')) {
- if (!function_exists('mb_str_pad')) {
- function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
- }
-}
-
-if (\PHP_VERSION_ID >= 80100) {
- return require __DIR__.'/bootstrap81.php';
-}
-
-if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
- function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
-}
-
-if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
- function ldap_connect_wallet(?string $uri, string $wallet, string $password, int $auth_mode = \GSLC_SSL_NO_AUTH) { return ldap_connect($uri, $wallet, $password, $auth_mode); }
-}
diff --git a/www/libs/vendor/symfony/polyfill-php83/bootstrap81.php b/www/libs/vendor/symfony/polyfill-php83/bootstrap81.php
index 68395b439..4c2581134 100644
--- a/www/libs/vendor/symfony/polyfill-php83/bootstrap81.php
+++ b/www/libs/vendor/symfony/polyfill-php83/bootstrap81.php
@@ -9,12 +9,30 @@
* file that was distributed with this source code.
*/
+use Symfony\Polyfill\Php83 as p;
+
if (\PHP_VERSION_ID >= 80300) {
return;
}
+if (!function_exists('json_validate')) {
+ function json_validate(string $json, int $depth = 512, int $flags = 0): bool { return p\Php83::json_validate($json, $depth, $flags); }
+}
+
+if (!function_exists('str_increment')) {
+ function str_increment(string $string): string { return p\Php83::str_increment($string); }
+}
+
+if (!function_exists('str_decrement')) {
+ function str_decrement(string $string): string { return p\Php83::str_decrement($string); }
+}
+
+if (extension_loaded('mbstring') && !function_exists('mb_str_pad')) {
+ function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); }
+}
+
if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
- function ldap_exop_sync(\LDAP\Connection $ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
+ function ldap_exop_sync(\LDAP\Connection $ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $response_data, $response_oid); }
}
if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
diff --git a/www/libs/vendor/symfony/routing/CompiledRoute.php b/www/libs/vendor/symfony/routing/CompiledRoute.php
index 03215e368..f2a06e32a 100644
--- a/www/libs/vendor/symfony/routing/CompiledRoute.php
+++ b/www/libs/vendor/symfony/routing/CompiledRoute.php
@@ -16,7 +16,7 @@
*
* @author Fabien Potencier
*/
-class CompiledRoute implements \Serializable
+class CompiledRoute
{
private array $variables;
private array $tokens;
@@ -63,16 +63,15 @@ public function __serialize(): array
];
}
- /**
- * @internal
- */
- final public function serialize(): string
- {
- throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
- }
-
public function __unserialize(array $data): void
{
+ if (($data['path_prefix'] ?? null) instanceof \Stringable
+ || ($data['path_regex'] ?? null) instanceof \Stringable
+ || ($data['host_regex'] ?? null) instanceof \Stringable
+ ) {
+ throw new \BadMethodCallException('Cannot unserialize '.self::class);
+ }
+
$this->variables = $data['vars'];
$this->staticPrefix = $data['path_prefix'];
$this->regex = $data['path_regex'];
@@ -83,14 +82,6 @@ public function __unserialize(array $data): void
$this->hostVariables = $data['host_vars'];
}
- /**
- * @internal
- */
- final public function unserialize(string $serialized): void
- {
- $this->__unserialize(unserialize($serialized, ['allowed_classes' => false]));
- }
-
/**
* Returns the static prefix.
*/
diff --git a/www/libs/vendor/symfony/routing/Generator/UrlGenerator.php b/www/libs/vendor/symfony/routing/Generator/UrlGenerator.php
index 754cf5e86..919017bef 100644
--- a/www/libs/vendor/symfony/routing/Generator/UrlGenerator.php
+++ b/www/libs/vendor/symfony/routing/Generator/UrlGenerator.php
@@ -177,7 +177,7 @@ protected function doGenerate(array $variables, array $defaults, array $requirem
if (!$optional || $important || !\array_key_exists($varName, $defaults) || (null !== $mergedParams[$varName] && (string) $mergedParams[$varName] !== (string) $defaults[$varName])) {
// check requirement (while ignoring look-around patterns)
- if (null !== $this->strictRequirements && !preg_match('#^'.preg_replace('/\(\?(?:=|<=|!|strictRequirements && !preg_match('#^(?:'.preg_replace('/\(\?(?:=|<=|!|strictRequirements) {
throw new InvalidParameterException(strtr($message, ['{parameter}' => $varName, '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$varName]]));
}
@@ -207,11 +207,16 @@ protected function doGenerate(array $variables, array $defaults, array $requirem
// the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3
// so we need to encode them as they are not used for this purpose here
// otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route
- $url = strtr($url, ['/../' => '/%2E%2E/', '/./' => '/%2E/']);
- if (str_ends_with($url, '/..')) {
- $url = substr($url, 0, -2).'%2E%2E';
- } elseif (str_ends_with($url, '/.')) {
- $url = substr($url, 0, -1).'%2E';
+ if (str_contains($url, '/.')) {
+ $segments = explode('/', $url);
+ foreach ($segments as $i => $segment) {
+ if ('.' === $segment) {
+ $segments[$i] = '%2E';
+ } elseif ('..' === $segment) {
+ $segments[$i] = '%2E%2E';
+ }
+ }
+ $url = implode('/', $segments);
}
$schemeAuthority = '';
@@ -230,7 +235,7 @@ protected function doGenerate(array $variables, array $defaults, array $requirem
foreach ($hostTokens as $token) {
if ('variable' === $token[0]) {
// check requirement (while ignoring look-around patterns)
- if (null !== $this->strictRequirements && !preg_match('#^'.preg_replace('/\(\?(?:=|<=|!|strictRequirements && !preg_match('#^(?:'.preg_replace('/\(\?(?:=|<=|!|strictRequirements) {
throw new InvalidParameterException(strtr($message, ['{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]]));
}
diff --git a/www/libs/vendor/symfony/routing/Route.php b/www/libs/vendor/symfony/routing/Route.php
index e34dcb75e..4cd1c5ae5 100644
--- a/www/libs/vendor/symfony/routing/Route.php
+++ b/www/libs/vendor/symfony/routing/Route.php
@@ -17,7 +17,7 @@
* @author Fabien Potencier
* @author Tobias Schultze
*/
-class Route implements \Serializable
+class Route
{
private string $path = '/';
private string $host = '';
@@ -73,16 +73,15 @@ public function __serialize(): array
];
}
- /**
- * @internal
- */
- final public function serialize(): string
- {
- throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
- }
-
public function __unserialize(array $data): void
{
+ if (($data['path'] ?? null) instanceof \Stringable
+ || ($data['host'] ?? null) instanceof \Stringable
+ || ($data['condition'] ?? null) instanceof \Stringable
+ ) {
+ throw new \BadMethodCallException('Cannot unserialize '.self::class);
+ }
+
$this->path = $data['path'];
$this->host = $data['host'];
$this->defaults = $data['defaults'];
@@ -99,14 +98,6 @@ public function __unserialize(array $data): void
}
}
- /**
- * @internal
- */
- final public function unserialize(string $serialized): void
- {
- $this->__unserialize(unserialize($serialized));
- }
-
public function getPath(): string
{
return $this->path;
diff --git a/www/models/Connection.php b/www/models/Connection.php
index 8690e5049..9781a6233 100644
--- a/www/models/Connection.php
+++ b/www/models/Connection.php
@@ -277,9 +277,7 @@ private function generateMainTables(): void
Time TIME NOT NULL,
Signed CHAR(5) NOT NULL, /* true, false */
Arch VARCHAR(255),
- Pkg_translation VARCHAR(255),
- Pkg_included VARCHAR(255),
- Pkg_excluded VARCHAR(255),
+ Advanced_params TEXT,
Type CHAR(6) NOT NULL,
Reconstruct CHAR(8), /* needed, running, failed */
Status CHAR(8) NOT NULL,
@@ -288,9 +286,9 @@ private function generateMainTables(): void
/**
* Create indexes
*/
- $this->exec("CREATE INDEX IF NOT EXISTS repos_snap_index ON repos_snap (Date, Time, Signed, Arch, Pkg_translation, Pkg_included, Pkg_excluded, Type, Reconstruct, Status, Id_repo)");
- $this->exec("CREATE INDEX IF NOT EXISTS repos_snap_status_id_repo_index ON repos_snap (Status, Id_repo)");
- $this->exec("CREATE INDEX IF NOT EXISTS repos_snap_id_repo_index ON repos_snap (Id_repo)");
+ $this->exec("CREATE INDEX IF NOT EXISTS idx_repos_snap ON repos_snap (Date, Time, Signed, Arch, Type, Reconstruct, Status, Id_repo)");
+ $this->exec("CREATE INDEX IF NOT EXISTS idx_repos_snap_status_id_repo ON repos_snap (Status, Id_repo)");
+ $this->exec("CREATE INDEX IF NOT EXISTS idx_repos_snap_id_repo ON repos_snap (Id_repo)");
/**
* repos_env table
@@ -682,7 +680,6 @@ private function generateMainTables(): void
DEB_REPO,
DEB_SIGN_REPO,
DEB_DEFAULT_ARCH,
- DEB_DEFAULT_TRANSLATION,
DEB_ALLOW_EMPTY_REPO,
DEB_INVALID_SIGNATURE,
GPG_SIGNING_KEYID,
@@ -736,7 +733,6 @@ private function generateMainTables(): void
'true',
'true',
'amd64',
- '',
'false',
'error',
'$gpgKeyId',
diff --git a/www/models/Host/Package/Package.php b/www/models/Host/Package/Package.php
index 805e53ab8..384660c27 100644
--- a/www/models/Host/Package/Package.php
+++ b/www/models/Host/Package/Package.php
@@ -185,18 +185,17 @@ public function getAvailable(bool $withOffset, int $offset) : array
$data = [];
try {
- $query = "SELECT * FROM packages_available";
+ $query = "SELECT packages_available.Name, packages.Version as Current_version, packages_available.Version, packages_available.Repository FROM packages_available";
- /**
- * Add offset if needed
- */
+ // Join with packages table to also get the current version of the package installed on the host
+ $query .= " LEFT JOIN packages ON packages_available.Name = packages.Name AND (packages.State = 'inventored' or packages.State = 'installed' or packages.State = 'dep-installed' or packages.State = 'upgraded' or packages.State = 'downgraded')";
+
+ // Add offset if needed
if ($withOffset === true) {
$query .= " LIMIT 10 OFFSET :offset";
}
- /**
- * Prepare query
- */
+ // Prepare query
$stmt = $this->db->prepare($query);
$stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
diff --git a/www/models/Repo/Environment.php b/www/models/Repo/Environment.php
index 336f392a8..284d322b4 100644
--- a/www/models/Repo/Environment.php
+++ b/www/models/Repo/Environment.php
@@ -40,7 +40,7 @@ public function getByRepoId(int $repoId): array
/**
* Associate a new env to a snapshot
*/
- public function add(string $env, string $description, int $snapId) : void
+ public function add(int $snapId, string $env, string $description) : void
{
try {
$stmt = $this->db->prepare("INSERT INTO repos_env ('Env', 'Description', 'Id_snap') VALUES (:env, :description, :snapId)");
diff --git a/www/models/Repo/Listing.php b/www/models/Repo/Listing.php
index fd5214195..83d7f2e28 100644
--- a/www/models/Repo/Listing.php
+++ b/www/models/Repo/Listing.php
@@ -35,7 +35,6 @@ public function list() : array
repos_snap.Time,
repos_snap.Signed,
repos_snap.Arch,
- repos_snap.Pkg_translation,
repos_snap.Type,
repos_env.Description
FROM repos
@@ -81,7 +80,6 @@ public function listByGroup(string $groupName) : array
repos_snap.Time,
repos_snap.Signed,
repos_snap.Arch,
- repos_snap.Pkg_translation,
repos_snap.Type,
repos_snap.Reconstruct,
repos_snap.Status,
@@ -109,7 +107,6 @@ public function listByGroup(string $groupName) : array
repos_snap.Time,
repos_snap.Signed,
repos_snap.Arch,
- repos_snap.Pkg_translation,
repos_snap.Type,
repos_snap.Reconstruct,
repos_snap.Status,
diff --git a/www/models/Repo/Repo.php b/www/models/Repo/Repo.php
index 6da38a5df..5cf8a11a8 100644
--- a/www/models/Repo/Repo.php
+++ b/www/models/Repo/Repo.php
@@ -57,9 +57,7 @@ public function getAllById(string|null $repoId, string|null $snapId, string|null
repos_snap.Time,
repos_snap.Signed,
repos_snap.Arch,
- repos_snap.Pkg_translation,
- repos_snap.Pkg_included,
- repos_snap.Pkg_excluded,
+ repos_snap.Advanced_params,
repos_snap.Type,
repos_snap.Reconstruct,
repos_snap.Status,
@@ -93,9 +91,7 @@ public function getAllById(string|null $repoId, string|null $snapId, string|null
repos_snap.Time,
repos_snap.Signed,
repos_snap.Arch,
- repos_snap.Pkg_translation,
- repos_snap.Pkg_included,
- repos_snap.Pkg_excluded,
+ repos_snap.Advanced_params,
repos_snap.Type,
repos_snap.Reconstruct,
repos_snap.Status,
@@ -133,9 +129,7 @@ public function getAllById(string|null $repoId, string|null $snapId, string|null
repos_snap.Time,
repos_snap.Signed,
repos_snap.Arch,
- repos_snap.Pkg_translation,
- repos_snap.Pkg_included,
- repos_snap.Pkg_excluded,
+ repos_snap.Advanced_params,
repos_snap.Type,
repos_snap.Reconstruct,
repos_snap.Status,
@@ -159,9 +153,7 @@ public function getAllById(string|null $repoId, string|null $snapId, string|null
repos_snap.Time,
repos_snap.Signed,
repos_snap.Arch,
- repos_snap.Pkg_translation,
- repos_snap.Pkg_included,
- repos_snap.Pkg_excluded,
+ repos_snap.Advanced_params,
repos_snap.Type,
repos_snap.Reconstruct,
repos_snap.Status,
diff --git a/www/models/Repo/Snapshot/Snapshot.php b/www/models/Repo/Snapshot/Snapshot.php
index 77e61d18c..3ede45612 100644
--- a/www/models/Repo/Snapshot/Snapshot.php
+++ b/www/models/Repo/Snapshot/Snapshot.php
@@ -91,17 +91,15 @@ public function getUnused(string $repoId, string $retention) : array
/**
* Add a snapshot in database
*/
- public function add(string $date, string $time, string $gpgSignature, array $arch, array $includeTranslation, array $packagesIncluded, array $packagesExcluded, string $type, string $status, int $repoId): void
+ public function add(string $date, string $time, string $gpgSignature, array $arch, string $advancedParams, string $type, string $status, int $repoId): void
{
try {
- $stmt = $this->db->prepare("INSERT INTO repos_snap ('Date', 'Time', 'Signed', 'Arch', 'Pkg_translation', 'Pkg_included', 'Pkg_excluded', 'Type', 'Status', 'Id_repo') VALUES (:date, :time, :signed, :arch, :includeTranslation, :packagesIncluded, :packagesExcluded, :type, :status, :repoId)");
+ $stmt = $this->db->prepare("INSERT INTO repos_snap ('Date', 'Time', 'Signed', 'Arch', 'Advanced_params', 'Type', 'Status', 'Id_repo') VALUES (:date, :time, :signed, :arch, :advancedParams, :type, :status, :repoId)");
$stmt->bindValue(':date', $date);
$stmt->bindValue(':time', $time);
$stmt->bindValue(':signed', $gpgSignature);
$stmt->bindValue(':arch', implode(',', $arch));
- $stmt->bindValue(':includeTranslation', implode(',', $includeTranslation));
- $stmt->bindValue(':packagesIncluded', implode(',', $packagesIncluded));
- $stmt->bindValue(':packagesExcluded', implode(',', $packagesExcluded));
+ $stmt->bindValue(':advancedParams', $advancedParams);
$stmt->bindValue(':type', $type);
$stmt->bindValue(':status', $status);
$stmt->bindValue(':repoId', $repoId);
@@ -156,36 +154,6 @@ public function updateGpgSignature(int $snapId, string $gpgSignature): void
}
}
- /**
- * Update snapshot included packages in the database
- */
- public function updatePackagesIncluded(int $snapId, string $packages): void
- {
- try {
- $stmt = $this->db->prepare("UPDATE repos_snap SET Pkg_included = :packagesIncluded WHERE Id = :snapId");
- $stmt->bindValue(':packagesIncluded', $packages);
- $stmt->bindValue(':snapId', $snapId);
- $stmt->execute();
- } catch (Exception $e) {
- DbLog::error($e);
- }
- }
-
- /**
- * Update snapshot excluded packages in the database
- */
- public function updatePackagesExcluded(int $snapId, string $packages): void
- {
- try {
- $stmt = $this->db->prepare("UPDATE repos_snap SET Pkg_excluded = :packagesExcluded WHERE Id = :snapId");
- $stmt->bindValue(':packagesExcluded', $packages);
- $stmt->bindValue(':snapId', $snapId);
- $stmt->execute();
- } catch (Exception $e) {
- DbLog::error($e);
- }
- }
-
/**
* Update snapshot status in the database
*/
@@ -231,13 +199,28 @@ public function updateArch(int $snapId, array $arch): void
}
}
+ /**
+ * Update snapshot advanced parameters in the database
+ */
+ public function updateAdvancedParams(int $snapId, string $advancedParams): void
+ {
+ try {
+ $stmt = $this->db->prepare("UPDATE repos_snap SET Advanced_params = :advancedParams WHERE Id = :snapId");
+ $stmt->bindValue(':advancedParams', $advancedParams);
+ $stmt->bindValue(':snapId', $snapId);
+ $stmt->execute();
+ } catch (Exception $e) {
+ DbLog::error($e);
+ }
+ }
+
/**
* Return true if a snapshot with the specified ID exists
*/
public function exists(int $id) : bool
{
try {
- $stmt = $this->db->prepare("SELECT Id FROM repos_snap WHERE Id = :id");
+ $stmt = $this->db->prepare("SELECT Id FROM repos_snap WHERE Id = :id AND Status = 'active'");
$stmt->bindValue(':id', $id);
$result = $stmt->execute();
} catch (Exception $e) {
diff --git a/www/models/Task/Listing.php b/www/models/Task/Listing.php
new file mode 100644
index 000000000..8f44e308e
--- /dev/null
+++ b/www/models/Task/Listing.php
@@ -0,0 +1,209 @@
+getConnection('main');
+ }
+
+ /**
+ * Get all tasks
+ */
+ public function get(): array
+ {
+ $data = [];
+
+ try {
+ $result = $this->db->query("SELECT * FROM tasks");
+ } catch (Exception $e) {
+ DbLog::error($e);
+ }
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $data[] = $row;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get all newest tasks
+ * It is possible to add an offset to the request
+ */
+ public function getQueued(string $type, bool $withOffset, int $offset): array
+ {
+ $data = [];
+
+ try {
+ // Case where we want all types
+ if (empty($type)) {
+ $query = "SELECT * FROM tasks
+ WHERE Status = 'queued'
+ ORDER BY Date DESC, Time DESC";
+ }
+
+ // Case where we want to filter by type
+ if (!empty($type)) {
+ $query = "SELECT * FROM tasks
+ WHERE Type = :type
+ AND Status = 'queued'
+ ORDER BY Date DESC, Time DESC";
+ }
+
+ // Add offset if needed
+ if ($withOffset === true) {
+ $query .= " LIMIT 10 OFFSET :offset";
+ }
+
+ // Prepare query
+ $stmt = $this->db->prepare($query);
+ $stmt->bindValue(':type', $type);
+ $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
+ $result = $stmt->execute();
+ } catch (Exception $e) {
+ DbLog::error($e);
+ }
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $data[] = $row;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get all running tasks
+ * It is possible to filter the type of task ('immediate' or 'scheduled')
+ * It is possible to add an offset to the request
+ */
+ public function getRunning(string $type, bool $withOffset, int $offset): array
+ {
+ $data = [];
+
+ // Case where we want all types
+ try {
+ if (empty($type)) {
+ $query = "SELECT * FROM tasks
+ WHERE Status = 'running'
+ ORDER BY Date DESC, Time DESC";
+
+ // Case where we want to filter by task type only
+ } else {
+ $query = "SELECT * FROM tasks
+ WHERE Status = 'running' and Type = :type
+ ORDER BY Date DESC, Time DESC";
+ }
+
+ // Add offset if needed
+ if ($withOffset === true) {
+ $query .= " LIMIT 10 OFFSET :offset";
+ }
+
+ // Prepare query
+ $stmt = $this->db->prepare($query);
+ $stmt->bindValue(':type', $type);
+ $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
+
+ $result = $stmt->execute();
+ } catch (Exception $e) {
+ DbLog::error($e);
+ }
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $data[] = $row;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get all scheduled tasks
+ * It is possible to add an offset to the request
+ */
+ public function getScheduled(bool $withOffset, int $offset): array
+ {
+ $data = [];
+
+ try {
+ $query = "SELECT * FROM tasks
+ WHERE TYPE = 'scheduled'
+ AND (Status = 'scheduled'
+ OR Status = 'disabled')
+ ORDER BY json_extract(Raw_params, '$.schedule.schedule-date') DESC, json_extract(Raw_params, '$.schedule.schedule-time') DESC";
+
+ // Add offset if needed
+ if ($withOffset === true) {
+ $query .= " LIMIT 10 OFFSET :offset";
+ }
+
+ // Prepare query
+ $stmt = $this->db->prepare($query);
+ $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
+ $result = $stmt->execute();
+ } catch (Exception $e) {
+ DbLog::error($e);
+ }
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $data[] = $row;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get all done tasks (with or without errors)
+ * It is possible to filter the type of task ('immediate' or 'scheduled')
+ * It is possible to add an offset to the request
+ */
+ public function getDone(string $type, bool $withOffset, int $offset): array
+ {
+ $data = [];
+
+ try {
+ // Case where we want all types
+ if (empty($type)) {
+ $query = "SELECT * FROM tasks
+ WHERE Status = 'error'
+ OR Status = 'done'
+ OR Status = 'stopped'
+ ORDER BY Date DESC, Time DESC";
+ }
+
+ // Case where we want to filter by type
+ if (!empty($type)) {
+ $query = "SELECT * FROM tasks
+ WHERE Type = :type
+ AND (Status = 'error'
+ OR Status = 'done'
+ OR Status = 'stopped')
+ ORDER BY Date DESC, Time DESC";
+ }
+
+ // Add offset if needed
+ if ($withOffset === true) {
+ $query .= " LIMIT 10 OFFSET :offset";
+ }
+
+ // Prepare query
+ $stmt = $this->db->prepare($query);
+ $stmt->bindValue(':type', $type);
+ $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
+ $result = $stmt->execute();
+ } catch (Exception $e) {
+ DbLog::error($e);
+ }
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $data[] = $row;
+ }
+
+ return $data;
+ }
+}
diff --git a/www/models/Task/Log/SubStep.php b/www/models/Task/Log/SubStep.php
index 1297b9936..8d1826dc3 100644
--- a/www/models/Task/Log/SubStep.php
+++ b/www/models/Task/Log/SubStep.php
@@ -76,15 +76,15 @@ public function get(int $stepId) : array
$stmt = $this->db->prepare("SELECT * FROM substeps WHERE Step_id = :stepId");
$stmt->bindValue(':stepId', $stepId);
$result = $stmt->execute();
-
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $data[] = $row;
- }
-
- return $data;
} catch (Exception $e) {
DbLog::error($e);
}
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $data[] = $row;
+ }
+
+ return $data;
}
/**
@@ -98,15 +98,15 @@ public function getLatestSubStepId(int $stepId) : int|null
$stmt = $this->db->prepare("SELECT Id FROM substeps WHERE Step_id = :stepId ORDER BY Id DESC LIMIT 1");
$stmt->bindValue(':stepId', $stepId);
$result = $stmt->execute();
-
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $data = $row['Id'];
- }
-
- return $data;
} catch (Exception $e) {
DbLog::error($e);
}
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $data = $row['Id'];
+ }
+
+ return $data;
}
/**
@@ -121,15 +121,15 @@ public function getSubStepIdByIdentifier(int $stepId, string $identifier) : int
$stmt->bindValue(':stepId', $stepId);
$stmt->bindValue(':identifier', $identifier);
$result = $stmt->execute();
-
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $data = $row['Id'];
- }
-
- return $data;
} catch (Exception $e) {
DbLog::error($e);
}
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $data = $row['Id'];
+ }
+
+ return $data;
}
/**
@@ -137,21 +137,21 @@ public function getSubStepIdByIdentifier(int $stepId, string $identifier) : int
*/
public function getOutput(int $substepId) : string|null
{
- $data = '';
+ $data = null;
try {
$stmt = $this->db->prepare("SELECT Output FROM substeps WHERE Id = :substepId");
$stmt->bindValue(':substepId', $substepId);
$result = $stmt->execute();
-
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $data = $row['Output'];
- }
-
- return $data;
} catch (Exception $e) {
DbLog::error($e);
}
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $data = $row['Output'];
+ }
+
+ return $data;
}
/**
diff --git a/www/models/Task/Task.php b/www/models/Task/Task.php
index a80476822..e863ccba4 100644
--- a/www/models/Task/Task.php
+++ b/www/models/Task/Task.php
@@ -154,209 +154,6 @@ public function somethingRunning()
return false;
}
- /**
- * List all newest tasks
- * It is possible to add an offset to the request
- */
- public function listQueued(string $type, bool $withOffset, int $offset)
- {
- $data = [];
-
- try {
- /**
- * Case where we want all types
- */
- if (empty($type)) {
- $query = "SELECT * FROM tasks
- WHERE Status = 'queued'
- ORDER BY Date DESC, Time DESC";
- }
-
- /**
- * Case where we want to filter by type
- */
- if (!empty($type)) {
- $query = "SELECT * FROM tasks
- WHERE Type = :type
- AND Status = 'queued'
- ORDER BY Date DESC, Time DESC";
- }
-
- /**
- * Add offset if needed
- */
- if ($withOffset === true) {
- $query .= " LIMIT 10 OFFSET :offset";
- }
-
- /**
- * Prepare query
- */
- $stmt = $this->db->prepare($query);
- $stmt->bindValue(':type', $type);
- $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
- $result = $stmt->execute();
- } catch (Exception $e) {
- DbLog::error($e);
- }
-
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $data[] = $row;
- }
-
- return $data;
- }
-
- /**
- * List all running tasks
- * It is possible to filter the type of task ('immediate' or 'scheduled')
- * It is possible to add an offset to the request
- */
- public function listRunning(string $type, bool $withOffset, int $offset)
- {
- $data = [];
-
- /**
- * Case where we want all types
- */
- try {
- if (empty($type)) {
- $query = "SELECT * FROM tasks
- WHERE Status = 'running'
- ORDER BY Date DESC, Time DESC";
-
- /**
- * Case where we want to filter by task type only
- */
- } else {
- $query = "SELECT * FROM tasks
- WHERE Status = 'running' and Type = :type
- ORDER BY Date DESC, Time DESC";
- }
-
- /**
- * Add offset if needed
- */
- if ($withOffset === true) {
- $query .= " LIMIT 10 OFFSET :offset";
- }
-
- /**
- * Prepare query
- */
- $stmt = $this->db->prepare($query);
- $stmt->bindValue(':type', $type);
- $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
-
- $result = $stmt->execute();
- } catch (Exception $e) {
- DbLog::error($e);
- }
-
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $data[] = $row;
- }
-
- return $data;
- }
-
- /**
- * List all scheduled tasks
- * It is possible to add an offset to the request
- */
- public function listScheduled(bool $withOffset, int $offset)
- {
- $data = [];
-
- try {
- $query = "SELECT * FROM tasks
- WHERE TYPE = 'scheduled'
- AND (Status = 'scheduled'
- OR Status = 'disabled')
- ORDER BY json_extract(Raw_params, '$.schedule.schedule-date') DESC, json_extract(Raw_params, '$.schedule.schedule-time') DESC";
-
- /**
- * Add offset if needed
- */
- if ($withOffset === true) {
- $query .= " LIMIT 10 OFFSET :offset";
- }
-
- /**
- * Prepare query
- */
- $stmt = $this->db->prepare($query);
- $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
- $result = $stmt->execute();
- } catch (Exception $e) {
- DbLog::error($e);
- }
-
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $data[] = $row;
- }
-
- return $data;
- }
-
- /**
- * List all done tasks (with or without errors)
- * It is possible to filter the type of task ('immediate' or 'scheduled')
- * It is possible to add an offset to the request
- */
- public function listDone(string $type, bool $withOffset, int $offset)
- {
- $data = [];
-
- try {
- /**
- * Case where we want all types
- */
- if (empty($type)) {
- $query = "SELECT * FROM tasks
- WHERE Status = 'error'
- OR Status = 'done'
- OR Status = 'stopped'
- ORDER BY Date DESC, Time DESC";
- }
-
- /**
- * Case where we want to filter by type
- */
- if (!empty($type)) {
- $query = "SELECT * FROM tasks
- WHERE Type = :type
- AND (Status = 'error'
- OR Status = 'done'
- OR Status = 'stopped')
- ORDER BY Date DESC, Time DESC";
- }
-
- /**
- * Add offset if needed
- */
- if ($withOffset === true) {
- $query .= " LIMIT 10 OFFSET :offset";
- }
-
- /**
- * Prepare query
- */
- $stmt = $this->db->prepare($query);
- $stmt->bindValue(':type', $type);
- $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
- $result = $stmt->execute();
- } catch (Exception $e) {
- DbLog::error($e);
- }
-
- while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
- $data[] = $row;
- }
-
- return $data;
- }
-
/**
* Return last done task Id
* Can return null if no task is found (e.g. brand new installation with no task)
diff --git a/www/public/api/v2/index.php b/www/public/api/v2/index.php
index 1f1a5804b..5e9d0227f 100644
--- a/www/public/api/v2/index.php
+++ b/www/public/api/v2/index.php
@@ -7,7 +7,7 @@
*/
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
-header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
+header("Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
diff --git a/www/public/resources/js/classes/Layout.js b/www/public/resources/js/classes/Layout.js
index 558f523e9..a98d72a75 100644
--- a/www/public/resources/js/classes/Layout.js
+++ b/www/public/resources/js/classes/Layout.js
@@ -56,6 +56,11 @@ class Layout {
$('body').append('
');
}
+ toggleElementById(id)
+ {
+ $('#' + id).toggle();
+ }
+
/**
* Reload opened or closed elements that where opened/closed before reloading
*/
diff --git a/www/public/resources/js/events/toggle.js b/www/public/resources/js/events/toggle.js
new file mode 100644
index 000000000..aab9b1dfc
--- /dev/null
+++ b/www/public/resources/js/events/toggle.js
@@ -0,0 +1,17 @@
+/**
+ * Event: Toggle element visibility
+ */
+$(document).on('click','.toggle-btn',function (e) {
+ // Prevent parent to be triggered
+ e.stopPropagation();
+
+ // Toggle element
+ $($(this).attr('toggle')).slideToggle();
+
+ // Change icon if exists
+ let icon = $(this).find('.toggle-icon');
+ icon.attr('src', icon.attr('src') === '/assets/icons/next.svg' ? '/assets/icons/down.svg' : '/assets/icons/next.svg');
+
+ // Change opacity
+ $(this).toggleClass('mediumopacity');
+});
diff --git a/www/public/resources/js/task.js b/www/public/resources/js/task.js
index e807c4e68..924d55a12 100644
--- a/www/public/resources/js/task.js
+++ b/www/public/resources/js/task.js
@@ -455,80 +455,51 @@ $(document).on('click',".task-schedule-btn", function () {
$(document).on('submit','#task-form',function (e) {
e.preventDefault();
- /**
- * Main array that will contain the schedule parameters
- */
+ // Main object that will contain the schedule parameters
var scheduleObj = {};
- /**
- * Retrieve the schedule parameters
- */
- $(this).find('.task-schedule-form-params').each(function () {
- /**
- * Retrieve the schedule parameters entered by the user and push them into the object
- * There is no associative array in js so we push an object.
- */
- var params = $(this).find('.task-param');
+ // Main array that will contain all the parameters of each repo to be processed (1 or more repos depending on the user's selection)
+ var taskParams = [];
- params.each(function () {
- /**
- * Retrieve the parameter name (input name) and its value (input value)
- */
- var param_name = $(this).attr('param-name');
+ // Retrieve the schedule parameters
+ $(this).find('.task-schedule-form-params').each(function () {
+ $(this).find('.task-param').each(function () {
+ // Parameter name (input name)
+ const name = $(this).attr('param-name');
- /**
- * If the input is a checkbox and it is checked then its value will be 'true'
- * If it is not checked then its value will be 'false'
- */
+ // If the input is a checkbox and it's checked then its value will be 'true' otherwise 'false'
if ($(this).attr('type') == 'checkbox') {
if ($(this).is(":checked")) {
- var param_value = 'true';
+ var value = 'true';
} else {
- var param_value = 'false';
+ var value = 'false';
}
- /**
- * If the input is a radio button then we only retrieve its value if it is checked, otherwise we move on to the next parameter
- */
+ // If the input is a radio button then we only retrieve its value if it is checked, otherwise we move on to the next parameter
} else if ($(this).attr('type') == 'radio') {
if ($(this).is(":checked")) {
- var param_value = $(this).val();
+ var value = $(this).val();
} else {
return; // return is the equivalent of 'continue' for jquery loops .each()
}
+ // If the input is not a checkbox then we retrieve its value
} else {
- /**
- * If the input is not a checkbox then we retrieve its value
- */
- var param_value = $(this).val();
+ var value = $(this).val();
}
- scheduleObj[param_name] = param_value;
+ scheduleObj[name] = value;
});
});
- /**
- * Main array that will contain all the parameters of each repo to be processed (1 or more repos depending on the user's selection)
- */
- var taskParams = [];
-
- /**
- * Retrieve the parameters entered in the form
- */
+ // Retrieve the parameters entered in the form
$(this).find('.task-form-params').each(function () {
- /**
- * Object that will contain the parameters entered in the form for this repo
- */
+ // Object that will contain the parameters entered in the form for this repo
var obj = {};
- /**
- * Retrieve the task action
- */
+ // Retrieve the task action
obj['action'] = $(this).attr('action');
- /**
- * If action is not 'create' then we retrieve the snap id and env id
- */
+ // If action is not 'create' then we retrieve the snap id and env id
if (obj['action'] != 'create') {
obj['repo-id'] = $(this).attr('repo-id');
obj['snap-id'] = $(this).attr('snap-id');
@@ -553,62 +524,65 @@ $(document).on('submit','#task-form',function (e) {
* then only retrieve input value if its package type is the same as the selected package type
* Else continue to the next parameter
*/
- if ($(this).attr('package-type')) {
- if ($(this).attr('package-type') != packageType && $(this).attr('package-type') != 'all') {
- return; // return is the equivalent of 'continue' for jquery loops .each()
+ if (obj['action'] == 'create') {
+ if ($(this).attr('package-type')) {
+ if ($(this).attr('package-type') != packageType && $(this).attr('package-type') != 'all') {
+ return; // continue
+ }
}
}
- /**
- * Retrieve the parameter name (input name) and its value (input value)
- */
- var param_name = $(this).attr('param-name');
+ // Retrieve the parameter name (input name) and its value (input value)
+ var name = $(this).attr('param-name');
- /**
- * If the input is a checkbox and it is checked then its value will be 'true'
- * If it is not checked then its value will be 'false'
- */
+ // If the input is a checkbox and it is checked then its value will be 'true', otherwise 'false'
if ($(this).attr('type') == 'checkbox') {
if ($(this).is(":checked")) {
- var param_value = 'true';
+ var value = 'true';
} else {
- var param_value = 'false';
+ var value = 'false';
}
- /**
- * If the input is a radio button then we only retrieve its value if it is checked, otherwise we move on to the next parameter
- */
+ // If the input is a radio button then we only retrieve its value if it is checked, otherwise we move on to the next parameter
} else if ($(this).attr('type') == 'radio') {
if ($(this).is(":checked")) {
- var param_value = $(this).val();
+ var value = $(this).val();
} else {
return; // return is the equivalent of 'continue' for jquery loops .each()
}
+ // If the input is not a checkbox then we retrieve its value
} else {
- /**
- * If the input is not a checkbox then we retrieve its value
- */
- var param_value = $(this).val();
+ var value = $(this).val();
}
- obj[param_name] = param_value;
+ // Si le nom contient des points, on crée des sous-objets imbriqués
+ if (name.includes('.')) {
+ var parts = name.split('.');
+ var ref = obj;
+ for (var i = 0; i < parts.length; i++) {
+ if (i === parts.length - 1) {
+ ref[parts[i]] = value;
+ } else {
+ if (typeof ref[parts[i]] !== 'object' || ref[parts[i]] === null) {
+ ref[parts[i]] = {};
+ }
+ ref = ref[parts[i]];
+ }
+ }
+ } else {
+ obj[name] = value;
+ }
});
- /**
- * Add the schedule parameters to the task itself
- */
+ // Add the schedule parameters to the task itself
obj['schedule'] = scheduleObj;
- /**
- * Push each repo parameter into the main array
- */
+ // Push each repo parameter into the main array
taskParams.push(obj);
});
- /**
- * Convert the main array to JSON format and send it to php for verification of the parameters
- */
- var taskParamsJson = JSON.stringify(taskParams);
+ // Convert the main array to JSON format and send it to php for verification of the parameters
+ const taskParamsJson = JSON.stringify(taskParams);
// for debug only
// console.log(taskParamsJson);
diff --git a/www/public/resources/styles/common.css b/www/public/resources/styles/common.css
index 212bbe953..a596d5c3d 100644
--- a/www/public/resources/styles/common.css
+++ b/www/public/resources/styles/common.css
@@ -88,6 +88,35 @@ h6 {
word-break: break-all;
}
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ row-gap: 8px;
+ padding: 18px;
+ margin-top: 15px;
+ margin-bottom: 15px;
+ border: 1px dashed #5473e8;
+ border-radius: 20px;
+ background-color: #13263a;
+}
+
+.empty-state p {
+ margin: 0;
+}
+
+.empty-state-title {
+ font-weight: bold;
+}
+
+.empty-state-actions {
+ display: flex;
+ flex-wrap: wrap;
+ column-gap: 10px;
+ row-gap: 8px;
+ margin-top: 4px;
+}
+
.required::after {
content: ' *';
font-size: 12px;
@@ -407,4 +436,4 @@ code {
#loading {
display: flex;
}
-}
\ No newline at end of file
+}
diff --git a/www/public/resources/styles/components/button.css b/www/public/resources/styles/components/button.css
index 368443e05..5a3494c1e 100644
--- a/www/public/resources/styles/components/button.css
+++ b/www/public/resources/styles/components/button.css
@@ -1,7 +1,7 @@
/* All buttons */
[class^="btn-"], [class*=" btn-"], input::file-selector-button {
- padding: 10px 5px 10px 5px;
+ padding: 10px;
border: none;
border-radius: 60px;
color: white;
diff --git a/www/tasks/execute.php b/www/tasks/execute.php
index e22edfb0e..bf2a316f0 100644
--- a/www/tasks/execute.php
+++ b/www/tasks/execute.php
@@ -14,6 +14,7 @@
$mysettings = new \Controllers\Settings();
$myTask = new \Controllers\Task\Task();
+$taskListingController = new \Controllers\Task\Listing();
$mylog = new \Controllers\Log\Log();
$myFatalErrorHandler = new \Controllers\FatalErrorHandler();
@@ -107,12 +108,12 @@
/**
* Get running tasks
*/
- $runningTasks = $myTask->listRunning();
+ $runningTasks = $taskListingController->getRunning();
/**
* Get all currently queued tasks
*/
- $queuedTasks = $myTask->listQueued();
+ $queuedTasks = $taskListingController->getQueued();
/**
* First, check if the taskId is still in the queued tasks list.
diff --git a/www/templates/source-repositories/rpm/almalinux-vault.yml b/www/templates/source-repositories/rpm/almalinux-vault.yml
new file mode 100644
index 000000000..d75bdc104
--- /dev/null
+++ b/www/templates/source-repositories/rpm/almalinux-vault.yml
@@ -0,0 +1,328 @@
+---
+description: Alma Linux official vaulted repositories
+type: rpm
+repositories:
+ # Alma Linux Vault BaseOS
+ - name: almalinux-vault-baseos
+ type: rpm
+ description: Alma Linux BaseOS
+ url: https://vault.almalinux.org/$releasever/BaseOS/$basearch/os
+ releasever:
+ - name: 9.6
+ description: Alma Linux 9.6
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.5
+ description: Alma Linux 9.5
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.4
+ description: Alma Linux 9.4
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.3
+ description: Alma Linux 9.3
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.2
+ description: Alma Linux 9.2
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.1
+ description: Alma Linux 9.1
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.0
+ description: Alma Linux 9.0
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 8.9
+ description: Alma Linux 8.9
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.8
+ description: Alma Linux 8.8
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.7
+ description: Alma Linux 8.7
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.6
+ description: Alma Linux 8.6
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.5
+ description: Alma Linux 8.5
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.4
+ description: Alma Linux 8.4
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.3
+ description: Alma Linux 8.3
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ # Alma Linux Vault AppStream
+ - name: almalinux-vault-appstream
+ type: rpm
+ description: Alma Linux AppStream
+ url: https://vault.almalinux.org/$releasever/AppStream/$basearch/os
+ releasever:
+ - name: 9.6
+ description: Alma Linux 9.6
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.5
+ description: Alma Linux 9.5
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.4
+ description: Alma Linux 9.4
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.3
+ description: Alma Linux 9.3
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.2
+ description: Alma Linux 9.2
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.1
+ description: Alma Linux 9.1
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.0
+ description: Alma Linux 9.0
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 8.9
+ description: Alma Linux 8.9
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.8
+ description: Alma Linux 8.8
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.7
+ description: Alma Linux 8.7
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.6
+ description: Alma Linux 8.6
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.5
+ description: Alma Linux 8.5
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.4
+ description: Alma Linux 8.4
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.3
+ description: Alma Linux 8.3
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ # Alma Linux Vault Extras
+ - name: almalinux-vault-extras
+ type: rpm
+ description: Alma Linux Extras
+ url: https://vault.almalinux.org/$releasever/extras/$basearch/os
+ releasever:
+ - name: 9.6
+ description: Alma Linux 9.6
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.5
+ description: Alma Linux 9.5
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.4
+ description: Alma Linux 9.4
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.3
+ description: Alma Linux 9.3
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.2
+ description: Alma Linux 9.2
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.1
+ description: Alma Linux 9.1
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.0
+ description: Alma Linux 9.0
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 8.9
+ description: Alma Linux 8.9
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.8
+ description: Alma Linux 8.8
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.7
+ description: Alma Linux 8.7
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.6
+ description: Alma Linux 8.6
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.5
+ description: Alma Linux 8.5
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.4
+ description: Alma Linux 8.4
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.3
+ description: Alma Linux 8.3
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ # Alma Linux Vault Plus
+ - name: almalinux-vault-plus
+ type: rpm
+ description: Alma Linux Plus
+ url: https://vault.almalinux.org/$releasever/plus/$basearch/os
+ releasever:
+ - name: 9.6
+ description: Alma Linux 9.6
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.5
+ description: Alma Linux 9.5
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.4
+ description: Alma Linux 9.4
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.3
+ description: Alma Linux 9.3
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.2
+ description: Alma Linux 9.2
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.1
+ description: Alma Linux 9.1
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 9.0
+ description: Alma Linux 9.0
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 8.9
+ description: Alma Linux 8.9
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.8
+ description: Alma Linux 8.8
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.7
+ description: Alma Linux 8.7
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.6
+ description: Alma Linux 8.6
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.5
+ description: Alma Linux 8.5
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.4
+ description: Alma Linux 8.4
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ - name: 8.3
+ description: Alma Linux 8.3
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
+
+ # Alma Linux Vault Devel
+ - name: almalinux-vault-devel
+ type: rpm
+ description: Alma Linux Devel
+ url: https://vault.almalinux.org/$releasever/devel/$basearch/os
+ releasever:
+ - name: 10
+ description: Alma Linux 10
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-10
+
+ - name: 9
+ description: Alma Linux 9
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9
+
+ - name: 8
+ description: Alma Linux 8
+ gpgkeys:
+ - link: https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8
diff --git a/www/update/database/5.11.0.php b/www/update/database/5.11.0.php
new file mode 100644
index 000000000..e17a74200
--- /dev/null
+++ b/www/update/database/5.11.0.php
@@ -0,0 +1,86 @@
+db->exec("DROP INDEX IF EXISTS repos_snap_index");
+ $this->db->exec("DROP INDEX IF EXISTS repos_snap_status_id_repo_index");
+ $this->db->exec("DROP INDEX IF EXISTS repos_snap_id_repo_index");
+} catch (Exception $e) {
+ throw new Exception('could not delete old indexes from repos database: ' . $e->getMessage());
+}
+
+// Add 'Advanced_params' column to repos_snap table
+if (!$this->db->columnExist('repos_snap', 'Advanced_params')) {
+ $this->db->exec("ALTER TABLE repos_snap ADD COLUMN Advanced_params TEXT");
+}
+
+// Create new indexes
+try {
+ $this->db->exec("CREATE INDEX IF NOT EXISTS idx_repos_snap ON repos_snap (Date, Time, Signed, Arch, Type, Reconstruct, Status, Id_repo)");
+ $this->db->exec("CREATE INDEX IF NOT EXISTS idx_repos_snap_status_id_repo ON repos_snap (Status, Id_repo)");
+ $this->db->exec("CREATE INDEX IF NOT EXISTS idx_repos_snap_id_repo ON repos_snap (Id_repo)");
+} catch (Exception $e) {
+ throw new Exception('could not create new indexes for repos database: ' . $e->getMessage());
+}
+
+// Get all snapshots
+try {
+ $snapshots = [];
+ $result = $this->db->query("SELECT * FROM repos_snap");
+
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $snapshots[] = $row;
+ }
+} catch (Exception $e) {
+ throw new Exception('could not fetch snapshots from repos database: ' . $e->getMessage());
+}
+
+// If snapshot has 'Pkg_included' or 'Pkg_excluded' columns, move their content to 'Advanced_params' column and remove them
+foreach ($snapshots as $snapshot) {
+ $advancedParams = [];
+
+ if (!empty($snapshot['Pkg_included'])) {
+ $advancedParams['packages']['include'] = explode(',', $snapshot['Pkg_included']);
+ }
+
+ if (!empty($snapshot['Pkg_excluded'])) {
+ $advancedParams['packages']['exclude'] = explode(',', $snapshot['Pkg_excluded']);
+ }
+
+ // Encode advanced params as JSON and update snapshot with new content
+ try {
+ $advancedParams = json_encode($advancedParams, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
+ } catch (JsonException $e) {
+ throw new Exception('could not encode advanced params as JSON: ' . $e->getMessage());
+ }
+
+ // Update snapshot with new 'Advanced_params' content
+ try {
+ $stmt = $this->db->prepare("UPDATE repos_snap SET Advanced_params = :advancedParams WHERE Id = :id");
+ $stmt->bindValue(':advancedParams', $advancedParams, SQLITE3_TEXT);
+ $stmt->bindValue(':id', $snapshot['Id'], SQLITE3_INTEGER);
+ $stmt->execute();
+ } catch (Exception $e) {
+ throw new Exception('could not update snapshot with new advanced params: ' . $e->getMessage());
+ }
+}
+
+// Finally, drop 'Pkg_included', 'Pkg_excluded' and 'DEB_DEFAULT_TRANSLATION' columns from database if they exist
+try {
+ if ($this->db->columnExist('repos_snap', 'Pkg_included')) {
+ $this->db->exec("ALTER TABLE repos_snap DROP COLUMN Pkg_included");
+ }
+
+ if ($this->db->columnExist('repos_snap', 'Pkg_excluded')) {
+ $this->db->exec("ALTER TABLE repos_snap DROP COLUMN Pkg_excluded");
+ }
+
+ if ($this->db->columnExist('settings', 'DEB_DEFAULT_TRANSLATION')) {
+ $this->db->exec("ALTER TABLE settings DROP COLUMN DEB_DEFAULT_TRANSLATION");
+ }
+} catch (Exception $e) {
+ throw new Exception('could not drop old columns from repos database: ' . $e->getMessage());
+}
diff --git a/www/version b/www/version
index c355d6e21..57f82f727 100644
--- a/www/version
+++ b/www/version
@@ -1 +1 @@
-5.10.0
\ No newline at end of file
+5.11.0
\ No newline at end of file
diff --git a/www/views/includes/containers/hosts/list.inc.php b/www/views/includes/containers/hosts/list.inc.php
index bf29f632d..ebf0a77cf 100644
--- a/www/views/includes/containers/hosts/list.inc.php
+++ b/www/views/includes/containers/hosts/list.inc.php
@@ -34,7 +34,7 @@
+ if (HostPermission::allowedAction('edit-groups') and $totalHosts > 0) : ?>
Groups
@@ -42,7 +42,7 @@
+ if (HostPermission::allowedAction('edit-settings') and $totalHosts > 0) : ?>
Settings
@@ -54,8 +54,14 @@
-
No host registered yet!
-
Install linupdate on your hosts to register them to Repomanager. This page will display dashboards and informations about the hosts and their packages (installed, available, updated...). See quick setup example
+
+
No host registered yet.
+
Install linupdate on your hosts to register them to Repomanager. This page will display dashboards and informations about the hosts and their packages (installed, available, updated...).