mirror of
https://github.com/kevinpapst/kimai2.git
synced 2025-04-11 00:00:26 +00:00
release 2.0 beta (#3722)
* remove twitter link * remove WIP file * adjust release draft message * reset code coverage threshold back to 0.5 * changed wordings * re-activate wizard for fixture accounts * fix repo url and license * license identifier * bump version * moved Kimai 1 import command from core to plugin * do not traverse into invoice template subdirectories (#3735) * fix branch alias * composer update * switch language on wizard select * new twig function to create qr code * fix daily stats in timesheet listing * improved html invoice templates
This commit is contained in:
parent
cbd65f1f1d
commit
8069e332fe
38 changed files with 386 additions and 2850 deletions
.codecov.yml
.github
README.mdTODOUPGRADING.mdassets/sass
composer.jsoncomposer.lockphpstan.neonpublic/build
src
API/Authentication
Command
Controller
DataFixtures
Entity
Repository
Security
Twig
templates
tests
|
@ -9,7 +9,7 @@ coverage:
|
|||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 2.5%
|
||||
threshold: 0.5%
|
||||
patch: off
|
||||
changes: no
|
||||
|
||||
|
|
6
.github/release-drafter.yml
vendored
6
.github/release-drafter.yml
vendored
|
@ -35,11 +35,7 @@ version-resolver:
|
|||
template: |
|
||||
[Upgrade Kimai](https://www.kimai.org/documentation/updates.html) - [Install Kimai](https://www.kimai.org/documentation/installation.html) - [Docker](https://tobybatch.github.io/kimai2/)
|
||||
|
||||
**PHP Version compatibility:**
|
||||
- PHP 7.3 and PHP 7.4 are [end-of-life](https://www.php.net/supported-versions.php)
|
||||
- PHP 8.0 and PHP 8.1 are supported
|
||||
|
||||
A feature freeze is in place and only bugfix releases will be published for 1.30.x. Next major release will be in the 2.x series (PHP >= 8.1, Symfony 6, Tabler UI, see #2902).
|
||||
**Compatible with PHP 8.1 and 8.2**
|
||||
|
||||
$CHANGES
|
||||
|
||||
|
|
4
.github/workflows/lock.yaml
vendored
4
.github/workflows/lock.yaml
vendored
|
@ -16,10 +16,10 @@ jobs:
|
|||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3
|
||||
- uses: dessant/lock-threads@v4
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '180'
|
||||
issue-inactive-days: '90'
|
||||
exclude-issue-created-before: ''
|
||||
exclude-issue-created-after: ''
|
||||
exclude-issue-created-between: ''
|
||||
|
|
17
README.md
17
README.md
|
@ -5,9 +5,8 @@
|
|||
<p align="center">
|
||||
<a href="https://github.com/kimai/kimai/actions"><img alt="CI Status" src="https://github.com/kimai/kimai/workflows/CI/badge.svg"></a>
|
||||
<a href="https://codecov.io/gh/kimai/kimai"><img alt="Code Coverage" src="https://codecov.io/gh/kimai/kimai/branch/main/graph/badge.svg"></a>
|
||||
<a href="https://packagist.org/packages/kevinpapst/kimai2"><img alt="Latest stable version" src="https://poser.pugx.org/kimai/kimai/v/stable"></a>
|
||||
<a href="https://packagist.org/packages/kevinpapst/kimai2"><img alt="License" src="https://poser.pugx.org/kimai/kimai/license"></a>
|
||||
<a href="https://twitter.com/kimai_org" rel="me"><img alt="Twitter" src="https://img.shields.io/badge/follow-%40kimai__org-00acee"></a>
|
||||
<a href="https://packagist.org/packages/kimai/kimai"><img alt="Latest stable version" src="https://poser.pugx.org/kimai/kimai/v/stable"></a>
|
||||
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html"><img alt="License" src="https://poser.pugx.org/kimai/kimai/license"></a>
|
||||
<a href="https://phpc.social/@kimai" rel="me"><img alt="Mastodon" src="https://img.shields.io/badge/toot-%40kimai-8c8dff"></a>
|
||||
</p>
|
||||
|
||||
|
@ -75,11 +74,11 @@ The best way to start is to [open a new issue](https://github.com/kimai/kimai/is
|
|||
|
||||
In case you want to contribute, but you wouldn't know how, here are some suggestions:
|
||||
|
||||
- Spread the word: More user means more people testing and contributing to Kimai - which in turn means better stability and more and better features. Please vote for Kimai on platforms like Slant, Product Hunt, Softpedia or AlternativeTo, you can tweet about it, share it on LinkedIn, reddit or any of your favorite social media platforms. Every bit helps!
|
||||
- Answer questions: You know the answer to another user's problem? Share your knowledge!
|
||||
- Make a feature request: Something can be done better? Something essential missing? Let us know!
|
||||
- Report bugs
|
||||
- You don't have to be programmer to help. The documentation and translation could use some love as well.
|
||||
- Sponsor the project, free software still costs money
|
||||
- Spread the word: More user means more people testing and contributing to Kimai - which in turn means better stability and more and better features. Please vote for Kimai on platforms like Slant, Product Hunt, Softpedia or AlternativeTo, you can toot or tweet about it, share it on LinkedIn, reddit or any of your favorite social media platforms. Every bit helps!
|
||||
- Answer questions: You know the answer to another user's problem? Share your knowledge.
|
||||
- Something can be done better? An essential feature is missing? Create a feature request.
|
||||
- Report bugs: that shouldn't happen too often.
|
||||
- You don't have to be programmer, the documentation and translation could use some love as well.
|
||||
- Sponsor the project: free software costs money to create!
|
||||
|
||||
There is one simple rule in our "Code of conduct": Don't be an ass!
|
||||
|
|
36
TODO
36
TODO
|
@ -1,36 +0,0 @@
|
|||
===========================================================================
|
||||
|
||||
INVOICES
|
||||
|
||||
- HTML Rechnungstemplates funktionieren nicht mehr richtig
|
||||
|
||||
===========================================================================
|
||||
|
||||
THEME
|
||||
|
||||
- Update to latest release und angepasstes CSS entfernen
|
||||
|
||||
- Theme Dark Mode mit Modus "Browser" sollte Standard sein:
|
||||
https://github.com/tabler/tabler/issues/892#event-5666309557
|
||||
|
||||
===========================================================================
|
||||
|
||||
MIXED
|
||||
|
||||
- Alle URLs ändern
|
||||
- /admin/activity/ zu /activity/
|
||||
- /admin/project/ zu /project/
|
||||
- /admin/..../ zu /..../
|
||||
- /team/timesheet/ zu /timesheets/
|
||||
|
||||
- Tags => immer Berechtigung prüfen und "create" option über die VIEW ans Javascript geben
|
||||
=> den FormType nur ändern zu "AutoComplete" wenn es mehr als 500 Tags sind
|
||||
=> Übersetzungen (Suche, Erstellen, Keine Ergebnisse) über data attribute ans JS durchreichen
|
||||
|
||||
===========================================================================
|
||||
|
||||
MIGRATIONS
|
||||
|
||||
- rename 2.0 migration once it will be released
|
||||
|
||||
===========================================================================
|
|
@ -8,7 +8,7 @@ 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.0](https://github.com/kevinpapst/kimai2/releases/tag/2.0)
|
||||
## [2.0](https://github.com/kimai/kimai/releases/tag/2.0)
|
||||
|
||||
**!! This release requires minimum PHP version to 8.1 !!**
|
||||
|
||||
|
|
|
@ -9,9 +9,6 @@
|
|||
@import "~bootstrap/scss/variables";
|
||||
@import "~bootstrap/scss/maps";
|
||||
@import "~bootstrap/scss/mixins";
|
||||
//@import "~bootstrap/scss/normalize";
|
||||
//@import "~bootstrap/scss/print";
|
||||
//@import "~bootstrap/scss/scaffolding";
|
||||
@import "~bootstrap/scss/reboot";
|
||||
@import "~bootstrap/scss/type";
|
||||
@import "~bootstrap/scss/grid";
|
||||
|
@ -19,12 +16,12 @@
|
|||
@import "~bootstrap/scss/list-group";
|
||||
@import "~bootstrap/scss/card";
|
||||
@import "~bootstrap/scss/utilities";
|
||||
//@import "~bootstrap/scss/responsive-utilities";
|
||||
|
||||
body {
|
||||
font-family: $font-family-sans-serif;
|
||||
|
||||
&.invoice_print {
|
||||
--bs-body-font-size: 13px;
|
||||
background-color: #eee;
|
||||
|
||||
.table.no-border, .table.no-border td, .table.no-border th {
|
||||
|
@ -35,6 +32,38 @@ body {
|
|||
font-family: $font-family-sans-serif;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
.pt-1 {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.pb-4 {
|
||||
padding-bottom: 4em;
|
||||
}
|
||||
|
||||
.ps-0 {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.bt-1 {
|
||||
border-top: 1px solid #000000;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.text-end {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.invoice {
|
||||
margin: 105px auto 30px auto;
|
||||
padding: 50px 65px;
|
||||
|
@ -48,7 +77,7 @@ body {
|
|||
|
||||
.page-header {
|
||||
margin: 10px 0 20px 0;
|
||||
font-size: 22px;
|
||||
font-size: 20px;
|
||||
|
||||
> small {
|
||||
color: #666;
|
||||
|
@ -79,14 +108,11 @@ body {
|
|||
}
|
||||
|
||||
.invoice-items {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 3em;
|
||||
|
||||
.table {
|
||||
thead th {
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-bottom: 15px
|
||||
padding-bottom: 10px
|
||||
}
|
||||
|
||||
tfoot {
|
||||
|
@ -105,7 +131,6 @@ body {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "kimai/kimai",
|
||||
"license": "MIT",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"type": "project",
|
||||
"description": "Kimai - Time Tracking",
|
||||
"authors": [
|
||||
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
{
|
||||
"name": "All contributors",
|
||||
"homepage": "https://github.com/kevinpapst/kimai2/contributors"
|
||||
"homepage": "https://github.com/kimai/kimai/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
|
@ -191,7 +191,7 @@
|
|||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"v2.x-dev": "2.0.x-dev"
|
||||
"dev-main": "2.0.x-dev"
|
||||
},
|
||||
"symfony": {
|
||||
"id": "01C3FWRDJJEX9K6Y3A4XDFXPBR",
|
||||
|
|
103
composer.lock
generated
103
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "16922ce13576f2e86b8a3c380fcf814a",
|
||||
"content-hash": "f3b0627c043009647c1ab9c3ddea3f22",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
|
@ -796,16 +796,16 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/doctrine-bundle",
|
||||
"version": "2.8.0",
|
||||
"version": "2.8.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/DoctrineBundle.git",
|
||||
"reference": "0421ebc069519a0f19b9c39e5dc18c359be0feab"
|
||||
"reference": "fe9b2cc1cd0c9b76553b1d4c1a077590ba231a2d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0421ebc069519a0f19b9c39e5dc18c359be0feab",
|
||||
"reference": "0421ebc069519a0f19b9c39e5dc18c359be0feab",
|
||||
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/fe9b2cc1cd0c9b76553b1d4c1a077590ba231a2d",
|
||||
"reference": "fe9b2cc1cd0c9b76553b1d4c1a077590ba231a2d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -891,7 +891,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/DoctrineBundle/issues",
|
||||
"source": "https://github.com/doctrine/DoctrineBundle/tree/2.8.0"
|
||||
"source": "https://github.com/doctrine/DoctrineBundle/tree/2.8.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -907,7 +907,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-28T16:35:32+00:00"
|
||||
"time": "2023-01-06T00:24:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/doctrine-migrations-bundle",
|
||||
|
@ -1685,16 +1685,16 @@
|
|||
},
|
||||
{
|
||||
"name": "egulias/email-validator",
|
||||
"version": "3.2.4",
|
||||
"version": "3.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/egulias/EmailValidator.git",
|
||||
"reference": "5f35e41eba05fdfbabd95d72f83795c835fb7ed2"
|
||||
"reference": "b531a2311709443320c786feb4519cfaf94af796"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/5f35e41eba05fdfbabd95d72f83795c835fb7ed2",
|
||||
"reference": "5f35e41eba05fdfbabd95d72f83795c835fb7ed2",
|
||||
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b531a2311709443320c786feb4519cfaf94af796",
|
||||
"reference": "b531a2311709443320c786feb4519cfaf94af796",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1703,7 +1703,6 @@
|
|||
"symfony/polyfill-intl-idn": "^1.15"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.2",
|
||||
"phpunit/phpunit": "^8.5.8|^9.3.3",
|
||||
"vimeo/psalm": "^4"
|
||||
},
|
||||
|
@ -1741,7 +1740,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/egulias/EmailValidator/issues",
|
||||
"source": "https://github.com/egulias/EmailValidator/tree/3.2.4"
|
||||
"source": "https://github.com/egulias/EmailValidator/tree/3.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1749,7 +1748,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-30T14:09:25+00:00"
|
||||
"time": "2023-01-02T17:26:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "endroid/qr-code",
|
||||
|
@ -9815,16 +9814,16 @@
|
|||
},
|
||||
{
|
||||
"name": "tijsverkoyen/css-to-inline-styles",
|
||||
"version": "2.2.5",
|
||||
"version": "2.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tijsverkoyen/CssToInlineStyles.git",
|
||||
"reference": "4348a3a06651827a27d989ad1d13efec6bb49b19"
|
||||
"reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/4348a3a06651827a27d989ad1d13efec6bb49b19",
|
||||
"reference": "4348a3a06651827a27d989ad1d13efec6bb49b19",
|
||||
"url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/c42125b83a4fa63b187fdf29f9c93cb7733da30c",
|
||||
"reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -9862,9 +9861,9 @@
|
|||
"homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
|
||||
"support": {
|
||||
"issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues",
|
||||
"source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.5"
|
||||
"source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.6"
|
||||
},
|
||||
"time": "2022-09-12T13:28:28+00:00"
|
||||
"time": "2023-01-03T09:29:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/cssinliner-extra",
|
||||
|
@ -10462,20 +10461,20 @@
|
|||
},
|
||||
{
|
||||
"name": "zircote/swagger-php",
|
||||
"version": "4.5.3",
|
||||
"version": "4.5.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/zircote/swagger-php.git",
|
||||
"reference": "e505bce612a86fe90f8fd50917e0848afc5d2ba8"
|
||||
"reference": "09356f4d68d29bdf3254811fb2602a5d5d1788ea"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/e505bce612a86fe90f8fd50917e0848afc5d2ba8",
|
||||
"reference": "e505bce612a86fe90f8fd50917e0848afc5d2ba8",
|
||||
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/09356f4d68d29bdf3254811fb2602a5d5d1788ea",
|
||||
"reference": "09356f4d68d29bdf3254811fb2602a5d5d1788ea",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/annotations": "^1.7",
|
||||
"doctrine/annotations": "^1.7 || ^2.0",
|
||||
"ext-json": "*",
|
||||
"php": ">=7.2",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
|
@ -10534,9 +10533,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/zircote/swagger-php/issues",
|
||||
"source": "https://github.com/zircote/swagger-php/tree/4.5.3"
|
||||
"source": "https://github.com/zircote/swagger-php/tree/4.5.4"
|
||||
},
|
||||
"time": "2022-12-21T18:26:59+00:00"
|
||||
"time": "2023-01-04T00:51:43+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
@ -10746,16 +10745,16 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/data-fixtures",
|
||||
"version": "1.6.1",
|
||||
"version": "1.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/data-fixtures.git",
|
||||
"reference": "1a4232c15143ca3c127812d19b23a7961c41eeed"
|
||||
"reference": "d52cc6d392717734fac908768a7319f8a417401a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/1a4232c15143ca3c127812d19b23a7961c41eeed",
|
||||
"reference": "1a4232c15143ca3c127812d19b23a7961c41eeed",
|
||||
"url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/d52cc6d392717734fac908768a7319f8a417401a",
|
||||
"reference": "d52cc6d392717734fac908768a7319f8a417401a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -10808,7 +10807,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/data-fixtures/issues",
|
||||
"source": "https://github.com/doctrine/data-fixtures/tree/1.6.1"
|
||||
"source": "https://github.com/doctrine/data-fixtures/tree/1.6.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -10824,7 +10823,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-23T12:13:51+00:00"
|
||||
"time": "2023-01-05T18:42:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/doctrine-fixtures-bundle",
|
||||
|
@ -10979,16 +10978,16 @@
|
|||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.13.1",
|
||||
"version": "v3.13.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "78d2251dd86b49c609a0fd37c20dcf0a00aea5a7"
|
||||
"reference": "3952f08a81bd3b1b15e11c3de0b6bf037faa8496"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/78d2251dd86b49c609a0fd37c20dcf0a00aea5a7",
|
||||
"reference": "78d2251dd86b49c609a0fd37c20dcf0a00aea5a7",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/3952f08a81bd3b1b15e11c3de0b6bf037faa8496",
|
||||
"reference": "3952f08a81bd3b1b15e11c3de0b6bf037faa8496",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -11056,7 +11055,7 @@
|
|||
"description": "A tool to automatically fix PHP code style",
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.13.1"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.13.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -11064,7 +11063,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-18T00:47:22+00:00"
|
||||
"time": "2023-01-02T23:53:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
|
@ -11235,16 +11234,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.9.4",
|
||||
"version": "1.9.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2"
|
||||
"reference": "0501435cd342eac7664bd62155b1ef907fc60b6f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d03bccee595e2146b7c9d174486b84f4dc61b0f2",
|
||||
"reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/0501435cd342eac7664bd62155b1ef907fc60b6f",
|
||||
"reference": "0501435cd342eac7664bd62155b1ef907fc60b6f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -11274,7 +11273,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||
"source": "https://github.com/phpstan/phpstan/tree/1.9.4"
|
||||
"source": "https://github.com/phpstan/phpstan/tree/1.9.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -11290,25 +11289,25 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-17T13:33:52+00:00"
|
||||
"time": "2023-01-04T21:59:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-doctrine",
|
||||
"version": "1.3.28",
|
||||
"version": "1.3.29",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-doctrine.git",
|
||||
"reference": "8302a6a214b8cbbda8249cce6ec627033af26c12"
|
||||
"reference": "4967ebbc24a2d7e94f5b2f6dad78e0087dd52fc3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/8302a6a214b8cbbda8249cce6ec627033af26c12",
|
||||
"reference": "8302a6a214b8cbbda8249cce6ec627033af26c12",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/4967ebbc24a2d7e94f5b2f6dad78e0087dd52fc3",
|
||||
"reference": "4967ebbc24a2d7e94f5b2f6dad78e0087dd52fc3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"phpstan/phpstan": "^1.8.11"
|
||||
"phpstan/phpstan": "^1.9.7"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/collections": "<1.0",
|
||||
|
@ -11357,9 +11356,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.28"
|
||||
"source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.29"
|
||||
},
|
||||
"time": "2022-12-30T21:24:11+00:00"
|
||||
"time": "2023-01-04T21:51:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
|
|
347
phpstan.neon
347
phpstan.neon
|
@ -570,316 +570,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Command/InvoiceCreateCommand.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method object\\:\\:getEventManager\\(\\)\\.$#"
|
||||
count: 3
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getCustomers\\(\\) on App\\\\Entity\\\\Team\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getName\\(\\) on App\\\\Entity\\\\Team\\|null\\.$#"
|
||||
count: 4
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getProjects\\(\\) on App\\\\Entity\\\\Team\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimezone\\(\\) on App\\\\Entity\\\\User\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getUsers\\(\\) on App\\\\Entity\\\\Team\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method hasUsers\\(\\) on App\\\\Entity\\\\Team\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:countFromImport\\(\\) has parameter \\$where with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:countFromImport\\(\\) should return int but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:createActivity\\(\\) has parameter \\$fixedRates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:createActivity\\(\\) has parameter \\$oldActivity with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:createActivity\\(\\) has parameter \\$rates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:createActivity\\(\\) should return App\\\\Entity\\\\Activity but returns App\\\\Entity\\\\Activity\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:createInstanceTeam\\(\\) has parameter \\$activities with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:createInstanceTeam\\(\\) has parameter \\$users with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:deactivateLifecycleCallbacks\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:fetchAllFromImport\\(\\) has parameter \\$where with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:fetchAllFromImport\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:fetchIteratorFromImport\\(\\) return type has no value type specified in iterable type Traversable\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importActivities\\(\\) has parameter \\$activities with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importActivities\\(\\) has parameter \\$fixedRates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importActivities\\(\\) has parameter \\$rates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importCustomers\\(\\) has parameter \\$customers with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importProjects\\(\\) has parameter \\$fixedRates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importProjects\\(\\) has parameter \\$projects with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importProjects\\(\\) has parameter \\$rates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importTimesheetRecords\\(\\) has parameter \\$fixedRates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importTimesheetRecords\\(\\) has parameter \\$rates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importUsers\\(\\) has parameter \\$rates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:importUsers\\(\\) has parameter \\$users with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:isKnownActivity\\(\\) has parameter \\$oldActivity with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:isKnownCustomer\\(\\) has parameter \\$oldCustomer with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:isKnownGroup\\(\\) has parameter \\$oldGroup with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:isKnownProject\\(\\) has parameter \\$oldProject with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:isKnownUser\\(\\) has parameter \\$oldUser with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:prepareOptionsFromInput\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:setActivityCache\\(\\) has parameter \\$oldActivity with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:setCustomerCache\\(\\) has parameter \\$oldCustomer with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:setGroupCache\\(\\) has parameter \\$oldGroup with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:setProjectCache\\(\\) has parameter \\$oldProject with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:setUserCache\\(\\) has parameter \\$oldUser with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:validateKimai1Data\\(\\) has parameter \\$activities with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:validateKimai1Data\\(\\) has parameter \\$customer with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:validateKimai1Data\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:validateKimai1Data\\(\\) has parameter \\$projects with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:validateKimai1Data\\(\\) has parameter \\$rates with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:validateKimai1Data\\(\\) has parameter \\$users with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:validateKimai1Data\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Command\\\\KimaiImporterCommand\\:\\:validateOptions\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$customer of method App\\\\Entity\\\\Team\\:\\:addCustomer\\(\\) expects App\\\\Entity\\\\Customer, App\\\\Entity\\\\Customer\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$name of method App\\\\Entity\\\\TimesheetMeta\\:\\:setName\\(\\) expects string, mixed given\\.$#"
|
||||
count: 3
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$object of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:persist\\(\\) expects object, App\\\\Entity\\\\Team\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$string of function strtolower expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$user of method App\\\\Entity\\\\Team\\:\\:addTeamlead\\(\\) expects App\\\\Entity\\\\User, App\\\\Entity\\\\User\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$user of method App\\\\Entity\\\\Team\\:\\:addUser\\(\\) expects App\\\\Entity\\\\User, App\\\\Entity\\\\User\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$user of method App\\\\Entity\\\\Timesheet\\:\\:setUser\\(\\) expects App\\\\Entity\\\\User, App\\\\Entity\\\\User\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$object of method App\\\\Command\\\\KimaiImporterCommand\\:\\:validateImport\\(\\) expects object, App\\\\Entity\\\\Team\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$plainPassword of method Symfony\\\\Component\\\\PasswordHasher\\\\Hasher\\\\UserPasswordHasherInterface\\:\\:hashPassword\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 2
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$team of method App\\\\Command\\\\KimaiImporterCommand\\:\\:setGroupCache\\(\\) expects App\\\\Entity\\\\Team, App\\\\Entity\\\\Team\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$version2 of function version_compare expects string, mixed given\\.$#"
|
||||
count: 2
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Property App\\\\Command\\\\KimaiImporterCommand\\:\\:\\$oldActivities type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Command/KimaiImporterCommand.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getKimaiVersion\\(\\) on App\\\\Plugin\\\\PluginMetadata\\|null\\.$#"
|
||||
count: 1
|
||||
|
@ -6761,7 +6451,7 @@ parameters:
|
|||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Strict comparison using \\!\\=\\= between null and array\\<array\\{id\\: int\\|numeric\\-string, duration\\: int\\|numeric\\-string, rate\\: float\\|int\\|numeric\\-string, internalRate\\: float\\|int\\|numeric\\-string, counter\\: int\\<0, max\\>\\|numeric\\-string, billable\\: bool, exported\\: bool\\}\\> will always evaluate to true\\.$#"
|
||||
message: "#^Strict comparison using \\!\\=\\= between null and list\\<array\\{id\\: int\\|numeric\\-string, duration\\: int\\|numeric\\-string, rate\\: float\\|int\\|numeric\\-string, internalRate\\: float\\|int\\|numeric\\-string, counter\\: int\\<0, max\\>\\|numeric\\-string, billable\\: bool, exported\\: bool\\}\\> will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
|
@ -6970,31 +6660,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Repository/CustomerRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\InvoiceDocumentRepository\\:\\:__construct\\(\\) has parameter \\$directories with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/InvoiceDocumentRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\InvoiceDocumentRepository\\:\\:addDirectory\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/InvoiceDocumentRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\InvoiceDocumentRepository\\:\\:findByPaths\\(\\) has parameter \\$paths with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/InvoiceDocumentRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\InvoiceDocumentRepository\\:\\:removeDirectory\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/InvoiceDocumentRepository.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$filename of function unlink expects string, string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/InvoiceDocumentRepository.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'counter' on mixed\\.$#"
|
||||
count: 1
|
||||
|
@ -7135,11 +6800,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Repository/Paginator/LoaderPaginator.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\Paginator\\\\LoaderPaginator\\:\\:getResults\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/Paginator/LoaderPaginator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$results of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\) expects array, mixed given\\.$#"
|
||||
count: 1
|
||||
|
@ -7165,11 +6825,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Repository/Paginator/QueryBuilderPaginator.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\Paginator\\\\QueryBuilderPaginator\\:\\:getResults\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/Paginator/QueryBuilderPaginator.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getSearchFields\\(\\) on App\\\\Utils\\\\SearchTerm\\|null\\.$#"
|
||||
count: 1
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"/build/invoice.c8ae95ad.js"
|
||||
],
|
||||
"css": [
|
||||
"/build/invoice.b17784c1.css"
|
||||
"/build/invoice.3c80ee80.css"
|
||||
]
|
||||
},
|
||||
"invoice-pdf": {
|
||||
|
@ -68,7 +68,7 @@
|
|||
"/build/export-pdf.587575e7.js": "sha384-J50GStmmfVwUTN4dIRQ02eg9hyzGFPSzpTtpPody92j0V6zCqw+s5l8+ZhVTugeW",
|
||||
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
|
||||
"/build/invoice.c8ae95ad.js": "sha384-2eVY7MBiMQxo1vhfizU+fYfEZbz9bYUdzqxnpTQhxpYiLaxeSV4WnaObq2G7/Pks",
|
||||
"/build/invoice.b17784c1.css": "sha384-Y1e218/TMmOrl/aQcb77ix5qgFQDPPWpeD6GUtnX16Buj7/Mr6arWMSXFdqlJzAc",
|
||||
"/build/invoice.3c80ee80.css": "sha384-xjFM2m/EeN7z42ygpt77ll5zAcHTeOjbZFAw2Qcu3IJutgebiAbrXjnH5aJfF5Z6",
|
||||
"/build/invoice-pdf.d86b82ee.js": "sha384-A0HJqP+MvEqQr1uG8wViCeEWxBRKyS6l8D+Ao4pFYHUA12gCC1gRYhk9I+SJPvZq",
|
||||
"/build/invoice-pdf.c88953bb.css": "sha384-ZvSi1e+ZKGzvZJUtAPLjzOSTh13N9zRevq44GKdYdBja/DAplGE55saY2Ur+83yv",
|
||||
"/build/chart.f5becfac.js": "sha384-GSqETm8wULiVXyizvwRompfwu63r/C0Qd/AvrHDE4cqAKiIGCssb3QyBtGu1WN+W",
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,7 +3,7 @@
|
|||
"build/app.js": "/build/app.694c6cb5.js",
|
||||
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
|
||||
"build/export-pdf.js": "/build/export-pdf.587575e7.js",
|
||||
"build/invoice.css": "/build/invoice.b17784c1.css",
|
||||
"build/invoice.css": "/build/invoice.3c80ee80.css",
|
||||
"build/invoice.js": "/build/invoice.c8ae95ad.js",
|
||||
"build/invoice-pdf.css": "/build/invoice-pdf.c88953bb.css",
|
||||
"build/invoice-pdf.js": "/build/invoice-pdf.d86b82ee.js",
|
||||
|
|
|
@ -9,12 +9,17 @@
|
|||
|
||||
namespace App\API\Authentication;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Security\Core\Exception\LogicException;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
|
||||
|
||||
final class ApiTokenUpgradeBadge implements BadgeInterface
|
||||
{
|
||||
/**
|
||||
* @param string|null $plaintextApiToken
|
||||
* @param PasswordUpgraderInterface<User> $passwordUpgrader
|
||||
*/
|
||||
public function __construct(private ?string $plaintextApiToken, private PasswordUpgraderInterface $passwordUpgrader)
|
||||
{
|
||||
}
|
||||
|
@ -31,6 +36,9 @@ final class ApiTokenUpgradeBadge implements BadgeInterface
|
|||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PasswordUpgraderInterface<User>
|
||||
*/
|
||||
public function getPasswordUpgrader(): PasswordUpgraderInterface
|
||||
{
|
||||
return $this->passwordUpgrader;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -77,18 +77,18 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||
$table->setPaginationRoute($paginationRoute);
|
||||
$table->setReloadEvents('kimai.timesheetUpdate kimai.timesheetDelete');
|
||||
|
||||
$table->addColumn('date', ['class' => 'alwaysVisible', 'orderBy' => 'begin']);
|
||||
$table->addColumn('date', ['class' => 'alwaysVisible text-nowrap', 'orderBy' => 'begin']);
|
||||
|
||||
if ($this->canSeeStartEndTime()) {
|
||||
$table->addColumn('starttime', ['class' => 'd-none d-sm-table-cell text-center', 'orderBy' => 'begin']);
|
||||
$table->addColumn('endtime', ['class' => 'd-none d-sm-table-cell text-center', 'orderBy' => 'end']);
|
||||
$table->addColumn('starttime', ['class' => 'd-none d-sm-table-cell text-center text-nowrap', 'orderBy' => 'begin']);
|
||||
$table->addColumn('endtime', ['class' => 'd-none d-sm-table-cell text-center text-nowrap', 'orderBy' => 'end']);
|
||||
}
|
||||
|
||||
$table->addColumn('duration', ['class' => 'text-end text-nowrap']);
|
||||
|
||||
if ($canSeeRate) {
|
||||
$table->addColumn('hourlyRate', ['class' => 'text-end d-none']);
|
||||
$table->addColumn('rate', ['class' => 'text-end']);
|
||||
$table->addColumn('hourlyRate', ['class' => 'text-end d-none text-nowrap']);
|
||||
$table->addColumn('rate', ['class' => 'text-end text-nowrap']);
|
||||
}
|
||||
|
||||
$table->addColumn('customer', ['class' => 'd-none d-md-table-cell']);
|
||||
|
@ -98,7 +98,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||
$table->addColumn('tags', ['class' => 'd-none badges', 'orderBy' => false]);
|
||||
|
||||
foreach ($metaColumns as $metaColumn) {
|
||||
$table->addColumn('mf_' . $metaColumn->getName(), ['title' => $metaColumn->getLabel(), 'class' => 'd-none', 'orderBy' => false]);
|
||||
$table->addColumn('mf_' . $metaColumn->getName(), ['title' => $metaColumn->getLabel(), 'class' => 'd-none', 'orderBy' => false, 'data' => $metaColumn]);
|
||||
}
|
||||
|
||||
if ($canSeeUsername) {
|
||||
|
@ -116,11 +116,8 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||
'page_setup' => $page,
|
||||
'dataTable' => $table,
|
||||
'action_single' => $this->getActionNameSingle(),
|
||||
'canSeeUsername' => $canSeeUsername,
|
||||
'canSeeRate' => $canSeeRate,
|
||||
'stats' => $result->getStatistic(),
|
||||
'showSummary' => $this->includeSummary(),
|
||||
'showStartEndTime' => $this->canSeeStartEndTime(),
|
||||
'metaColumns' => $metaColumns,
|
||||
'allowMarkdown' => $this->hasMarkdownSupport(),
|
||||
'editRoute' => $this->getEditRoute()
|
||||
|
|
|
@ -16,6 +16,7 @@ use App\Form\Type\SkinType;
|
|||
use App\Form\Type\TimezoneType;
|
||||
use App\User\UserService;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
@ -42,18 +43,17 @@ final class WizardController extends AbstractController
|
|||
|
||||
if ($wizard === 'profile') {
|
||||
$data = [
|
||||
'language' => $request->getLocale(),
|
||||
'timezone' => $user->getTimezone(),
|
||||
UserPreference::LOCALE => $request->getLocale(),
|
||||
UserPreference::TIMEZONE => $user->getTimezone(),
|
||||
UserPreference::SKIN => $user->getSkin(),
|
||||
'reload' => '0',
|
||||
];
|
||||
|
||||
$form = $this->createFormBuilder($data)
|
||||
->add(UserPreference::LOCALE, LanguageType::class)
|
||||
->add(UserPreference::TIMEZONE, TimezoneType::class)
|
||||
->add(UserPreference::SKIN, SkinType::class, [
|
||||
'attr' => [
|
||||
'onchange' => "document.body.classList.remove('theme-light');document.body.classList.remove('theme-light');"
|
||||
],
|
||||
])
|
||||
->add(UserPreference::SKIN, SkinType::class)
|
||||
->add('reload', HiddenType::class)
|
||||
->setAction($this->generateUrl('wizard', ['wizard' => 'profile']))
|
||||
->setMethod('POST')
|
||||
->getForm();
|
||||
|
@ -69,7 +69,11 @@ final class WizardController extends AbstractController
|
|||
$user->setWizardAsSeen('profile');
|
||||
$userService->updateUser($user);
|
||||
|
||||
return $this->redirectToRoute('wizard', ['wizard' => 'done', '_locale' => $data['language']]);
|
||||
if ($data['reload'] === '1') {
|
||||
return $this->redirectToRoute('wizard', ['wizard' => 'profile', '_locale' => $data['language']]);
|
||||
} else {
|
||||
return $this->redirectToRoute('wizard', ['wizard' => 'done', '_locale' => $data['language']]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('wizard/profile.html.twig', [
|
||||
|
|
|
@ -80,9 +80,12 @@ final class UserFixtures extends Fixture implements FixtureGroupInterface
|
|||
|
||||
$prefs = $this->getUserPreferences($user, $userData[7]);
|
||||
$user->setPreferences($prefs);
|
||||
// better to be able to test the wizard in demo installations
|
||||
/*
|
||||
foreach (User::WIZARDS as $wizard) {
|
||||
$user->setWizardAsSeen($wizard);
|
||||
}
|
||||
*/
|
||||
$manager->persist($prefs[0]);
|
||||
$manager->persist($prefs[1]);
|
||||
}
|
||||
|
|
|
@ -439,6 +439,11 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
|
|||
return (bool) $this->getPreferenceValue('export_decimal', false, false);
|
||||
}
|
||||
|
||||
public function getSkin(): string
|
||||
{
|
||||
return (string) $this->getPreferenceValue(UserPreference::SKIN, 'default', false);
|
||||
}
|
||||
|
||||
public function setTimezone(?string $timezone)
|
||||
{
|
||||
if ($timezone === null) {
|
||||
|
|
|
@ -15,6 +15,9 @@ use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
|||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* @template-implements PasswordUpgraderInterface<User>
|
||||
*/
|
||||
class ApiUserRepository implements UserLoaderInterface, PasswordUpgraderInterface
|
||||
{
|
||||
public function __construct(private UserRepository $userRepository)
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace App\Repository;
|
|||
|
||||
use App\Model\InvoiceDocument;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
final class InvoiceDocumentRepository
|
||||
{
|
||||
|
@ -21,6 +22,9 @@ final class InvoiceDocumentRepository
|
|||
*/
|
||||
private array $documentDirs = [];
|
||||
|
||||
/**
|
||||
* @param array<string> $directories
|
||||
*/
|
||||
public function __construct(array $directories)
|
||||
{
|
||||
foreach ($directories as $directory) {
|
||||
|
@ -31,23 +35,19 @@ final class InvoiceDocumentRepository
|
|||
/**
|
||||
* @CloudRequired
|
||||
*/
|
||||
public function addDirectory(string $directory)
|
||||
public function addDirectory(string $directory): void
|
||||
{
|
||||
$this->documentDirs[] = $directory;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @CloudRequired
|
||||
*/
|
||||
public function removeDirectory(string $directory)
|
||||
public function removeDirectory(string $directory): void
|
||||
{
|
||||
if (($key = array_search($directory, $this->documentDirs)) !== false) {
|
||||
unset($this->documentDirs[$key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +59,12 @@ final class InvoiceDocumentRepository
|
|||
throw new \InvalidArgumentException('Cannot delete built-in invoice template');
|
||||
}
|
||||
|
||||
@unlink(realpath($invoiceDocument->getFilename()));
|
||||
$realpath = realpath($invoiceDocument->getFilename());
|
||||
if ($realpath === false) {
|
||||
throw new \InvalidArgumentException('Template does not exist: ' . $invoiceDocument->getFilename());
|
||||
}
|
||||
|
||||
@unlink($realpath);
|
||||
}
|
||||
|
||||
public function getUploadDirectory(): string
|
||||
|
@ -135,6 +140,7 @@ final class InvoiceDocumentRepository
|
|||
/**
|
||||
* Returns an array of invoice documents.
|
||||
*
|
||||
* @param array<string> $paths
|
||||
* @return InvoiceDocument[]
|
||||
*/
|
||||
private function findByPaths(array $paths): array
|
||||
|
@ -153,7 +159,8 @@ final class InvoiceDocumentRepository
|
|||
continue;
|
||||
}
|
||||
|
||||
$finder = Finder::create()->ignoreDotFiles(true)->files()->in($searchDir)->name('*.*');
|
||||
$finder = Finder::create()->ignoreDotFiles(true)->files()->in($searchDir)->depth(0)->name('*.*');
|
||||
/** @var SplFileInfo $file */
|
||||
foreach ($finder->getIterator() as $file) {
|
||||
$doc = new InvoiceDocument($file);
|
||||
// the first found invoice document wins
|
||||
|
|
|
@ -24,6 +24,9 @@ final class LoaderPaginator implements PaginatorInterface
|
|||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array-key, iterable<mixed>>
|
||||
*/
|
||||
public function getSlice(int $offset, int $length): iterable
|
||||
{
|
||||
$query = $this->query
|
||||
|
@ -34,13 +37,17 @@ final class LoaderPaginator implements PaginatorInterface
|
|||
return $this->getResults($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Query<null, mixed> $query
|
||||
* @return iterable<array-key, iterable<mixed>>
|
||||
*/
|
||||
private function getResults(Query $query)
|
||||
{
|
||||
$results = $query->execute();
|
||||
|
||||
$this->loader->loadResults($results);
|
||||
|
||||
return $results;
|
||||
return $results; // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
public function getAll(): iterable
|
||||
|
|
|
@ -23,6 +23,9 @@ final class QueryBuilderPaginator implements PaginatorInterface
|
|||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array-key, iterable<mixed>>
|
||||
*/
|
||||
public function getSlice(int $offset, int $length): iterable
|
||||
{
|
||||
$query = $this->query
|
||||
|
@ -33,9 +36,13 @@ final class QueryBuilderPaginator implements PaginatorInterface
|
|||
return $this->getResults($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Query<null, mixed> $query
|
||||
* @return iterable<array-key, iterable<mixed>>
|
||||
*/
|
||||
private function getResults(Query $query)
|
||||
{
|
||||
return $query->execute();
|
||||
return $query->execute(); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
public function getAll(): iterable
|
||||
|
|
|
@ -34,6 +34,7 @@ use Symfony\Component\Security\Core\User\UserProviderInterface;
|
|||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<User>
|
||||
* @template-implements PasswordUpgraderInterface<User>
|
||||
*/
|
||||
class UserRepository extends EntityRepository implements UserLoaderInterface, UserProviderInterface, PasswordUpgraderInterface
|
||||
{
|
||||
|
@ -58,7 +59,7 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||
$entityManager->flush();
|
||||
}
|
||||
|
||||
public function upgradePassword(PasswordAuthenticatedUserInterface|UserInterface $user, string $newHashedPassword): void
|
||||
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
|
||||
{
|
||||
if (!($user instanceof User)) {
|
||||
return;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
namespace App\Security;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\User;
|
||||
use App\Ldap\LdapUserProvider;
|
||||
use Symfony\Component\Security\Core\User\ChainUserProvider;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
|
@ -17,6 +18,9 @@ use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
|||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
/**
|
||||
* @template-implements PasswordUpgraderInterface<User>
|
||||
*/
|
||||
final class KimaiUserProvider implements UserProviderInterface, PasswordUpgraderInterface
|
||||
{
|
||||
private ?ChainUserProvider $provider = null;
|
||||
|
|
39
src/Twig/Runtime/QrCodeExtension.php
Normal file
39
src/Twig/Runtime/QrCodeExtension.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?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\Twig\Runtime;
|
||||
|
||||
use Endroid\QrCode\Builder\Builder;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelMedium;
|
||||
use Endroid\QrCode\Writer\PngWriter;
|
||||
use Twig\Extension\RuntimeExtensionInterface;
|
||||
|
||||
final class QrCodeExtension implements RuntimeExtensionInterface
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param array<string, mixed> $writerOptions
|
||||
* @return string
|
||||
*/
|
||||
public function qrCodeDataUriFunction(string $data, array $writerOptions = []): string
|
||||
{
|
||||
return Builder::create()
|
||||
->writer(new PngWriter())
|
||||
->writerOptions($writerOptions)
|
||||
->data($data)
|
||||
// if this causes errors at some point and needs to be configurable, keep this default!
|
||||
->errorCorrectionLevel(new ErrorCorrectionLevelMedium())
|
||||
->build()
|
||||
->getDataUri();
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ namespace App\Twig;
|
|||
|
||||
use App\Twig\Runtime\EncoreExtension;
|
||||
use App\Twig\Runtime\MarkdownExtension;
|
||||
use App\Twig\Runtime\QrCodeExtension;
|
||||
use App\Twig\Runtime\ThemeExtension;
|
||||
use App\Twig\Runtime\TimesheetExtension;
|
||||
use App\Twig\Runtime\WidgetExtension;
|
||||
|
@ -35,6 +36,7 @@ final class RuntimeExtensions extends AbstractExtension
|
|||
new TwigFunction('encore_entry_css_source', [EncoreExtension::class, 'getEncoreEntryCssSource']),
|
||||
new TwigFunction('render_widget', [WidgetExtension::class, 'renderWidget'], ['is_safe' => ['html'], 'needs_environment' => true]),
|
||||
new TwigFunction('icon', [RuntimeExtension::class, 'createIcon'], ['is_safe' => ['html']]),
|
||||
new TwigFunction('qr_code_data_uri', [QrCodeExtension::class, 'qrCodeDataUriFunction']),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -27,19 +27,21 @@
|
|||
{% block datatable_before %}{% endblock %}
|
||||
|
||||
{% set sortedColumns = dataTable.sortedColumnNames %}
|
||||
{% for entry in dataTable %}
|
||||
{% block datatable_row %}
|
||||
<tr{% block datatable_row_attr %}{% endblock %}>
|
||||
{% for column, data in sortedColumns %}
|
||||
{% block datatable_column %}
|
||||
<td class="{{ tables.class(dataTable, column) }}"{% block datatable_column_attr %}{% endblock %}>
|
||||
{% block datatable_column_value %}{% endblock %}
|
||||
</td>
|
||||
{% endblock %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endblock %}
|
||||
{% endfor %}
|
||||
{% block datatable_outer %}
|
||||
{% for entry in dataTable %}
|
||||
{% block datatable_row %}
|
||||
<tr{% block datatable_row_attr %}{% endblock %}>
|
||||
{% for column, data in sortedColumns %}
|
||||
{% block datatable_column %}
|
||||
<td class="{{ tables.class(dataTable, column) }}"{% block datatable_column_attr %}{% endblock %}>
|
||||
{% block datatable_column_value %}{% endblock %}
|
||||
</td>
|
||||
{% endblock %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endblock %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block datatable_after %}{% endblock %}
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<div class="col-sm-7"></div>
|
||||
</div>
|
||||
|
||||
<div class="row invoice-items">
|
||||
<div class="row invoice-items mt-2 mb-3">
|
||||
<div class="col-xs-12 table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<table class="table no-border table-sm">
|
||||
<table class="table no-border table-sm">
|
||||
<tr>
|
||||
<th>{{ 'invoice.from'|trans }}</th>
|
||||
<th class="ps-0">{{ 'invoice.from'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
{% if model.query.user is not empty %}
|
||||
{{ widgets.username(model.query.user) }}
|
||||
|
@ -27,7 +27,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'date'|trans }}</th>
|
||||
<th class="ps-0">{{ 'date'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
{% if model.query.begin|date('m') != model.query.end|date('m') or model.query.begin|date('Y') != model.query.end|date('Y') %}
|
||||
{{ model.query.begin|date_short }} - {{ model.query.end|date_short }}
|
||||
|
@ -37,7 +37,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'customer'|trans }}</th>
|
||||
<th class="ps-0">{{ 'customer'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
{% if model.customer.number is not empty %}[{{ model.customer.number }}]{% endif %}
|
||||
{{ model.customer.name }}{% if model.customer.contact is not empty %} / {{ model.customer.contact }}{% endif %}
|
||||
|
@ -45,7 +45,7 @@
|
|||
</tr>
|
||||
{% if project is not null %}
|
||||
<tr>
|
||||
<th>{{ 'project'|trans }}</th>
|
||||
<th class="ps-0">{{ 'project'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
{{ project.name }}
|
||||
{% if project.orderNumber is not empty %}
|
||||
|
@ -56,7 +56,7 @@
|
|||
{% endif %}
|
||||
{% if activity is not null %}
|
||||
<tr>
|
||||
<th>{{ 'activity'|trans }}</th>
|
||||
<th class="ps-0">{{ 'activity'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
{{ activity.name }}
|
||||
</td>
|
||||
|
@ -66,7 +66,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row invoice-items">
|
||||
<div class="row invoice-items mt-2 mb-3">
|
||||
<div class="col-xs-12 table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
|
@ -122,16 +122,8 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th style="padding-bottom: 60px">{{ 'invoice.signature_user'|trans }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'invoice.signature_customer'|trans }}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="bt-1 pb-4 pt-1">{{ 'invoice.signature_user'|trans }}</p>
|
||||
<p class="bt-1 pb-4 pt-1">{{ 'invoice.signature_customer'|trans }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,44 +2,68 @@
|
|||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
|
||||
{% set checkOverlappingDesc = false %}
|
||||
{% set checkOverlappingAsc = false %}
|
||||
{% set query = dataTable.getQuery() %}
|
||||
{% if query.orderBy == 'begin' or query.orderBy == 'end' %}
|
||||
{% set checkOverlappingDesc = (query.order == 'DESC') %}
|
||||
{% set checkOverlappingAsc = not checkOverlappingDesc %}
|
||||
{% endif %}
|
||||
{% block datatable_outer %}
|
||||
{% set checkOverlappingDesc = false %}
|
||||
{% set checkOverlappingAsc = false %}
|
||||
{% set query = dataTable.getQuery() %}
|
||||
{% if query.orderBy == 'begin' or query.orderBy == 'end' %}
|
||||
{% set checkOverlappingDesc = (query.order == 'DESC') %}
|
||||
{% set checkOverlappingAsc = not checkOverlappingDesc %}
|
||||
{% endif %}
|
||||
|
||||
{% set day = null %}
|
||||
{% set dayDuration = 0 %}
|
||||
{% set dayRate = {} %}
|
||||
{% set dayHourlyRate = 0 %}
|
||||
{% set lastEntry = null %}
|
||||
{% set day = null %}
|
||||
{% set dayDuration = 0 %}
|
||||
{% set dayRate = {} %}
|
||||
{% set dayHourlyRate = 0 %}
|
||||
{% set lastEntry = null %}
|
||||
|
||||
{% for entry in dataTable %}
|
||||
{%- if day is same as(null) -%}
|
||||
{% set day = entry.begin|date_short %}
|
||||
{% endif %}
|
||||
{%- if showSummary and day is not same as(entry.begin|date_short) -%}
|
||||
{{ _self.summary(day, dayDuration, dayHourlyRate, dayRate, sortedColumns, dataTable) }}
|
||||
{% set day = entry.begin|date_short %}
|
||||
{% set dayDuration = 0 %}
|
||||
{% set dayRate = {} %}
|
||||
{% set dayHourlyRate = 0 %}
|
||||
{%- endif -%}
|
||||
{%- set customerCurrency = entry.project.customer.currency -%}
|
||||
{%- set entryHourlyRate = entry.hourlyRate|money(customerCurrency) -%}
|
||||
{% block datatable_row %}
|
||||
<tr{{ block('datatable_row_attr') }}>
|
||||
{% for column, data in sortedColumns %}
|
||||
{{ block('datatable_column') }}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endblock %}
|
||||
{%- if entry.end -%}
|
||||
{% if dayRate[customerCurrency] is not defined %}
|
||||
{% set dayRate = dayRate|merge({(customerCurrency): 0}) %}
|
||||
{% endif %}
|
||||
{% set dayRate = dayRate|merge({(customerCurrency): dayRate[customerCurrency] + entry.rate}) %}
|
||||
{%- endif -%}
|
||||
{% if dayHourlyRate is not null %}
|
||||
{% if dayHourlyRate == 0 %}
|
||||
{% set dayHourlyRate = entryHourlyRate %}
|
||||
{% elseif dayHourlyRate != entryHourlyRate %}
|
||||
{% set dayHourlyRate = null %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- set dayDuration = dayDuration + entry.duration -%}
|
||||
{% set lastEntry = entry %}
|
||||
{% endfor %}
|
||||
{% if showSummary %}
|
||||
{{ _self.summary(day, dayDuration, dayHourlyRate, dayRate, sortedColumns, dataTable) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block status %}
|
||||
{% from "macros/status.html.twig" import status_duration %}
|
||||
{{ status_duration(stats.duration|duration) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block datatable_after %}
|
||||
{% if showSummary %}
|
||||
{{ _self.summary(day, dayDuration, dayHourlyRate, dayRate, columns, canSeeRate, canSeeUsername, showStartEndTime, tableName, metaColumns) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block datatable_row %}
|
||||
{%- set customerCurrency = entry.project.customer.currency -%}
|
||||
{%- set entryHourlyRate = entry.hourlyRate|money(customerCurrency) -%}
|
||||
{%- if day is same as(null) -%}
|
||||
{% set day = entry.begin|date_short %}
|
||||
{% endif %}
|
||||
{%- if showSummary and day is not same as(entry.begin|date_short) -%}
|
||||
{{ _self.summary(day, dayDuration, dayHourlyRate, dayRate, columns, canSeeRate, canSeeUsername, showStartEndTime, tableName, metaColumns) }}
|
||||
{% set day = entry.begin|date_short %}
|
||||
{% set dayDuration = 0 %}
|
||||
{% set dayRate = {} %}
|
||||
{% set dayHourlyRate = 0 %}
|
||||
{%- endif -%}
|
||||
{% block datatable_row_attr %}
|
||||
{% set class = '' %}
|
||||
{% if checkOverlappingDesc or checkOverlappingAsc %}
|
||||
{% if lastEntry is not null and entry.end is not null and entry.user is same as (lastEntry.user) %}
|
||||
|
@ -53,144 +77,94 @@
|
|||
{% if not entry.end %}
|
||||
{% set class = class ~ ' recording' %}
|
||||
{% endif %}
|
||||
<tr{% if is_granted('edit', entry) %} class="modal-ajax-form open-edit{{ class }}" data-href="{{ path(editRoute, {'id': entry.id}) }}"{% endif %}>
|
||||
<td class="text-nowrap">
|
||||
{% if is_granted('edit', entry) %} class="modal-ajax-form open-edit{{ class }}" data-href="{{ path(editRoute, {'id': entry.id}) }}"{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block datatable_column %}
|
||||
<td class="{{ tables.class(dataTable, column) }}{% if column == 'description' %} timesheet-description{% endif %}">
|
||||
{% if column == 'id' %}
|
||||
{% if is_granted('edit', entry) or is_granted('delete', entry) %}
|
||||
{{ tables.datatable_multiupdate_row(entry.id) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'date') }}">{{ entry.begin|date_short }}</td>
|
||||
|
||||
{% if showStartEndTime %}
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'starttime') }}">{{ entry.begin|time }}</td>
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'endtime') }}">
|
||||
{% if entry.end %}
|
||||
{{ entry.end|time }}
|
||||
{% else %}
|
||||
‐
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
{% elseif column == 'date' %}
|
||||
{{ entry.begin|date_short }}
|
||||
{% elseif column == 'starttime' %}
|
||||
{{ entry.begin|time }}
|
||||
{% elseif column == 'endtime' %}
|
||||
{% if entry.end %}
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'duration') }}">{{ entry.duration|duration }}</td>
|
||||
{{ entry.end|time }}
|
||||
{% else %}
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'duration') }}">
|
||||
<i data-since="{{ entry.begin.format(constant('DATE_ISO8601')) }}">{{ entry|duration }}</i>
|
||||
</td>
|
||||
‐
|
||||
{% endif %}
|
||||
|
||||
{% if canSeeRate %}
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'hourlyRate') }}">
|
||||
{{ entryHourlyRate }}
|
||||
</td>
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'rate') }}">
|
||||
{% if not entry.end or not is_granted('view_rate', entry) %}
|
||||
‐
|
||||
{% else %}
|
||||
{{ entry.rate|money(customerCurrency) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% elseif column == 'duration' %}
|
||||
{% if entry.end %}
|
||||
{{ entry.duration|duration }}
|
||||
{% else %}
|
||||
<i data-since="{{ entry.begin.format(constant('DATE_ISO8601')) }}">{{ entry|duration }}</i>
|
||||
{% endif %}
|
||||
|
||||
<td class="{{ tables.class(dataTable, 'customer') }}">
|
||||
{{ widgets.label_customer(entry.project.customer) }}
|
||||
</td>
|
||||
<td class="{{ tables.class(dataTable, 'project') }}">
|
||||
{{ widgets.label_project(entry.project) }}
|
||||
</td>
|
||||
<td class="{{ tables.class(dataTable, 'activity') }}">
|
||||
{% if entry.activity is not null %}
|
||||
{{ widgets.label_activity(entry.activity) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.class(dataTable, 'description') }} timesheet-description">
|
||||
{% if allowMarkdown %}
|
||||
{{ entry.description|desc2html }}
|
||||
{% else %}
|
||||
{{ entry.description|nl2br }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.class(dataTable, 'tags') }}">{{ widgets.tag_list(entry.tags) }}</td>
|
||||
|
||||
{% for field in metaColumns %}
|
||||
<td class="{{ tables.class(dataTable, 'mf_' ~ field.name) }}">
|
||||
{{ tables.datatable_meta_column(entry, field) }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
{% if canSeeUsername %}
|
||||
<td class="{{ tables.class(dataTable, 'username') }}">
|
||||
{{ widgets.label_user(entry.user) }}
|
||||
</td>
|
||||
{% elseif column == 'hourlyRate' %}
|
||||
{{ entryHourlyRate }}
|
||||
{% elseif column == 'rate' %}
|
||||
{% if not entry.end or not is_granted('view_rate', entry) %}
|
||||
‐
|
||||
{% else %}
|
||||
{{ entry.rate|money(customerCurrency) }}
|
||||
{% endif %}
|
||||
|
||||
<td class="{{ tables.class(dataTable, 'billable') }}">
|
||||
{{ widgets.label_boolean(entry.billable) }}
|
||||
</td>
|
||||
<td class="{{ tables.class(dataTable, 'exported') }}">
|
||||
{{ widgets.label_boolean(entry.exported) }}
|
||||
</td>
|
||||
<td class="{{ tables.class(dataTable, 'actions') }}">
|
||||
{% set event = actions(app.user, action_single, 'index', {'timesheet': entry}) %}
|
||||
{{ widgets.table_actions(event.actions) }}
|
||||
</td>
|
||||
</tr>
|
||||
{%- if entry.end -%}
|
||||
{% if dayRate[customerCurrency] is not defined %}
|
||||
{% set dayRate = dayRate|merge({(customerCurrency): 0}) %}
|
||||
{% elseif column == 'customer' %}
|
||||
{{ widgets.label_customer(entry.project.customer) }}
|
||||
{% elseif column == 'project' %}
|
||||
{{ widgets.label_project(entry.project) }}
|
||||
{% elseif column == 'activity' %}
|
||||
{% if entry.activity is not null %}
|
||||
{{ widgets.label_activity(entry.activity) }}
|
||||
{% endif %}
|
||||
{% set dayRate = dayRate|merge({(customerCurrency): dayRate[customerCurrency] + entry.rate}) %}
|
||||
{%- endif -%}
|
||||
{% if dayHourlyRate is not null %}
|
||||
{% if dayHourlyRate == 0 %}
|
||||
{% set dayHourlyRate = entryHourlyRate %}
|
||||
{% elseif dayHourlyRate != entryHourlyRate %}
|
||||
{% set dayHourlyRate = null %}
|
||||
{% elseif column == 'description' %}
|
||||
{% if allowMarkdown %}
|
||||
{{ entry.description|desc2html }}
|
||||
{% else %}
|
||||
{{ entry.description|nl2br }}
|
||||
{% endif %}
|
||||
{% elseif column == 'tags' %}
|
||||
{{ widgets.tag_list(entry.tags) }}
|
||||
{% elseif column == 'billable' %}
|
||||
{{ widgets.label_boolean(entry.billable) }}
|
||||
{% elseif column == 'exported' %}
|
||||
{{ widgets.label_boolean(entry.exported) }}
|
||||
{% elseif column == 'username' %}
|
||||
{{ widgets.label_user(entry.user) }}
|
||||
{% elseif column == 'actions' %}
|
||||
{% set event = actions(app.user, action_single, 'index', {'timesheet': entry}) %}
|
||||
{{ widgets.table_actions(event.actions) }}
|
||||
{% elseif column starts with 'mf_' %}
|
||||
{{ widgets.meta_field_value(entry, data) }}
|
||||
{% endif %}
|
||||
{%- set dayDuration = dayDuration + entry.duration -%}
|
||||
{% set lastEntry = entry %}
|
||||
</td>
|
||||
{% endblock %}
|
||||
|
||||
{% macro summary(day, duration, dayHourlyRate, dayRates, columns, canSeeRate, canSeeUsername, showStartEndTime, tableName, metaColumns) %}
|
||||
{% macro summary(day, duration, dayHourlyRate, dayRates, sortedColumns, dataTable) %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
<tr class="summary info">
|
||||
<td></td>
|
||||
<td class="text-nowrap">{{ day }}</td>
|
||||
{% if showStartEndTime %}
|
||||
<td class="{{ tables.class(dataTable, 'starttime') }}"></td>
|
||||
<td class="{{ tables.class(dataTable, 'endtime') }}"></td>
|
||||
{% endif %}
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'duration') }}">{{ duration|duration }}</td>
|
||||
{% if canSeeRate %}
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'hourlyRate') }}">
|
||||
{% if dayHourlyRate is not null and dayHourlyRate != 0 %}
|
||||
{{ dayHourlyRate }}
|
||||
{% for column, data in sortedColumns %}
|
||||
<td class="{{ tables.class(dataTable, column) }}">
|
||||
{% if column == 'date' %}
|
||||
{{ day }}
|
||||
{% elseif column == 'duration' %}
|
||||
{{ duration|duration }}
|
||||
{% elseif column == 'hourlyRate' %}
|
||||
{% if dayHourlyRate is not null and dayHourlyRate != 0 %}
|
||||
{{ dayHourlyRate }}
|
||||
{% endif %}
|
||||
{% elseif column == 'rate' %}
|
||||
{% for currency, rate in dayRates %}
|
||||
{{ rate|money(currency) }}
|
||||
{% if not loop.last %}
|
||||
<br>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-nowrap {{ tables.class(dataTable, 'rate') }}">
|
||||
{% for currency, rate in dayRates %}
|
||||
{{ rate|money(currency) }}
|
||||
{% if not loop.last %}
|
||||
<br>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
<td class="{{ tables.class(dataTable, 'customer') }}"></td>
|
||||
<td class="{{ tables.class(dataTable, 'project') }}"></td>
|
||||
<td class="{{ tables.class(dataTable, 'activity') }}"></td>
|
||||
<td class="{{ tables.class(dataTable, 'description') }}"></td>
|
||||
<td class="{{ tables.class(dataTable, 'tags') }}"></td>
|
||||
{% for field in metaColumns %}
|
||||
<td class="{{ tables.class(dataTable, 'mf_' ~ field.name) }}"></td>
|
||||
{% endfor %}
|
||||
{% if canSeeUsername %}
|
||||
<td class="{{ tables.class(dataTable, 'username') }}"></td>
|
||||
{% endif %}
|
||||
<td class="{{ tables.class(dataTable, 'billable') }}"></td>
|
||||
<td class="{{ tables.class(dataTable, 'exported') }}"></td>
|
||||
<td class="actions"></td>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -25,6 +25,14 @@
|
|||
document.body.classList.add('theme-' + skinChooser.value);
|
||||
});
|
||||
}
|
||||
|
||||
const localeChooser = document.getElementById('form_language');
|
||||
if (localeChooser !== null) {
|
||||
localeChooser.addEventListener('change', (ev) => {
|
||||
document.getElementById('form_reload').value = '1';
|
||||
ev.target.form.submit();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -99,7 +99,7 @@ class CreateUserCommandTest extends KernelTestCase
|
|||
$commandTester = $this->createUser('MyTestUser2', 'user@example.com', 'ROLE_USER', 'foobar');
|
||||
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('[ERROR] email: The email is already used.', $output);
|
||||
$this->assertStringContainsString('[ERROR] email: This e-mail address is already in use.', $output);
|
||||
}
|
||||
|
||||
public function testUserEmail(): void
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\KimaiImporterCommand;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\KimaiImporterCommand
|
||||
* @group integration
|
||||
*/
|
||||
class KimaiImporterCommandTest extends KernelTestCase
|
||||
{
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $application;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$kernel = self::bootKernel();
|
||||
$this->application = new Application($kernel);
|
||||
|
||||
$encoder = $this->createMock(UserPasswordHasherInterface::class);
|
||||
$registry = $this->createMock(ManagerRegistry::class);
|
||||
$validator = $this->createMock(ValidatorInterface::class);
|
||||
|
||||
$this->application->add(new KimaiImporterCommand($encoder, $registry, $validator));
|
||||
}
|
||||
|
||||
public function testCommandName()
|
||||
{
|
||||
$command = $this->application->find('kimai:import-v1');
|
||||
self::assertInstanceOf(KimaiImporterCommand::class, $command);
|
||||
}
|
||||
}
|
|
@ -116,7 +116,7 @@ class SelfRegistrationControllerTest extends ControllerBaseTest
|
|||
|
||||
$content = $client->getResponse()->getContent();
|
||||
$this->assertStringContainsString('<title>Kimai – Time Tracking</title>', $content);
|
||||
$this->assertStringContainsString('An email has been sent to register@example.com. It contains an activation link you must click to activate your account.', $content);
|
||||
$this->assertStringContainsString('An e-mail has been sent to register@example.com. It contains a link you must click to activate your account.', $content);
|
||||
$this->assertStringContainsString('<a href="/en/login">', $content);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
"type": "kimai-plugin",
|
||||
"version": "1.0",
|
||||
"require": {
|
||||
"kimai/kimai2-composer": "*",
|
||||
"kevinpapst/kimai2": "*"
|
||||
"kimai/kimai": "*"
|
||||
},
|
||||
"keywords": [
|
||||
"kimai",
|
||||
|
|
|
@ -49,6 +49,7 @@ class RuntimeExtensionsTest extends TestCase
|
|||
'encore_entry_css_source',
|
||||
'render_widget',
|
||||
'icon',
|
||||
'qr_code_data_uri',
|
||||
];
|
||||
|
||||
$i = 0;
|
||||
|
|
|
@ -2302,11 +2302,6 @@ parameters:
|
|||
count: 1
|
||||
path: Command/InvoiceCreateCommandTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Command\\\\KimaiImporterCommandTest\\:\\:testCommandName\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Command/KimaiImporterCommandTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Command\\\\PluginCommandTest\\:\\:getCommandTester\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
|
|
Loading…
Add table
Reference in a new issue