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
# 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
if [ $? != 0 ]; then
echo "PHP/Kimai not responding"

View File

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

View File

@@ -43,7 +43,7 @@
"onelogin/php-saml": "^4.0",
"pagerfanta/pagerfanta": "^3.0",
"phpoffice/phpspreadsheet": "^1.16",
"phpoffice/phpword": "1.1.*",
"phpoffice/phpword": "^1.0",
"psr/container": "^2.0",
"psr/log": "^3.0",
"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",
"This file is @generated automatically"
],
"content-hash": "7b0ed62d4f9b4d06a6748c700fd04238",
"content-hash": "a307b9a36c22711439283a3e5bc3249c",
"packages": [
{
"name": "azuyalabs/yasumi",
"version": "2.6.0",
"version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/azuyalabs/yasumi.git",
"reference": "a73f198d796100c237adf94f3fca1d15eb82b22b"
"reference": "37d1215d4f4012d3185bb9990c76ca17a4ff1c30"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/a73f198d796100c237adf94f3fca1d15eb82b22b",
"reference": "a73f198d796100c237adf94f3fca1d15eb82b22b",
"url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/37d1215d4f4012d3185bb9990c76ca17a4ff1c30",
"reference": "37d1215d4f4012d3185bb9990c76ca17a4ff1c30",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=7.4"
"php": ">=8.0"
},
"require-dev": {
"ext-intl": "*",
"friendsofphp/php-cs-fixer": "^2.19 || ^3.16",
"infection/infection": "^0.17 || ^0.26",
"friendsofphp/php-cs-fixer": "^2.19 || ^3.40",
"mikey179/vfsstream": "^1.6",
"phan/phan": "^5.4",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8.5 || ^9.6",
"vimeo/psalm": "^5.9"
"vimeo/psalm": "^5.16"
},
"suggest": {
"ext-calendar": "For calculating the date of Easter"
@@ -78,7 +77,7 @@
"type": "other"
}
],
"time": "2023-04-26T15:35:45+00:00"
"time": "2024-01-07T14:12:44+00:00"
},
{
"name": "bacon/bacon-qr-code",
@@ -2519,68 +2518,6 @@
],
"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",
"version": "9.14.0",
@@ -3246,16 +3183,16 @@
},
{
"name": "nelmio/api-doc-bundle",
"version": "v4.15.1",
"version": "v4.16.2",
"source": {
"type": "git",
"url": "https://github.com/nelmio/NelmioApiDocBundle.git",
"reference": "bef8b654ed410d16bf1086e2319ab215e5f36193"
"reference": "31da761b6c9d275fb3bbee87c4c6888b17aec4ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/bef8b654ed410d16bf1086e2319ab215e5f36193",
"reference": "bef8b654ed410d16bf1086e2319ab215e5f36193",
"url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/31da761b6c9d275fb3bbee87c4c6888b17aec4ad",
"reference": "31da761b6c9d275fb3bbee87c4c6888b17aec4ad",
"shasum": ""
},
"require": {
@@ -3352,9 +3289,9 @@
],
"support": {
"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",
@@ -3862,6 +3799,59 @@
},
"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",
"version": "1.29.0",
@@ -3969,24 +3959,24 @@
},
{
"name": "phpoffice/phpword",
"version": "1.1.0",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PHPWord.git",
"reference": "90a55955e6a772bb4cd9b1ef6a7e88c8976c2561"
"reference": "e76b701ef538cb749641514fcbc31a68078550fa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PHPWord/zipball/90a55955e6a772bb4cd9b1ef6a7e88c8976c2561",
"reference": "90a55955e6a772bb4cd9b1ef6a7e88c8976c2561",
"url": "https://api.github.com/repos/PHPOffice/PHPWord/zipball/e76b701ef538cb749641514fcbc31a68078550fa",
"reference": "e76b701ef538cb749641514fcbc31a68078550fa",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-json": "*",
"ext-xml": "*",
"laminas/laminas-escaper": ">=2.6",
"php": "^7.1|^8.0"
"php": "^7.1|^8.0",
"phpoffice/math": "^0.1"
},
"require-dev": {
"dompdf/dompdf": "^2.0",
@@ -3996,8 +3986,9 @@
"friendsofphp/php-cs-fixer": "^3.3",
"mpdf/mpdf": "^8.1",
"phpmd/phpmd": "^2.13",
"phpstan/phpstan-phpunit": "@stable",
"phpunit/phpunit": ">=7.0",
"symfony/process": "^4.4",
"symfony/process": "^4.4 || ^5.0",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
@@ -4043,7 +4034,7 @@
}
],
"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": [
"ISO IEC 29500",
"OOXML",
@@ -4071,22 +4062,22 @@
],
"support": {
"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",
"version": "1.24.5",
"version": "1.25.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc"
"reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fedf211ff14ec8381c9bf5714e33a7a552dd1acc",
"reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240",
"reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240",
"shasum": ""
},
"require": {
@@ -4118,9 +4109,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"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",
@@ -10340,16 +10331,16 @@
},
{
"name": "zircote/swagger-php",
"version": "4.8.2",
"version": "4.8.3",
"source": {
"type": "git",
"url": "https://github.com/zircote/swagger-php.git",
"reference": "a70a5dc5db26183c86f589ff3d76fb11e141fc58"
"reference": "598958d8a83cfbd44ba36388b2f9ed69e8b86ed4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/a70a5dc5db26183c86f589ff3d76fb11e141fc58",
"reference": "a70a5dc5db26183c86f589ff3d76fb11e141fc58",
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/598958d8a83cfbd44ba36388b2f9ed69e8b86ed4",
"reference": "598958d8a83cfbd44ba36388b2f9ed69e8b86ed4",
"shasum": ""
},
"require": {
@@ -10415,9 +10406,9 @@
],
"support": {
"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": [
@@ -10861,21 +10852,22 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.45.0",
"version": "v3.46.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "c0daa33cb2533cd73f48dde1c70c2afa3e7953b5"
"reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/c0daa33cb2533cd73f48dde1c70c2afa3e7953b5",
"reference": "c0daa33cb2533cd73f48dde1c70c2afa3e7953b5",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/be6831c9af1740470d2a773119b9273f8ac1c3d2",
"reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2",
"shasum": ""
},
"require": {
"composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.3",
"ext-filter": "*",
"ext-json": "*",
"ext-tokenizer": "*",
"php": "^7.4 || ^8.0",
@@ -10939,7 +10931,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.45.0"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.46.0"
},
"funding": [
{
@@ -10947,7 +10939,7 @@
"type": "github"
}
],
"time": "2023-12-30T02:07:07+00:00"
"time": "2024-01-03T21:38:46+00:00"
},
{
"name": "masterminds/html5",
@@ -11018,25 +11010,27 @@
},
{
"name": "nikic/php-parser",
"version": "v4.18.0",
"version": "v5.0.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999"
"reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
"reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-json": "*",
"ext-tokenizer": "*",
"php": ">=7.0"
"php": ">=7.4"
},
"require-dev": {
"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/php-parse"
@@ -11044,7 +11038,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.9-dev"
"dev-master": "5.0-dev"
}
},
"autoload": {
@@ -11068,9 +11062,9 @@
],
"support": {
"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",
@@ -11185,16 +11179,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.50",
"version": "1.10.55",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4"
"reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4",
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9a88f9d18ddf4cf54c922fbeac16c4cb164c5949",
"reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949",
"shasum": ""
},
"require": {
@@ -11243,25 +11237,25 @@
"type": "tidelift"
}
],
"time": "2023-12-13T10:59:42+00:00"
"time": "2024-01-08T12:32:40+00:00"
},
{
"name": "phpstan/phpstan-doctrine",
"version": "1.3.53",
"version": "1.3.54",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-doctrine.git",
"reference": "85def57e5db6ac6c8a512200c0cfadf7b6621b10"
"reference": "f9555a2d54d685efd7003ae33c15e3d19d7d0c36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/85def57e5db6ac6c8a512200c0cfadf7b6621b10",
"reference": "85def57e5db6ac6c8a512200c0cfadf7b6621b10",
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/f9555a2d54d685efd7003ae33c15e3d19d7d0c36",
"reference": "f9555a2d54d685efd7003ae33c15e3d19d7d0c36",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.10.12"
"phpstan/phpstan": "^1.10.48"
},
"conflict": {
"doctrine/collections": "<1.0",
@@ -11278,7 +11272,7 @@
"doctrine/dbal": "^2.13.8 || ^3.3.3",
"doctrine/lexer": "^1.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",
"gedmo/doctrine-extensions": "^3.8",
"nesbot/carbon": "^2.49",
@@ -11311,9 +11305,9 @@
"description": "Doctrine extensions for PHPStan",
"support": {
"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",

View File

@@ -1,21 +1,23 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
parameters:
env(TRUSTED_PROXIES): ''
framework:
# Deprecations for 7.0
secret: '%env(APP_SECRET)%'
csrf_protection: true
annotations: false
handle_all_throwables: true
annotations:
enabled: false
serializer:
enable_attributes: true
# ---------------------------
trusted_proxies: '%env(string:TRUSTED_PROXIES)%'
exceptions:
App\Validator\ValidationFailedException:
log_level: debug
secret: '%env(APP_SECRET)%'
default_locale: en
csrf_protection: true
http_method_override: false
# 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%'
$kernelEnvironment: '%kernel.environment%'
# make classes in src/ available to be used as services
# creates one service per class whose id is the fully-qualified class name
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude:

View File

@@ -151,46 +151,6 @@ parameters:
count: 1
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\\.$#"
count: 1
@@ -1106,21 +1066,6 @@ parameters:
count: 1
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\\.$#"
count: 3
@@ -1291,46 +1236,6 @@ parameters:
count: 1
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\\.$#"
count: 1
@@ -3686,11 +3591,6 @@ parameters:
count: 1
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\\.$#"
count: 1
@@ -5746,11 +5646,6 @@ parameters:
count: 2
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\\.$#"
count: 1
@@ -5771,31 +5666,11 @@ parameters:
count: 1
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\\(\\)$#"
count: 2
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\\.$#"
count: 3

View File

@@ -16,7 +16,8 @@ use App\Model\ActivityBudgetStatisticModel;
use App\Model\ActivityStatistic;
use App\Repository\TimesheetRepository;
use App\Timesheet\DateTimeFactory;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -26,19 +27,15 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*/
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".
*
* @param Activity $activity
* @param DateTime|null $begin
* @param DateTime|null $end
* @return ActivityStatistic
* WARNING: this method does not respect the budget type.
* Your results will always be with the "full lifetime data" or the "selected date-range".
*/
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);
$event = new ActivityStatisticEvent($activity, array_pop($statistics), $begin, $end);
@@ -47,7 +44,7 @@ class ActivityStatisticService
return $event->getStatistic();
}
public function getBudgetStatisticModel(Activity $activity, DateTime $today): ActivityBudgetStatisticModel
public function getBudgetStatisticModel(Activity $activity, DateTimeInterface $today): ActivityBudgetStatisticModel
{
$stats = new ActivityBudgetStatisticModel($activity);
$stats->setStatisticTotal($this->getActivityStatistics($activity));
@@ -68,10 +65,9 @@ class ActivityStatisticService
/**
* @param Activity[] $activities
* @param DateTime $today
* @return ActivityBudgetStatisticModel[]
*/
public function getBudgetStatisticModelForActivities(array $activities, DateTime $today): array
public function getBudgetStatisticModelForActivities(array $activities, DateTimeInterface $today): array
{
$models = [];
$monthly = [];
@@ -121,11 +117,9 @@ class ActivityStatisticService
/**
* @param Activity[] $activities
* @param DateTime|null $begin
* @param DateTime|null $end
* @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 = [];
foreach ($activities as $activity) {
@@ -139,25 +133,25 @@ class ActivityStatisticService
if (null !== $result) {
foreach ($result as $resultRow) {
$statistic = $statistics[$resultRow['id']];
$statistic->setDuration($statistic->getDuration() + $resultRow['duration']);
$statistic->setRate($statistic->getRate() + $resultRow['rate']);
$statistic->setInternalRate($statistic->getInternalRate() + $resultRow['internalRate']);
$statistic->setCounter($statistic->getCounter() + $resultRow['counter']);
$statistic->addDuration((int) $resultRow['duration']);
$statistic->addRate((float) $resultRow['rate']);
$statistic->addInternalRate((float) $resultRow['internalRate']);
$statistic->addCounter((int) $resultRow['counter']);
if ($resultRow['billable']) {
$statistic->setDurationBillable($statistic->getDurationBillable() + $resultRow['duration']);
$statistic->setRateBillable($statistic->getRateBillable() + $resultRow['rate']);
$statistic->setInternalRateBillable($statistic->getInternalRateBillable() + $resultRow['internalRate']);
$statistic->setCounterBillable($statistic->getCounterBillable() + $resultRow['counter']);
$statistic->addDurationBillable((int) $resultRow['duration']);
$statistic->addRateBillable((float) $resultRow['rate']);
$statistic->addInternalRateBillable((float) $resultRow['internalRate']);
$statistic->addCounterBillable((int) $resultRow['counter']);
if ($resultRow['exported']) {
$statistic->setDurationBillableExported($statistic->getDurationBillableExported() + $resultRow['duration']);
$statistic->setRateBillableExported($statistic->getRateBillableExported() + $resultRow['rate']);
$statistic->addDurationBillableExported((int) $resultRow['duration']);
$statistic->addRateBillableExported((float) $resultRow['rate']);
}
}
if ($resultRow['exported']) {
$statistic->setDurationExported($statistic->getDurationExported() + $resultRow['duration']);
$statistic->setRateExported($statistic->getRateExported() + $resultRow['rate']);
$statistic->setInternalRateExported($statistic->getInternalRateExported() + $resultRow['internalRate']);
$statistic->setCounterExported($statistic->getCounterExported() + $resultRow['counter']);
$statistic->addDurationExported((int) $resultRow['duration']);
$statistic->addRateExported((float) $resultRow['rate']);
$statistic->addInternalRateExported((float) $resultRow['internalRate']);
$statistic->addCounterExported((int) $resultRow['counter']);
}
}
}
@@ -165,7 +159,7 @@ class ActivityStatisticService
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
@@ -187,14 +181,14 @@ class ActivityStatisticService
if ($begin !== null) {
$qb
->andWhere($qb->expr()->gte('t.begin', ':begin'))
->setParameter('begin', $begin, Types::DATETIME_MUTABLE)
->setParameter('begin', DateTimeImmutable::createFromInterface($begin), Types::DATETIME_IMMUTABLE)
;
}
if ($end !== null) {
$qb
->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;
}
}
if (!$start instanceof \DateTime) {
if (!$start instanceof \DateTimeInterface) {
$start = $dateFactory->getStartOfMonth();
}
$start->setTime(0, 0, 0);
$start = \DateTimeImmutable::createFromInterface($start);
$start = $start->setTime(0, 0, 0);
$end = $input->getOption('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->setTime(23, 59, 59);
$end = \DateTimeImmutable::createFromInterface($end);
$end = $end->setTime(23, 59, 59);
$directory = rtrim(sys_get_temp_dir(), '/') . '/';
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('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('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('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)
@@ -156,10 +156,12 @@ final class InvoiceCreateCommand extends Command
return Command::FAILURE;
}
}
if (!$start instanceof \DateTime) {
if (!$start instanceof \DateTimeInterface) {
$start = $dateFactory->getStartOfMonth();
}
$start->setTime(0, 0, 0);
$start = \DateTimeImmutable::createFromInterface($start);
$start = $start->setTime(0, 0, 0);
$end = $input->getOption('end');
if (!empty($end)) {
@@ -171,17 +173,18 @@ final class InvoiceCreateCommand extends Command
return Command::FAILURE;
}
}
if (!$end instanceof \DateTime) {
if (!$end instanceof \DateTimeInterface) {
$end = $dateFactory->getEndOfMonth();
}
$end->setTime(23, 59, 59);
$end = \DateTimeImmutable::createFromInterface($end);
$end = $end->setTime(23, 59, 59);
$searchTerm = null;
if (null !== $input->getOption('search')) {
$searchTerm = new SearchTerm($input->getOption('search'));
}
$markAsExported = false;
if ($input->getOption('preview') !== null) {
$this->previewUniqueFile = (bool) $input->getOption('preview-unique');
$this->previewDirectory = rtrim($input->getOption('preview'), '/') . '/';
@@ -191,7 +194,7 @@ final class InvoiceCreateCommand extends Command
return Command::FAILURE;
}
} 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 ===============

View File

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

View File

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

View File

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

View File

@@ -13,13 +13,27 @@ use App\Model\ActivityBudgetStatisticModel;
final class ActivityBudgetStatisticEvent
{
private readonly ?\DateTime $begin;
private readonly ?\DateTime $end;
/**
* @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

View File

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

View File

@@ -10,16 +10,32 @@
namespace App\Event;
use App\Model\ProjectBudgetStatisticModel;
use DateTime;
use DateTimeInterface;
final class ProjectBudgetStatisticEvent
{
private readonly ?DateTime $begin;
private readonly ?DateTime $end;
/**
* @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
@@ -45,12 +61,12 @@ final class ProjectBudgetStatisticEvent
return $this->models;
}
public function getBegin(): ?\DateTime
public function getBegin(): ?DateTime
{
return $this->begin;
}
public function getEnd(): ?\DateTime
public function getEnd(): ?DateTime
{
return $this->end;
}

View File

@@ -14,7 +14,12 @@ use App\Model\ProjectStatistic;
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);
}
@@ -24,12 +29,12 @@ final class ProjectStatisticEvent extends AbstractProjectEvent
return $this->statistic;
}
public function getBegin(): ?\DateTime
public function getBegin(): ?\DateTimeInterface
{
return $this->begin;
}
public function getEnd(): ?\DateTime
public function getEnd(): ?\DateTimeInterface
{
return $this->end;
}

View File

@@ -186,7 +186,7 @@ abstract class AbstractSpreadsheetRenderer
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), $excelDate);
// 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

View File

@@ -24,11 +24,11 @@ final class DateFormatter implements CellFormatterInterface
return;
}
if (!$value instanceof \DateTime) {
throw new \InvalidArgumentException('Unsupported value given, only DateTime is supported');
if (!$value instanceof \DateTimeInterface) {
throw new \InvalidArgumentException('Unsupported value given, only DateTimeInterface is supported');
}
$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;
}
if (!$value instanceof \DateTime) {
throw new \InvalidArgumentException('Unsupported value given, only DateTime is supported');
if (!$value instanceof \DateTimeInterface) {
throw new \InvalidArgumentException('Unsupported value given, only DateTimeInterface is supported');
}
$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;
}
if (!$value instanceof \DateTime) {
throw new \InvalidArgumentException('Unsupported value given, only DateTime is supported');
if (!$value instanceof \DateTimeInterface) {
throw new \InvalidArgumentException('Unsupported value given, only DateTimeInterface is supported');
}
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value));

View File

@@ -26,9 +26,9 @@ final class DateRange implements EquatableInterface
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) {
$this->begin->setTime(0, 0, 0);
}
@@ -41,9 +41,9 @@ final class DateRange implements EquatableInterface
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) {
$this->end->setTime(23, 59, 59);
}

View File

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

View File

@@ -48,7 +48,9 @@ final class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
'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));

View File

@@ -57,7 +57,12 @@ final class DocxRenderer extends AbstractRenderer implements RendererInterface
$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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -62,10 +62,10 @@ final class LocaleFormatExtensions extends AbstractExtension implements LocaleAw
return [
new TwigTest('weekend', [$this, 'isWeekend']),
new TwigTest('today', function ($dateTime): bool {
if (!$dateTime instanceof \DateTime) {
if (!$dateTime instanceof \DateTimeInterface) {
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');
}),

View File

@@ -58,8 +58,10 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
return;
}
$begin = $timesheet->getBegin();
// we can only work with stopped entries
if (null === $timesheet->getEnd() || null === $timesheet->getUser()) {
if ($begin === null || $timesheet->getEnd() === null || $timesheet->getUser() === null) {
return;
}
@@ -115,7 +117,7 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
if ($duration === $rawData['duration'] &&
$rate === $rawData['rate'] &&
$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->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());
$recordDate = $timesheet->getBegin();
$now = new DateTime('now', $begin->getTimezone());
$recordDate = $begin;
if (null !== ($activity = $timesheet->getActivity()) && $activity->hasBudgets()) {
$dateTime = $activity->isMonthlyBudget() ? $recordDate : $now;

View File

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

View File

@@ -443,13 +443,23 @@
"version": "v4.0.3"
},
"symfony/framework-bundle": {
"version": "3.3",
"version": "6.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "3.3",
"ref": "137a14eeb6b3f5370e7147af8aff6518504f50c7"
}
"branch": "main",
"version": "6.4",
"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": {
"version": "v4.4.5"

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,7 +41,7 @@ class DateFormatterTest extends AbstractFormatterTest
public function testFormattedValueWithInvalidValue()
{
$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();
$worksheet = $spreadsheet->getActiveSheet();

View File

@@ -40,7 +40,7 @@ class DateTimeFormatterTest extends AbstractFormatterTest
public function testFormattedValueWithInvalidValue()
{
$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();
$worksheet = $spreadsheet->getActiveSheet();

View File

@@ -40,7 +40,7 @@ class TimeFormatterTest extends AbstractFormatterTest
public function testFormattedValueWithInvalidValue()
{
$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();
$worksheet = $spreadsheet->getActiveSheet();

View File

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

View File

@@ -17,6 +17,7 @@ use App\Entity\Team;
use App\Form\Model\DateRange;
use App\Repository\Query\ActivityQuery;
use App\Repository\Query\BaseQuery;
use App\Repository\Query\DateRangeInterface;
use App\Repository\Query\ProjectQuery;
use App\Repository\Query\TimesheetQuery;
use App\Utils\SearchTerm;
@@ -334,7 +335,7 @@ class BaseQueryTest extends TestCase
$this->assertEquals([13, 27], $sut->getProjectIds());
}
protected function assertDateRangeTrait($sut): void
protected function assertDateRangeTrait(DateRangeInterface $sut): void
{
self::assertNull($sut->getBegin());
self::assertNull($sut->getEnd());
@@ -346,14 +347,15 @@ class BaseQueryTest extends TestCase
self::assertNull($sut->getBegin());
self::assertNull($sut->getEnd());
$begin = new \DateTime('2013-11-23 13:45:07');
$end = new \DateTime('2014-01-01 23:45:11');
$dateRange->setBegin($begin);
$dateRange->setEnd($end);
$dateRange->setBegin(new \DateTimeImmutable('2013-11-23 13:45:07'));
$dateRange->setEnd(new \DateTimeImmutable('2014-01-01 23:45:11'));
self::assertSame($begin, $sut->getDateRange()->getBegin());
self::assertSame($begin, $sut->getBegin());
self::assertSame($end, $sut->getDateRange()->getEnd());
self::assertSame($end, $sut->getEnd());
$begin1 = new \DateTimeImmutable('2013-11-23 00:00:00');
$end1 = new \DateTimeImmutable('2014-01-01 23:59:59');
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
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\\.$#"
count: 1
@@ -4087,11 +4082,6 @@ parameters:
count: 1
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\\.$#"
count: 1
@@ -4202,11 +4192,6 @@ parameters:
count: 5
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\\.$#"
count: 1
@@ -4222,11 +4207,6 @@ parameters:
count: 1
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\\.$#"
count: 1
@@ -5912,16 +5892,6 @@ parameters:
count: 1
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\\.$#"
count: 1
@@ -6907,11 +6877,6 @@ parameters:
count: 1
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\\.$#"
count: 1