Compare commits
10 Commits
09ae8babda
...
553c87d7ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
553c87d7ea | ||
|
|
0bedb7a877 | ||
|
|
1f905c5c74 | ||
|
|
2750bf6b86 | ||
|
|
8bf711c417 | ||
|
|
f8bc853d2f | ||
|
|
7f716fa2d6 | ||
|
|
23f5628cc9 | ||
|
|
dd55a3be06 | ||
|
|
e66ecc6b8b |
2
.github/workflows/frontend.yaml
vendored
2
.github/workflows/frontend.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
|
||||
2
.github/workflows/linting.yaml
vendored
2
.github/workflows/linting.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
|
||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
coverage: pcov
|
||||
|
||||
2
.github/workflows/release-drafter.yaml
vendored
2
.github/workflows/release-drafter.yaml
vendored
@@ -29,6 +29,6 @@ jobs:
|
||||
needs: correct_repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@693d20e7c1ce1a81d3a41962f85914253b518449 # v7.3.1
|
||||
- uses: release-drafter/release-drafter@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7.4.0
|
||||
env:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/renovate.yaml
vendored
2
.github/workflows/renovate.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run Renovate
|
||||
uses: renovatebot/github-action@693b9ef15eec82123529a37c782242f091365961 # v46.1.14
|
||||
uses: renovatebot/github-action@8217b3fc286df088d7c27f3255fe8414463bc0fd # v46.1.15
|
||||
with:
|
||||
token: ${{ secrets.RENOVATE_TOKEN }}
|
||||
env:
|
||||
|
||||
4
.github/workflows/testing.yaml
vendored
4
.github/workflows/testing.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
mysql:
|
||||
image: mysql@sha256:c36050afdca850f23cef85703f84c7531a5ae155a11b5ee1c60acb09937c4084 # 8.4.9
|
||||
image: mysql@sha256:e370cd5f64599d46985b7729b452f2153825246f88d82753ec595c5dfc6fef6a # 8.4.9
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: false
|
||||
MYSQL_ROOT_PASSWORD: kimai
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
|
||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
coverage: pcov
|
||||
|
||||
@@ -98,12 +98,18 @@
|
||||
"symfony/css-selector": "^6.0",
|
||||
"symfony/debug-bundle": "^6.0",
|
||||
"symfony/dom-crawler": "^6.0",
|
||||
"symfony/maker-bundle": "^1.67",
|
||||
"symfony/phpunit-bridge": "^6.0",
|
||||
"symfony/stopwatch": "^6.0",
|
||||
"symfony/var-dumper": "^6.0",
|
||||
"symfony/web-profiler-bundle": "^6.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"name": "import-plugin",
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/kevinpapst/ImportBundle.git"
|
||||
},
|
||||
{
|
||||
"type": "artifact",
|
||||
"url": "var/packages/"
|
||||
|
||||
225
composer.lock
generated
225
composer.lock
generated
@@ -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": "3a36f355f322598267719beb6a7c22e0",
|
||||
"content-hash": "ccb50983df5715995863ee9aa6d53de2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "azuyalabs/yasumi",
|
||||
@@ -135,28 +135,29 @@
|
||||
},
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.3.2",
|
||||
"version": "3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
|
||||
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed",
|
||||
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan": "<1.11.10"
|
||||
"phpstan/phpstan": "<2.2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.12 || ^2",
|
||||
"phpstan/phpstan-strict-rules": "^1 || ^2",
|
||||
"phpunit/phpunit": "^8 || ^9"
|
||||
"phpstan/phpstan": "^2",
|
||||
"phpstan/phpstan-deprecation-rules": "^2",
|
||||
"phpstan/phpstan-strict-rules": "^2",
|
||||
"phpunit/phpunit": "^9"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -194,7 +195,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.3.2"
|
||||
"source": "https://github.com/composer/pcre/tree/3.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -204,13 +205,9 @@
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-12T16:29:46+00:00"
|
||||
"time": "2026-06-07T11:47:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
@@ -774,16 +771,16 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/doctrine-bundle",
|
||||
"version": "2.18.2",
|
||||
"version": "2.18.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/DoctrineBundle.git",
|
||||
"reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546"
|
||||
"reference": "241d61f6bbc77275d5a9f95d514241c058bf2d0a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0ff098b29b8b3c68307c8987dcaed7fd829c6546",
|
||||
"reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546",
|
||||
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/241d61f6bbc77275d5a9f95d514241c058bf2d0a",
|
||||
"reference": "241d61f6bbc77275d5a9f95d514241c058bf2d0a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -820,6 +817,7 @@
|
||||
"psr/log": "^1.1.4 || ^2.0 || ^3.0",
|
||||
"symfony/doctrine-messenger": "^6.4 || ^7.0",
|
||||
"symfony/expression-language": "^6.4 || ^7.0",
|
||||
"symfony/http-kernel": "^6.4 || ^7.0",
|
||||
"symfony/messenger": "^6.4 || ^7.0",
|
||||
"symfony/property-info": "^6.4 || ^7.0",
|
||||
"symfony/security-bundle": "^6.4 || ^7.0",
|
||||
@@ -875,7 +873,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/DoctrineBundle/issues",
|
||||
"source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.2"
|
||||
"source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -891,7 +889,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-20T21:35:32+00:00"
|
||||
"time": "2026-06-08T08:22:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/doctrine-migrations-bundle",
|
||||
@@ -5187,16 +5185,16 @@
|
||||
},
|
||||
{
|
||||
"name": "setasign/fpdi",
|
||||
"version": "v2.6.7",
|
||||
"version": "v2.6.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Setasign/FPDI.git",
|
||||
"reference": "388c51e69982a3fc16698710b763e8107a49f510"
|
||||
"reference": "881945be29a4996ad3d008eb18ddc01fa3df890c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/388c51e69982a3fc16698710b763e8107a49f510",
|
||||
"reference": "388c51e69982a3fc16698710b763e8107a49f510",
|
||||
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/881945be29a4996ad3d008eb18ddc01fa3df890c",
|
||||
"reference": "881945be29a4996ad3d008eb18ddc01fa3df890c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5208,7 +5206,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.52",
|
||||
"setasign/fpdf": "~1.8.6",
|
||||
"setasign/fpdf": "^1.9.0",
|
||||
"setasign/tfpdf": "~1.33",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"tecnickcom/tcpdf": "^6.8"
|
||||
@@ -5247,7 +5245,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Setasign/FPDI/issues",
|
||||
"source": "https://github.com/Setasign/FPDI/tree/v2.6.7"
|
||||
"source": "https://github.com/Setasign/FPDI/tree/v2.6.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5255,7 +5253,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-13T10:16:22+00:00"
|
||||
"time": "2026-06-11T10:37:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "smalot/pdfparser",
|
||||
@@ -8028,16 +8026,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php83",
|
||||
"version": "v1.38.1",
|
||||
"version": "v1.38.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php83.git",
|
||||
"reference": "8339098cae28673c15cce00d80734af0453054e2"
|
||||
"reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2",
|
||||
"reference": "8339098cae28673c15cce00d80734af0453054e2",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8",
|
||||
"reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8084,7 +8082,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1"
|
||||
"source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8104,7 +8102,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-26T12:51:13+00:00"
|
||||
"time": "2026-05-27T06:51:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php84",
|
||||
@@ -10749,16 +10747,16 @@
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webmozarts/assert.git",
|
||||
"reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155"
|
||||
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155",
|
||||
"reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155",
|
||||
"url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10809,9 +10807,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webmozarts/assert/issues",
|
||||
"source": "https://github.com/webmozarts/assert/tree/2.4.0"
|
||||
"source": "https://github.com/webmozarts/assert/tree/2.4.1"
|
||||
},
|
||||
"time": "2026-05-20T13:07:01+00:00"
|
||||
"time": "2026-06-15T15:31:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "willdurand/jsonp-callback-validator",
|
||||
@@ -11617,16 +11615,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.95.4",
|
||||
"version": "v3.95.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "3f8f68856837a77e1f1d870354eca3c8747f2f72"
|
||||
"reference": "4140023f552ff02346df9b1329742532166f677f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/3f8f68856837a77e1f1d870354eca3c8747f2f72",
|
||||
"reference": "3f8f68856837a77e1f1d870354eca3c8747f2f72",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/4140023f552ff02346df9b1329742532166f677f",
|
||||
"reference": "4140023f552ff02346df9b1329742532166f677f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11644,7 +11642,7 @@
|
||||
"react/event-loop": "^1.5",
|
||||
"react/socket": "^1.16",
|
||||
"react/stream": "^1.4",
|
||||
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0",
|
||||
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0 || ^9.0",
|
||||
"symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0",
|
||||
"symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0",
|
||||
"symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0",
|
||||
@@ -11660,16 +11658,16 @@
|
||||
"require-dev": {
|
||||
"facile-it/paraunit": "^1.3.1 || ^2.11.0",
|
||||
"infection/infection": "^0.32.7",
|
||||
"justinrainbow/json-schema": "^6.8.0",
|
||||
"justinrainbow/json-schema": "^6.9.0",
|
||||
"keradus/cli-executor": "^2.3",
|
||||
"mikey179/vfsstream": "^1.6.12",
|
||||
"php-coveralls/php-coveralls": "^2.9.1",
|
||||
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8",
|
||||
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8",
|
||||
"phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.55",
|
||||
"symfony/polyfill-php85": "^1.37",
|
||||
"symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.8",
|
||||
"symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.11"
|
||||
"symfony/polyfill-php85": "^1.38",
|
||||
"symfony/var-dumper": "^5.4.48 || ^6.4.36 || ^7.4.8 || ^8.1.0",
|
||||
"symfony/yaml": "^5.4.53 || ^6.4.41 || ^7.4.13 || ^8.1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "For handling output formats in XML",
|
||||
@@ -11710,7 +11708,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.4"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11718,7 +11716,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-03T18:02:44+00:00"
|
||||
"time": "2026-06-16T09:52:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "masterminds/html5",
|
||||
@@ -12021,21 +12019,21 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-doctrine",
|
||||
"version": "2.0.25",
|
||||
"version": "2.0.27",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-doctrine.git",
|
||||
"reference": "e20e8bf3223ae6eba9c4b5987c391d922e094b3c"
|
||||
"reference": "39b4ca45a07cdd6366eeefa2f7a993cddf3b9f9f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/e20e8bf3223ae6eba9c4b5987c391d922e094b3c",
|
||||
"reference": "e20e8bf3223ae6eba9c4b5987c391d922e094b3c",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/39b4ca45a07cdd6366eeefa2f7a993cddf3b9f9f",
|
||||
"reference": "39b4ca45a07cdd6366eeefa2f7a993cddf3b9f9f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"phpstan/phpstan": "^2.1.34"
|
||||
"phpstan/phpstan": "^2.2.2"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/collections": "<1.0",
|
||||
@@ -12092,9 +12090,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-doctrine/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.25"
|
||||
"source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.27"
|
||||
},
|
||||
"time": "2026-06-02T20:27:36+00:00"
|
||||
"time": "2026-06-10T10:39:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
@@ -12205,16 +12203,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-symfony",
|
||||
"version": "2.0.19",
|
||||
"version": "2.0.20",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
||||
"reference": "546071ed7f80a89ec30909346eb7cc741800740a"
|
||||
"reference": "53f1a6462dbe71fad36ce054caf5e1b725b740fd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/546071ed7f80a89ec30909346eb7cc741800740a",
|
||||
"reference": "546071ed7f80a89ec30909346eb7cc741800740a",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/53f1a6462dbe71fad36ce054caf5e1b725b740fd",
|
||||
"reference": "53f1a6462dbe71fad36ce054caf5e1b725b740fd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -12273,9 +12271,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.19"
|
||||
"source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.20"
|
||||
},
|
||||
"time": "2026-05-29T12:52:44+00:00"
|
||||
"time": "2026-06-16T09:17:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@@ -14407,6 +14405,105 @@
|
||||
],
|
||||
"time": "2026-05-19T20:33:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/maker-bundle",
|
||||
"version": "v1.67.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/maker-bundle.git",
|
||||
"reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/maker-bundle/zipball/6ce8b313845f16bcf385ee3cb31d8b24e30d5516",
|
||||
"reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-runtime-api": "^2.1",
|
||||
"doctrine/inflector": "^2.0",
|
||||
"nikic/php-parser": "^5.0",
|
||||
"php": ">=8.1",
|
||||
"symfony/config": "^6.4|^7.0|^8.0",
|
||||
"symfony/console": "^6.4|^7.0|^8.0",
|
||||
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
|
||||
"symfony/deprecation-contracts": "^2.2|^3",
|
||||
"symfony/filesystem": "^6.4|^7.0|^8.0",
|
||||
"symfony/finder": "^6.4|^7.0|^8.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.0|^8.0",
|
||||
"symfony/http-kernel": "^6.4|^7.0|^8.0",
|
||||
"symfony/process": "^6.4|^7.0|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/doctrine-bundle": "<2.10",
|
||||
"doctrine/orm": "<2.15"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/semver": "^3.0",
|
||||
"doctrine/doctrine-bundle": "^2.10|^3.0",
|
||||
"doctrine/orm": "^2.15|^3",
|
||||
"doctrine/persistence": "^3.1|^4.0",
|
||||
"symfony/http-client": "^6.4|^7.0|^8.0",
|
||||
"symfony/phpunit-bridge": "^6.4.1|^7.0|^8.0",
|
||||
"symfony/security-core": "^6.4|^7.0|^8.0",
|
||||
"symfony/security-http": "^6.4|^7.0|^8.0",
|
||||
"symfony/yaml": "^6.4|^7.0|^8.0",
|
||||
"twig/twig": "^3.0|^4.x-dev"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Bundle\\MakerBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.",
|
||||
"homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html",
|
||||
"keywords": [
|
||||
"code generator",
|
||||
"dev",
|
||||
"generator",
|
||||
"scaffold",
|
||||
"scaffolding"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/symfony/maker-bundle/issues",
|
||||
"source": "https://github.com/symfony/maker-bundle/tree/v1.67.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-18T13:39:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/phpunit-bridge",
|
||||
"version": "v6.4.35",
|
||||
|
||||
@@ -19,4 +19,5 @@ return [
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
KevinPapst\TablerBundle\TablerBundle::class => ['all' => true],
|
||||
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
];
|
||||
|
||||
@@ -74,6 +74,7 @@ security:
|
||||
login_link:
|
||||
check_route: link_login_check
|
||||
lifetime: 900
|
||||
login_path: login
|
||||
max_uses: 3
|
||||
# 'password' binds the HMAC signature to the password hash, so a
|
||||
# link becomes invalid as soon as the user changes the password
|
||||
@@ -93,6 +94,8 @@ security:
|
||||
access_control:
|
||||
- {path: '^/auth/2fa', role: IS_AUTHENTICATED_2FA_IN_PROGRESS}
|
||||
- {path: '^/auth', roles: PUBLIC_ACCESS}
|
||||
- {path: '^/login', roles: PUBLIC_ACCESS}
|
||||
- {path: '^/logout', roles: PUBLIC_ACCESS}
|
||||
- {path: '^/{_locale}$', role: PUBLIC_ACCESS}
|
||||
- {path: '^/{_locale}/auth', role: PUBLIC_ACCESS}
|
||||
- {path: '^/{_locale}/login', role: PUBLIC_ACCESS}
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
home:
|
||||
path: /
|
||||
defaults:
|
||||
_controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
|
||||
route: homepage
|
||||
permanent: true
|
||||
|
||||
# SF DefaultAuthenticationSuccessHandler uses /login as default target
|
||||
loginFallback:
|
||||
path: /login
|
||||
defaults:
|
||||
_controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
|
||||
_locale: '%locale%'
|
||||
route: login
|
||||
permanent: true
|
||||
|
||||
# SF LogoutListener uses /logout as default target
|
||||
logoutFallback:
|
||||
path: /logout
|
||||
defaults:
|
||||
_controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
|
||||
_locale: '%locale%'
|
||||
route: logout
|
||||
permanent: true
|
||||
|
||||
controllers:
|
||||
resource: ../src/Controller/
|
||||
type: attribute
|
||||
@@ -31,13 +56,6 @@ kernel:
|
||||
resource: ../src/Kernel.php
|
||||
type: attribute
|
||||
|
||||
home:
|
||||
path: /
|
||||
defaults:
|
||||
_controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
|
||||
route: homepage
|
||||
permanent: true
|
||||
|
||||
homeLocale:
|
||||
path: /{_locale}
|
||||
defaults:
|
||||
|
||||
656
migrate.php
Normal file
656
migrate.php
Normal file
@@ -0,0 +1,656 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Kimai v1 → Kimai v2 Migration Script
|
||||
*
|
||||
* Reads from `kimai` database (prefix: kimai15_)
|
||||
* Writes to `kimai2` database (prefix: kimai2_)
|
||||
*
|
||||
* Usage: php8.3 /root/workspace/kimai2/migrate.php
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// ── Config ──────────────────────────────────────────────────────────────────
|
||||
const SOURCE_DSN = 'mysql:host=localhost;dbname=kimai;charset=utf8mb4';
|
||||
const TARGET_DSN = 'mysql:host=localhost;dbname=kimai2;charset=utf8mb4';
|
||||
const DB_USER = 'root';
|
||||
const DB_PASS = '';
|
||||
|
||||
const DEFAULT_PASSWORD = 'changeme';
|
||||
const DEFAULT_TIMEZONE = 'UTC';
|
||||
const DEFAULT_CURRENCY = 'EUR';
|
||||
const DEFAULT_COUNTRY = '';
|
||||
const BATCH_SIZE = 2000; // rows per INSERT batch
|
||||
|
||||
// ── Bootstrap ───────────────────────────────────────────────────────────────
|
||||
$src = new PDO(SOURCE_DSN, DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
$dst = new PDO(TARGET_DSN, DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
// Disable foreign key checks and autocommit for speed
|
||||
$dst->exec('SET FOREIGN_KEY_CHECKS = 0');
|
||||
$dst->exec('SET AUTOCOMMIT = 0');
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
function progress(string $label, int $done, int $total): void {
|
||||
$pct = $total > 0 ? round($done / $total * 100, 1) : 100.0;
|
||||
printf("\r %s: %d / %d (%s%%)", str_pad($label, 20), $done, $total, $pct);
|
||||
}
|
||||
|
||||
function done(string $label, int $count, float $elapsed): void {
|
||||
printf("\r %s: %d rows (%.2f s)\n", str_pad($label, 20), $count, $elapsed);
|
||||
}
|
||||
|
||||
function batchInsert(PDO $dst, string $table, array $columns, array $rows, int $batchSize = BATCH_SIZE): int {
|
||||
if (empty($rows)) {
|
||||
return 0;
|
||||
}
|
||||
$total = 0;
|
||||
$placeholders = '(' . implode(',', array_fill(0, count($columns), '?')) . ')';
|
||||
$sql = sprintf(
|
||||
'INSERT INTO `%s` (`%s`) VALUES %s',
|
||||
$table,
|
||||
implode('`, `', $columns),
|
||||
implode(',', array_fill(0, min($batchSize, count($rows)), $placeholders))
|
||||
);
|
||||
|
||||
foreach (array_chunk($rows, $batchSize) as $chunk) {
|
||||
// Rebuild SQL if chunk is smaller than batchSize (last chunk)
|
||||
if (count($chunk) < $batchSize) {
|
||||
$sql = sprintf(
|
||||
'INSERT INTO `%s` (`%s`) VALUES %s',
|
||||
$table,
|
||||
implode('`, `', $columns),
|
||||
implode(',', array_fill(0, count($chunk), $placeholders))
|
||||
);
|
||||
}
|
||||
$flat = [];
|
||||
foreach ($chunk as $row) {
|
||||
foreach ($row as $val) {
|
||||
$flat[] = $val;
|
||||
}
|
||||
}
|
||||
$stmt = $dst->prepare($sql);
|
||||
$stmt->execute($flat);
|
||||
$total += count($chunk);
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
function safeString(?string $val, int $maxLen = 255): ?string {
|
||||
if ($val === null || $val === '') {
|
||||
return null;
|
||||
}
|
||||
$val = trim($val);
|
||||
if ($val === '') {
|
||||
return null;
|
||||
}
|
||||
return mb_substr($val, 0, $maxLen);
|
||||
}
|
||||
|
||||
function timestampToDateTime(int $ts): ?string {
|
||||
if ($ts <= 0) {
|
||||
return null;
|
||||
}
|
||||
return date('Y-m-d H:i:s', $ts);
|
||||
}
|
||||
|
||||
// ── Truncate target tables first ────────────────────────────────────────────
|
||||
echo "Clearing target tables...\n";
|
||||
$tables = [
|
||||
'kimai2_timesheet_tags', 'kimai2_timesheet_meta', 'kimai2_timesheet',
|
||||
'kimai2_activities_rates', 'kimai2_activities_teams', 'kimai2_activities_meta', 'kimai2_activities',
|
||||
'kimai2_projects_rates', 'kimai2_projects_teams', 'kimai2_projects_comments', 'kimai2_projects_meta', 'kimai2_projects',
|
||||
'kimai2_customers_rates', 'kimai2_customers_teams', 'kimai2_customers_comments', 'kimai2_customers_meta', 'kimai2_customers',
|
||||
'kimai2_users_teams', 'kimai2_user_preferences', 'kimai2_users',
|
||||
'kimai2_teams',
|
||||
];
|
||||
foreach ($tables as $table) {
|
||||
try {
|
||||
$dst->exec("DELETE FROM `$table`");
|
||||
echo " Cleared: $table\n";
|
||||
} catch (PDOException $e) {
|
||||
echo " Skipped (doesn't exist): $table\n";
|
||||
}
|
||||
}
|
||||
$dst->exec('COMMIT');
|
||||
echo "\n";
|
||||
|
||||
// ── Migration order: master data first, then transactions ───────────────────
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ 1. Groups → Teams ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
echo "--- Migrating Groups → Teams ---\n";
|
||||
|
||||
$t0 = microtime(true);
|
||||
$groups = $src->query('SELECT grp_ID, grp_name FROM kimai15_grp WHERE grp_trash = 0')->fetchAll();
|
||||
$rows = [];
|
||||
foreach ($groups as $g) {
|
||||
$rows[] = [
|
||||
'id' => (int) $g['grp_ID'],
|
||||
'name' => safeString($g['grp_name'], 100) ?? '',
|
||||
'color' => null,
|
||||
];
|
||||
}
|
||||
$count = batchInsert($dst, 'kimai2_teams', ['id', 'name', 'color'], $rows);
|
||||
$dst->exec('COMMIT');
|
||||
done('Teams', $count, microtime(true) - $t0);
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ 2. Customers ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
echo "--- Migrating Customers ---\n";
|
||||
|
||||
$t0 = microtime(true);
|
||||
$customers = $src->query('SELECT * FROM kimai15_knd WHERE knd_trash = 0')->fetchAll();
|
||||
$total = count($customers);
|
||||
$count = 0;
|
||||
|
||||
$rows = [];
|
||||
foreach ($customers as $c) {
|
||||
$rows[] = [
|
||||
'id' => (int) $c['knd_ID'],
|
||||
'name' => safeString($c['knd_name'], 150) ?: '',
|
||||
'number' => null,
|
||||
'comment' => $c['knd_comment'] ?: null,
|
||||
'visible' => (int) $c['knd_visible'],
|
||||
'company' => safeString($c['knd_company'], 100),
|
||||
'contact' => safeString($c['knd_contact'], 100),
|
||||
'address' => $c['knd_street'] ?: null,
|
||||
'country' => DEFAULT_COUNTRY,
|
||||
'currency' => DEFAULT_CURRENCY,
|
||||
'phone' => safeString($c['knd_tel'], 30),
|
||||
'fax' => safeString($c['knd_fax'], 30),
|
||||
'mobile' => safeString($c['knd_mobile'], 30),
|
||||
'email' => safeString($c['knd_mail'], 75),
|
||||
'homepage' => safeString($c['knd_homepage'], 100),
|
||||
'timezone' => DEFAULT_TIMEZONE,
|
||||
'color' => null,
|
||||
'time_budget' => 0,
|
||||
'budget' => 0.0,
|
||||
'vat_id' => safeString($c['knd_vat'], 50),
|
||||
'budget_type' => null,
|
||||
'billable' => 1,
|
||||
'invoice_template_id' => null,
|
||||
'invoice_text' => null,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'address_line1' => safeString($c['knd_street'], 150),
|
||||
'address_line2' => null,
|
||||
'address_line3' => null,
|
||||
'postcode' => safeString($c['knd_zipcode'], 20),
|
||||
'city' => safeString($c['knd_city'], 50),
|
||||
'buyer_reference' => null,
|
||||
];
|
||||
$count++;
|
||||
if ($count % 100 === 0) {
|
||||
progress('Customers', $count, $total);
|
||||
}
|
||||
}
|
||||
$imported = batchInsert($dst, 'kimai2_customers', [
|
||||
'id', 'name', 'number', 'comment', 'visible', 'company', 'contact', 'address',
|
||||
'country', 'currency', 'phone', 'fax', 'mobile', 'email', 'homepage',
|
||||
'timezone', 'color', 'time_budget', 'budget', 'vat_id', 'budget_type',
|
||||
'billable', 'invoice_template_id', 'invoice_text', 'created_at',
|
||||
'address_line1', 'address_line2', 'address_line3', 'postcode', 'city', 'buyer_reference',
|
||||
], $rows);
|
||||
$dst->exec('COMMIT');
|
||||
done('Customers', $imported, microtime(true) - $t0);
|
||||
|
||||
// Reset auto-increment after inserting with explicit IDs
|
||||
$dst->exec("ALTER TABLE kimai2_customers AUTO_INCREMENT = " . ((int) $src->query('SELECT MAX(knd_ID) FROM kimai15_knd')->fetchColumn() + 1));
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ 3. Projects ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
echo "--- Migrating Projects ---\n";
|
||||
|
||||
$t0 = microtime(true);
|
||||
$projects = $src->query('SELECT * FROM kimai15_pct WHERE pct_trash = 0')->fetchAll();
|
||||
$total = count($projects);
|
||||
$count = 0;
|
||||
|
||||
$rows = [];
|
||||
foreach ($projects as $p) {
|
||||
$rows[] = [
|
||||
'id' => (int) $p['pct_ID'],
|
||||
'customer_id' => (int) $p['pct_kndID'],
|
||||
'name' => safeString($p['pct_name'], 150) ?: '',
|
||||
'order_number'=> null,
|
||||
'comment' => $p['pct_comment'] ?: null,
|
||||
'visible' => (int) $p['pct_visible'],
|
||||
'budget' => (float) $p['pct_budget'],
|
||||
'color' => null,
|
||||
'time_budget' => 0,
|
||||
'order_date' => null,
|
||||
'start' => null,
|
||||
'end' => null,
|
||||
'timezone' => null,
|
||||
'budget_type' => null,
|
||||
'billable' => (int) $p['pct_billable'],
|
||||
'invoice_text'=> null,
|
||||
'global_activities' => 1,
|
||||
'number' => null,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
$count++;
|
||||
if ($count % 100 === 0) {
|
||||
progress('Projects', $count, $total);
|
||||
}
|
||||
}
|
||||
$imported = batchInsert($dst, 'kimai2_projects', [
|
||||
'id', 'customer_id', 'name', 'order_number', 'comment', 'visible',
|
||||
'budget', 'color', 'time_budget', 'order_date', 'start', 'end',
|
||||
'timezone', 'budget_type', 'billable', 'invoice_text',
|
||||
'global_activities', 'number', 'created_at',
|
||||
], $rows);
|
||||
$dst->exec('COMMIT');
|
||||
done('Projects', $imported, microtime(true) - $t0);
|
||||
|
||||
$dst->exec("ALTER TABLE kimai2_projects AUTO_INCREMENT = " . ((int) $src->query('SELECT MAX(pct_ID) FROM kimai15_pct')->fetchColumn() + 1));
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ 4. Activities (global) ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
echo "--- Migrating Activities ---\n";
|
||||
|
||||
$t0 = microtime(true);
|
||||
$activities = $src->query('SELECT * FROM kimai15_evt WHERE evt_trash = 0')->fetchAll();
|
||||
$total = count($activities);
|
||||
$count = 0;
|
||||
|
||||
$rows = [];
|
||||
foreach ($activities as $a) {
|
||||
$rows[] = [
|
||||
'id' => (int) $a['evt_ID'],
|
||||
'project_id' => null, // global activities
|
||||
'name' => safeString($a['evt_name'], 150) ?: '',
|
||||
'comment' => $a['evt_comment'] ?: null,
|
||||
'visible' => (int) $a['evt_visible'],
|
||||
'color' => null,
|
||||
'time_budget'=> 0,
|
||||
'budget' => 0.0,
|
||||
'budget_type'=> null,
|
||||
'billable' => 1,
|
||||
'invoice_text'=> null,
|
||||
'number' => null,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
$count++;
|
||||
if ($count % 100 === 0) {
|
||||
progress('Activities', $count, $total);
|
||||
}
|
||||
}
|
||||
$imported = batchInsert($dst, 'kimai2_activities', [
|
||||
'id', 'project_id', 'name', 'comment', 'visible', 'color',
|
||||
'time_budget', 'budget', 'budget_type', 'billable', 'invoice_text',
|
||||
'number', 'created_at',
|
||||
], $rows);
|
||||
$dst->exec('COMMIT');
|
||||
done('Activities', $imported, microtime(true) - $t0);
|
||||
|
||||
$dst->exec("ALTER TABLE kimai2_activities AUTO_INCREMENT = " . ((int) $src->query('SELECT MAX(evt_ID) FROM kimai15_evt')->fetchColumn() + 1));
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ 5. Users ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
echo "--- Migrating Users ---\n";
|
||||
|
||||
$t0 = microtime(true);
|
||||
$users = $src->query('SELECT * FROM kimai15_usr ORDER BY usr_ID')->fetchAll();
|
||||
$total = count($users);
|
||||
$count = 0;
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$passwordHash = password_hash(DEFAULT_PASSWORD, PASSWORD_BCRYPT);
|
||||
|
||||
$userRows = [];
|
||||
$prefRows = [];
|
||||
$teamRows = [];
|
||||
|
||||
$seenEmails = [];
|
||||
$seenUsernames = [];
|
||||
|
||||
foreach ($users as $u) {
|
||||
$userId = (int) $u['usr_ID'];
|
||||
$username = safeString($u['usr_name'], 180) ?: 'user_' . $userId;
|
||||
$alias = safeString($u['usr_alias'], 60);
|
||||
$email = safeString($u['usr_mail'], 180) ?: ($username . '@migrated.local');
|
||||
$enabled = (int) $u['usr_active'];
|
||||
$v1group = (int) $u['usr_grp'];
|
||||
|
||||
// Deduplicate username (case-insensitive)
|
||||
$origUsername = $username;
|
||||
$suffix = 1;
|
||||
while (isset($seenUsernames[mb_strtolower($username)])) {
|
||||
$username = mb_substr($origUsername, 0, 170) . '_' . $suffix;
|
||||
$suffix++;
|
||||
}
|
||||
$seenUsernames[mb_strtolower($username)] = true;
|
||||
|
||||
// Deduplicate email
|
||||
$origEmail = $email;
|
||||
$suffix = 1;
|
||||
$emailLower = mb_strtolower($email);
|
||||
while (isset($seenEmails[$emailLower])) {
|
||||
$atPos = strpos($origEmail, '@');
|
||||
if ($atPos !== false) {
|
||||
$email = mb_substr($origEmail, 0, min($atPos, 170)) . '+' . $suffix . mb_substr($origEmail, $atPos);
|
||||
} else {
|
||||
$email = mb_substr($origEmail, 0, 170) . '+' . $suffix . '@migrated.local';
|
||||
}
|
||||
$email = mb_substr($email, 0, 180);
|
||||
$emailLower = mb_strtolower($email);
|
||||
$suffix++;
|
||||
}
|
||||
$seenEmails[$emailLower] = true;
|
||||
|
||||
// Role mapping: admin user gets SUPER_ADMIN, others get USER
|
||||
if ($origUsername === 'admin' || $origUsername === 'administrator') {
|
||||
$roles = serialize(['ROLE_SUPER_ADMIN']);
|
||||
} else {
|
||||
$roles = serialize(['ROLE_USER']);
|
||||
}
|
||||
|
||||
$userRows[] = [
|
||||
'id' => $userId,
|
||||
'username' => $username,
|
||||
'email' => $email,
|
||||
'password' => $passwordHash,
|
||||
'alias' => $alias,
|
||||
'enabled' => $enabled,
|
||||
'registration_date' => $now,
|
||||
'title' => null,
|
||||
'avatar' => null,
|
||||
'roles' => $roles,
|
||||
'last_login' => null,
|
||||
'confirmation_token' => null,
|
||||
'password_requested_at' => null,
|
||||
'api_token' => null,
|
||||
'auth' => 'kimai',
|
||||
'color' => null,
|
||||
'account' => null,
|
||||
'totp_secret' => null,
|
||||
'totp_enabled' => 0,
|
||||
'system_account' => 0,
|
||||
'supervisor_id' => null,
|
||||
'signature_date' => null,
|
||||
];
|
||||
|
||||
// User preferences: hourly_rate
|
||||
$prefRows[] = [
|
||||
'user_id' => $userId,
|
||||
'name' => 'hourly_rate',
|
||||
'value' => '0',
|
||||
];
|
||||
$prefRows[] = [
|
||||
'user_id' => $userId,
|
||||
'name' => 'timezone',
|
||||
'value' => DEFAULT_TIMEZONE,
|
||||
];
|
||||
|
||||
// Team membership
|
||||
if ($v1group > 0) {
|
||||
$teamRows[] = [
|
||||
'user_id' => $userId,
|
||||
'team_id' => $v1group,
|
||||
'teamlead' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$count++;
|
||||
if ($count % 100 === 0) {
|
||||
progress('Users', $count, $total);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify no duplicate emails before insert
|
||||
$emailCheck = [];
|
||||
$dupFound = false;
|
||||
foreach ($userRows as $r) {
|
||||
$em = mb_strtolower($r['email']);
|
||||
if (isset($emailCheck[$em])) {
|
||||
echo "\n *** DUPLICATE EMAIL: $em (IDs: {$emailCheck[$em]} and {$r['id']})\n";
|
||||
$dupFound = true;
|
||||
}
|
||||
$emailCheck[$em] = $r['id'];
|
||||
}
|
||||
echo " Verified: " . count($userRows) . " users, " . count($emailCheck) . " unique emails\n";
|
||||
if ($dupFound) {
|
||||
echo " ERROR: Duplicate emails detected, aborting.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$imported = batchInsert($dst, 'kimai2_users', [
|
||||
'id', 'username', 'email', 'password', 'alias', 'enabled',
|
||||
'registration_date', 'title', 'avatar', 'roles', 'last_login',
|
||||
'confirmation_token', 'password_requested_at', 'api_token', 'auth',
|
||||
'color', 'account', 'totp_secret', 'totp_enabled', 'system_account',
|
||||
'supervisor_id', 'signature_date',
|
||||
], $userRows);
|
||||
$dst->exec('COMMIT');
|
||||
done('Users', $imported, microtime(true) - $t0);
|
||||
$dst->exec("ALTER TABLE kimai2_users AUTO_INCREMENT = " . ((int) $src->query('SELECT MAX(usr_ID) FROM kimai15_usr')->fetchColumn() + 1));
|
||||
|
||||
// User preferences
|
||||
echo "--- Migrating User Preferences ---\n";
|
||||
$t0 = microtime(true);
|
||||
$imported = batchInsert($dst, 'kimai2_user_preferences', ['user_id', 'name', 'value'], $prefRows);
|
||||
$dst->exec('COMMIT');
|
||||
done('User Preferences', $imported, microtime(true) - $t0);
|
||||
|
||||
// User-Team mappings
|
||||
echo "--- Migrating User-Team Mappings ---\n";
|
||||
$t0 = microtime(true);
|
||||
$imported = batchInsert($dst, 'kimai2_users_teams', ['user_id', 'team_id', 'teamlead'], $teamRows);
|
||||
$dst->exec('COMMIT');
|
||||
done('User-Team Mappings', $imported, microtime(true) - $t0);
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ 6. Rates ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
echo "--- Migrating Rates ---\n";
|
||||
|
||||
$t0 = microtime(true);
|
||||
$rates = $src->query('SELECT * FROM kimai15_rates')->fetchAll();
|
||||
$projectRates = [];
|
||||
$activityRates = [];
|
||||
|
||||
foreach ($rates as $r) {
|
||||
if (!empty($r['project_id'])) {
|
||||
$projectRates[] = [
|
||||
'user_id' => $r['user_id'] ? (int) $r['user_id'] : null,
|
||||
'project_id' => (int) $r['project_id'],
|
||||
'rate' => (float) $r['rate'],
|
||||
'fixed' => 0,
|
||||
'internal_rate' => null,
|
||||
];
|
||||
}
|
||||
if (!empty($r['event_id'])) {
|
||||
$activityRates[] = [
|
||||
'user_id' => $r['user_id'] ? (int) $r['user_id'] : null,
|
||||
'activity_id' => (int) $r['event_id'],
|
||||
'rate' => (float) $r['rate'],
|
||||
'fixed' => 0,
|
||||
'internal_rate' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$count1 = batchInsert($dst, 'kimai2_projects_rates', ['user_id', 'project_id', 'rate', 'fixed', 'internal_rate'], $projectRates);
|
||||
$count2 = batchInsert($dst, 'kimai2_activities_rates', ['user_id', 'activity_id', 'rate', 'fixed', 'internal_rate'], $activityRates);
|
||||
$dst->exec('COMMIT');
|
||||
done('Rates (projects)', $count1, microtime(true) - $t0);
|
||||
done('Rates (activities)', $count2, microtime(true) - $t0);
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ 7. Timesheets (the big one: ~908K rows) ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
echo "--- Migrating Timesheets (~908K rows) ---\n";
|
||||
|
||||
$t0 = microtime(true);
|
||||
|
||||
// Count total
|
||||
$total = (int) $src->query('SELECT COUNT(*) FROM kimai15_zef')->fetchColumn();
|
||||
$count = 0;
|
||||
|
||||
// Stream with unbuffered query
|
||||
$stmt = $src->prepare('SELECT zef_ID, zef_in, zef_out, zef_time, zef_usrID, zef_pctID, zef_evtID, zef_comment, zef_rate FROM kimai15_zef ORDER BY zef_ID');
|
||||
$stmt->execute();
|
||||
|
||||
$rows = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$startTime = timestampToDateTime((int) $row['zef_in']);
|
||||
|
||||
// Skip entries with no valid start time
|
||||
if ($startTime === null) {
|
||||
$count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$endTime = timestampToDateTime((int) $row['zef_out']);
|
||||
$duration = (int) $row['zef_time']; // seconds
|
||||
|
||||
// date_tz: extract date portion from start_time
|
||||
$dateTz = $startTime ? substr($startTime, 0, 10) : null;
|
||||
|
||||
$rows[] = [
|
||||
'id' => (int) $row['zef_ID'],
|
||||
'user' => (int) $row['zef_usrID'],
|
||||
'activity_id' => (int) $row['zef_evtID'],
|
||||
'project_id' => (int) $row['zef_pctID'],
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'duration' => $duration > 0 ? $duration : null,
|
||||
'description' => $row['zef_comment'] ?: null,
|
||||
'rate' => (float) $row['zef_rate'],
|
||||
'fixed_rate' => null,
|
||||
'hourly_rate' => null,
|
||||
'exported' => 0,
|
||||
'timezone' => DEFAULT_TIMEZONE,
|
||||
'internal_rate'=> null,
|
||||
'billable' => 1,
|
||||
'category' => 'work',
|
||||
'modified_at' => null,
|
||||
'date_tz' => $dateTz,
|
||||
'break' => null,
|
||||
];
|
||||
|
||||
$count++;
|
||||
if ($count % BATCH_SIZE === 0) {
|
||||
batchInsert($dst, 'kimai2_timesheet', [
|
||||
'id', 'user', 'activity_id', 'project_id', 'start_time', 'end_time',
|
||||
'duration', 'description', 'rate', 'fixed_rate', 'hourly_rate',
|
||||
'exported', 'timezone', 'internal_rate', 'billable', 'category',
|
||||
'modified_at', 'date_tz', 'break',
|
||||
], $rows, BATCH_SIZE);
|
||||
$dst->exec('COMMIT');
|
||||
$rows = [];
|
||||
progress('Timesheets', $count, $total);
|
||||
}
|
||||
}
|
||||
|
||||
// Insert final batch
|
||||
if (!empty($rows)) {
|
||||
batchInsert($dst, 'kimai2_timesheet', [
|
||||
'id', 'user', 'activity_id', 'project_id', 'start_time', 'end_time',
|
||||
'duration', 'description', 'rate', 'fixed_rate', 'hourly_rate',
|
||||
'exported', 'timezone', 'internal_rate', 'billable', 'category',
|
||||
'modified_at', 'date_tz', 'break',
|
||||
], $rows, BATCH_SIZE);
|
||||
$dst->exec('COMMIT');
|
||||
}
|
||||
|
||||
done('Timesheets', $count, microtime(true) - $t0);
|
||||
$dst->exec("ALTER TABLE kimai2_timesheet AUTO_INCREMENT = " . ((int) $src->query('SELECT MAX(zef_ID) FROM kimai15_zef')->fetchColumn() + 1));
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ 8. Expenses (optional) ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
echo "--- Migrating Expenses ---\n";
|
||||
|
||||
$t0 = microtime(true);
|
||||
$expenses = $src->query('SELECT * FROM kimai15_exp')->fetchAll();
|
||||
$total = count($expenses);
|
||||
|
||||
// Expenses are stored as timesheet entries with category='expense' in Kimai2
|
||||
// Expenses: need a fallback activity_id since Kimai2 requires it NOT NULL
|
||||
$fallbackActivityId = (int) ($dst->query('SELECT MIN(id) FROM kimai2_activities')->fetchColumn() ?: 1);
|
||||
|
||||
$rows = [];
|
||||
foreach ($expenses as $e) {
|
||||
$startTime = timestampToDateTime((int) $e['exp_timestamp']);
|
||||
$dateTz = $startTime ? substr($startTime, 0, 10) : null;
|
||||
|
||||
$rows[] = [
|
||||
'user' => (int) $e['exp_usrID'],
|
||||
'activity_id' => $fallbackActivityId,
|
||||
'project_id' => (int) $e['exp_pctID'],
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $startTime, // expenses: same as start
|
||||
'duration' => null,
|
||||
'description' => ($e['exp_designation'] ?: '') . ($e['exp_comment'] ? ' — ' . $e['exp_comment'] : ''),
|
||||
'rate' => (float) $e['exp_value'],
|
||||
'fixed_rate' => null,
|
||||
'hourly_rate' => null,
|
||||
'exported' => (int) $e['exp_cleared'],
|
||||
'timezone' => DEFAULT_TIMEZONE,
|
||||
'internal_rate'=> null,
|
||||
'billable' => (int) $e['exp_refundable'],
|
||||
'category' => 'expense',
|
||||
'modified_at' => null,
|
||||
'date_tz' => $dateTz,
|
||||
'break' => null,
|
||||
];
|
||||
}
|
||||
$imported = batchInsert($dst, 'kimai2_timesheet', [
|
||||
'user', 'activity_id', 'project_id', 'start_time', 'end_time',
|
||||
'duration', 'description', 'rate', 'fixed_rate', 'hourly_rate',
|
||||
'exported', 'timezone', 'internal_rate', 'billable', 'category',
|
||||
'modified_at', 'date_tz', 'break',
|
||||
], $rows);
|
||||
$dst->exec('COMMIT');
|
||||
done('Expenses', $imported, microtime(true) - $t0);
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ Re-enable foreign keys ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
$dst->exec('SET FOREIGN_KEY_CHECKS = 1');
|
||||
$dst->exec('SET AUTOCOMMIT = 1');
|
||||
|
||||
// ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
// ║ Summary ║
|
||||
// ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
echo "\n";
|
||||
echo str_repeat('=', 60) . "\n";
|
||||
echo "MIGRATION SUMMARY\n";
|
||||
echo str_repeat('=', 60) . "\n";
|
||||
|
||||
$summaryTables = [
|
||||
'Teams' => 'kimai2_teams',
|
||||
'Customers' => 'kimai2_customers',
|
||||
'Projects' => 'kimai2_projects',
|
||||
'Activities' => 'kimai2_activities',
|
||||
'Users' => 'kimai2_users',
|
||||
'User Preferences' => 'kimai2_user_preferences',
|
||||
'User-Team Mappings'=> 'kimai2_users_teams',
|
||||
'Timesheets' => 'kimai2_timesheet',
|
||||
];
|
||||
|
||||
foreach ($summaryTables as $label => $table) {
|
||||
$cnt = $dst->query("SELECT COUNT(*) FROM `$table`")->fetchColumn();
|
||||
printf(" %-20s → %s: %d rows\n", $label, $table, $cnt);
|
||||
}
|
||||
|
||||
echo "\nDefault password for all users: " . DEFAULT_PASSWORD . "\n";
|
||||
echo "Password hash algorithm: BCRYPT\n";
|
||||
echo "\nDone.\n";
|
||||
@@ -42,14 +42,14 @@
|
||||
"bootstrap": "^5.3.8",
|
||||
"chart.js": "^4.5.1",
|
||||
"core-js": "^3.49.0",
|
||||
"dompurify": "^3.4.7",
|
||||
"dompurify": "^3.4.10",
|
||||
"eslint": "^9.39.4",
|
||||
"globals": "^15.15.0",
|
||||
"gridstack": "^7.3.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"litepicker": "^2.0.12",
|
||||
"luxon": "^3.7.2",
|
||||
"sass": "^1.100.0",
|
||||
"sass": "^1.101.0",
|
||||
"sass-loader": "^16.0.8",
|
||||
"tom-select": "2.5.2",
|
||||
"webpack": "^5.107.2",
|
||||
|
||||
165
pnpm-lock.yaml
generated
165
pnpm-lock.yaml
generated
@@ -52,7 +52,7 @@ importers:
|
||||
version: 2.11.8
|
||||
'@symfony/webpack-encore':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(postcss@8.5.15)(sass-loader@16.0.8(sass@1.100.0)(webpack@5.107.2))(sass@1.100.0)(webpack-cli@6.0.1)(webpack@5.107.2)
|
||||
version: 6.0.0(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(postcss@8.5.15)(sass-loader@16.0.8(sass@1.101.0)(webpack@5.107.2))(sass@1.101.0)(webpack-cli@6.0.1)(webpack@5.107.2)
|
||||
'@tabler/core':
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
@@ -66,8 +66,8 @@ importers:
|
||||
specifier: ^3.49.0
|
||||
version: 3.49.0
|
||||
dompurify:
|
||||
specifier: ^3.4.7
|
||||
version: 3.4.7
|
||||
specifier: ^3.4.10
|
||||
version: 3.4.10
|
||||
eslint:
|
||||
specifier: ^9.39.4
|
||||
version: 9.39.4
|
||||
@@ -87,11 +87,11 @@ importers:
|
||||
specifier: ^3.7.2
|
||||
version: 3.7.2
|
||||
sass:
|
||||
specifier: ^1.100.0
|
||||
version: 1.100.0
|
||||
specifier: ^1.101.0
|
||||
version: 1.101.0
|
||||
sass-loader:
|
||||
specifier: ^16.0.8
|
||||
version: 16.0.8(sass@1.100.0)(webpack@5.107.2)
|
||||
version: 16.0.8(sass@1.101.0)(webpack@5.107.2)
|
||||
tom-select:
|
||||
specifier: 2.5.2
|
||||
version: 2.5.2
|
||||
@@ -945,8 +945,8 @@ packages:
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/node@25.9.1':
|
||||
resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==}
|
||||
'@types/node@25.9.3':
|
||||
resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
@@ -1047,8 +1047,8 @@ packages:
|
||||
peerDependencies:
|
||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
acorn@8.16.0:
|
||||
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
||||
acorn@8.17.0:
|
||||
resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1117,8 +1117,8 @@ packages:
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
baseline-browser-mapping@2.10.33:
|
||||
resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==}
|
||||
baseline-browser-mapping@2.10.37:
|
||||
resolution: {integrity: sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1156,8 +1156,8 @@ packages:
|
||||
caniuse-api@3.0.0:
|
||||
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
|
||||
|
||||
caniuse-lite@1.0.30001793:
|
||||
resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==}
|
||||
caniuse-lite@1.0.30001799:
|
||||
resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
@@ -1351,8 +1351,8 @@ packages:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
dompurify@3.4.7:
|
||||
resolution: {integrity: sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==}
|
||||
dompurify@3.4.10:
|
||||
resolution: {integrity: sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==}
|
||||
|
||||
domutils@2.8.0:
|
||||
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||
@@ -1360,8 +1360,8 @@ packages:
|
||||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
|
||||
electron-to-chromium@1.5.364:
|
||||
resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==}
|
||||
electron-to-chromium@1.5.374:
|
||||
resolution: {integrity: sha512-HCF5i7izveksHSGqa7mhDh6tr3Uz9Dar2RAjwuh69bw3QGPVObjQIgLwQWeO/Rxp9/r0KdboKy9RbpQDl97fjg==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
@@ -1370,8 +1370,8 @@ packages:
|
||||
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
enhanced-resolve@5.22.1:
|
||||
resolution: {integrity: sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==}
|
||||
enhanced-resolve@5.24.0:
|
||||
resolution: {integrity: sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
entities@2.2.0:
|
||||
@@ -1744,8 +1744,8 @@ packages:
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-releases@2.0.46:
|
||||
resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==}
|
||||
node-releases@2.0.47:
|
||||
resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
nth-check@2.1.1:
|
||||
@@ -1975,8 +1975,8 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.5.13
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
||||
postcss-selector-parser@7.1.4:
|
||||
resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
postcss-svgo@7.1.3:
|
||||
@@ -2037,8 +2037,8 @@ packages:
|
||||
regjsgen@0.8.0:
|
||||
resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==}
|
||||
|
||||
regjsparser@0.13.1:
|
||||
resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==}
|
||||
regjsparser@0.13.2:
|
||||
resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==}
|
||||
hasBin: true
|
||||
|
||||
renderkid@3.0.0:
|
||||
@@ -2090,8 +2090,8 @@ packages:
|
||||
webpack:
|
||||
optional: true
|
||||
|
||||
sass@1.100.0:
|
||||
resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==}
|
||||
sass@1.101.0:
|
||||
resolution: {integrity: sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2107,8 +2107,8 @@ packages:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
|
||||
semver@7.8.1:
|
||||
resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
|
||||
semver@7.8.4:
|
||||
resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2286,8 +2286,8 @@ packages:
|
||||
utila@0.4.0:
|
||||
resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==}
|
||||
|
||||
watchpack@2.5.1:
|
||||
resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==}
|
||||
watchpack@2.5.2:
|
||||
resolution: {integrity: sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
webpack-cli@6.0.1:
|
||||
@@ -3143,7 +3143,7 @@ snapshots:
|
||||
|
||||
'@jest/pattern@30.4.0':
|
||||
dependencies:
|
||||
'@types/node': 25.9.1
|
||||
'@types/node': 25.9.3
|
||||
jest-regex-util: 30.4.0
|
||||
|
||||
'@jest/schemas@30.4.1':
|
||||
@@ -3156,7 +3156,7 @@ snapshots:
|
||||
'@jest/schemas': 30.4.1
|
||||
'@types/istanbul-lib-coverage': 2.0.6
|
||||
'@types/istanbul-reports': 3.0.4
|
||||
'@types/node': 25.9.1
|
||||
'@types/node': 25.9.3
|
||||
'@types/yargs': 17.0.35
|
||||
chalk: 4.1.2
|
||||
|
||||
@@ -3269,7 +3269,7 @@ snapshots:
|
||||
|
||||
'@sinclair/typebox@0.34.49': {}
|
||||
|
||||
'@symfony/webpack-encore@6.0.0(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(postcss@8.5.15)(sass-loader@16.0.8(sass@1.100.0)(webpack@5.107.2))(sass@1.100.0)(webpack-cli@6.0.1)(webpack@5.107.2)':
|
||||
'@symfony/webpack-encore@6.0.0(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(postcss@8.5.15)(sass-loader@16.0.8(sass@1.101.0)(webpack@5.107.2))(sass@1.101.0)(webpack-cli@6.0.1)(webpack@5.107.2)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/preset-env': 7.29.7(@babel/core@7.29.7)
|
||||
@@ -3282,7 +3282,7 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
pretty-error: 4.0.0
|
||||
resolve-url-loader: 5.0.0
|
||||
semver: 7.8.1
|
||||
semver: 7.8.4
|
||||
style-loader: 4.0.0(webpack@5.107.2)
|
||||
tapable: 2.3.3
|
||||
terser-webpack-plugin: 5.6.1(postcss@8.5.15)(webpack@5.107.2)
|
||||
@@ -3293,8 +3293,8 @@ snapshots:
|
||||
yargs-parser: 21.1.1
|
||||
optionalDependencies:
|
||||
postcss: 8.5.15
|
||||
sass: 1.100.0
|
||||
sass-loader: 16.0.8(sass@1.100.0)(webpack@5.107.2)
|
||||
sass: 1.101.0
|
||||
sass-loader: 16.0.8(sass@1.101.0)(webpack@5.107.2)
|
||||
transitivePeerDependencies:
|
||||
- '@minify-html/node'
|
||||
- '@parcel/css'
|
||||
@@ -3329,7 +3329,7 @@ snapshots:
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/node@25.9.1':
|
||||
'@types/node@25.9.3':
|
||||
dependencies:
|
||||
undici-types: 7.24.6
|
||||
|
||||
@@ -3439,15 +3439,15 @@ snapshots:
|
||||
|
||||
'@xtuc/long@4.2.2': {}
|
||||
|
||||
acorn-import-phases@1.0.4(acorn@8.16.0):
|
||||
acorn-import-phases@1.0.4(acorn@8.17.0):
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
acorn: 8.17.0
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.16.0):
|
||||
acorn-jsx@5.3.2(acorn@8.17.0):
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
acorn: 8.17.0
|
||||
|
||||
acorn@8.16.0: {}
|
||||
acorn@8.17.0: {}
|
||||
|
||||
adjust-sourcemap-loader@4.0.0:
|
||||
dependencies:
|
||||
@@ -3518,7 +3518,7 @@ snapshots:
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
baseline-browser-mapping@2.10.33: {}
|
||||
baseline-browser-mapping@2.10.37: {}
|
||||
|
||||
big.js@5.2.2: {}
|
||||
|
||||
@@ -3539,10 +3539,10 @@ snapshots:
|
||||
|
||||
browserslist@4.28.2:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.10.33
|
||||
caniuse-lite: 1.0.30001793
|
||||
electron-to-chromium: 1.5.364
|
||||
node-releases: 2.0.46
|
||||
baseline-browser-mapping: 2.10.37
|
||||
caniuse-lite: 1.0.30001799
|
||||
electron-to-chromium: 1.5.374
|
||||
node-releases: 2.0.47
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
@@ -3552,11 +3552,11 @@ snapshots:
|
||||
caniuse-api@3.0.0:
|
||||
dependencies:
|
||||
browserslist: 4.28.2
|
||||
caniuse-lite: 1.0.30001793
|
||||
caniuse-lite: 1.0.30001799
|
||||
lodash.memoize: 4.1.2
|
||||
lodash.uniq: 4.5.0
|
||||
|
||||
caniuse-lite@1.0.30001793: {}
|
||||
caniuse-lite@1.0.30001799: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
@@ -3628,7 +3628,7 @@ snapshots:
|
||||
postcss-modules-scope: 3.2.1(postcss@8.5.15)
|
||||
postcss-modules-values: 4.0.0(postcss@8.5.15)
|
||||
postcss-value-parser: 4.2.0
|
||||
semver: 7.8.1
|
||||
semver: 7.8.4
|
||||
optionalDependencies:
|
||||
webpack: 5.107.2(postcss@8.5.15)(webpack-cli@6.0.1)
|
||||
|
||||
@@ -3755,7 +3755,7 @@ snapshots:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
dompurify@3.4.7:
|
||||
dompurify@3.4.10:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
@@ -3771,13 +3771,13 @@ snapshots:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
electron-to-chromium@1.5.364: {}
|
||||
electron-to-chromium@1.5.374: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emojis-list@3.0.0: {}
|
||||
|
||||
enhanced-resolve@5.22.1:
|
||||
enhanced-resolve@5.24.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.3
|
||||
@@ -3857,8 +3857,8 @@ snapshots:
|
||||
|
||||
espree@10.4.0:
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
acorn-jsx: 5.3.2(acorn@8.16.0)
|
||||
acorn: 8.17.0
|
||||
acorn-jsx: 5.3.2(acorn@8.17.0)
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
esquery@1.7.0:
|
||||
@@ -3992,7 +3992,7 @@ snapshots:
|
||||
jest-util@30.4.1:
|
||||
dependencies:
|
||||
'@jest/types': 30.4.1
|
||||
'@types/node': 25.9.1
|
||||
'@types/node': 25.9.3
|
||||
chalk: 4.1.2
|
||||
ci-info: 4.4.0
|
||||
graceful-fs: 4.2.11
|
||||
@@ -4000,13 +4000,13 @@ snapshots:
|
||||
|
||||
jest-worker@27.5.1:
|
||||
dependencies:
|
||||
'@types/node': 25.9.1
|
||||
'@types/node': 25.9.3
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
|
||||
jest-worker@30.4.1:
|
||||
dependencies:
|
||||
'@types/node': 25.9.1
|
||||
'@types/node': 25.9.3
|
||||
'@ungap/structured-clone': 1.3.1
|
||||
jest-util: 30.4.1
|
||||
merge-stream: 2.0.0
|
||||
@@ -4106,7 +4106,7 @@ snapshots:
|
||||
node-addon-api@7.1.1:
|
||||
optional: true
|
||||
|
||||
node-releases@2.0.46: {}
|
||||
node-releases@2.0.47: {}
|
||||
|
||||
nth-check@2.1.1:
|
||||
dependencies:
|
||||
@@ -4160,7 +4160,7 @@ snapshots:
|
||||
postcss-calc@10.1.1(postcss@8.5.15):
|
||||
dependencies:
|
||||
postcss: 8.5.15
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-selector-parser: 7.1.4
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-colormin@7.0.10(postcss@8.5.15):
|
||||
@@ -4180,7 +4180,7 @@ snapshots:
|
||||
postcss-discard-comments@7.0.8(postcss@8.5.15):
|
||||
dependencies:
|
||||
postcss: 8.5.15
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-selector-parser: 7.1.4
|
||||
|
||||
postcss-discard-duplicates@7.0.4(postcss@8.5.15):
|
||||
dependencies:
|
||||
@@ -4206,7 +4206,7 @@ snapshots:
|
||||
caniuse-api: 3.0.0
|
||||
cssnano-utils: 5.0.3(postcss@8.5.15)
|
||||
postcss: 8.5.15
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-selector-parser: 7.1.4
|
||||
|
||||
postcss-minify-font-values@7.0.3(postcss@8.5.15):
|
||||
dependencies:
|
||||
@@ -4233,7 +4233,7 @@ snapshots:
|
||||
caniuse-api: 3.0.0
|
||||
cssesc: 3.0.0
|
||||
postcss: 8.5.15
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-selector-parser: 7.1.4
|
||||
|
||||
postcss-modules-extract-imports@3.1.0(postcss@8.5.15):
|
||||
dependencies:
|
||||
@@ -4243,13 +4243,13 @@ snapshots:
|
||||
dependencies:
|
||||
icss-utils: 5.1.0(postcss@8.5.15)
|
||||
postcss: 8.5.15
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-selector-parser: 7.1.4
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-modules-scope@3.2.1(postcss@8.5.15):
|
||||
dependencies:
|
||||
postcss: 8.5.15
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-selector-parser: 7.1.4
|
||||
|
||||
postcss-modules-values@4.0.0(postcss@8.5.15):
|
||||
dependencies:
|
||||
@@ -4318,7 +4318,7 @@ snapshots:
|
||||
postcss: 8.5.15
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
postcss-selector-parser@7.1.4:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
util-deprecate: 1.0.2
|
||||
@@ -4332,7 +4332,7 @@ snapshots:
|
||||
postcss-unique-selectors@7.0.7(postcss@8.5.15):
|
||||
dependencies:
|
||||
postcss: 8.5.15
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-selector-parser: 7.1.4
|
||||
|
||||
postcss-value-parser@4.2.0: {}
|
||||
|
||||
@@ -4372,13 +4372,13 @@ snapshots:
|
||||
regenerate: 1.4.2
|
||||
regenerate-unicode-properties: 10.2.2
|
||||
regjsgen: 0.8.0
|
||||
regjsparser: 0.13.1
|
||||
regjsparser: 0.13.2
|
||||
unicode-match-property-ecmascript: 2.0.0
|
||||
unicode-match-property-value-ecmascript: 2.2.1
|
||||
|
||||
regjsgen@0.8.0: {}
|
||||
|
||||
regjsparser@0.13.1:
|
||||
regjsparser@0.13.2:
|
||||
dependencies:
|
||||
jsesc: 3.1.0
|
||||
|
||||
@@ -4415,14 +4415,14 @@ snapshots:
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
sass-loader@16.0.8(sass@1.100.0)(webpack@5.107.2):
|
||||
sass-loader@16.0.8(sass@1.101.0)(webpack@5.107.2):
|
||||
dependencies:
|
||||
neo-async: 2.6.2
|
||||
optionalDependencies:
|
||||
sass: 1.100.0
|
||||
sass: 1.101.0
|
||||
webpack: 5.107.2(postcss@8.5.15)(webpack-cli@6.0.1)
|
||||
|
||||
sass@1.100.0:
|
||||
sass@1.101.0:
|
||||
dependencies:
|
||||
chokidar: 5.0.0
|
||||
immutable: 5.1.6
|
||||
@@ -4441,7 +4441,7 @@ snapshots:
|
||||
|
||||
semver@6.3.1: {}
|
||||
|
||||
semver@7.8.1: {}
|
||||
semver@7.8.4: {}
|
||||
|
||||
serialize-javascript@7.0.5: {}
|
||||
|
||||
@@ -4488,7 +4488,7 @@ snapshots:
|
||||
dependencies:
|
||||
browserslist: 4.28.2
|
||||
postcss: 8.5.15
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-selector-parser: 7.1.4
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
@@ -4525,7 +4525,7 @@ snapshots:
|
||||
terser@5.48.0:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.11
|
||||
acorn: 8.16.0
|
||||
acorn: 8.17.0
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
|
||||
@@ -4569,9 +4569,8 @@ snapshots:
|
||||
|
||||
utila@0.4.0: {}
|
||||
|
||||
watchpack@2.5.1:
|
||||
watchpack@2.5.2:
|
||||
dependencies:
|
||||
glob-to-regexp: 0.4.1
|
||||
graceful-fs: 4.2.11
|
||||
|
||||
webpack-cli@6.0.1(webpack@5.107.2):
|
||||
@@ -4617,11 +4616,11 @@ snapshots:
|
||||
'@webassemblyjs/ast': 1.14.1
|
||||
'@webassemblyjs/wasm-edit': 1.14.1
|
||||
'@webassemblyjs/wasm-parser': 1.14.1
|
||||
acorn: 8.16.0
|
||||
acorn-import-phases: 1.0.4(acorn@8.16.0)
|
||||
acorn: 8.17.0
|
||||
acorn-import-phases: 1.0.4(acorn@8.17.0)
|
||||
browserslist: 4.28.2
|
||||
chrome-trace-event: 1.0.4
|
||||
enhanced-resolve: 5.22.1
|
||||
enhanced-resolve: 5.24.0
|
||||
es-module-lexer: 2.1.0
|
||||
eslint-scope: 5.1.1
|
||||
events: 3.3.0
|
||||
@@ -4633,7 +4632,7 @@ snapshots:
|
||||
schema-utils: 4.3.3
|
||||
tapable: 2.3.3
|
||||
terser-webpack-plugin: 5.6.1(postcss@8.5.15)(webpack@5.107.2)
|
||||
watchpack: 2.5.1
|
||||
watchpack: 2.5.2
|
||||
webpack-sources: 3.5.0
|
||||
optionalDependencies:
|
||||
webpack-cli: 6.0.1(webpack@5.107.2)
|
||||
|
||||
@@ -135,9 +135,9 @@ final class UserController extends BaseApiController
|
||||
* Create user
|
||||
*/
|
||||
#[IsGranted('create_user')]
|
||||
#[OA\Post(description: 'Creates a new user and returns it afterwards')]
|
||||
#[OA\Post(description: 'Creates a new user and returns it afterwards', responses: [new OA\Response(response: 200, description: 'Returns the new created user', content: new OA\JsonContent(ref: '#/components/schemas/UserEntity'))])]
|
||||
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/UserCreateForm'))]
|
||||
#[Route(methods: ['POST'], path: '', name: 'post_user')]
|
||||
#[Route(path: '', name: 'post_user', methods: ['POST'])]
|
||||
public function postAction(Request $request, UserService $userService): Response
|
||||
{
|
||||
$user = $userService->createNewUser();
|
||||
|
||||
@@ -17,11 +17,11 @@ final class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.60.0';
|
||||
public const VERSION = '2.61.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 26000;
|
||||
public const VERSION_ID = 26100;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ final class Configuration
|
||||
private array $options = [];
|
||||
private bool $enabled = true;
|
||||
private bool $required = true;
|
||||
private ?string $formTheme = null;
|
||||
/**
|
||||
* @var Constraint[]
|
||||
*/
|
||||
@@ -149,4 +150,16 @@ final class Configuration
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFormTheme(): ?string
|
||||
{
|
||||
return $this->formTheme;
|
||||
}
|
||||
|
||||
public function setFormTheme(string $formTheme): Configuration
|
||||
{
|
||||
$this->formTheme = $formTheme;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ final class MultiUserTimesheet extends Timesheet
|
||||
public function removeUser(User $user): void
|
||||
{
|
||||
if ($this->users->contains($user)) {
|
||||
$this->users->remove($user);
|
||||
$this->users->removeElement($user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ final class MultiUserTimesheet extends Timesheet
|
||||
public function removeTeam(Team $team): void
|
||||
{
|
||||
if ($this->teams->contains($team)) {
|
||||
$this->teams->remove($team);
|
||||
$this->teams->removeElement($team);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ final class TotpActivation
|
||||
{
|
||||
private ?string $code = null;
|
||||
|
||||
public function __construct(private User $user)
|
||||
public function __construct(private readonly User $user)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ final class SamlAuthenticationSuccessHandler extends DefaultAuthenticationSucces
|
||||
{
|
||||
protected $defaultOptions = [
|
||||
'always_use_default_target_path' => false,
|
||||
'default_target_path' => '/',
|
||||
'default_target_path' => 'homepage',
|
||||
'login_path' => 'saml_login',
|
||||
'target_path_parameter' => '_target_path',
|
||||
'use_referer' => false,
|
||||
|
||||
@@ -461,6 +461,15 @@
|
||||
"config/packages/mailer.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/maker-bundle": {
|
||||
"version": "1.67",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||
}
|
||||
},
|
||||
"symfony/mime": {
|
||||
"version": "v4.3.3"
|
||||
},
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
{% embed formEditTemplate with {'title': title, 'form': section.form} %}
|
||||
{% block form_before %}<span id="conf_{{ section.model.section }}"></span>{% endblock %}
|
||||
{% block form_body %}
|
||||
{% for pref in form.children.configuration %}
|
||||
{% if pref.vars.data.formTheme is not null %}
|
||||
{% form_theme pref pref.vars.data.formTheme %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for pref in form.children.configuration %}
|
||||
{{ form_widget(pref) }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
{% endif %}
|
||||
{% embed formEditTemplate with {'title': title, 'form': section.form} %}
|
||||
{% block form_body %}
|
||||
{% for pref in form.children.configuration %}
|
||||
{% if pref.vars.data.formTheme is not null %}
|
||||
{% form_theme pref pref.vars.data.formTheme %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for pref in form.children.configuration %}
|
||||
{{ form_widget(pref) }}
|
||||
{% endfor %}
|
||||
|
||||
125
tests/Form/Model/ConfigurationTest.php
Normal file
125
tests/Form/Model/ConfigurationTest.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Form\Model;
|
||||
|
||||
use App\Form\Model\Configuration;
|
||||
use App\Form\Type\YesNoType;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
#[CoversClass(Configuration::class)]
|
||||
class ConfigurationTest extends TestCase
|
||||
{
|
||||
public function testDefaultValues(): void
|
||||
{
|
||||
$sut = new Configuration('foo');
|
||||
|
||||
self::assertSame('foo', $sut->getName());
|
||||
self::assertNull($sut->getLabel());
|
||||
self::assertSame('messages', $sut->getTranslationDomain());
|
||||
self::assertNull($sut->getValue());
|
||||
self::assertNull($sut->getType());
|
||||
self::assertSame([], $sut->getOptions());
|
||||
self::assertTrue($sut->isEnabled());
|
||||
self::assertTrue($sut->isRequired());
|
||||
self::assertNull($sut->getFormTheme());
|
||||
self::assertSame([], $sut->getConstraints());
|
||||
}
|
||||
|
||||
public function testFluentSetterAndGetter(): void
|
||||
{
|
||||
$sut = new Configuration('foo');
|
||||
$constraints = [new NotBlank()];
|
||||
$options = ['attr' => ['data-test' => 'value'], 'empty_data' => 0];
|
||||
|
||||
$result = $sut
|
||||
->setLabel('Foo label')
|
||||
->setTranslationDomain('admin')
|
||||
->setType('custom-type')
|
||||
->setValue(42.5)
|
||||
->setOptions($options)
|
||||
->setConstraints($constraints)
|
||||
->setEnabled(false)
|
||||
->setRequired(false)
|
||||
->setFormTheme('@MyBundle/form/test.html.twig');
|
||||
|
||||
self::assertSame($sut, $result);
|
||||
self::assertSame('Foo label', $sut->getLabel());
|
||||
self::assertSame('admin', $sut->getTranslationDomain());
|
||||
self::assertSame('custom-type', $sut->getType());
|
||||
self::assertSame(42.5, $sut->getValue());
|
||||
self::assertSame($options, $sut->getOptions());
|
||||
self::assertSame($constraints, $sut->getConstraints());
|
||||
self::assertFalse($sut->isEnabled());
|
||||
self::assertFalse($sut->isRequired());
|
||||
self::assertSame('@MyBundle/form/test.html.twig', $sut->getFormTheme());
|
||||
}
|
||||
|
||||
#[DataProvider('provideScalarValues')]
|
||||
public function testSetValueKeepsOriginalTypeForNonBooleanField(string|int|null|bool|float $value): void
|
||||
{
|
||||
$sut = new Configuration('foo');
|
||||
|
||||
self::assertSame($sut, $sut->setValue($value));
|
||||
self::assertSame($value, $sut->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, array{0: string, 1: string|int|null|bool|float, 2: bool}>
|
||||
*/
|
||||
public static function provideBooleanTypeValues(): iterable
|
||||
{
|
||||
yield 'checkbox true string' => [CheckboxType::class, '1', true];
|
||||
yield 'checkbox zero string' => [CheckboxType::class, '0', false];
|
||||
yield 'checkbox integer zero' => [CheckboxType::class, 0, false];
|
||||
yield 'checkbox integer one' => [CheckboxType::class, 1, true];
|
||||
yield 'checkbox null' => [CheckboxType::class, null, false];
|
||||
yield 'checkbox empty string' => [CheckboxType::class, '', false];
|
||||
yield 'yes no text value' => [YesNoType::class, 'yes', true];
|
||||
yield 'yes no false bool' => [YesNoType::class, false, false];
|
||||
yield 'yes no float' => [YesNoType::class, 3.14, true];
|
||||
}
|
||||
|
||||
#[DataProvider('provideBooleanTypeValues')]
|
||||
public function testSetValueCastsToBoolForBooleanTypes(string $type, string|int|null|bool|float $value, bool $expected): void
|
||||
{
|
||||
$sut = new Configuration('foo');
|
||||
|
||||
self::assertSame($sut, $sut->setType($type));
|
||||
self::assertSame($sut, $sut->setValue($value));
|
||||
self::assertSame($expected, $sut->getValue());
|
||||
}
|
||||
|
||||
public function testChangingTypeAfterSettingValueDoesNotRetroactivelyCastValue(): void
|
||||
{
|
||||
$sut = new Configuration('foo');
|
||||
|
||||
$sut->setValue('1');
|
||||
$sut->setType(CheckboxType::class);
|
||||
|
||||
self::assertSame('1', $sut->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, array{0: string|int|null|bool|float}>
|
||||
*/
|
||||
public static function provideScalarValues(): iterable
|
||||
{
|
||||
yield 'null' => [null];
|
||||
yield 'string' => ['hello world'];
|
||||
yield 'integer' => [123];
|
||||
yield 'float' => [123.45];
|
||||
yield 'true' => [true];
|
||||
yield 'false' => [false];
|
||||
}
|
||||
}
|
||||
122
tests/Form/Model/MultiUserTimesheetTest.php
Normal file
122
tests/Form/Model/MultiUserTimesheetTest.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Form\Model;
|
||||
|
||||
use App\Entity\Team;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Form\Model\MultiUserTimesheet;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(MultiUserTimesheet::class)]
|
||||
class MultiUserTimesheetTest extends TestCase
|
||||
{
|
||||
public function testDefaultCollectionsAreEmpty(): void
|
||||
{
|
||||
$sut = new MultiUserTimesheet();
|
||||
|
||||
self::assertInstanceOf(Timesheet::class, $sut);
|
||||
self::assertEmpty($sut->getTeams());
|
||||
self::assertEmpty($sut->getUsers());
|
||||
}
|
||||
|
||||
public function testAddAndRemoveUser(): void
|
||||
{
|
||||
$sut = new MultiUserTimesheet();
|
||||
$user = $this->createUser('alpha');
|
||||
|
||||
$sut->addUser($user);
|
||||
|
||||
self::assertCount(1, $sut->getUsers());
|
||||
self::assertSame($user, $sut->getUsers()->first());
|
||||
|
||||
$sut->removeUser($user);
|
||||
|
||||
self::assertCount(0, $sut->getUsers());
|
||||
}
|
||||
|
||||
public function testRemovingUnknownUserDoesNothing(): void
|
||||
{
|
||||
$sut = new MultiUserTimesheet();
|
||||
$user = $this->createUser('alpha');
|
||||
|
||||
$sut->removeUser($user);
|
||||
|
||||
self::assertCount(0, $sut->getUsers());
|
||||
}
|
||||
|
||||
public function testRemovingUserOnlyRemovesOneDuplicateEntry(): void
|
||||
{
|
||||
$sut = new MultiUserTimesheet();
|
||||
$user = $this->createUser('alpha');
|
||||
|
||||
$sut->addUser($user);
|
||||
$sut->addUser($user);
|
||||
|
||||
self::assertCount(2, $sut->getUsers());
|
||||
|
||||
$sut->removeUser($user);
|
||||
|
||||
self::assertCount(1, $sut->getUsers());
|
||||
self::assertSame($user, $sut->getUsers()->first());
|
||||
}
|
||||
|
||||
public function testAddAndRemoveTeam(): void
|
||||
{
|
||||
$sut = new MultiUserTimesheet();
|
||||
$team = new Team('Team Alpha');
|
||||
|
||||
$sut->addTeam($team);
|
||||
|
||||
self::assertCount(1, $sut->getTeams());
|
||||
self::assertSame($team, $sut->getTeams()->first());
|
||||
|
||||
$sut->removeTeam($team);
|
||||
|
||||
self::assertCount(0, $sut->getTeams());
|
||||
}
|
||||
|
||||
public function testRemovingUnknownTeamDoesNothing(): void
|
||||
{
|
||||
$sut = new MultiUserTimesheet();
|
||||
$team = new Team('Team Alpha');
|
||||
|
||||
$sut->removeTeam($team);
|
||||
|
||||
self::assertCount(0, $sut->getTeams());
|
||||
}
|
||||
|
||||
public function testRemovingTeamOnlyRemovesOneDuplicateEntry(): void
|
||||
{
|
||||
$sut = new MultiUserTimesheet();
|
||||
$team = new Team('Team Alpha');
|
||||
|
||||
$sut->addTeam($team);
|
||||
$sut->addTeam($team);
|
||||
|
||||
self::assertCount(2, $sut->getTeams());
|
||||
|
||||
$sut->removeTeam($team);
|
||||
|
||||
self::assertCount(1, $sut->getTeams());
|
||||
self::assertSame($team, $sut->getTeams()->first());
|
||||
}
|
||||
|
||||
private function createUser(string $username): User
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername($username);
|
||||
$user->setAlias($username);
|
||||
$user->setEmail($username . '@example.com');
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ use App\Form\Model\SystemConfiguration;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(Configuration::class)]
|
||||
#[CoversClass(SystemConfiguration::class)]
|
||||
class SystemConfigurationTest extends TestCase
|
||||
{
|
||||
|
||||
34
tests/Form/Model/TotpActivationTest.php
Normal file
34
tests/Form/Model/TotpActivationTest.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Form\Model;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\Model\TotpActivation;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(TotpActivation::class)]
|
||||
class TotpActivationTest extends TestCase
|
||||
{
|
||||
public function testDefaultValues(): void
|
||||
{
|
||||
$user = new User();
|
||||
$sut = new TotpActivation($user);
|
||||
|
||||
self::assertSame($user, $sut->getUser());
|
||||
self::assertNull($sut->getCode());
|
||||
$sut->setCode('');
|
||||
self::assertEquals('', $sut->getCode());
|
||||
$sut->setCode('jztfztfjzfjhgfjhgfjhgfjtzfiuzgbljv');
|
||||
self::assertEquals('jztfztfjzfjhgfjhgfjhgfjtzfiuzgbljv', $sut->getCode());
|
||||
$sut->setCode(null);
|
||||
self::assertNull($sut->getCode());
|
||||
}
|
||||
}
|
||||
89
tests/Form/Model/UserContractModelTest.php
Normal file
89
tests/Form/Model/UserContractModelTest.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Form\Model;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\Model\UserContractModel;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(UserContractModel::class)]
|
||||
class UserContractModelTest extends TestCase
|
||||
{
|
||||
public function testIssetAlwaysReturnsTrue(): void
|
||||
{
|
||||
$user = new User();
|
||||
$sut = new UserContractModel($user);
|
||||
|
||||
self::assertTrue($sut->__isset('alias'));
|
||||
self::assertTrue($sut->__isset('unknownPreference'));
|
||||
}
|
||||
|
||||
public function testSetAndGetExistingUserPropertyUsesUserMethods(): void
|
||||
{
|
||||
$user = new User();
|
||||
$sut = new UserContractModel($user);
|
||||
|
||||
$sut->__set('alias', 'contract-user');
|
||||
|
||||
self::assertSame('contract-user', $sut->__get('alias'));
|
||||
self::assertSame('contract-user', $user->getAlias());
|
||||
}
|
||||
|
||||
public function testSetAndGetExistingPreferenceBackedMethodUsesUserMethod(): void
|
||||
{
|
||||
$user = new User();
|
||||
$sut = new UserContractModel($user);
|
||||
|
||||
$sut->__set('workContractMode', 'default');
|
||||
|
||||
self::assertSame('default', $sut->__get('workContractMode'));
|
||||
self::assertSame('default', $user->getWorkContractMode());
|
||||
}
|
||||
|
||||
public function testSetAndGetUnknownPropertyUsesPreferenceFallback(): void
|
||||
{
|
||||
$user = new User();
|
||||
$sut = new UserContractModel($user);
|
||||
|
||||
$sut->__set('customContractField', 'weekly');
|
||||
|
||||
self::assertSame('weekly', $sut->__get('customContractField'));
|
||||
self::assertSame('weekly', $user->getPreferenceValue('customContractField'));
|
||||
}
|
||||
|
||||
public function testSetUnknownPropertyAllowsNullPreferenceValue(): void
|
||||
{
|
||||
$user = new User();
|
||||
$sut = new UserContractModel($user);
|
||||
|
||||
$sut->__set('customContractField', null);
|
||||
|
||||
self::assertNull($sut->__get('customContractField'));
|
||||
self::assertNull($user->getPreferenceValue('customContractField'));
|
||||
}
|
||||
|
||||
public function testGetUnknownPropertyWithoutPreferenceReturnsNull(): void
|
||||
{
|
||||
$sut = new UserContractModel(new User());
|
||||
|
||||
self::assertNull($sut->__get('missingPreference'));
|
||||
}
|
||||
|
||||
public function testSetUnknownPropertyRejectsNonScalarValues(): void
|
||||
{
|
||||
$sut = new UserContractModel(new User());
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Invalid value passed');
|
||||
|
||||
$sut->__set('customContractField', ['invalid']);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
{
|
||||
public function testRelayStateWithInvalidHost(): void
|
||||
{
|
||||
$handler = new SamlAuthenticationSuccessHandler(new HttpUtils($this->getUrlGenerator()));
|
||||
$handler = new SamlAuthenticationSuccessHandler(new HttpUtils($this->getUrlGenerator(['homepage' => 'http://example.com/'])));
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest('http://example.com/sso/login', 'https://localhost/relayed'), $this->getSamlToken());
|
||||
self::assertInstanceOf(RedirectResponse::class, $response);
|
||||
$target = $response->getTargetUrl();
|
||||
@@ -57,9 +57,9 @@ class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
|
||||
public function testWithoutRelayState(): void
|
||||
{
|
||||
$httpUtils = new HttpUtils($this->getUrlGenerator());
|
||||
$httpUtils = new HttpUtils($this->getUrlGenerator(['homepage' => '/homepage']));
|
||||
$handler = new SamlAuthenticationSuccessHandler($httpUtils);
|
||||
$defaultTargetPath = $httpUtils->generateUri($this->getRequest('/sso/login'), '/');
|
||||
$defaultTargetPath = $httpUtils->generateUri($this->getRequest('/sso/login'), 'homepage');
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest(), $this->getSamlToken());
|
||||
self::assertInstanceOf(RedirectResponse::class, $response);
|
||||
self::assertTrue($response->isRedirect($defaultTargetPath));
|
||||
@@ -75,13 +75,17 @@ class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
self::assertTrue(!$response->isRedirect($loginPath));
|
||||
}
|
||||
|
||||
private function getUrlGenerator(): UrlGeneratorInterface
|
||||
private function getUrlGenerator(array $rules = []): UrlGeneratorInterface
|
||||
{
|
||||
$urlGenerator = $this->getMockBuilder(UrlGeneratorInterface::class)->getMock();
|
||||
$urlGenerator
|
||||
->expects($this->any())
|
||||
->method('generate')
|
||||
->willReturnCallback(function ($name) {
|
||||
->willReturnCallback(function ($name) use ($rules) {
|
||||
if (\array_key_exists($name, $rules)) {
|
||||
return $rules[$name];
|
||||
}
|
||||
|
||||
return (string) $name;
|
||||
})
|
||||
;
|
||||
|
||||
Reference in New Issue
Block a user