From 087350ab72aa616f04aee50f2578aab8725f675a Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Sat, 25 Apr 2026 09:58:43 +0200 Subject: [PATCH] Release 2.56 (#5909) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/release-drafter.yml | 2 +- .github/workflows/testing.yaml | 2 +- README.md | 2 +- UPGRADING.md | 9 + composer.json | 4 +- composer.lock | 396 ++++++++++++++++-------- config/packages/nelmio_api_doc.yaml | 2 + kimai.sh | 180 +++++++++-- src/Constants.php | 4 +- src/Export/Package/SpoutSpreadsheet.php | 5 +- templates/macros/widgets.html.twig | 20 ++ templates/timesheet/index.html.twig | 6 +- tests/phpstan.neon | 5 - 14 files changed, 477 insertions(+), 162 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 93d715a0..ef7ea6a2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -55,8 +55,8 @@ body: options: - "8.5" - "8.4" - - "8.2" - "8.3" + - "8.2" - "8.1" - Unknown - Other (please mention below) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 76f8741e..509aa719 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -22,7 +22,7 @@ version-resolver: - 'translation' default: patch template: | - **Compatible with PHP 8.1 to 8.5** + **Compatible with PHP 8.2 to 8.5** $CHANGES diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 395d4ad6..b1fd1846 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -19,7 +19,7 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 strategy: matrix: - php: ['8.1', '8.2', '8.3', '8.4', '8.5'] + php: ['8.2', '8.3', '8.4', '8.5'] name: Integration (${{ matrix.php }}) steps: diff --git a/README.md b/README.md index 92a1db19..1625477f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ and so much more. ### Requirements -- PHP 8.1.3 minimum with support for 8.2, 8.3, 8.4, 8.5 +- PHP 8.2 minimum with support for 8.3, 8.4, 8.5 - MariaDB / MySQL: oldest maintained LTS release (MariaDB >= [10.6](https://endoflife.date/mariadb) or MySQL >= [8.4](https://endoflife.date/mysql)) or newer - A webserver and subdomain (subdirectory is not supported) - PHP extensions: `gd`, `intl`, `json`, `mbstring`, `pdo`, `tokenizer`, `xml`, `xsl`, `zip` diff --git a/UPGRADING.md b/UPGRADING.md index 67733df6..b8f70657 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -8,6 +8,15 @@ you can upgrade your Kimai installation to the latest stable release. Check below if there are more version specific steps required, which need to be executed after the normal update process. Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation. +## [2.56.0](https://github.com/kimai/kimai/releases/tag/2.56.0) + +The required minimum PHP version is now 8.2, read https://www.php.net/supported-versions.php + +If you are still using PHP 8.1, please be aware it is EOL and does not receive any security updates. + +If you have to upgrade to a newer version, do yourself the favor and upgrade directly to PHP 8.5. +The requirement for 8.2 is an intermediate solution for the near future, and the requirement will be raised to 8.5 rather sooner than later. + ## [2.0.30](https://github.com/kimai/kimai/releases/tag/2.0.30) The `DATABASE_URL` in your environment settings (e.g. [.env](https://github.com/kimai/kimai/issues/4246), [docker-compose.yaml](https://github.com/tobybatch/kimai2/issues/531) or webserver config) diff --git a/composer.json b/composer.json index 507a4a91..b47afb44 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "8.1.*||8.2.*||8.3.*||8.4.*||8.5.*", + "php": "8.2.*||8.3.*||8.4.*||8.5.*", "ext-gd": "*", "ext-intl": "*", "ext-json": "*", @@ -117,7 +117,7 @@ }, "optimize-autoloader": true, "platform": { - "php": "8.1.3" + "php": "8.2" }, "preferred-install": { "*": "dist" diff --git a/composer.lock b/composer.lock index cd78d404..c6dd3cef 100644 --- a/composer.lock +++ b/composer.lock @@ -4,25 +4,25 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "81e3162783fcc7f01c10601aeb09b48f", + "content-hash": "9408273b8cfa9c509c6e483245818e8e", "packages": [ { "name": "azuyalabs/yasumi", - "version": "2.9.0", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/azuyalabs/yasumi.git", - "reference": "36005a0796b64bc07257f39c11c42f147fe5320f" + "reference": "e24bc345d2dd0b9425ff19abf7f11574bbb1d7be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/36005a0796b64bc07257f39c11c42f147fe5320f", - "reference": "36005a0796b64bc07257f39c11c42f147fe5320f", + "url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/e24bc345d2dd0b9425ff19abf7f11574bbb1d7be", + "reference": "e24bc345d2dd0b9425ff19abf7f11574bbb1d7be", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { "azuyalabs/php-cs-fixer-config": "^0.3", @@ -30,7 +30,7 @@ "mikey179/vfsstream": "^1.6", "phpstan/phpstan": "^2.1", "phpstan/phpstan-deprecation-rules": "^2.0", - "phpunit/phpunit": "^8.5 || ^9.6" + "phpunit/phpunit": "^11.5" }, "suggest": { "ext-calendar": "For calculating the date of Easter" @@ -77,7 +77,7 @@ "type": "other" } ], - "time": "2025-12-29T16:13:54+00:00" + "time": "2026-03-30T13:35:38+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1663,22 +1663,22 @@ }, { "name": "easybill/zugferd-php", - "version": "v2.1.1", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/easybill/zugferd-php.git", - "reference": "376749138f5ecd216b73b5cdd5d4473f2a2d3252" + "reference": "fba2a504feb58ae2b3a19da5a0c051ce8fe13895" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easybill/zugferd-php/zipball/376749138f5ecd216b73b5cdd5d4473f2a2d3252", - "reference": "376749138f5ecd216b73b5cdd5d4473f2a2d3252", + "url": "https://api.github.com/repos/easybill/zugferd-php/zipball/fba2a504feb58ae2b3a19da5a0c051ce8fe13895", + "reference": "fba2a504feb58ae2b3a19da5a0c051ce8fe13895", "shasum": "" }, "require": { "ext-dom": "*", "jms/serializer": "^3.16", - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "ext-libxml": "*", @@ -1721,9 +1721,9 @@ ], "support": { "issues": "https://github.com/easybill/zugferd-php/issues", - "source": "https://github.com/easybill/zugferd-php/tree/v2.1.1" + "source": "https://github.com/easybill/zugferd-php/tree/v2.3.1" }, - "time": "2024-11-07T08:28:44+00:00" + "time": "2025-09-13T13:57:46+00:00" }, { "name": "egulias/email-validator", @@ -2947,31 +2947,32 @@ }, { "name": "maennchen/zipstream-php", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "6187e9cc4493da94b9b63eb2315821552015fca9" + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/6187e9cc4493da94b9b63eb2315821552015fca9", - "reference": "6187e9cc4493da94b9b63eb2315821552015fca9", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-zlib": "*", - "php-64bit": "^8.1" + "php-64bit": "^8.2" }, "require-dev": { + "brianium/paratest": "^7.7", "ext-zip": "*", "friendsofphp/php-cs-fixer": "^3.16", "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.5", - "phpunit/phpunit": "^10.0", - "vimeo/psalm": "^5.0" + "phpunit/phpunit": "^11.0", + "vimeo/psalm": "^6.0" }, "suggest": { "guzzlehttp/psr7": "^2.4", @@ -3012,7 +3013,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.1" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" }, "funding": [ { @@ -3020,7 +3021,7 @@ "type": "github" } ], - "time": "2024-10-10T12:33:01+00:00" + "time": "2025-01-27T12:07:53+00:00" }, { "name": "markbaker/complex", @@ -3468,42 +3469,45 @@ }, { "name": "nelmio/api-doc-bundle", - "version": "v5.7.1", + "version": "v5.10.0", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioApiDocBundle.git", - "reference": "772d26a9d940c9a8d173f75f0fdbaab47c1c87f2" + "reference": "212d490a85589ddce0f2dc683bef89bc62485340" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/772d26a9d940c9a8d173f75f0fdbaab47c1c87f2", - "reference": "772d26a9d940c9a8d173f75f0fdbaab47c1c87f2", + "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/212d490a85589ddce0f2dc683bef89bc62485340", + "reference": "212d490a85589ddce0f2dc683bef89bc62485340", "shasum": "" }, "require": { - "php": ">=8.1", - "phpdocumentor/reflection-docblock": "^5.0", - "phpdocumentor/type-resolver": "^1.8.2", + "php": ">=8.2", + "phpdocumentor/reflection-docblock": "^5.0 || ^6.0", + "phpdocumentor/type-resolver": "^1.8.2 || ^2.0", "psr/cache": "^1.0 || ^2.0 || ^3.0", "psr/container": "^1.0 || ^2.0", "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/config": "^6.4 || ^7.1", - "symfony/console": "^6.4 || ^7.1", - "symfony/dependency-injection": "^6.4 || ^7.1", + "symfony/config": "^6.4 || ^7.2 || ^8.0", + "symfony/console": "^6.4 || ^7.2 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.2 || ^8.0", "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/framework-bundle": "^6.4 || ^7.1", - "symfony/http-foundation": "^6.4 || ^7.1", - "symfony/http-kernel": "^6.4 || ^7.1", - "symfony/options-resolver": "^6.4 || ^7.1", - "symfony/property-info": "^6.4 || ^7.1", - "symfony/routing": "^6.4 || ^7.1", - "zircote/swagger-php": "^4.11.1 || ^5.0" + "symfony/framework-bundle": "^6.4 || ^7.2 || ^8.0", + "symfony/http-foundation": "^6.4 || ^7.2 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.2 || ^8.0", + "symfony/options-resolver": "^6.4 || ^7.2 || ^8.0", + "symfony/property-info": "^6.4 || ^7.2 || ^8.0", + "symfony/routing": "^6.4 || ^7.2 || ^8.0", + "symfony/type-info": "^7.2 || ^8.0", + "zircote/swagger-php": "^4.11.1 || ^5.0 || ^6.0" }, "conflict": { + "symfony/property-info": "6.4.32 || 7.3.10 || 7.4.4 || 8.0.4", "zircote/swagger-php": "4.8.7 || 5.5.0" }, "require-dev": { - "api-platform/core": "^3.2", + "api-platform/core": "^3.2 || ^4.0", + "doctrine/inflector": "^2.0", "friendsofphp/php-cs-fixer": "^3.52", "friendsofsymfony/rest-bundle": "^3.2.0", "jms/serializer": "^3.32", @@ -3513,25 +3517,22 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpstan/phpstan-symfony": "^2.0", "phpunit/phpunit": "^10.5", - "symfony/asset": "^6.4 || ^7.1", - "symfony/browser-kit": "^6.4 || ^7.1", - "symfony/cache": "^6.4 || ^7.1", - "symfony/dom-crawler": "^6.4 || ^7.1", - "symfony/expression-language": "^6.4 || ^7.1", - "symfony/finder": "^6.4 || ^7.1", - "symfony/form": "^6.4 || ^7.1", - "symfony/phpunit-bridge": "^6.4 || ^7.1", - "symfony/property-access": "^6.4 || ^7.1", - "symfony/security-csrf": "^6.4 || ^7.1", - "symfony/security-http": "^6.4 || ^7.1", - "symfony/serializer": "^6.4 || ^7.1", - "symfony/stopwatch": "^6.4 || ^7.1", - "symfony/templating": "^6.4 || ^7.1", - "symfony/translation": "^6.4 || ^7.1", - "symfony/twig-bundle": "^6.4 || ^7.1", - "symfony/uid": "^6.4 || ^7.1", - "symfony/validator": "^6.4 || ^7.1", - "willdurand/hateoas-bundle": "^2.7", + "symfony/asset": "^6.4 || ^7.2 || ^8.0", + "symfony/browser-kit": "^6.4 || ^7.2 || ^8.0", + "symfony/cache": "^6.4 || ^7.2 || ^8.0", + "symfony/dom-crawler": "^6.4 || ^7.2 || ^8.0", + "symfony/expression-language": "^6.4 || ^7.2 || ^8.0", + "symfony/finder": "^6.4 || ^7.2 || ^8.0", + "symfony/form": "^6.4 || ^7.2 || ^8.0", + "symfony/phpunit-bridge": "^6.4 || ^7.2 || ^8.0", + "symfony/security-csrf": "^6.4 || ^7.2 || ^8.0", + "symfony/security-http": "^6.4 || ^7.2 || ^8.0", + "symfony/serializer": "^6.4 || ^7.2 || ^8.0", + "symfony/translation": "^6.4 || ^7.2 || ^8.0", + "symfony/twig-bundle": "^6.4 || ^7.2 || ^8.0", + "symfony/uid": "^6.4 || ^7.2 || ^8.0", + "symfony/validator": "^6.4 || ^7.2 || ^8.0", + "willdurand/hateoas-bundle": "^2.7 || ^3.0", "willdurand/negotiation": "^3.0" }, "suggest": { @@ -3579,7 +3580,7 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioApiDocBundle/issues", - "source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v5.7.1" + "source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v5.10.0" }, "funding": [ { @@ -3587,7 +3588,7 @@ "type": "github" } ], - "time": "2025-11-13T10:32:15+00:00" + "time": "2026-04-10T14:26:01+00:00" }, { "name": "nelmio/cors-bundle", @@ -3778,16 +3779,16 @@ }, { "name": "openspout/openspout", - "version": "v4.25.0", + "version": "v4.28.5", "source": { "type": "git", "url": "https://github.com/openspout/openspout.git", - "reference": "519affe730d92e1598720a6467227fc28550f0e6" + "reference": "ab05a09fe6fce57c90338f83280648a9786ce36b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/openspout/openspout/zipball/519affe730d92e1598720a6467227fc28550f0e6", - "reference": "519affe730d92e1598720a6467227fc28550f0e6", + "url": "https://api.github.com/repos/openspout/openspout/zipball/ab05a09fe6fce57c90338f83280648a9786ce36b", + "reference": "ab05a09fe6fce57c90338f83280648a9786ce36b", "shasum": "" }, "require": { @@ -3797,17 +3798,17 @@ "ext-libxml": "*", "ext-xmlreader": "*", "ext-zip": "*", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { "ext-zlib": "*", - "friendsofphp/php-cs-fixer": "^3.64.0", - "infection/infection": "^0.29.6", - "phpbench/phpbench": "^1.3.1", - "phpstan/phpstan": "^1.12.4", - "phpstan/phpstan-phpunit": "^1.4.0", - "phpstan/phpstan-strict-rules": "^1.6.1", - "phpunit/phpunit": "^10.5.20 || ^11.3.6" + "friendsofphp/php-cs-fixer": "^3.68.3", + "infection/infection": "^0.29.10", + "phpbench/phpbench": "^1.4.0", + "phpstan/phpstan": "^2.1.2", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^11.5.4" }, "suggest": { "ext-iconv": "To handle non UTF-8 CSV files (if \"php-mbstring\" is not already installed or is too limited)", @@ -3855,7 +3856,7 @@ ], "support": { "issues": "https://github.com/openspout/openspout/issues", - "source": "https://github.com/openspout/openspout/tree/v4.25.0" + "source": "https://github.com/openspout/openspout/tree/v4.28.5" }, "funding": [ { @@ -3867,7 +3868,7 @@ "type": "github" } ], - "time": "2024-09-24T09:03:42+00:00" + "time": "2025-01-30T13:51:11+00:00" }, { "name": "pagerfanta/pagerfanta", @@ -4316,16 +4317,16 @@ }, { "name": "phpoffice/phpspreadsheet", - "version": "2.4.4", + "version": "2.4.5", "source": { "type": "git", "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "78bf6f0b4945ab31f1935741324ef3f0bf59a6fe" + "reference": "ec7815be350e03df90f3e2ace92653fa6cb4327c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/78bf6f0b4945ab31f1935741324ef3f0bf59a6fe", - "reference": "78bf6f0b4945ab31f1935741324ef3f0bf59a6fe", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/ec7815be350e03df90f3e2ace92653fa6cb4327c", + "reference": "ec7815be350e03df90f3e2ace92653fa6cb4327c", "shasum": "" }, "require": { @@ -4416,9 +4417,9 @@ ], "support": { "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/2.4.4" + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/2.4.5" }, - "time": "2026-04-10T03:20:38+00:00" + "time": "2026-04-19T05:48:49+00:00" }, { "name": "phpoffice/phpword", @@ -4929,6 +4930,68 @@ }, "time": "2021-10-29T13:26:27+00:00" }, + { + "name": "radebatz/type-info-extras", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DerManoMann/type-info-extras.git", + "reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DerManoMann/type-info-extras/zipball/95a524a74a61648b44e355cb33d38db4b17ef5ce", + "reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "phpstan/phpdoc-parser": "^2.0", + "symfony/type-info": "^7.3.8 || ^7.4.1 || ^8.0 || ^8.1-@dev" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.70", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Radebatz\\TypeInfoExtras\\": "src" + }, + "exclude-from-classmap": [ + "/tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Martin Rademacher", + "email": "mano@radebatz.org" + } + ], + "description": "Extras for symfony/type-info", + "homepage": "http://radebatz.net/mano/", + "keywords": [ + "component", + "symfony", + "type-info", + "types" + ], + "support": { + "issues": "https://github.com/DerManoMann/type-info-extras/issues", + "source": "https://github.com/DerManoMann/type-info-extras/tree/1.0.7" + }, + "time": "2026-03-06T22:40:29+00:00" + }, { "name": "robrichards/xmlseclibs", "version": "3.1.5", @@ -5259,16 +5322,16 @@ }, { "name": "smalot/pdfparser", - "version": "v2.12.4", + "version": "v2.12.5", "source": { "type": "git", "url": "https://github.com/smalot/pdfparser.git", - "reference": "028d7cc0ceff323bc001d763caa2bbdf611866c4" + "reference": "2cfa0d92bd557875c9f52a75fde0e8392302a354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/smalot/pdfparser/zipball/028d7cc0ceff323bc001d763caa2bbdf611866c4", - "reference": "028d7cc0ceff323bc001d763caa2bbdf611866c4", + "url": "https://api.github.com/repos/smalot/pdfparser/zipball/2cfa0d92bd557875c9f52a75fde0e8392302a354", + "reference": "2cfa0d92bd557875c9f52a75fde0e8392302a354", "shasum": "" }, "require": { @@ -5304,9 +5367,9 @@ ], "support": { "issues": "https://github.com/smalot/pdfparser/issues", - "source": "https://github.com/smalot/pdfparser/tree/v2.12.4" + "source": "https://github.com/smalot/pdfparser/tree/v2.12.5" }, - "time": "2026-03-10T15:39:47+00:00" + "time": "2026-04-17T11:37:58+00:00" }, { "name": "spomky-labs/otphp", @@ -9747,6 +9810,89 @@ ], "time": "2026-01-05T12:44:39+00:00" }, + { + "name": "symfony/type-info", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "6bf34da885ff5143a3dfd8f1b863bb8ab95f50bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/6bf34da885ff5143a3dfd8f1b863bb8ab95f50bd", + "reference": "6bf34da885ff5143a3dfd8f1b863bb8ab95f50bd", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-24T13:12:05+00:00" + }, { "name": "symfony/validator", "version": "v6.4.36", @@ -10650,23 +10796,23 @@ }, { "name": "webmozart/assert", - "version": "1.12.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4", "shasum": "" }, "require": { "ext-ctype": "*", "ext-date": "*", "ext-filter": "*", - "php": "^7.2 || ^8.0" + "php": "^8.2" }, "suggest": { "ext-intl": "", @@ -10676,7 +10822,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10-dev" + "dev-feature/2-0": "2.0-dev" } }, "autoload": { @@ -10692,6 +10838,10 @@ { "name": "Bernhard Schussek", "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" } ], "description": "Assertions to validate method input/output with nice error messages.", @@ -10702,9 +10852,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.12.1" + "source": "https://github.com/webmozarts/assert/tree/2.3.0" }, - "time": "2025-10-29T15:56:20+00:00" + "time": "2026-04-11T10:33:05+00:00" }, { "name": "willdurand/jsonp-callback-validator", @@ -10807,24 +10957,25 @@ }, { "name": "zircote/swagger-php", - "version": "5.8.3", + "version": "6.0.6", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "098223019f764a16715f64089a58606096719c98" + "reference": "9447c1f45b5ae93185caea9a0c8e298399188a25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/098223019f764a16715f64089a58606096719c98", - "reference": "098223019f764a16715f64089a58606096719c98", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/9447c1f45b5ae93185caea9a0c8e298399188a25", + "reference": "9447c1f45b5ae93185caea9a0c8e298399188a25", "shasum": "" }, "require": { "ext-json": "*", "nikic/php-parser": "^4.19 || ^5.0", - "php": ">=7.4", + "php": ">=8.2", "phpstan/phpdoc-parser": "^2.0", "psr/log": "^1.1 || ^2.0 || ^3.0", + "radebatz/type-info-extras": "^1.0.2", "symfony/deprecation-contracts": "^2 || ^3", "symfony/finder": "^5.0 || ^6.0 || ^7.0 || ^8.0", "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" @@ -10836,14 +10987,9 @@ "composer/package-versions-deprecated": "^1.11", "doctrine/annotations": "^2.0", "friendsofphp/php-cs-fixer": "^3.62.0", - "phpstan/phpstan": "^1.6 || ^2.0", - "phpunit/phpunit": "^9.0", - "rector/rector": "^1.0 || ^2.3.1", - "vimeo/psalm": "^4.30 || ^5.0" - }, - "suggest": { - "doctrine/annotations": "^2.0", - "radebatz/type-info-extras": "^1.0.2" + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11.5", + "rector/rector": "^2.3.1" }, "bin": [ "bin/openapi" @@ -10851,7 +10997,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-master": "6.x-dev" } }, "autoload": { @@ -10889,7 +11035,7 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/5.8.3" + "source": "https://github.com/zircote/swagger-php/tree/6.0.6" }, "funding": [ { @@ -10897,7 +11043,7 @@ "type": "github" } ], - "time": "2026-03-02T00:47:18+00:00" + "time": "2026-02-28T06:42:58+00:00" } ], "packages-dev": [ @@ -11804,11 +11950,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.48", + "version": "2.1.51", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/231397213efb7c0a066ee024b5c3c87f2d3adfa0", - "reference": "231397213efb7c0a066ee024b5c3c87f2d3adfa0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc3b523c45e714c70de2ac5113b958223b55dc59", + "reference": "dc3b523c45e714c70de2ac5113b958223b55dc59", "shasum": "" }, "require": { @@ -11853,7 +11999,7 @@ "type": "github" } ], - "time": "2026-04-15T20:24:19+00:00" + "time": "2026-04-21T18:22:01+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -11907,16 +12053,16 @@ }, { "name": "phpstan/phpstan-doctrine", - "version": "2.0.20", + "version": "2.0.21", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "72f4f7a02d6c98d9101e8616e0488bc0a785196d" + "reference": "81dac0ee4363c2359128aec844df31efb215dddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/72f4f7a02d6c98d9101e8616e0488bc0a785196d", - "reference": "72f4f7a02d6c98d9101e8616e0488bc0a785196d", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/81dac0ee4363c2359128aec844df31efb215dddc", + "reference": "81dac0ee4363c2359128aec844df31efb215dddc", "shasum": "" }, "require": { @@ -11977,9 +12123,9 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.20" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.21" }, - "time": "2026-03-13T13:44:51+00:00" + "time": "2026-04-17T13:00:39+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -14524,7 +14670,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "8.1.*||8.2.*||8.3.*||8.4.*||8.5.*", + "php": "8.2.*||8.3.*||8.4.*||8.5.*", "ext-gd": "*", "ext-intl": "*", "ext-json": "*", @@ -14540,7 +14686,7 @@ "ext-simplexml": "*" }, "platform-overrides": { - "php": "8.1.3" + "php": "8.2" }, "plugin-api-version": "2.9.0" } diff --git a/config/packages/nelmio_api_doc.yaml b/config/packages/nelmio_api_doc.yaml index 6046dc26..6fa7f441 100644 --- a/config/packages/nelmio_api_doc.yaml +++ b/config/packages/nelmio_api_doc.yaml @@ -67,5 +67,7 @@ nelmio_api_doc: swagger_ui_config: [] # https://redocly.com/docs/redoc/config/ redocly_config: [] + # https://scalar.com/products/api-references/configuration + scalar_config: [] # https://docs.stoplight.io/docs/elements/b074dc47b2826-elements-configuration-options stoplight_config: { basePath: '/api/doc', router: 'memory', logo: '/touch-icon-192x192.png', hideInternal: true } diff --git a/kimai.sh b/kimai.sh index f018edeb..64a8d236 100755 --- a/kimai.sh +++ b/kimai.sh @@ -6,13 +6,106 @@ # To improve this script across platforms I need your feedback! # -------------------------------------------------------------------------------- +function verbose() { + if [[ "${IS_VERBOSE}" == "1" ]]; then + echo "$@" + fi +} + +function flush_cache() { + rm -rf var/cache/* 2>&1 +} + +function reload_cache() { + flush_cache + bin/console cache:warmup +} + +function composer_is_phar() { + [[ "${KIMAI_COMPOSER}" == *.phar ]] +} + +function find_command_path() { + which "$1" 2>/dev/null +} + +function resolve_executable() { + local executable="$1" + + if [[ -z "${executable}" || "${executable}" == */* || "${executable}" == *.phar ]]; then + echo "${executable}" + return + fi + + local resolved + resolved="$(find_command_path "${executable}")" + if [[ -n "${resolved}" ]]; then + echo "${resolved}" + else + echo "${executable}" + fi +} + +function composer_exists() { + if composer_is_phar; then + [[ -f "${KIMAI_COMPOSER}" ]] + elif [[ "${KIMAI_COMPOSER}" == */* ]]; then + [[ -x "${KIMAI_COMPOSER}" ]] + else + command -v "${KIMAI_COMPOSER}" >/dev/null 2>&1 + fi +} + +function run_composer() { + #export COMPOSER_ALLOW_SUPERUSER=1 + if composer_is_phar; then + "$KIMAI_PHP" "$KIMAI_COMPOSER" "$@" + else + "$KIMAI_COMPOSER" "$@" + fi +} + +function confirm_update() { + local version="$1" + local answer + + echo "About to update Kimai to version ${version}." + read -r -p "Continue? [y/N] " answer + if [[ ! "${answer}" =~ ^[Yy]([Ee][Ss])?$ ]]; then + echo "Update cancelled." + exit 1 + fi +} + function update_kimai() { - if [[ "$1" =~ ^([0-9]+\.){2,3}[0-9]+$ ]]; then + if [[ "$1" == "latest" ]]; then + if ! command -v sort >/dev/null 2>&1 || ! command -v tail >/dev/null 2>&1; then + echo "we could not detect the latest kimai version due to missing commands: sort, tail" + exit 1 + fi + + git fetch --tags + export VERSION="$(git tag --list | sort -V | tail -n 1)" + if [ -z "$VERSION" ]; then + echo "Failed loading Kimai version" + exit 1 + fi + elif [[ "$1" =~ ^([0-9]+\.){2,3}[0-9]+$ ]]; then export VERSION=$1 else echo "You need to supply a full Kimai version like: \"2.24.0\"" exit 1 fi + + git fetch --tags + + if ! git rev-parse --verify --quiet "refs/tags/$VERSION" >/dev/null; then + echo "Requested Kimai version does not exist: $VERSION" + exit 1 + fi + + confirm_update "$VERSION" + git checkout -- composer.json git checkout -- composer.lock git checkout -- symfony.lock @@ -23,10 +116,10 @@ function update_kimai() { fi rm -rf var/sessions/ 2>&1 - rm -rf var/cache/* 2>&1 - git fetch --tags + flush_cache + git checkout "$VERSION" - $KIMAI_PHP "$KIMAI_COMPOSER" install --no-dev --optimize-autoloader + run_composer install --no-dev --optimize-autoloader || exit 1 $KIMAI_PHP bin/console kimai:install @@ -39,11 +132,22 @@ function update_kimai() { function install_plugins() { # detect if there are additional plugins that we need to install - packages="$($PHP bin/console kimai:plugin --composer)" - export PACKAGES=$packages - if [ -n "$PACKAGES" ]; then - $KIMAI_PHP "$KIMAI_COMPOSER" require "$PACKAGES" - $KIMAI_PHP bin/console kimai:plugins --install + local packages_output + local -a packages + + if ! packages_output="$($KIMAI_PHP bin/console kimai:plugin --composer)"; then + echo "Failed loading plugin list from kimai:plugin --composer" + exit 1 + fi + + read -r -a packages <<< "$packages_output" + + if [[ ${#packages[@]} -gt 0 ]]; then + verbose "Installing Composer plugins: ${packages[*]}" + run_composer require "${packages[@]} --update-no-dev --optimize-autoloader" || exit 1 + $KIMAI_PHP bin/console kimai:plugins --install || exit 1 + else + verbose "No Composer plugins detected." fi } @@ -53,6 +157,21 @@ function set_permission() { chmod -R g+rw var/ } +# --------------------------------------------------------- +# runtime logic below + +export IS_VERBOSE=0 + +POSITIONAL_ARGS=() +for arg in "$@"; do + if [[ "${arg}" == "-v" ]]; then + export IS_VERBOSE=1 + else + POSITIONAL_ARGS+=("${arg}") + fi +done +set -- "${POSITIONAL_ARGS[@]}" + if [[ -z "${KIMAI_USER}" ]]; then export KIMAI_USER="" fi @@ -62,19 +181,36 @@ if [[ -z "${KIMAI_GROUP}" ]]; then fi if [[ -z "${KIMAI_PHP}" ]]; then - export KIMAI_PHP="php" + php_path="$(find_command_path php)" + if [[ -n "${php_path}" ]]; then + export KIMAI_PHP="${php_path}" + else + export KIMAI_PHP="php" + fi +else + export KIMAI_PHP="$(resolve_executable "${KIMAI_PHP}")" fi if [[ -z "${KIMAI_COMPOSER}" ]]; then - export KIMAI_COMPOSER="composer" + composer_path="$(find_command_path composer)" + if [[ -n "${composer_path}" ]]; then + export KIMAI_COMPOSER="${composer_path}" + else + export KIMAI_COMPOSER="composer" + fi +else + export KIMAI_COMPOSER="$(resolve_executable "${KIMAI_COMPOSER}")" fi cd "$(dirname "$0")" || { echo "Cannot change working directory."; exit 1; } # we need a few commands installed in order for this script to complete -command -v $KIMAI_COMPOSER >/dev/null 2>&1 || { echo >&2 "Update requires 'composer' but it's not installed."; exit 1; } +composer_exists || { echo >&2 "Update requires 'composer' but it's not installed or not executable."; exit 1; } command -v git >/dev/null 2>&1 || { echo >&2 "Update requires 'git' but it's not installed."; exit 1; } -command -v $KIMAI_PHP >/dev/null 2>&1 || { echo >&2 "Update requires 'php' but it's not installed."; exit 1; } +command -v "$KIMAI_PHP" >/dev/null 2>&1 || { echo >&2 "Update requires 'php' but it's not installed."; exit 1; } + +verbose "Using PHP: $KIMAI_PHP" +verbose "Using Composer: $KIMAI_COMPOSER" if [[ -n $1 ]]; then if [ "$1" == 'update' ]; then @@ -83,9 +219,12 @@ if [[ -n $1 ]]; then elif [ "$1" == 'permission' ]; then set_permission exit - elif [ "$1" == 'plugins' ]; then + elif [ "$1" == 'plugins' ] || [ "$1" == 'plugin' ]; then install_plugins exit + elif [ "$1" == 'cache' ]; then + reload_cache + exit else echo "" echo ">> Unknown command: $1" @@ -95,9 +234,13 @@ fi echo "" echo "This script has the following sub-commands:" echo "" -echo "$0 update - Install Kimai version " +echo "$0 update - Update Kimai to the latest version" +echo "$0 update - Update Kimai to the given version " echo "$0 permission - Fix file permissions" -echo "$0 plugins - Install plugins from var/packages/*.zip" +echo "$0 plugin[s] - Install available plugins from var/packages/*.zip" +echo "$0 cache - Clear application cache" +echo "" +echo "Append -v anywhere in the command to enable verbose output" echo "" echo "Use the following environment variables to customize the runtime:" echo "" @@ -109,8 +252,9 @@ echo "KIMAI_NO_PERMS - Skip changing permissions" echo "" echo "Examples:" echo "" -echo "$0 2.24.0" -echo "KIMAI_PHP=/usr/bin/php8.3 $0 2.24.0" +echo "$0 -v update" +echo "$0 update 2.54.0 -v" +echo "KIMAI_PHP=/usr/bin/php8.3 $0 2.54.0" echo "KIMAI_PHP=/usr/bin/php8.3 KIMAI_COMPOSER=/tmp/composer.phar $0 2.24.0" echo "KIMAI_PHP=php8.3 KIMAI_GROUP=httpd $0 2.24.0" echo "KIMAI_NO_PERMS=1 KIMAI_PHP=/usr/bin/php8.3 $0 2.24.0" diff --git a/src/Constants.php b/src/Constants.php index e593a8d4..5161dae9 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -17,11 +17,11 @@ final class Constants /** * The current release version */ - public const VERSION = '2.55.0'; + public const VERSION = '2.56.0'; /** * The current release: major * 10000 + minor * 100 + patch */ - public const VERSION_ID = 25500; + public const VERSION_ID = 25600; /** * The software name */ diff --git a/src/Export/Package/SpoutSpreadsheet.php b/src/Export/Package/SpoutSpreadsheet.php index 5ee11953..7f3e8956 100644 --- a/src/Export/Package/SpoutSpreadsheet.php +++ b/src/Export/Package/SpoutSpreadsheet.php @@ -36,7 +36,6 @@ class SpoutSpreadsheet implements SpreadsheetPackage private readonly ?string $locale = null, ) { - $this->writer->setCreator(Constants::SOFTWARE); } /** @@ -138,6 +137,10 @@ class SpoutSpreadsheet implements SpreadsheetPackage public function save(): void { + if ($this->writer instanceof XLSXWriter) { + $this->writer->setCreator(Constants::SOFTWARE); + } + $this->writer->close(); } } diff --git a/templates/macros/widgets.html.twig b/templates/macros/widgets.html.twig index 7d3fc742..36b372ae 100644 --- a/templates/macros/widgets.html.twig +++ b/templates/macros/widgets.html.twig @@ -1,3 +1,23 @@ +{%- macro duration(timesheet, options) -%} + + {{- timesheet.begin is null ? 0|duration : timesheet|duration -}} + +{% endmacro %} + +{%- macro duration_seconds(seconds, options) -%} + + {{- seconds|duration -}} + +{% endmacro %} + {%- macro search_filter(route, options) -%} {% set opts = {'performSearch': 1} %} {% if options.begin is defined and options.end is defined %} diff --git a/templates/timesheet/index.html.twig b/templates/timesheet/index.html.twig index d928f581..b800a71f 100644 --- a/templates/timesheet/index.html.twig +++ b/templates/timesheet/index.html.twig @@ -105,11 +105,7 @@ ‐ {% endif %} {% elseif column == 'duration' %} - {% if entry.end %} - {{ entry.duration|duration }} - {% else %} - {{ entry|duration }} - {% endif %} + {{ widgets.duration(entry) }} {% elseif column == 'break' %} {{ entry.break|duration }} {% elseif column == 'hourlyRate' %} diff --git a/tests/phpstan.neon b/tests/phpstan.neon index 93175fee..e3f18926 100644 --- a/tests/phpstan.neon +++ b/tests/phpstan.neon @@ -2506,11 +2506,6 @@ parameters: count: 1 path: Widget/Type/MoreTest.php - - - message: "#^Cannot access offset 'financialBegin' on mixed\\.$#" - count: 2 - path: Widget/Type/PaginatedWorkingTimeChartTest.php - - message: "#^Parameter \\#2 \\$array of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertArrayHasKey\\(\\) expects array\\|ArrayAccess, mixed given\\.$#" count: 2