Release 2.9.0 (#4526)

* added fix to work around bc break in new phpword version
* fix phpoffice deprecations
* mark unused option as deprecated
* support for DateTimeInterface and DateTimeImmutable where possible
* use TRUSTED_PROXIES setting - fixes #4533
* re-enable Kimai test in docker test build script (#4541)
* bump dependencies
This commit is contained in:
Kevin Papst
2024-01-10 12:43:07 +01:00
committed by GitHub
parent c6a651456e
commit 6531e7fe52
46 changed files with 487 additions and 485 deletions

View File

@@ -1,6 +1,9 @@
#!/bin/sh -e #!/bin/sh -e
# Test PHP/Kimai if [ -z "$DATABASE_URL" ]; then
DATABASE_URL="mysql://kimai:kimai@127.0.0.1:3306/kimai?charset=utf8mb4&serverVersion=5.7.40"
fi
/opt/kimai/bin/console kimai:version /opt/kimai/bin/console kimai:version
if [ $? != 0 ]; then if [ $? != 0 ]; then
echo "PHP/Kimai not responding" echo "PHP/Kimai not responding"

View File

@@ -49,9 +49,8 @@ jobs:
kimai/kimai2:${{ matrix.server }}-${{ matrix.type }} kimai/kimai2:${{ matrix.server }}-${{ matrix.type }}
kimai/kimai2:${{ matrix.server }}-${{ env.kimai_version }}-${{ matrix.type }} kimai/kimai2:${{ matrix.server }}-${{ env.kimai_version }}-${{ matrix.type }}
push: true push: true
# see https://github.com/kimai/kimai/issues/4503 - name: Test Lite
# - name: Test Lite run: docker run --rm --entrypoint /assets/test-lite.sh kimai/kimai2:${{ matrix.server }}-${{ matrix.type }}
# run: docker run --rm --entrypoint /assets/test-lite.sh kimai/kimai2:${{ matrix.server }}-${{ matrix.type }}
tag: tag:
needs: build needs: build

View File

@@ -43,7 +43,7 @@
"onelogin/php-saml": "^4.0", "onelogin/php-saml": "^4.0",
"pagerfanta/pagerfanta": "^3.0", "pagerfanta/pagerfanta": "^3.0",
"phpoffice/phpspreadsheet": "^1.16", "phpoffice/phpspreadsheet": "^1.16",
"phpoffice/phpword": "1.1.*", "phpoffice/phpword": "^1.0",
"psr/container": "^2.0", "psr/container": "^2.0",
"psr/log": "^3.0", "psr/log": "^3.0",
"scheb/2fa-backup-code": "^6.2", "scheb/2fa-backup-code": "^6.2",

250
composer.lock generated
View File

@@ -4,35 +4,34 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7b0ed62d4f9b4d06a6748c700fd04238", "content-hash": "a307b9a36c22711439283a3e5bc3249c",
"packages": [ "packages": [
{ {
"name": "azuyalabs/yasumi", "name": "azuyalabs/yasumi",
"version": "2.6.0", "version": "2.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/azuyalabs/yasumi.git", "url": "https://github.com/azuyalabs/yasumi.git",
"reference": "a73f198d796100c237adf94f3fca1d15eb82b22b" "reference": "37d1215d4f4012d3185bb9990c76ca17a4ff1c30"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/a73f198d796100c237adf94f3fca1d15eb82b22b", "url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/37d1215d4f4012d3185bb9990c76ca17a4ff1c30",
"reference": "a73f198d796100c237adf94f3fca1d15eb82b22b", "reference": "37d1215d4f4012d3185bb9990c76ca17a4ff1c30",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"php": ">=7.4" "php": ">=8.0"
}, },
"require-dev": { "require-dev": {
"ext-intl": "*", "ext-intl": "*",
"friendsofphp/php-cs-fixer": "^2.19 || ^3.16", "friendsofphp/php-cs-fixer": "^2.19 || ^3.40",
"infection/infection": "^0.17 || ^0.26",
"mikey179/vfsstream": "^1.6", "mikey179/vfsstream": "^1.6",
"phan/phan": "^5.4", "phan/phan": "^5.4",
"phpstan/phpstan": "^1.10", "phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8.5 || ^9.6", "phpunit/phpunit": "^8.5 || ^9.6",
"vimeo/psalm": "^5.9" "vimeo/psalm": "^5.16"
}, },
"suggest": { "suggest": {
"ext-calendar": "For calculating the date of Easter" "ext-calendar": "For calculating the date of Easter"
@@ -78,7 +77,7 @@
"type": "other" "type": "other"
} }
], ],
"time": "2023-04-26T15:35:45+00:00" "time": "2024-01-07T14:12:44+00:00"
}, },
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@@ -2519,68 +2518,6 @@
], ],
"time": "2023-12-01T15:39:05+00:00" "time": "2023-12-01T15:39:05+00:00"
}, },
{
"name": "laminas/laminas-escaper",
"version": "2.13.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/af459883f4018d0f8a0c69c7a209daef3bf973ba",
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-mbstring": "*",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
"infection/infection": "^0.27.0",
"laminas/laminas-coding-standard": "~2.5.0",
"maglnet/composer-require-checker": "^3.8.0",
"phpunit/phpunit": "^9.6.7",
"psalm/plugin-phpunit": "^0.18.4",
"vimeo/psalm": "^5.9"
},
"type": "library",
"autoload": {
"psr-4": {
"Laminas\\Escaper\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs",
"homepage": "https://laminas.dev",
"keywords": [
"escaper",
"laminas"
],
"support": {
"chat": "https://laminas.dev/chat",
"docs": "https://docs.laminas.dev/laminas-escaper/",
"forum": "https://discourse.laminas.dev",
"issues": "https://github.com/laminas/laminas-escaper/issues",
"rss": "https://github.com/laminas/laminas-escaper/releases.atom",
"source": "https://github.com/laminas/laminas-escaper"
},
"funding": [
{
"url": "https://funding.communitybridge.org/projects/laminas-project",
"type": "community_bridge"
}
],
"time": "2023-10-10T08:35:13+00:00"
},
{ {
"name": "league/csv", "name": "league/csv",
"version": "9.14.0", "version": "9.14.0",
@@ -3246,16 +3183,16 @@
}, },
{ {
"name": "nelmio/api-doc-bundle", "name": "nelmio/api-doc-bundle",
"version": "v4.15.1", "version": "v4.16.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nelmio/NelmioApiDocBundle.git", "url": "https://github.com/nelmio/NelmioApiDocBundle.git",
"reference": "bef8b654ed410d16bf1086e2319ab215e5f36193" "reference": "31da761b6c9d275fb3bbee87c4c6888b17aec4ad"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/bef8b654ed410d16bf1086e2319ab215e5f36193", "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/31da761b6c9d275fb3bbee87c4c6888b17aec4ad",
"reference": "bef8b654ed410d16bf1086e2319ab215e5f36193", "reference": "31da761b6c9d275fb3bbee87c4c6888b17aec4ad",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -3352,9 +3289,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nelmio/NelmioApiDocBundle/issues", "issues": "https://github.com/nelmio/NelmioApiDocBundle/issues",
"source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v4.15.1" "source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v4.16.2"
}, },
"time": "2024-01-02T14:56:33+00:00" "time": "2024-01-06T21:33:48+00:00"
}, },
{ {
"name": "nelmio/cors-bundle", "name": "nelmio/cors-bundle",
@@ -3862,6 +3799,59 @@
}, },
"time": "2023-08-12T11:01:26+00:00" "time": "2023-08-12T11:01:26+00:00"
}, },
{
"name": "phpoffice/math",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/Math.git",
"reference": "f0f8cad98624459c540cdd61d2a174d834471773"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/Math/zipball/f0f8cad98624459c540cdd61d2a174d834471773",
"reference": "f0f8cad98624459c540cdd61d2a174d834471773",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xml": "*",
"php": "^7.1|^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.88 || ^1.0.0",
"phpunit/phpunit": "^7.0 || ^9.0"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\Math\\": "src/Math/",
"Tests\\PhpOffice\\Math\\": "tests/Math/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Progi1984",
"homepage": "https://lefevre.dev"
}
],
"description": "Math - Manipulate Math Formula",
"homepage": "https://phpoffice.github.io/Math/",
"keywords": [
"MathML",
"officemathml",
"php"
],
"support": {
"issues": "https://github.com/PHPOffice/Math/issues",
"source": "https://github.com/PHPOffice/Math/tree/0.1.0"
},
"time": "2023-09-25T12:08:20+00:00"
},
{ {
"name": "phpoffice/phpspreadsheet", "name": "phpoffice/phpspreadsheet",
"version": "1.29.0", "version": "1.29.0",
@@ -3969,24 +3959,24 @@
}, },
{ {
"name": "phpoffice/phpword", "name": "phpoffice/phpword",
"version": "1.1.0", "version": "1.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHPOffice/PHPWord.git", "url": "https://github.com/PHPOffice/PHPWord.git",
"reference": "90a55955e6a772bb4cd9b1ef6a7e88c8976c2561" "reference": "e76b701ef538cb749641514fcbc31a68078550fa"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PHPWord/zipball/90a55955e6a772bb4cd9b1ef6a7e88c8976c2561", "url": "https://api.github.com/repos/PHPOffice/PHPWord/zipball/e76b701ef538cb749641514fcbc31a68078550fa",
"reference": "90a55955e6a772bb4cd9b1ef6a7e88c8976c2561", "reference": "e76b701ef538cb749641514fcbc31a68078550fa",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-json": "*", "ext-json": "*",
"ext-xml": "*", "ext-xml": "*",
"laminas/laminas-escaper": ">=2.6", "php": "^7.1|^8.0",
"php": "^7.1|^8.0" "phpoffice/math": "^0.1"
}, },
"require-dev": { "require-dev": {
"dompdf/dompdf": "^2.0", "dompdf/dompdf": "^2.0",
@@ -3996,8 +3986,9 @@
"friendsofphp/php-cs-fixer": "^3.3", "friendsofphp/php-cs-fixer": "^3.3",
"mpdf/mpdf": "^8.1", "mpdf/mpdf": "^8.1",
"phpmd/phpmd": "^2.13", "phpmd/phpmd": "^2.13",
"phpstan/phpstan-phpunit": "@stable",
"phpunit/phpunit": ">=7.0", "phpunit/phpunit": ">=7.0",
"symfony/process": "^4.4", "symfony/process": "^4.4 || ^5.0",
"tecnickcom/tcpdf": "^6.5" "tecnickcom/tcpdf": "^6.5"
}, },
"suggest": { "suggest": {
@@ -4043,7 +4034,7 @@
} }
], ],
"description": "PHPWord - A pure PHP library for reading and writing word processing documents (OOXML, ODF, RTF, HTML, PDF)", "description": "PHPWord - A pure PHP library for reading and writing word processing documents (OOXML, ODF, RTF, HTML, PDF)",
"homepage": "https://phpword.readthedocs.io/", "homepage": "https://phpoffice.github.io/PHPWord/",
"keywords": [ "keywords": [
"ISO IEC 29500", "ISO IEC 29500",
"OOXML", "OOXML",
@@ -4071,22 +4062,22 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHPOffice/PHPWord/issues", "issues": "https://github.com/PHPOffice/PHPWord/issues",
"source": "https://github.com/PHPOffice/PHPWord/tree/1.1.0" "source": "https://github.com/PHPOffice/PHPWord/tree/1.2.0"
}, },
"time": "2023-05-30T07:59:14+00:00" "time": "2023-11-30T11:22:23+00:00"
}, },
{ {
"name": "phpstan/phpdoc-parser", "name": "phpstan/phpdoc-parser",
"version": "1.24.5", "version": "1.25.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git", "url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc" "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fedf211ff14ec8381c9bf5714e33a7a552dd1acc", "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240",
"reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc", "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -4118,9 +4109,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types", "description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": { "support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues", "issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.5" "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0"
}, },
"time": "2023-12-16T09:33:33+00:00" "time": "2024-01-04T17:06:16+00:00"
}, },
{ {
"name": "psr/cache", "name": "psr/cache",
@@ -10340,16 +10331,16 @@
}, },
{ {
"name": "zircote/swagger-php", "name": "zircote/swagger-php",
"version": "4.8.2", "version": "4.8.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/zircote/swagger-php.git", "url": "https://github.com/zircote/swagger-php.git",
"reference": "a70a5dc5db26183c86f589ff3d76fb11e141fc58" "reference": "598958d8a83cfbd44ba36388b2f9ed69e8b86ed4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/a70a5dc5db26183c86f589ff3d76fb11e141fc58", "url": "https://api.github.com/repos/zircote/swagger-php/zipball/598958d8a83cfbd44ba36388b2f9ed69e8b86ed4",
"reference": "a70a5dc5db26183c86f589ff3d76fb11e141fc58", "reference": "598958d8a83cfbd44ba36388b2f9ed69e8b86ed4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -10415,9 +10406,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/zircote/swagger-php/issues", "issues": "https://github.com/zircote/swagger-php/issues",
"source": "https://github.com/zircote/swagger-php/tree/4.8.2" "source": "https://github.com/zircote/swagger-php/tree/4.8.3"
}, },
"time": "2023-12-19T19:59:07+00:00" "time": "2024-01-07T22:33:09+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@@ -10861,21 +10852,22 @@
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v3.45.0", "version": "v3.46.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "c0daa33cb2533cd73f48dde1c70c2afa3e7953b5" "reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/c0daa33cb2533cd73f48dde1c70c2afa3e7953b5", "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/be6831c9af1740470d2a773119b9273f8ac1c3d2",
"reference": "c0daa33cb2533cd73f48dde1c70c2afa3e7953b5", "reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer/semver": "^3.4", "composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.3", "composer/xdebug-handler": "^3.0.3",
"ext-filter": "*",
"ext-json": "*", "ext-json": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"php": "^7.4 || ^8.0", "php": "^7.4 || ^8.0",
@@ -10939,7 +10931,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.45.0" "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.46.0"
}, },
"funding": [ "funding": [
{ {
@@ -10947,7 +10939,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-12-30T02:07:07+00:00" "time": "2024-01-03T21:38:46+00:00"
}, },
{ {
"name": "masterminds/html5", "name": "masterminds/html5",
@@ -11018,25 +11010,27 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v4.18.0", "version": "v5.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-ctype": "*",
"ext-json": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"php": ">=7.0" "php": ">=7.4"
}, },
"require-dev": { "require-dev": {
"ircmaxell/php-yacc": "^0.0.7", "ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
}, },
"bin": [ "bin": [
"bin/php-parse" "bin/php-parse"
@@ -11044,7 +11038,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "4.9-dev" "dev-master": "5.0-dev"
} }
}, },
"autoload": { "autoload": {
@@ -11068,9 +11062,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0"
}, },
"time": "2023-12-10T21:03:43+00:00" "time": "2024-01-07T17:17:35+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
@@ -11185,16 +11179,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.10.50", "version": "1.10.55",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" "reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9a88f9d18ddf4cf54c922fbeac16c4cb164c5949",
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", "reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -11243,25 +11237,25 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-12-13T10:59:42+00:00" "time": "2024-01-08T12:32:40+00:00"
}, },
{ {
"name": "phpstan/phpstan-doctrine", "name": "phpstan/phpstan-doctrine",
"version": "1.3.53", "version": "1.3.54",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan-doctrine.git", "url": "https://github.com/phpstan/phpstan-doctrine.git",
"reference": "85def57e5db6ac6c8a512200c0cfadf7b6621b10" "reference": "f9555a2d54d685efd7003ae33c15e3d19d7d0c36"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/85def57e5db6ac6c8a512200c0cfadf7b6621b10", "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/f9555a2d54d685efd7003ae33c15e3d19d7d0c36",
"reference": "85def57e5db6ac6c8a512200c0cfadf7b6621b10", "reference": "f9555a2d54d685efd7003ae33c15e3d19d7d0c36",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2 || ^8.0", "php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.10.12" "phpstan/phpstan": "^1.10.48"
}, },
"conflict": { "conflict": {
"doctrine/collections": "<1.0", "doctrine/collections": "<1.0",
@@ -11278,7 +11272,7 @@
"doctrine/dbal": "^2.13.8 || ^3.3.3", "doctrine/dbal": "^2.13.8 || ^3.3.3",
"doctrine/lexer": "^1.2.1", "doctrine/lexer": "^1.2.1",
"doctrine/mongodb-odm": "^1.3 || ^2.1", "doctrine/mongodb-odm": "^1.3 || ^2.1",
"doctrine/orm": "^2.11.0", "doctrine/orm": "^2.14.0",
"doctrine/persistence": "^1.3.8 || ^2.2.1", "doctrine/persistence": "^1.3.8 || ^2.2.1",
"gedmo/doctrine-extensions": "^3.8", "gedmo/doctrine-extensions": "^3.8",
"nesbot/carbon": "^2.49", "nesbot/carbon": "^2.49",
@@ -11311,9 +11305,9 @@
"description": "Doctrine extensions for PHPStan", "description": "Doctrine extensions for PHPStan",
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan-doctrine/issues", "issues": "https://github.com/phpstan/phpstan-doctrine/issues",
"source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.53" "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.54"
}, },
"time": "2023-11-21T10:31:58+00:00" "time": "2024-01-05T15:44:44+00:00"
}, },
{ {
"name": "phpstan/phpstan-phpunit", "name": "phpstan/phpstan-phpunit",

View File

@@ -1,21 +1,23 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
parameters:
env(TRUSTED_PROXIES): ''
framework: framework:
# Deprecations for 7.0 secret: '%env(APP_SECRET)%'
csrf_protection: true
annotations: false
handle_all_throwables: true handle_all_throwables: true
annotations:
enabled: false
serializer: serializer:
enable_attributes: true enable_attributes: true
# --------------------------- trusted_proxies: '%env(string:TRUSTED_PROXIES)%'
exceptions: exceptions:
App\Validator\ValidationFailedException: App\Validator\ValidationFailedException:
log_level: debug log_level: debug
secret: '%env(APP_SECRET)%'
default_locale: en default_locale: en
csrf_protection: true
http_method_override: false http_method_override: false
# Enables session support. Note that the session will ONLY be started if you read or write from it. # Enables session support. Note that the session will ONLY be started if you read or write from it.

View File

@@ -16,8 +16,8 @@ services:
$projectDirectory: '%kernel.project_dir%' $projectDirectory: '%kernel.project_dir%'
$kernelEnvironment: '%kernel.environment%' $kernelEnvironment: '%kernel.environment%'
# make classes in src/ available to be used as services # makes classes in src/ available to be used as services
# creates one service per class whose id is the fully-qualified class name # this creates a service per class whose id is the fully-qualified class name
App\: App\:
resource: '../src/*' resource: '../src/*'
exclude: exclude:

View File

@@ -151,46 +151,6 @@ parameters:
count: 1 count: 1
path: src/API/UserController.php path: src/API/UserController.php
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 1
path: src/Activity/ActivityStatisticService.php
-
message: "#^Cannot access offset 'billable' on mixed\\.$#"
count: 1
path: src/Activity/ActivityStatisticService.php
-
message: "#^Cannot access offset 'counter' on mixed\\.$#"
count: 3
path: src/Activity/ActivityStatisticService.php
-
message: "#^Cannot access offset 'duration' on mixed\\.$#"
count: 4
path: src/Activity/ActivityStatisticService.php
-
message: "#^Cannot access offset 'exported' on mixed\\.$#"
count: 2
path: src/Activity/ActivityStatisticService.php
-
message: "#^Cannot access offset 'id' on mixed\\.$#"
count: 1
path: src/Activity/ActivityStatisticService.php
-
message: "#^Cannot access offset 'internalRate' on mixed\\.$#"
count: 3
path: src/Activity/ActivityStatisticService.php
-
message: "#^Cannot access offset 'rate' on mixed\\.$#"
count: 4
path: src/Activity/ActivityStatisticService.php
- -
message: "#^Method App\\\\Activity\\\\ActivityStatisticService\\:\\:createStatisticQueryBuilder\\(\\) has parameter \\$activities with no value type specified in iterable type array\\.$#" message: "#^Method App\\\\Activity\\\\ActivityStatisticService\\:\\:createStatisticQueryBuilder\\(\\) has parameter \\$activities with no value type specified in iterable type array\\.$#"
count: 1 count: 1
@@ -1106,21 +1066,6 @@ parameters:
count: 1 count: 1
path: src/Controller/Reporting/CustomerMonthlyProjectsController.php path: src/Controller/Reporting/CustomerMonthlyProjectsController.php
-
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
count: 3
path: src/Controller/Reporting/ProjectDateRangeController.php
-
message: "#^Parameter \\#2 \\$begin of method App\\\\Project\\\\ProjectStatisticService\\:\\:getBudgetStatisticModelForProjectsByDateRange\\(\\) expects DateTime, DateTime\\|null given\\.$#"
count: 1
path: src/Controller/Reporting/ProjectDateRangeController.php
-
message: "#^Parameter \\#3 \\$end of method App\\\\Project\\\\ProjectStatisticService\\:\\:getBudgetStatisticModelForProjectsByDateRange\\(\\) expects DateTime, DateTime\\|null given\\.$#"
count: 1
path: src/Controller/Reporting/ProjectDateRangeController.php
- -
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#" message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
count: 3 count: 3
@@ -1291,46 +1236,6 @@ parameters:
count: 1 count: 1
path: src/Controller/WidgetController.php path: src/Controller/WidgetController.php
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 1
path: src/Customer/CustomerStatisticService.php
-
message: "#^Cannot access offset 'billable' on mixed\\.$#"
count: 1
path: src/Customer/CustomerStatisticService.php
-
message: "#^Cannot access offset 'counter' on mixed\\.$#"
count: 3
path: src/Customer/CustomerStatisticService.php
-
message: "#^Cannot access offset 'duration' on mixed\\.$#"
count: 4
path: src/Customer/CustomerStatisticService.php
-
message: "#^Cannot access offset 'exported' on mixed\\.$#"
count: 2
path: src/Customer/CustomerStatisticService.php
-
message: "#^Cannot access offset 'id' on mixed\\.$#"
count: 1
path: src/Customer/CustomerStatisticService.php
-
message: "#^Cannot access offset 'internalRate' on mixed\\.$#"
count: 3
path: src/Customer/CustomerStatisticService.php
-
message: "#^Cannot access offset 'rate' on mixed\\.$#"
count: 4
path: src/Customer/CustomerStatisticService.php
- -
message: "#^Method App\\\\Customer\\\\CustomerStatisticService\\:\\:createStatisticQueryBuilder\\(\\) has parameter \\$customers with no value type specified in iterable type array\\.$#" message: "#^Method App\\\\Customer\\\\CustomerStatisticService\\:\\:createStatisticQueryBuilder\\(\\) has parameter \\$customers with no value type specified in iterable type array\\.$#"
count: 1 count: 1
@@ -3686,11 +3591,6 @@ parameters:
count: 1 count: 1
path: src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php path: src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php
-
message: "#^Parameter \\#2 \\$today of method App\\\\Customer\\\\CustomerStatisticService\\:\\:getBudgetStatisticModel\\(\\) expects DateTime, DateTime\\|null given\\.$#"
count: 1
path: src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php
- -
message: "#^Cannot call method getAddress\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#" message: "#^Cannot call method getAddress\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#"
count: 1 count: 1
@@ -5746,11 +5646,6 @@ parameters:
count: 2 count: 2
path: src/Validator/Constraints/TeamValidator.php path: src/Validator/Constraints/TeamValidator.php
-
message: "#^Cannot call method format\\(\\) on DateTime\\|null\\.$#"
count: 2
path: src/Validator/Constraints/TimesheetBudgetUsedValidator.php
- -
message: "#^Cannot call method getCurrency\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#" message: "#^Cannot call method getCurrency\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
count: 1 count: 1
@@ -5771,31 +5666,11 @@ parameters:
count: 1 count: 1
path: src/Validator/Constraints/TimesheetBudgetUsedValidator.php path: src/Validator/Constraints/TimesheetBudgetUsedValidator.php
-
message: "#^Cannot call method getTimezone\\(\\) on DateTime\\|null\\.$#"
count: 1
path: src/Validator/Constraints/TimesheetBudgetUsedValidator.php
- -
message: "#^Parameter \\#1 \\$timesheet \\(App\\\\Entity\\\\Timesheet\\) of method App\\\\Validator\\\\Constraints\\\\TimesheetBudgetUsedValidator\\:\\:validate\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Validator\\\\ConstraintValidatorInterface\\:\\:validate\\(\\)$#" message: "#^Parameter \\#1 \\$timesheet \\(App\\\\Entity\\\\Timesheet\\) of method App\\\\Validator\\\\Constraints\\\\TimesheetBudgetUsedValidator\\:\\:validate\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Validator\\\\ConstraintValidatorInterface\\:\\:validate\\(\\)$#"
count: 2 count: 2
path: src/Validator/Constraints/TimesheetBudgetUsedValidator.php path: src/Validator/Constraints/TimesheetBudgetUsedValidator.php
-
message: "#^Parameter \\#2 \\$today of method App\\\\Activity\\\\ActivityStatisticService\\:\\:getBudgetStatisticModel\\(\\) expects DateTime, DateTime\\|null given\\.$#"
count: 1
path: src/Validator/Constraints/TimesheetBudgetUsedValidator.php
-
message: "#^Parameter \\#2 \\$today of method App\\\\Customer\\\\CustomerStatisticService\\:\\:getBudgetStatisticModel\\(\\) expects DateTime, DateTime\\|null given\\.$#"
count: 1
path: src/Validator/Constraints/TimesheetBudgetUsedValidator.php
-
message: "#^Parameter \\#2 \\$today of method App\\\\Project\\\\ProjectStatisticService\\:\\:getBudgetStatisticModel\\(\\) expects DateTime, DateTime\\|null given\\.$#"
count: 1
path: src/Validator/Constraints/TimesheetBudgetUsedValidator.php
- -
message: "#^Parameter \\#4 \\$duration of method App\\\\Validator\\\\Constraints\\\\TimesheetBudgetUsedValidator\\:\\:checkBudgets\\(\\) expects int, float\\|int\\|null given\\.$#" message: "#^Parameter \\#4 \\$duration of method App\\\\Validator\\\\Constraints\\\\TimesheetBudgetUsedValidator\\:\\:checkBudgets\\(\\) expects int, float\\|int\\|null given\\.$#"
count: 3 count: 3

View File

@@ -16,7 +16,8 @@ use App\Model\ActivityBudgetStatisticModel;
use App\Model\ActivityStatistic; use App\Model\ActivityStatistic;
use App\Repository\TimesheetRepository; use App\Repository\TimesheetRepository;
use App\Timesheet\DateTimeFactory; use App\Timesheet\DateTimeFactory;
use DateTime; use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -26,19 +27,15 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*/ */
class ActivityStatisticService class ActivityStatisticService
{ {
public function __construct(private TimesheetRepository $timesheetRepository, private EventDispatcherInterface $dispatcher) public function __construct(private readonly TimesheetRepository $timesheetRepository, private readonly EventDispatcherInterface $dispatcher)
{ {
} }
/** /**
* WARNING: this method does not respect the budget type. Your results will always be wither the "full lifetime data" or the "selected date-range". * WARNING: this method does not respect the budget type.
* * Your results will always be with the "full lifetime data" or the "selected date-range".
* @param Activity $activity
* @param DateTime|null $begin
* @param DateTime|null $end
* @return ActivityStatistic
*/ */
public function getActivityStatistics(Activity $activity, ?DateTime $begin = null, ?DateTime $end = null): ActivityStatistic public function getActivityStatistics(Activity $activity, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): ActivityStatistic
{ {
$statistics = $this->getBudgetStatistic([$activity], $begin, $end); $statistics = $this->getBudgetStatistic([$activity], $begin, $end);
$event = new ActivityStatisticEvent($activity, array_pop($statistics), $begin, $end); $event = new ActivityStatisticEvent($activity, array_pop($statistics), $begin, $end);
@@ -47,7 +44,7 @@ class ActivityStatisticService
return $event->getStatistic(); return $event->getStatistic();
} }
public function getBudgetStatisticModel(Activity $activity, DateTime $today): ActivityBudgetStatisticModel public function getBudgetStatisticModel(Activity $activity, DateTimeInterface $today): ActivityBudgetStatisticModel
{ {
$stats = new ActivityBudgetStatisticModel($activity); $stats = new ActivityBudgetStatisticModel($activity);
$stats->setStatisticTotal($this->getActivityStatistics($activity)); $stats->setStatisticTotal($this->getActivityStatistics($activity));
@@ -68,10 +65,9 @@ class ActivityStatisticService
/** /**
* @param Activity[] $activities * @param Activity[] $activities
* @param DateTime $today
* @return ActivityBudgetStatisticModel[] * @return ActivityBudgetStatisticModel[]
*/ */
public function getBudgetStatisticModelForActivities(array $activities, DateTime $today): array public function getBudgetStatisticModelForActivities(array $activities, DateTimeInterface $today): array
{ {
$models = []; $models = [];
$monthly = []; $monthly = [];
@@ -121,11 +117,9 @@ class ActivityStatisticService
/** /**
* @param Activity[] $activities * @param Activity[] $activities
* @param DateTime|null $begin
* @param DateTime|null $end
* @return array<int, ActivityStatistic> * @return array<int, ActivityStatistic>
*/ */
private function getBudgetStatistic(array $activities, ?DateTime $begin = null, ?DateTime $end = null): array private function getBudgetStatistic(array $activities, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): array
{ {
$statistics = []; $statistics = [];
foreach ($activities as $activity) { foreach ($activities as $activity) {
@@ -139,25 +133,25 @@ class ActivityStatisticService
if (null !== $result) { if (null !== $result) {
foreach ($result as $resultRow) { foreach ($result as $resultRow) {
$statistic = $statistics[$resultRow['id']]; $statistic = $statistics[$resultRow['id']];
$statistic->setDuration($statistic->getDuration() + $resultRow['duration']); $statistic->addDuration((int) $resultRow['duration']);
$statistic->setRate($statistic->getRate() + $resultRow['rate']); $statistic->addRate((float) $resultRow['rate']);
$statistic->setInternalRate($statistic->getInternalRate() + $resultRow['internalRate']); $statistic->addInternalRate((float) $resultRow['internalRate']);
$statistic->setCounter($statistic->getCounter() + $resultRow['counter']); $statistic->addCounter((int) $resultRow['counter']);
if ($resultRow['billable']) { if ($resultRow['billable']) {
$statistic->setDurationBillable($statistic->getDurationBillable() + $resultRow['duration']); $statistic->addDurationBillable((int) $resultRow['duration']);
$statistic->setRateBillable($statistic->getRateBillable() + $resultRow['rate']); $statistic->addRateBillable((float) $resultRow['rate']);
$statistic->setInternalRateBillable($statistic->getInternalRateBillable() + $resultRow['internalRate']); $statistic->addInternalRateBillable((float) $resultRow['internalRate']);
$statistic->setCounterBillable($statistic->getCounterBillable() + $resultRow['counter']); $statistic->addCounterBillable((int) $resultRow['counter']);
if ($resultRow['exported']) { if ($resultRow['exported']) {
$statistic->setDurationBillableExported($statistic->getDurationBillableExported() + $resultRow['duration']); $statistic->addDurationBillableExported((int) $resultRow['duration']);
$statistic->setRateBillableExported($statistic->getRateBillableExported() + $resultRow['rate']); $statistic->addRateBillableExported((float) $resultRow['rate']);
} }
} }
if ($resultRow['exported']) { if ($resultRow['exported']) {
$statistic->setDurationExported($statistic->getDurationExported() + $resultRow['duration']); $statistic->addDurationExported((int) $resultRow['duration']);
$statistic->setRateExported($statistic->getRateExported() + $resultRow['rate']); $statistic->addRateExported((float) $resultRow['rate']);
$statistic->setInternalRateExported($statistic->getInternalRateExported() + $resultRow['internalRate']); $statistic->addInternalRateExported((float) $resultRow['internalRate']);
$statistic->setCounterExported($statistic->getCounterExported() + $resultRow['counter']); $statistic->addCounterExported((int) $resultRow['counter']);
} }
} }
} }
@@ -165,7 +159,7 @@ class ActivityStatisticService
return $statistics; return $statistics;
} }
private function createStatisticQueryBuilder(array $activities, DateTime $begin = null, ?DateTime $end = null): QueryBuilder private function createStatisticQueryBuilder(array $activities, \DateTimeInterface $begin = null, ?\DateTimeInterface $end = null): QueryBuilder
{ {
$qb = $this->timesheetRepository->createQueryBuilder('t'); $qb = $this->timesheetRepository->createQueryBuilder('t');
$qb $qb
@@ -187,14 +181,14 @@ class ActivityStatisticService
if ($begin !== null) { if ($begin !== null) {
$qb $qb
->andWhere($qb->expr()->gte('t.begin', ':begin')) ->andWhere($qb->expr()->gte('t.begin', ':begin'))
->setParameter('begin', $begin, Types::DATETIME_MUTABLE) ->setParameter('begin', DateTimeImmutable::createFromInterface($begin), Types::DATETIME_IMMUTABLE)
; ;
} }
if ($end !== null) { if ($end !== null) {
$qb $qb
->andWhere($qb->expr()->lte('t.begin', ':end')) ->andWhere($qb->expr()->lte('t.begin', ':end'))
->setParameter('end', $end, Types::DATETIME_MUTABLE) ->setParameter('end', DateTimeImmutable::createFromInterface($end), Types::DATETIME_IMMUTABLE)
; ;
} }

View File

@@ -158,10 +158,12 @@ final class ExportCreateCommand extends Command
return Command::FAILURE; return Command::FAILURE;
} }
} }
if (!$start instanceof \DateTime) { if (!$start instanceof \DateTimeInterface) {
$start = $dateFactory->getStartOfMonth(); $start = $dateFactory->getStartOfMonth();
} }
$start->setTime(0, 0, 0);
$start = \DateTimeImmutable::createFromInterface($start);
$start = $start->setTime(0, 0, 0);
$end = $input->getOption('end'); $end = $input->getOption('end');
if (!empty($end)) { if (!empty($end)) {
@@ -174,11 +176,12 @@ final class ExportCreateCommand extends Command
} }
} }
if (empty($end)) { if (!$end instanceof \DateTimeInterface) {
$end = $dateFactory->getEndOfMonth($start); $end = $dateFactory->getEndOfMonth($start);
} }
$end->setTime(23, 59, 59); $end = \DateTimeImmutable::createFromInterface($end);
$end = $end->setTime(23, 59, 59);
$directory = rtrim(sys_get_temp_dir(), '/') . '/'; $directory = rtrim(sys_get_temp_dir(), '/') . '/';
if ($input->getOption('directory') !== null) { if ($input->getOption('directory') !== null) {

View File

@@ -64,7 +64,7 @@ final class InvoiceCreateCommand extends Command
->addOption('project', null, InputOption::VALUE_OPTIONAL, 'Comma separated list of project IDs', null) ->addOption('project', null, InputOption::VALUE_OPTIONAL, 'Comma separated list of project IDs', null)
->addOption('by-customer', null, InputOption::VALUE_NONE, 'If set, one invoice for each active customer in the given timerange is created') ->addOption('by-customer', null, InputOption::VALUE_NONE, 'If set, one invoice for each active customer in the given timerange is created')
->addOption('by-project', null, InputOption::VALUE_NONE, 'If set, one invoice for each active project in the given timerange is created') ->addOption('by-project', null, InputOption::VALUE_NONE, 'If set, one invoice for each active project in the given timerange is created')
->addOption('set-exported', null, InputOption::VALUE_NONE, 'Whether the invoice items should be marked as exported') ->addOption('set-exported', null, InputOption::VALUE_NONE, '[DEPRECATED] this flag has no meaning any more: invoiced items are always exported')
->addOption('template', null, InputOption::VALUE_OPTIONAL, 'Invoice template', null) ->addOption('template', null, InputOption::VALUE_OPTIONAL, 'Invoice template', null)
->addOption('search', null, InputOption::VALUE_OPTIONAL, 'Search term to filter invoice entries', null) ->addOption('search', null, InputOption::VALUE_OPTIONAL, 'Search term to filter invoice entries', null)
->addOption('exported', null, InputOption::VALUE_OPTIONAL, 'Exported filter for invoice entries (possible values: exported, all), by default only "not exported" items are fetched', null) ->addOption('exported', null, InputOption::VALUE_OPTIONAL, 'Exported filter for invoice entries (possible values: exported, all), by default only "not exported" items are fetched', null)
@@ -156,10 +156,12 @@ final class InvoiceCreateCommand extends Command
return Command::FAILURE; return Command::FAILURE;
} }
} }
if (!$start instanceof \DateTime) { if (!$start instanceof \DateTimeInterface) {
$start = $dateFactory->getStartOfMonth(); $start = $dateFactory->getStartOfMonth();
} }
$start->setTime(0, 0, 0);
$start = \DateTimeImmutable::createFromInterface($start);
$start = $start->setTime(0, 0, 0);
$end = $input->getOption('end'); $end = $input->getOption('end');
if (!empty($end)) { if (!empty($end)) {
@@ -171,17 +173,18 @@ final class InvoiceCreateCommand extends Command
return Command::FAILURE; return Command::FAILURE;
} }
} }
if (!$end instanceof \DateTime) { if (!$end instanceof \DateTimeInterface) {
$end = $dateFactory->getEndOfMonth(); $end = $dateFactory->getEndOfMonth();
} }
$end->setTime(23, 59, 59);
$end = \DateTimeImmutable::createFromInterface($end);
$end = $end->setTime(23, 59, 59);
$searchTerm = null; $searchTerm = null;
if (null !== $input->getOption('search')) { if (null !== $input->getOption('search')) {
$searchTerm = new SearchTerm($input->getOption('search')); $searchTerm = new SearchTerm($input->getOption('search'));
} }
$markAsExported = false;
if ($input->getOption('preview') !== null) { if ($input->getOption('preview') !== null) {
$this->previewUniqueFile = (bool) $input->getOption('preview-unique'); $this->previewUniqueFile = (bool) $input->getOption('preview-unique');
$this->previewDirectory = rtrim($input->getOption('preview'), '/') . '/'; $this->previewDirectory = rtrim($input->getOption('preview'), '/') . '/';
@@ -191,7 +194,7 @@ final class InvoiceCreateCommand extends Command
return Command::FAILURE; return Command::FAILURE;
} }
} elseif ($input->getOption('set-exported')) { } elseif ($input->getOption('set-exported')) {
$markAsExported = true; @trigger_error('The "set-exported" option of kimai:invoice:create command has no meaning anymore, it will be removed soon', E_USER_DEPRECATED);
} }
// =============== VALIDATION END =============== // =============== VALIDATION END ===============

View File

@@ -17,11 +17,11 @@ class Constants
/** /**
* The current release version * The current release version
*/ */
public const VERSION = '2.8.0'; public const VERSION = '2.9.0';
/** /**
* The current release: major * 10000 + minor * 100 + patch * The current release: major * 10000 + minor * 100 + patch
*/ */
public const VERSION_ID = 20800; public const VERSION_ID = 20900;
/** /**
* The software name * The software name
*/ */

View File

@@ -10,6 +10,7 @@
namespace App\Controller\Reporting; namespace App\Controller\Reporting;
use App\Controller\AbstractController; use App\Controller\AbstractController;
use App\Entity\Customer;
use App\Form\Model\DateRange; use App\Form\Model\DateRange;
use App\Project\ProjectStatisticService; use App\Project\ProjectStatisticService;
use App\Reporting\ProjectDateRange\ProjectDateRangeForm; use App\Reporting\ProjectDateRange\ProjectDateRangeForm;
@@ -37,15 +38,20 @@ final class ProjectDateRangeController extends AbstractController
]); ]);
$form->submit($request->query->all(), false); $form->submit($request->query->all(), false);
$begin = $query->getMonth() ?? $defaultStart;
$dateRange = new DateRange(true); $dateRange = new DateRange(true);
$dateRange->setBegin($query->getMonth() ?? $defaultStart); $dateRange->setBegin($begin);
$dateRange->setEnd($dateFactory->getEndOfMonth($dateRange->getBegin())); $end = $dateFactory->getEndOfMonth($dateRange->getBegin()); // this resets the time
$dateRange->setEnd($end);
$projects = $service->findProjectsForDateRange($query, $dateRange); $projects = $service->findProjectsForDateRange($query, $dateRange);
$entries = $service->getBudgetStatisticModelForProjectsByDateRange($projects, $dateRange->getBegin(), $dateRange->getEnd(), $dateRange->getEnd()); $entries = $service->getBudgetStatisticModelForProjectsByDateRange($projects, $begin, $end, $end);
$byCustomer = []; $byCustomer = [];
foreach ($entries as $entry) { foreach ($entries as $entry) {
/** @var Customer $customer */
$customer = $entry->getProject()->getCustomer(); $customer = $entry->getProject()->getCustomer();
if (!isset($byCustomer[$customer->getId()])) { if (!isset($byCustomer[$customer->getId()])) {
$byCustomer[$customer->getId()] = ['customer' => $customer, 'projects' => []]; $byCustomer[$customer->getId()] = ['customer' => $customer, 'projects' => []];

View File

@@ -17,6 +17,8 @@ use App\Model\CustomerStatistic;
use App\Repository\TimesheetRepository; use App\Repository\TimesheetRepository;
use App\Timesheet\DateTimeFactory; use App\Timesheet\DateTimeFactory;
use DateTime; use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@@ -27,17 +29,12 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*/ */
class CustomerStatisticService class CustomerStatisticService
{ {
public function __construct(private TimesheetRepository $timesheetRepository, private EventDispatcherInterface $dispatcher) public function __construct(private readonly TimesheetRepository $timesheetRepository, private readonly EventDispatcherInterface $dispatcher)
{ {
} }
/** /**
* WARNING: this method does not respect the budget type. Your results will always be wither the "full lifetime data" or the "selected date-range". * WARNING: this method does not respect the budget type. Your results will always be with the "full lifetime data" or the "selected date-range".
*
* @param Customer $customer
* @param DateTime|null $begin
* @param DateTime|null $end
* @return CustomerStatistic
*/ */
public function getCustomerStatistics(Customer $customer, ?DateTime $begin = null, ?DateTime $end = null): CustomerStatistic public function getCustomerStatistics(Customer $customer, ?DateTime $begin = null, ?DateTime $end = null): CustomerStatistic
{ {
@@ -48,13 +45,13 @@ class CustomerStatisticService
return $event->getStatistic(); return $event->getStatistic();
} }
public function getBudgetStatisticModel(Customer $customer, DateTime $today): CustomerBudgetStatisticModel public function getBudgetStatisticModel(Customer $customer, DateTimeInterface $today): CustomerBudgetStatisticModel
{ {
$stats = new CustomerBudgetStatisticModel($customer); $stats = new CustomerBudgetStatisticModel($customer);
$stats->setStatisticTotal($this->getCustomerStatistics($customer)); $stats->setStatisticTotal($this->getCustomerStatistics($customer));
$begin = null; $begin = null;
$end = $today; $end = DateTime::createFromInterface($today);
if ($customer->isMonthlyBudget()) { if ($customer->isMonthlyBudget()) {
$dateFactory = new DateTimeFactory($today->getTimezone()); $dateFactory = new DateTimeFactory($today->getTimezone());
@@ -69,11 +66,9 @@ class CustomerStatisticService
/** /**
* @param Customer[] $customers * @param Customer[] $customers
* @param DateTime|null $begin
* @param DateTime|null $end
* @return array<int, CustomerStatistic> * @return array<int, CustomerStatistic>
*/ */
private function getBudgetStatistic(array $customers, ?DateTime $begin = null, ?DateTime $end = null): array private function getBudgetStatistic(array $customers, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): array
{ {
$statistics = []; $statistics = [];
foreach ($customers as $customer) { foreach ($customers as $customer) {
@@ -87,25 +82,25 @@ class CustomerStatisticService
if (null !== $result) { if (null !== $result) {
foreach ($result as $resultRow) { foreach ($result as $resultRow) {
$statistic = $statistics[$resultRow['id']]; $statistic = $statistics[$resultRow['id']];
$statistic->setDuration($statistic->getDuration() + $resultRow['duration']); $statistic->addDuration((int) $resultRow['duration']);
$statistic->setRate($statistic->getRate() + $resultRow['rate']); $statistic->addRate((float) $resultRow['rate']);
$statistic->setInternalRate($statistic->getInternalRate() + $resultRow['internalRate']); $statistic->addInternalRate((float) $resultRow['internalRate']);
$statistic->setCounter($statistic->getCounter() + $resultRow['counter']); $statistic->addCounter((int) $resultRow['counter']);
if ($resultRow['billable']) { if ($resultRow['billable']) {
$statistic->setDurationBillable($statistic->getDurationBillable() + $resultRow['duration']); $statistic->addDurationBillable((int) $resultRow['duration']);
$statistic->setRateBillable($statistic->getRateBillable() + $resultRow['rate']); $statistic->addRateBillable((float) $resultRow['rate']);
$statistic->setInternalRateBillable($statistic->getInternalRateBillable() + $resultRow['internalRate']); $statistic->addInternalRateBillable((float) $resultRow['internalRate']);
$statistic->setCounterBillable($statistic->getCounterBillable() + $resultRow['counter']); $statistic->addCounterBillable((int) $resultRow['counter']);
if ($resultRow['exported']) { if ($resultRow['exported']) {
$statistic->setDurationBillableExported($statistic->getDurationBillableExported() + $resultRow['duration']); $statistic->addDurationBillableExported((int) $resultRow['duration']);
$statistic->setRateBillableExported($statistic->getRateBillableExported() + $resultRow['rate']); $statistic->addRateBillableExported((float) $resultRow['rate']);
} }
} }
if ($resultRow['exported']) { if ($resultRow['exported']) {
$statistic->setDurationExported($statistic->getDurationExported() + $resultRow['duration']); $statistic->addDurationExported((int) $resultRow['duration']);
$statistic->setRateExported($statistic->getRateExported() + $resultRow['rate']); $statistic->addRateExported((float) $resultRow['rate']);
$statistic->setInternalRateExported($statistic->getInternalRateExported() + $resultRow['internalRate']); $statistic->addInternalRateExported((float) $resultRow['internalRate']);
$statistic->setCounterExported($statistic->getCounterExported() + $resultRow['counter']); $statistic->addCounterExported((int) $resultRow['counter']);
} }
} }
} }
@@ -113,7 +108,7 @@ class CustomerStatisticService
return $statistics; return $statistics;
} }
private function createStatisticQueryBuilder(array $customers, DateTime $begin = null, ?DateTime $end = null): QueryBuilder private function createStatisticQueryBuilder(array $customers, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): QueryBuilder
{ {
$qb = $this->timesheetRepository->createQueryBuilder('t'); $qb = $this->timesheetRepository->createQueryBuilder('t');
$qb $qb
@@ -136,14 +131,14 @@ class CustomerStatisticService
if ($begin !== null) { if ($begin !== null) {
$qb $qb
->andWhere($qb->expr()->gte('t.begin', ':begin')) ->andWhere($qb->expr()->gte('t.begin', ':begin'))
->setParameter('begin', $begin, Types::DATETIME_MUTABLE) ->setParameter('begin', DateTimeImmutable::createFromInterface($begin), Types::DATETIME_IMMUTABLE)
; ;
} }
if ($end !== null) { if ($end !== null) {
$qb $qb
->andWhere($qb->expr()->lte('t.begin', ':end')) ->andWhere($qb->expr()->lte('t.begin', ':end'))
->setParameter('end', $end, Types::DATETIME_MUTABLE) ->setParameter('end', DateTimeImmutable::createFromInterface($end), Types::DATETIME_IMMUTABLE)
; ;
} }

View File

@@ -109,8 +109,7 @@ final class AppExtension extends Extension
$locales = explode('|', $container->getParameter('app_locales')); $locales = explode('|', $container->getParameter('app_locales'));
$directory = $container->getParameter('kernel.project_dir'); $directory = $container->getParameter('kernel.project_dir');
$config = $directory . DIRECTORY_SEPARATOR . 'config/locales.php'; $settings = include $directory . DIRECTORY_SEPARATOR . 'config/locales.php';
$settings = include $config;
$appLocales = []; $appLocales = [];
$defaults = [ $defaults = [

View File

@@ -13,13 +13,27 @@ use App\Model\ActivityBudgetStatisticModel;
final class ActivityBudgetStatisticEvent final class ActivityBudgetStatisticEvent
{ {
private readonly ?\DateTime $begin;
private readonly ?\DateTime $end;
/** /**
* @param ActivityBudgetStatisticModel[] $models * @param ActivityBudgetStatisticModel[] $models
* @param \DateTime|null $begin
* @param \DateTime|null $end
*/ */
public function __construct(private array $models, private ?\DateTime $begin = null, private ?\DateTime $end = null) public function __construct(
private readonly array $models,
?\DateTimeInterface $begin = null,
?\DateTimeInterface $end = null
)
{ {
if ($begin !== null) {
$begin = \DateTime::createFromInterface($begin);
}
$this->begin = $begin;
if ($end !== null) {
$end = \DateTime::createFromInterface($end);
}
$this->end = $end;
} }
public function getModel(int $activityId): ?ActivityBudgetStatisticModel public function getModel(int $activityId): ?ActivityBudgetStatisticModel

View File

@@ -11,12 +11,32 @@ namespace App\Event;
use App\Entity\Activity; use App\Entity\Activity;
use App\Model\ActivityStatistic; use App\Model\ActivityStatistic;
use DateTime;
use DateTimeInterface;
final class ActivityStatisticEvent extends AbstractActivityEvent final class ActivityStatisticEvent extends AbstractActivityEvent
{ {
public function __construct(Activity $activity, private ActivityStatistic $statistic, private ?\DateTime $begin = null, private ?\DateTime $end = null) private readonly ?DateTime $begin;
private readonly ?DateTime $end;
public function __construct(
Activity $activity,
private readonly ActivityStatistic $statistic,
?DateTimeInterface $begin = null,
?DateTimeInterface $end = null
)
{ {
parent::__construct($activity); parent::__construct($activity);
if ($begin !== null) {
$begin = DateTime::createFromInterface($begin);
}
$this->begin = $begin;
if ($end !== null) {
$end = DateTime::createFromInterface($end);
}
$this->end = $end;
} }
public function getStatistic(): ActivityStatistic public function getStatistic(): ActivityStatistic
@@ -24,12 +44,12 @@ final class ActivityStatisticEvent extends AbstractActivityEvent
return $this->statistic; return $this->statistic;
} }
public function getBegin(): ?\DateTime public function getBegin(): ?DateTime
{ {
return $this->begin; return $this->begin;
} }
public function getEnd(): ?\DateTime public function getEnd(): ?DateTime
{ {
return $this->end; return $this->end;
} }

View File

@@ -10,16 +10,32 @@
namespace App\Event; namespace App\Event;
use App\Model\ProjectBudgetStatisticModel; use App\Model\ProjectBudgetStatisticModel;
use DateTime;
use DateTimeInterface;
final class ProjectBudgetStatisticEvent final class ProjectBudgetStatisticEvent
{ {
private readonly ?DateTime $begin;
private readonly ?DateTime $end;
/** /**
* @param ProjectBudgetStatisticModel[] $models * @param ProjectBudgetStatisticModel[] $models
* @param \DateTime|null $begin
* @param \DateTime|null $end
*/ */
public function __construct(private array $models, private ?\DateTime $begin = null, private ?\DateTime $end = null) public function __construct(
private readonly array $models,
?DateTimeInterface $begin = null,
?DateTimeInterface $end = null
)
{ {
if ($begin !== null) {
$begin = \DateTime::createFromInterface($begin);
}
$this->begin = $begin;
if ($end !== null) {
$end = \DateTime::createFromInterface($end);
}
$this->end = $end;
} }
public function getModel(int $projectId): ?ProjectBudgetStatisticModel public function getModel(int $projectId): ?ProjectBudgetStatisticModel
@@ -45,12 +61,12 @@ final class ProjectBudgetStatisticEvent
return $this->models; return $this->models;
} }
public function getBegin(): ?\DateTime public function getBegin(): ?DateTime
{ {
return $this->begin; return $this->begin;
} }
public function getEnd(): ?\DateTime public function getEnd(): ?DateTime
{ {
return $this->end; return $this->end;
} }

View File

@@ -14,7 +14,12 @@ use App\Model\ProjectStatistic;
final class ProjectStatisticEvent extends AbstractProjectEvent final class ProjectStatisticEvent extends AbstractProjectEvent
{ {
public function __construct(Project $project, private ProjectStatistic $statistic, private ?\DateTime $begin = null, private ?\DateTime $end = null) public function __construct(
Project $project,
private readonly ProjectStatistic $statistic,
private readonly ?\DateTimeInterface $begin = null,
private readonly ?\DateTimeInterface $end = null
)
{ {
parent::__construct($project); parent::__construct($project);
} }
@@ -24,12 +29,12 @@ final class ProjectStatisticEvent extends AbstractProjectEvent
return $this->statistic; return $this->statistic;
} }
public function getBegin(): ?\DateTime public function getBegin(): ?\DateTimeInterface
{ {
return $this->begin; return $this->begin;
} }
public function getEnd(): ?\DateTime public function getEnd(): ?\DateTimeInterface
{ {
return $this->end; return $this->end;
} }

View File

@@ -186,7 +186,7 @@ abstract class AbstractSpreadsheetRenderer
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), $excelDate); $sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), $excelDate);
// TODO why is that format hardcoded and does not depend on the users locale? // TODO why is that format hardcoded and does not depend on the users locale?
$sheet->getStyle(CellAddress::fromColumnAndRow($column, $row))->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2); $sheet->getStyle(CellAddress::fromColumnAndRow($column, $row))->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD);
} }
protected function setDurationTotal(Worksheet $sheet, int $column, int $row, string $startCoordinate, string $endCoordinate): void protected function setDurationTotal(Worksheet $sheet, int $column, int $row, string $startCoordinate, string $endCoordinate): void

View File

@@ -24,11 +24,11 @@ final class DateFormatter implements CellFormatterInterface
return; return;
} }
if (!$value instanceof \DateTime) { if (!$value instanceof \DateTimeInterface) {
throw new \InvalidArgumentException('Unsupported value given, only DateTime is supported'); throw new \InvalidArgumentException('Unsupported value given, only DateTimeInterface is supported');
} }
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value)); $sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value));
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2); $sheet->getStyle(CellAddress::fromColumnAndRow($column, $row))->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD);
} }
} }

View File

@@ -25,11 +25,11 @@ final class DateTimeFormatter implements CellFormatterInterface
return; return;
} }
if (!$value instanceof \DateTime) { if (!$value instanceof \DateTimeInterface) {
throw new \InvalidArgumentException('Unsupported value given, only DateTime is supported'); throw new \InvalidArgumentException('Unsupported value given, only DateTimeInterface is supported');
} }
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value)); $sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value));
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(self::DATETIME_FORMAT); $sheet->getStyle(CellAddress::fromColumnAndRow($column, $row))->getNumberFormat()->setFormatCode(self::DATETIME_FORMAT);
} }
} }

View File

@@ -25,8 +25,8 @@ final class TimeFormatter implements CellFormatterInterface
return; return;
} }
if (!$value instanceof \DateTime) { if (!$value instanceof \DateTimeInterface) {
throw new \InvalidArgumentException('Unsupported value given, only DateTime is supported'); throw new \InvalidArgumentException('Unsupported value given, only DateTimeInterface is supported');
} }
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value)); $sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value));

View File

@@ -26,9 +26,9 @@ final class DateRange implements EquatableInterface
return $this->begin; return $this->begin;
} }
public function setBegin(DateTime $begin): DateRange public function setBegin(\DateTimeInterface $begin): DateRange
{ {
$this->begin = $begin; $this->begin = DateTime::createFromInterface($begin);
if ($this->resetTimes) { if ($this->resetTimes) {
$this->begin->setTime(0, 0, 0); $this->begin->setTime(0, 0, 0);
} }
@@ -41,9 +41,9 @@ final class DateRange implements EquatableInterface
return $this->end; return $this->end;
} }
public function setEnd(DateTime $end): DateRange public function setEnd(\DateTimeInterface $end): DateRange
{ {
$this->end = $end; $this->end = DateTime::createFromInterface($end);
if ($this->resetTimes) { if ($this->resetTimes) {
$this->end->setTime(23, 59, 59); $this->end->setTime(23, 59, 59);
} }

View File

@@ -39,10 +39,12 @@ class DatePickerType extends AbstractType
return null; return null;
} }
if ($reverseTransform instanceof \DateTime && $options['force_time']) { if ($reverseTransform instanceof \DateTimeInterface && $options['force_time']) {
if ($options['force_time'] === 'start') { if ($options['force_time'] === 'start') {
$reverseTransform = \DateTime::createFromInterface($reverseTransform);
$reverseTransform->setTime(0, 0, 0); $reverseTransform->setTime(0, 0, 0);
} elseif ($options['force_time'] === 'end') { } elseif ($options['force_time'] === 'end') {
$reverseTransform = \DateTime::createFromInterface($reverseTransform);
$reverseTransform->setTime(23, 59, 59); $reverseTransform->setTime(23, 59, 59);
} }
} }

View File

@@ -48,7 +48,9 @@ final class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
'customer.invoice_text' => $customer->getInvoiceText() ?? '', 'customer.invoice_text' => $customer->getInvoiceText() ?? '',
]; ];
$statistic = $this->customerStatisticService->getBudgetStatisticModel($customer, $model->getQuery()->getEnd()); /** @var \DateTime $end */
$end = $model->getQuery()->getEnd();
$statistic = $this->customerStatisticService->getBudgetStatisticModel($customer, $end);
$values = array_merge($values, $this->getBudgetValues('customer.', $statistic, $model)); $values = array_merge($values, $this->getBudgetValues('customer.', $statistic, $model));

View File

@@ -57,7 +57,12 @@ final class DocxRenderer extends AbstractRenderer implements RendererInterface
$i++; $i++;
} }
$cacheFile = $template->save(); $cacheFile = @tempnam(sys_get_temp_dir(), 'kimai-invoice-docx');
if (false === $cacheFile) {
throw new \Exception('Could not open temporary file');
}
$template->saveAs($cacheFile);
clearstatcache(true, $cacheFile); clearstatcache(true, $cacheFile);

View File

@@ -47,6 +47,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->counter = $counter; $this->counter = $counter;
} }
public function addCounter(int $counter): void
{
$this->counter += $counter;
}
public function getCounterBillable(): int public function getCounterBillable(): int
{ {
return $this->counterBillable; return $this->counterBillable;
@@ -57,6 +62,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->counterBillable = $counter; $this->counterBillable = $counter;
} }
public function addCounterBillable(int $counter): void
{
$this->counterBillable += $counter;
}
public function getCounterExported(): int public function getCounterExported(): int
{ {
return $this->counterExported; return $this->counterExported;
@@ -67,6 +77,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->counterExported = $counter; $this->counterExported = $counter;
} }
public function addCounterExported(int $counter): void
{
$this->counterExported += $counter;
}
/** /**
* For unified access, used in frontend. * For unified access, used in frontend.
* *
@@ -82,6 +97,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->recordDuration = $duration; $this->recordDuration = $duration;
} }
public function addDuration(int $duration): void
{
$this->recordDuration += $duration;
}
/** /**
* For unified access, used in frontend. * For unified access, used in frontend.
* *
@@ -107,6 +127,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->recordRate = $rate; $this->recordRate = $rate;
} }
public function addRate(float $rate): void
{
$this->recordRate += $rate;
}
/** /**
* Returns the total internal rate of all included timesheet records. * Returns the total internal rate of all included timesheet records.
* *
@@ -127,6 +152,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->internalRateBillable = $internalRateBillable; $this->internalRateBillable = $internalRateBillable;
} }
public function addInternalRateBillable(float $internalRateBillable): void
{
$this->internalRateBillable += $internalRateBillable;
}
public function getInternalRateExported(): float public function getInternalRateExported(): float
{ {
return $this->internalRateExported; return $this->internalRateExported;
@@ -137,11 +167,21 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->internalRateExported = $internalRateExported; $this->internalRateExported = $internalRateExported;
} }
public function addInternalRateExported(float $internalRateExported): void
{
$this->internalRateExported += $internalRateExported;
}
public function setInternalRate(float $internalRate): void public function setInternalRate(float $internalRate): void
{ {
$this->internalRate = $internalRate; $this->internalRate = $internalRate;
} }
public function addInternalRate(float $internalRate): void
{
$this->internalRate += $internalRate;
}
public function getDurationBillable(): int public function getDurationBillable(): int
{ {
return $this->recordDurationBillable; return $this->recordDurationBillable;
@@ -152,6 +192,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->recordDurationBillable = $recordDuration; $this->recordDurationBillable = $recordDuration;
} }
public function addDurationBillable(int $recordDuration): void
{
$this->recordDurationBillable += $recordDuration;
}
public function getDurationBillableExported(): int public function getDurationBillableExported(): int
{ {
return $this->recordDurationBillableExported; return $this->recordDurationBillableExported;
@@ -162,6 +207,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->recordDurationBillableExported = $recordDuration; $this->recordDurationBillableExported = $recordDuration;
} }
public function addDurationBillableExported(int $recordDuration): void
{
$this->recordDurationBillableExported += $recordDuration;
}
public function getRateBillable(): float public function getRateBillable(): float
{ {
return $this->recordRateBillable; return $this->recordRateBillable;
@@ -172,6 +222,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->recordRateBillable = $recordRate; $this->recordRateBillable = $recordRate;
} }
public function addRateBillable(float $recordRate): void
{
$this->recordRateBillable += $recordRate;
}
public function getRateBillableExported(): float public function getRateBillableExported(): float
{ {
return $this->recordRateBillableExported; return $this->recordRateBillableExported;
@@ -182,6 +237,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->recordRateBillableExported = $recordRate; $this->recordRateBillableExported = $recordRate;
} }
public function addRateBillableExported(float $recordRate): void
{
$this->recordRateBillableExported += $recordRate;
}
public function getDurationExported(): int public function getDurationExported(): int
{ {
return $this->recordDurationExported; return $this->recordDurationExported;
@@ -192,6 +252,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->recordDurationExported = $recordDuration; $this->recordDurationExported = $recordDuration;
} }
public function addDurationExported(int $recordDuration): void
{
$this->recordDurationExported += $recordDuration;
}
public function getRateExported(): float public function getRateExported(): float
{ {
return $this->recordRateExported; return $this->recordRateExported;
@@ -202,6 +267,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
$this->recordRateExported = $recordRate; $this->recordRateExported = $recordRate;
} }
public function addRateExported(float $recordRate): void
{
$this->recordRateExported += $recordRate;
}
public function jsonSerialize(): mixed public function jsonSerialize(): mixed
{ {
return [ return [

View File

@@ -34,7 +34,8 @@ use App\Repository\ProjectRepository;
use App\Repository\TimesheetRepository; use App\Repository\TimesheetRepository;
use App\Repository\UserRepository; use App\Repository\UserRepository;
use App\Timesheet\DateTimeFactory; use App\Timesheet\DateTimeFactory;
use DateTime; use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Psr\EventDispatcher\EventDispatcherInterface; use Psr\EventDispatcher\EventDispatcherInterface;
@@ -54,14 +55,9 @@ class ProjectStatisticService
} }
/** /**
* WARNING: this method does not respect the budget type. Your results will always be wither the "full lifetime data" or the "selected date-range". * WARNING: this method does not respect the budget type. Your results will always be with the "full lifetime data" or the "selected date-range".
*
* @param Project $project
* @param DateTime|null $begin
* @param DateTime|null $end
* @return ProjectStatistic
*/ */
public function getProjectStatistics(Project $project, ?DateTime $begin = null, ?DateTime $end = null): ProjectStatistic public function getProjectStatistics(Project $project, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): ProjectStatistic
{ {
$statistics = $this->getBudgetStatistic([$project], $begin, $end); $statistics = $this->getBudgetStatistic([$project], $begin, $end);
$event = new ProjectStatisticEvent($project, array_pop($statistics), $begin, $end); $event = new ProjectStatisticEvent($project, array_pop($statistics), $begin, $end);
@@ -71,14 +67,13 @@ class ProjectStatisticService
} }
/** /**
* @param ProjectInactiveQuery $query
* @return Project[] * @return Project[]
*/ */
public function findInactiveProjects(ProjectInactiveQuery $query): array public function findInactiveProjects(ProjectInactiveQuery $query): array
{ {
$user = $query->getUser(); $user = $query->getUser();
$lastChange = clone $query->getLastChange(); $lastChange = DateTimeImmutable::createFromInterface($query->getLastChange());
$now = new DateTime('now', $lastChange->getTimezone()); $now = new DateTimeImmutable('now', $lastChange->getTimezone());
$qb2 = $this->projectRepository->createQueryBuilder('t1'); $qb2 = $this->projectRepository->createQueryBuilder('t1');
$qb2 $qb2
@@ -101,15 +96,15 @@ class ProjectStatisticService
$qb->expr()->lte('p.start', ':project_start') $qb->expr()->lte('p.start', ':project_start')
) )
) )
->setParameter('project_start', $now, Types::DATETIME_MUTABLE) ->setParameter('project_start', $now, Types::DATETIME_IMMUTABLE)
->andWhere( ->andWhere(
$qb->expr()->orX( $qb->expr()->orX(
$qb->expr()->isNull('p.end'), $qb->expr()->isNull('p.end'),
$qb->expr()->gte('p.end', ':project_end') $qb->expr()->gte('p.end', ':project_end')
) )
) )
->setParameter('project_end', $now, Types::DATETIME_MUTABLE) ->setParameter('project_end', $now, Types::DATETIME_IMMUTABLE)
->setParameter('begin', $lastChange, Types::DATETIME_MUTABLE) ->setParameter('begin', $lastChange, Types::DATETIME_IMMUTABLE)
; ;
$this->projectRepository->addPermissionCriteria($qb, $user); $this->projectRepository->addPermissionCriteria($qb, $user);
@@ -125,7 +120,6 @@ class ProjectStatisticService
} }
/** /**
* @param ProjectDateRangeQuery $query
* @return Project[] * @return Project[]
*/ */
public function findProjectsForDateRange(ProjectDateRangeQuery $query, DateRange $dateRange): array public function findProjectsForDateRange(ProjectDateRangeQuery $query, DateRange $dateRange): array
@@ -208,7 +202,7 @@ class ProjectStatisticService
return $projects; return $projects;
} }
public function getBudgetStatisticModel(Project $project, DateTime $today): ProjectBudgetStatisticModel public function getBudgetStatisticModel(Project $project, DateTimeInterface $today): ProjectBudgetStatisticModel
{ {
$stats = new ProjectBudgetStatisticModel($project); $stats = new ProjectBudgetStatisticModel($project);
$stats->setStatisticTotal($this->getProjectStatistics($project)); $stats->setStatisticTotal($this->getProjectStatistics($project));
@@ -229,10 +223,9 @@ class ProjectStatisticService
/** /**
* @param Project[] $projects * @param Project[] $projects
* @param DateTime $today
* @return ProjectBudgetStatisticModel[] * @return ProjectBudgetStatisticModel[]
*/ */
public function getBudgetStatisticModelForProjects(array $projects, DateTime $today): array public function getBudgetStatisticModelForProjects(array $projects, DateTimeInterface $today): array
{ {
$models = []; $models = [];
$monthly = []; $monthly = [];
@@ -282,12 +275,9 @@ class ProjectStatisticService
/** /**
* @param Project[] $projects * @param Project[] $projects
* @param DateTime $begin
* @param DateTime $end
* @param DateTime|null $totalsEnd
* @return ProjectBudgetStatisticModel[] * @return ProjectBudgetStatisticModel[]
*/ */
public function getBudgetStatisticModelForProjectsByDateRange(array $projects, DateTime $begin, DateTime $end, ?DateTime $totalsEnd = null): array public function getBudgetStatisticModelForProjectsByDateRange(array $projects, DateTimeInterface $begin, DateTimeInterface $end, ?DateTimeInterface $totalsEnd = null): array
{ {
$models = []; $models = [];
@@ -313,11 +303,9 @@ class ProjectStatisticService
/** /**
* @param Project[] $projects * @param Project[] $projects
* @param DateTime|null $begin
* @param DateTime|null $end
* @return array<int, ProjectStatistic> * @return array<int, ProjectStatistic>
*/ */
public function getBudgetStatistic(array $projects, ?DateTime $begin = null, ?DateTime $end = null): array public function getBudgetStatistic(array $projects, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): array
{ {
$statistics = []; $statistics = [];
foreach ($projects as $project) { foreach ($projects as $project) {
@@ -711,15 +699,13 @@ class ProjectStatisticService
} }
/** /**
* @param User $user
* @param Project[] $projects * @param Project[] $projects
* @param DateTime $today
* @return ProjectViewModel[] * @return ProjectViewModel[]
*/ */
public function getProjectView(User $user, array $projects, DateTime $today): array public function getProjectView(User $user, array $projects, DateTimeInterface $today): array
{ {
$factory = DateTimeFactory::createByUser($user); $factory = DateTimeFactory::createByUser($user);
$today = clone $today; $today = DateTimeImmutable::createFromInterface($today);
$startOfWeek = $factory->getStartOfWeek($today); $startOfWeek = $factory->getStartOfWeek($today);
$endOfWeek = $factory->getEndOfWeek($today); $endOfWeek = $factory->getEndOfWeek($today);

View File

@@ -0,0 +1,30 @@
<?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\Repository\Query;
use App\Form\Model\DateRange;
/**
* @internal
*/
interface DateRangeInterface
{
public function getBegin(): ?\DateTime;
public function setBegin(\DateTimeInterface $begin): void;
public function getEnd(): ?\DateTime;
public function setEnd(\DateTimeInterface $end): void;
public function getDateRange(): ?DateRange;
public function setDateRange(DateRange $dateRange): void;
}

View File

@@ -20,9 +20,9 @@ trait DateRangeTrait
return $this->dateRange?->getBegin(); return $this->dateRange?->getBegin();
} }
public function setBegin(\DateTime $begin): void public function setBegin(\DateTimeInterface $begin): void
{ {
$this->dateRange->setBegin($begin); $this->dateRange->setBegin(\DateTime::createFromInterface($begin));
} }
public function getEnd(): ?\DateTime public function getEnd(): ?\DateTime
@@ -30,9 +30,9 @@ trait DateRangeTrait
return $this->dateRange?->getEnd(); return $this->dateRange?->getEnd();
} }
public function setEnd(\DateTime $end): void public function setEnd(\DateTimeInterface $end): void
{ {
$this->dateRange->setEnd($end); $this->dateRange->setEnd(\DateTime::createFromInterface($end));
} }
public function getDateRange(): ?DateRange public function getDateRange(): ?DateRange

View File

@@ -16,7 +16,7 @@ use App\Form\Model\DateRange;
/** /**
* Query for created invoices. * Query for created invoices.
*/ */
class InvoiceArchiveQuery extends BaseQuery class InvoiceArchiveQuery extends BaseQuery implements DateRangeInterface
{ {
use DateRangeTrait; use DateRangeTrait;

View File

@@ -14,7 +14,7 @@ use App\Entity\Tag;
use App\Entity\User; use App\Entity\User;
use App\Form\Model\DateRange; use App\Form\Model\DateRange;
class TimesheetQuery extends ActivityQuery implements BillableInterface class TimesheetQuery extends ActivityQuery implements BillableInterface, DateRangeInterface
{ {
use BillableTrait; use BillableTrait;
use DateRangeTrait; use DateRangeTrait;

View File

@@ -62,10 +62,10 @@ final class LocaleFormatExtensions extends AbstractExtension implements LocaleAw
return [ return [
new TwigTest('weekend', [$this, 'isWeekend']), new TwigTest('weekend', [$this, 'isWeekend']),
new TwigTest('today', function ($dateTime): bool { new TwigTest('today', function ($dateTime): bool {
if (!$dateTime instanceof \DateTime) { if (!$dateTime instanceof \DateTimeInterface) {
return false; return false;
} }
$compare = new \DateTime('now', $dateTime->getTimezone()); $compare = new \DateTimeImmutable('now', $dateTime->getTimezone());
return $compare->format('Y-m-d') === $dateTime->format('Y-m-d'); return $compare->format('Y-m-d') === $dateTime->format('Y-m-d');
}), }),

View File

@@ -58,8 +58,10 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
return; return;
} }
$begin = $timesheet->getBegin();
// we can only work with stopped entries // we can only work with stopped entries
if (null === $timesheet->getEnd() || null === $timesheet->getUser()) { if ($begin === null || $timesheet->getEnd() === null || $timesheet->getUser() === null) {
return; return;
} }
@@ -115,7 +117,7 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
if ($duration === $rawData['duration'] && if ($duration === $rawData['duration'] &&
$rate === $rawData['rate'] && $rate === $rawData['rate'] &&
$timesheet->isBillable() === $rawData['billable'] && $timesheet->isBillable() === $rawData['billable'] &&
$timesheet->getBegin()->format('Y.m.d') === $rawData['begin']->format('Y.m.d') && $begin->format('Y.m.d') === $rawData['begin']->format('Y.m.d') &&
$timesheet->getProject()->getId() === $projectId && $timesheet->getProject()->getId() === $projectId &&
($timesheet->getActivity() === null || $timesheet->getActivity()->getId() === $activityId) ($timesheet->getActivity() === null || $timesheet->getActivity()->getId() === $activityId)
) { ) {
@@ -145,11 +147,11 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
} }
} }
$monthWasChanged = $timesheet->getBegin()->format('Y.m') !== $rawData['begin']->format('Y.m'); $monthWasChanged = $begin->format('Y.m') !== $rawData['begin']->format('Y.m');
} }
$now = new DateTime('now', $timesheet->getBegin()->getTimezone()); $now = new DateTime('now', $begin->getTimezone());
$recordDate = $timesheet->getBegin(); $recordDate = $begin;
if (null !== ($activity = $timesheet->getActivity()) && $activity->hasBudgets()) { if (null !== ($activity = $timesheet->getActivity()) && $activity->hasBudgets()) {
$dateTime = $activity->isMonthlyBudget() ? $recordDate : $now; $dateTime = $activity->isMonthlyBudget() ? $recordDate : $now;

View File

@@ -52,7 +52,7 @@ final class TimesheetLockdownValidator extends ConstraintValidator
$now = new \DateTime('now', $timesheetStart->getTimezone()); $now = new \DateTime('now', $timesheetStart->getTimezone());
if (!empty($constraint->now)) { if (!empty($constraint->now)) {
if ($constraint->now instanceof \DateTime) { if ($constraint->now instanceof \DateTimeInterface) {
$now = $constraint->now; $now = $constraint->now;
} elseif (\is_string($constraint->now)) { } elseif (\is_string($constraint->now)) {
try { try {

View File

@@ -443,13 +443,23 @@
"version": "v4.0.3" "version": "v4.0.3"
}, },
"symfony/framework-bundle": { "symfony/framework-bundle": {
"version": "3.3", "version": "6.4",
"recipe": { "recipe": {
"repo": "github.com/symfony/recipes", "repo": "github.com/symfony/recipes",
"branch": "master", "branch": "main",
"version": "3.3", "version": "6.4",
"ref": "137a14eeb6b3f5370e7147af8aff6518504f50c7" "ref": "a91c965766ad3ff2ae15981801643330eb42b6a5"
} },
"files": [
"config/packages/cache.yaml",
"config/packages/framework.yaml",
"config/preload.php",
"config/routes/framework.yaml",
"config/services.yaml",
"public/index.php",
"src/Controller/.gitignore",
"src/Kernel.php"
]
}, },
"symfony/http-client": { "symfony/http-client": {
"version": "v4.4.5" "version": "v4.4.5"

View File

@@ -19,7 +19,7 @@ use PHPUnit\Framework\TestCase;
*/ */
class ActivityBudgetStatisticEventTest extends TestCase class ActivityBudgetStatisticEventTest extends TestCase
{ {
public function testStatistic() public function testStatistic(): void
{ {
$activity = $this->createMock(Activity::class); $activity = $this->createMock(Activity::class);
$activity->expects($this->exactly(2))->method('getId')->willReturn(12); $activity->expects($this->exactly(2))->method('getId')->willReturn(12);
@@ -39,7 +39,7 @@ class ActivityBudgetStatisticEventTest extends TestCase
self::assertNull($sut->getModel(1)); self::assertNull($sut->getModel(1));
self::assertSame($model1, $sut->getModel(12)); self::assertSame($model1, $sut->getModel(12));
self::assertSame($model2, $sut->getModel(4)); self::assertSame($model2, $sut->getModel(4));
self::assertSame($begin, $sut->getBegin()); self::assertEquals($begin, $sut->getBegin());
self::assertSame($end, $sut->getEnd()); self::assertEquals($end, $sut->getEnd());
} }
} }

View File

@@ -25,7 +25,7 @@ class ActivityStatisticEventTest extends AbstractActivityEventTest
return new ActivityStatisticEvent($activity, new ActivityStatistic()); return new ActivityStatisticEvent($activity, new ActivityStatistic());
} }
public function testStatistic() public function testStatistic(): void
{ {
$activity = new Activity(); $activity = new Activity();
$statistic = new ActivityStatistic(); $statistic = new ActivityStatistic();
@@ -39,7 +39,7 @@ class ActivityStatisticEventTest extends AbstractActivityEventTest
$begin = new \DateTime('2020-08-08 12:34:56'); $begin = new \DateTime('2020-08-08 12:34:56');
$end = new \DateTime('2020-09-08 12:34:56'); $end = new \DateTime('2020-09-08 12:34:56');
$sut = new ActivityStatisticEvent($activity, $statistic, $begin, $end); $sut = new ActivityStatisticEvent($activity, $statistic, $begin, $end);
self::assertSame($begin, $sut->getBegin()); self::assertEquals($begin, $sut->getBegin());
self::assertSame($end, $sut->getEnd()); self::assertEquals($end, $sut->getEnd());
} }
} }

View File

@@ -19,7 +19,7 @@ use PHPUnit\Framework\TestCase;
*/ */
class ProjectBudgetStatisticEventTest extends TestCase class ProjectBudgetStatisticEventTest extends TestCase
{ {
public function testStatistic() public function testStatistic(): void
{ {
$project = $this->createMock(Project::class); $project = $this->createMock(Project::class);
$project->expects($this->exactly(2))->method('getId')->willReturn(12); $project->expects($this->exactly(2))->method('getId')->willReturn(12);
@@ -39,7 +39,7 @@ class ProjectBudgetStatisticEventTest extends TestCase
self::assertNull($sut->getModel(1)); self::assertNull($sut->getModel(1));
self::assertSame($model1, $sut->getModel(12)); self::assertSame($model1, $sut->getModel(12));
self::assertSame($model2, $sut->getModel(4)); self::assertSame($model2, $sut->getModel(4));
self::assertSame($begin, $sut->getBegin()); self::assertEquals($begin, $sut->getBegin());
self::assertSame($end, $sut->getEnd()); self::assertEquals($end, $sut->getEnd());
} }
} }

View File

@@ -25,7 +25,7 @@ class ProjectStatisticEventTest extends AbstractProjectEventTest
return new ProjectStatisticEvent($project, new ProjectStatistic()); return new ProjectStatisticEvent($project, new ProjectStatistic());
} }
public function testStatistic() public function testStatistic(): void
{ {
$project = new Project(); $project = new Project();
$statistic = new ProjectStatistic(); $statistic = new ProjectStatistic();
@@ -36,8 +36,8 @@ class ProjectStatisticEventTest extends AbstractProjectEventTest
self::assertNull($sut->getBegin()); self::assertNull($sut->getBegin());
self::assertNull($sut->getEnd()); self::assertNull($sut->getEnd());
$begin = new \DateTime('2020-08-08 12:34:56'); $begin = new \DateTimeImmutable('2020-08-08 12:34:56');
$end = new \DateTime('2020-09-08 12:34:56'); $end = new \DateTimeImmutable('2020-09-08 12:34:56');
$sut = new ProjectStatisticEvent($project, $statistic, $begin, $end); $sut = new ProjectStatisticEvent($project, $statistic, $begin, $end);
self::assertSame($begin, $sut->getBegin()); self::assertSame($begin, $sut->getBegin());
self::assertSame($end, $sut->getEnd()); self::assertSame($end, $sut->getEnd());

View File

@@ -41,7 +41,7 @@ class DateFormatterTest extends AbstractFormatterTest
public function testFormattedValueWithInvalidValue() public function testFormattedValueWithInvalidValue()
{ {
$this->expectException(\InvalidArgumentException::class); $this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Unsupported value given, only DateTime is supported'); $this->expectExceptionMessage('Unsupported value given, only DateTimeInterface is supported');
$spreadsheet = new Spreadsheet(); $spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet(); $worksheet = $spreadsheet->getActiveSheet();

View File

@@ -40,7 +40,7 @@ class DateTimeFormatterTest extends AbstractFormatterTest
public function testFormattedValueWithInvalidValue() public function testFormattedValueWithInvalidValue()
{ {
$this->expectException(\InvalidArgumentException::class); $this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Unsupported value given, only DateTime is supported'); $this->expectExceptionMessage('Unsupported value given, only DateTimeInterface is supported');
$spreadsheet = new Spreadsheet(); $spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet(); $worksheet = $spreadsheet->getActiveSheet();

View File

@@ -40,7 +40,7 @@ class TimeFormatterTest extends AbstractFormatterTest
public function testFormattedValueWithInvalidValue() public function testFormattedValueWithInvalidValue()
{ {
$this->expectException(\InvalidArgumentException::class); $this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Unsupported value given, only DateTime is supported'); $this->expectExceptionMessage('Unsupported value given, only DateTimeInterface is supported');
$spreadsheet = new Spreadsheet(); $spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet(); $worksheet = $spreadsheet->getActiveSheet();

View File

@@ -22,7 +22,7 @@ class DocxRendererTest extends TestCase
{ {
use RendererTestTrait; use RendererTestTrait;
public function testSupports() public function testSupports(): void
{ {
$sut = $this->getAbstractRenderer(DocxRenderer::class); $sut = $this->getAbstractRenderer(DocxRenderer::class);
@@ -34,7 +34,7 @@ class DocxRendererTest extends TestCase
$this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true))); $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true)));
} }
public function testRender() public function testRender(): void
{ {
/** @var DocxRenderer $sut */ /** @var DocxRenderer $sut */
$sut = $this->getAbstractRenderer(DocxRenderer::class); $sut = $this->getAbstractRenderer(DocxRenderer::class);
@@ -48,7 +48,7 @@ class DocxRendererTest extends TestCase
$this->assertEquals('application/vnd.openxmlformats-officedocument.wordprocessingml.document', $response->headers->get('Content-Type')); $this->assertEquals('application/vnd.openxmlformats-officedocument.wordprocessingml.document', $response->headers->get('Content-Type'));
$this->assertEquals('attachment; filename=' . $filename, $response->headers->get('Content-Disposition')); $this->assertEquals('attachment; filename=' . $filename, $response->headers->get('Content-Disposition'));
$this->assertTrue(file_exists($file->getRealPath())); $this->assertTrue(file_exists($file->getPathname()));
ob_start(); ob_start();
$response->sendContent(); $response->sendContent();

View File

@@ -17,6 +17,7 @@ use App\Entity\Team;
use App\Form\Model\DateRange; use App\Form\Model\DateRange;
use App\Repository\Query\ActivityQuery; use App\Repository\Query\ActivityQuery;
use App\Repository\Query\BaseQuery; use App\Repository\Query\BaseQuery;
use App\Repository\Query\DateRangeInterface;
use App\Repository\Query\ProjectQuery; use App\Repository\Query\ProjectQuery;
use App\Repository\Query\TimesheetQuery; use App\Repository\Query\TimesheetQuery;
use App\Utils\SearchTerm; use App\Utils\SearchTerm;
@@ -334,7 +335,7 @@ class BaseQueryTest extends TestCase
$this->assertEquals([13, 27], $sut->getProjectIds()); $this->assertEquals([13, 27], $sut->getProjectIds());
} }
protected function assertDateRangeTrait($sut): void protected function assertDateRangeTrait(DateRangeInterface $sut): void
{ {
self::assertNull($sut->getBegin()); self::assertNull($sut->getBegin());
self::assertNull($sut->getEnd()); self::assertNull($sut->getEnd());
@@ -346,14 +347,15 @@ class BaseQueryTest extends TestCase
self::assertNull($sut->getBegin()); self::assertNull($sut->getBegin());
self::assertNull($sut->getEnd()); self::assertNull($sut->getEnd());
$begin = new \DateTime('2013-11-23 13:45:07'); $dateRange->setBegin(new \DateTimeImmutable('2013-11-23 13:45:07'));
$end = new \DateTime('2014-01-01 23:45:11'); $dateRange->setEnd(new \DateTimeImmutable('2014-01-01 23:45:11'));
$dateRange->setBegin($begin);
$dateRange->setEnd($end);
self::assertSame($begin, $sut->getDateRange()->getBegin()); $begin1 = new \DateTimeImmutable('2013-11-23 00:00:00');
self::assertSame($begin, $sut->getBegin()); $end1 = new \DateTimeImmutable('2014-01-01 23:59:59');
self::assertSame($end, $sut->getDateRange()->getEnd());
self::assertSame($end, $sut->getEnd()); self::assertEquals($begin1, $sut->getDateRange()->getBegin());
self::assertEquals($begin1, $sut->getBegin());
self::assertEquals($end1, $sut->getDateRange()->getEnd());
self::assertEquals($end1, $sut->getEnd());
} }
} }

View File

@@ -4072,11 +4072,6 @@ parameters:
count: 1 count: 1
path: Event/AbstractTimesheetMultipleEventTest.php path: Event/AbstractTimesheetMultipleEventTest.php
-
message: "#^Method App\\\\Tests\\\\Event\\\\ActivityBudgetStatisticEventTest\\:\\:testStatistic\\(\\) has no return type specified\\.$#"
count: 1
path: Event/ActivityBudgetStatisticEventTest.php
- -
message: "#^Method App\\\\Tests\\\\Event\\\\ActivityMetaDefinitionEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#" message: "#^Method App\\\\Tests\\\\Event\\\\ActivityMetaDefinitionEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#"
count: 1 count: 1
@@ -4087,11 +4082,6 @@ parameters:
count: 1 count: 1
path: Event/ActivityMetaDisplayEventTest.php path: Event/ActivityMetaDisplayEventTest.php
-
message: "#^Method App\\\\Tests\\\\Event\\\\ActivityStatisticEventTest\\:\\:testStatistic\\(\\) has no return type specified\\.$#"
count: 1
path: Event/ActivityStatisticEventTest.php
- -
message: "#^Method App\\\\Tests\\\\Event\\\\CalendarConfigurationEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#" message: "#^Method App\\\\Tests\\\\Event\\\\CalendarConfigurationEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#"
count: 1 count: 1
@@ -4202,11 +4192,6 @@ parameters:
count: 5 count: 5
path: Event/PermissionsEventTest.php path: Event/PermissionsEventTest.php
-
message: "#^Method App\\\\Tests\\\\Event\\\\ProjectBudgetStatisticEventTest\\:\\:testStatistic\\(\\) has no return type specified\\.$#"
count: 1
path: Event/ProjectBudgetStatisticEventTest.php
- -
message: "#^Method App\\\\Tests\\\\Event\\\\ProjectMetaDefinitionEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#" message: "#^Method App\\\\Tests\\\\Event\\\\ProjectMetaDefinitionEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#"
count: 1 count: 1
@@ -4222,11 +4207,6 @@ parameters:
count: 1 count: 1
path: Event/ProjectMetaQueryDisplayTest.php path: Event/ProjectMetaQueryDisplayTest.php
-
message: "#^Method App\\\\Tests\\\\Event\\\\ProjectStatisticEventTest\\:\\:testStatistic\\(\\) has no return type specified\\.$#"
count: 1
path: Event/ProjectStatisticEventTest.php
- -
message: "#^Method App\\\\Tests\\\\Event\\\\RecentActivityEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#" message: "#^Method App\\\\Tests\\\\Event\\\\RecentActivityEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#"
count: 1 count: 1
@@ -5912,16 +5892,6 @@ parameters:
count: 1 count: 1
path: Invoice/Renderer/DocxRendererTest.php path: Invoice/Renderer/DocxRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DocxRendererTest\\:\\:testRender\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Renderer/DocxRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DocxRendererTest\\:\\:testSupports\\(\\) has no return type specified\\.$#"
count: 1
path: Invoice/Renderer/DocxRendererTest.php
- -
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\OdsRendererTest\\:\\:getAbstractRenderer\\(\\) should return App\\\\Invoice\\\\Renderer\\\\AbstractRenderer but returns object\\.$#" message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\OdsRendererTest\\:\\:getAbstractRenderer\\(\\) should return App\\\\Invoice\\\\Renderer\\\\AbstractRenderer but returns object\\.$#"
count: 1 count: 1
@@ -6907,11 +6877,6 @@ parameters:
count: 1 count: 1
path: Repository/Query/BaseQueryTest.php path: Repository/Query/BaseQueryTest.php
-
message: "#^Method App\\\\Tests\\\\Repository\\\\Query\\\\BaseQueryTest\\:\\:assertDateRangeTrait\\(\\) has parameter \\$sut with no type specified\\.$#"
count: 1
path: Repository/Query/BaseQueryTest.php
- -
message: "#^Method App\\\\Tests\\\\Repository\\\\Query\\\\BaseQueryTest\\:\\:assertOrder\\(\\) has parameter \\$order with no type specified\\.$#" message: "#^Method App\\\\Tests\\\\Repository\\\\Query\\\\BaseQueryTest\\:\\:assertOrder\\(\\) has parameter \\$order with no type specified\\.$#"
count: 1 count: 1