Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff62c7da87 | ||
|
|
fd94f033c8 | ||
|
|
f2cfe593bf | ||
|
|
5dc054dc64 | ||
|
|
5d48d463d9 | ||
|
|
f295d6e30e | ||
|
|
162acf37a8 | ||
|
|
37c741c48d | ||
|
|
bbdcf16a5d | ||
|
|
dfb0134137 | ||
|
|
6a86afb5fd | ||
|
|
677f8d776c | ||
|
|
960d4f09fa | ||
|
|
aaa58e6d20 | ||
|
|
c92a8949b2 | ||
|
|
78854b7807 | ||
|
|
348da17347 | ||
|
|
f0c72d8b76 | ||
|
|
b19feb400e | ||
|
|
b5edb6a560 | ||
|
|
9e87fc131b | ||
|
|
5a0c783a76 | ||
|
|
26361da873 | ||
|
|
26f8abfcc6 | ||
|
|
27c2b912f7 | ||
|
|
7e72f144cb | ||
|
|
3c3d6379a8 | ||
|
|
806bb97e60 | ||
|
|
3fcaf55d02 | ||
|
|
f0e2337d67 | ||
|
|
00d801616a | ||
|
|
afb5a0bba4 | ||
|
|
8c1ed68817 | ||
|
|
b61420d633 | ||
|
|
595b5f4b25 | ||
|
|
07ac6c308f | ||
|
|
a6766373b4 | ||
|
|
a15c1e56cb | ||
|
|
8a42c9640f | ||
|
|
8e6764b67a | ||
|
|
ea29799b89 | ||
|
|
a3ef1fd1cd | ||
|
|
1a38c7d7a3 | ||
|
|
636a51e721 | ||
|
|
cc64acf0f8 | ||
|
|
76821c24ed | ||
|
|
190b1dd4f3 | ||
|
|
7937fa281a | ||
|
|
6d78c6ba36 | ||
|
|
5d3e46cce2 | ||
|
|
1c3aac78f6 | ||
|
|
b1fb300a71 | ||
|
|
a4d658b821 | ||
|
|
4920ea5075 | ||
|
|
03dbbdc5ba | ||
|
|
023048e7c0 | ||
|
|
24778d3ffb | ||
|
|
04331420ae | ||
|
|
f3890691ce | ||
|
|
dfec807166 | ||
|
|
06b3060fe1 | ||
|
|
b6a8ae783f | ||
|
|
3d8fa3af44 | ||
|
|
59dc82351e | ||
|
|
da10099248 | ||
|
|
05aaa1950a | ||
|
|
46129c7ab9 | ||
|
|
c8b4e4eabb | ||
|
|
2f2ebd6293 | ||
|
|
594385f741 | ||
|
|
12190141d7 | ||
|
|
996e440c46 | ||
|
|
9bd0d8baf4 | ||
|
|
f74425fec6 | ||
|
|
8a51b9c47a | ||
|
|
9051fd1e1d | ||
|
|
c61638eba4 | ||
|
|
a836051dff | ||
|
|
50a3cbbbea | ||
|
|
75ee0b7b99 | ||
|
|
3572a0fb62 |
@@ -1,23 +1,17 @@
|
||||
; top-most EditorConfig file
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
; Unix-style newlines
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
|
||||
[*.php]
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
|
||||
[*.twig]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.yaml]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
[{compose.yaml,compose.*.yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
61
.env.dist
61
.env.dist
@@ -1,7 +1,18 @@
|
||||
# In all environments, the following files are loaded if they exist,
|
||||
# the latter taking precedence over the former:
|
||||
#
|
||||
# You should NOT use this file in production, but instead move the environment variables to your webserver configuration.
|
||||
# The .env file is only existing to simplify the initial setup!
|
||||
# * .env contains default values for the environment variables needed by the app
|
||||
# * .env.local uncommitted file with local overrides
|
||||
# * .env.$APP_ENV committed environment-specific defaults
|
||||
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||
#
|
||||
# Real environment variables win over .env files.
|
||||
#
|
||||
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||
# https://symfony.com/doc/current/configuration/secrets.html
|
||||
#
|
||||
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||
|
||||
#================================================================================
|
||||
# Configure your database connection and set the correct server version.
|
||||
@@ -16,32 +27,34 @@
|
||||
# For MySQL that would be "serverVersion=5.7" as in:
|
||||
# DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=5.7
|
||||
#
|
||||
# For MariaDB it would be "serverVersion=10.5.8-MariaDB":
|
||||
# DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.5.8-MariaDB
|
||||
# For MariaDB it would be "serverVersion=10.11.15-MariaDB":
|
||||
# DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.11.15-MariaDB
|
||||
#
|
||||
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.5.8-MariaDB
|
||||
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.11.15-MariaDB
|
||||
|
||||
#================================================================================
|
||||
# The full documentation can be found at https://www.kimai.org/documentation/emails.html
|
||||
#
|
||||
# Email will be sent with this address as sender:
|
||||
# Running behind reverse proxies? Try these:
|
||||
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2
|
||||
# TRUSTED_HOSTS=localhost|example.com
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=prod
|
||||
APP_SECRET=
|
||||
APP_SHARE_DIR=var/share
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> symfony/mailer ###
|
||||
# Documentation at https://www.kimai.org/documentation/emails.html
|
||||
MAILER_FROM=kimai@example.com
|
||||
# Email connection (disabled by default) - see documentation for the format
|
||||
MAILER_URL=null://null
|
||||
###< symfony/mailer ###
|
||||
|
||||
#================================================================================
|
||||
# do not change, unless you are developing for Kimai
|
||||
APP_ENV=prod
|
||||
###> nelmio/cors-bundle ###
|
||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||
###< nelmio/cors-bundle ###
|
||||
|
||||
#================================================================================
|
||||
# should be changed to a unique character sequence, used for hashing cookies
|
||||
APP_SECRET=change_this_to_something_unique
|
||||
|
||||
#================================================================================
|
||||
# Running behind reverse proxies? Try these:
|
||||
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2
|
||||
# TRUSTED_HOSTS=localhost,example.com
|
||||
|
||||
#================================================================================
|
||||
# unlikely, that you need to change this one
|
||||
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
|
||||
###> symfony/routing ###
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
DEFAULT_URI=http://localhost
|
||||
###< symfony/routing ###
|
||||
|
||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -5,8 +5,10 @@ tests export-ignore
|
||||
.codecov.yml export-ignore
|
||||
.editorconfig export-ignore
|
||||
eslint.config.js export-ignore
|
||||
eslint.config.mjs export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.php-cs-fixer.dist.php export-ignore
|
||||
php-cs-fixer.dist.php export-ignore
|
||||
babel.config.js export-ignore
|
||||
package.json export-ignore
|
||||
@@ -15,4 +17,4 @@ phpstan.neon export-ignore
|
||||
phpstan.sh export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
webpack.config.js export-ignore
|
||||
yarn.lock export-ignore
|
||||
yarn.lock export-ignore
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -53,6 +53,7 @@ body:
|
||||
attributes:
|
||||
label: Which PHP version are you using?
|
||||
options:
|
||||
- "8.5"
|
||||
- "8.4"
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
|
||||
2
.github/release-drafter.yml
vendored
2
.github/release-drafter.yml
vendored
@@ -22,7 +22,7 @@ version-resolver:
|
||||
- 'translation'
|
||||
default: patch
|
||||
template: |
|
||||
**Compatible with PHP 8.1 to 8.4**
|
||||
**Compatible with PHP 8.1 to 8.5**
|
||||
|
||||
$CHANGES
|
||||
|
||||
|
||||
16
.github/workflows/lockfiles.yaml
vendored
16
.github/workflows/lockfiles.yaml
vendored
@@ -1,23 +1,21 @@
|
||||
name: Check .lock files
|
||||
on:
|
||||
pull_request: null
|
||||
pull_request_target: null
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
lockfiles:
|
||||
runs-on: ubuntu-latest
|
||||
name: Verify lock file integrity
|
||||
steps:
|
||||
|
||||
- name: Clone Kimai
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Prevent file change
|
||||
uses: xalvarez/prevent-file-change-action@v1
|
||||
uses: xalvarez/prevent-file-change-action@v3
|
||||
with:
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
pattern: .*\.lock$
|
||||
pattern: .*\.lock$|^\.github\/.*$
|
||||
trustedAuthors: kevinpapst, dependabot
|
||||
|
||||
10
.github/workflows/testing.yaml
vendored
10
.github/workflows/testing.yaml
vendored
@@ -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']
|
||||
php: ['8.4', '8.5']
|
||||
|
||||
name: Integration (${{ matrix.php }})
|
||||
steps:
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
run: APP_ENV=dev bin/console kimai:reload -n
|
||||
|
||||
- name: Check codestyles
|
||||
run: PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run --verbose --config=.php-cs-fixer.dist.php --using-cache=no --show-progress=none --format=checkstyle | cs2pr
|
||||
run: vendor/bin/php-cs-fixer fix --dry-run --verbose --config=.php-cs-fixer.dist.php --using-cache=no --show-progress=none --format=checkstyle | cs2pr
|
||||
|
||||
- name: Run PHPStan for application
|
||||
run: vendor/bin/phpstan analyse -c phpstan.neon --no-progress --error-format=checkstyle | cs2pr
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
MAILER_URL: null://localhost
|
||||
|
||||
- name: Full test-suite
|
||||
if: matrix.php != '8.2'
|
||||
if: matrix.php != '8.5'
|
||||
run: vendor/bin/phpunit tests/
|
||||
env:
|
||||
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?charset=utf8mb4&serverVersion=8.0.35
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
MAILER_URL: null://localhost
|
||||
|
||||
- name: Full test-suite with coverage
|
||||
if: matrix.php == '8.2'
|
||||
if: matrix.php == '8.5'
|
||||
run: vendor/bin/phpunit tests/ --coverage-clover=coverage.xml
|
||||
env:
|
||||
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?charset=utf8mb4&serverVersion=8.0.35
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
MAILER_URL: null://localhost
|
||||
|
||||
- name: Upload code coverage
|
||||
if: matrix.php == '8.2'
|
||||
if: matrix.php == '8.5'
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
2
.github/workflows/website.yaml
vendored
2
.github/workflows/website.yaml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
echo "Using input provided: $input"
|
||||
version="$input"
|
||||
fi
|
||||
|
||||
|
||||
echo "kimai_version=$version" >> $GITHUB_ENV
|
||||
|
||||
if [[ ! $version =~ ^2\.(0|[1-9]*)(0?)\.(0|[0-9]*)(0?)$ ]]; then
|
||||
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,8 +1,9 @@
|
||||
# some hosters require a htaccess to change the PHP version
|
||||
/public/.htaccess
|
||||
/.env-*
|
||||
/.idea/
|
||||
.DS_Store
|
||||
var/templates/
|
||||
|
||||
|
||||
# custom apache rules e.g. to deactivate ioncube loader
|
||||
/public/.user.ini
|
||||
@@ -14,6 +15,7 @@
|
||||
# YARN 2
|
||||
/.yarnrc.yml
|
||||
/.yarn
|
||||
/.pnp.*
|
||||
|
||||
# for keeping empty directories
|
||||
/config/packages/local.yaml
|
||||
@@ -21,6 +23,7 @@
|
||||
/var/dev/*
|
||||
/var/data/*
|
||||
/var/cache/*
|
||||
/var/share/*
|
||||
/var/invoices*
|
||||
/var/export*
|
||||
/var/log/*
|
||||
@@ -34,7 +37,6 @@
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
.env
|
||||
/public/bundles/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
@@ -45,9 +47,8 @@
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> friendsofphp/php-cs-fixer ###
|
||||
.php_cs
|
||||
.php_cs.cache
|
||||
.php-cs-fixer.cache
|
||||
/.php-cs-fixer.php
|
||||
/.php-cs-fixer.cache
|
||||
###< friendsofphp/php-cs-fixer ###
|
||||
|
||||
###> symfony/phpunit-bridge ###
|
||||
|
||||
@@ -8,6 +8,7 @@ file that was distributed with this source code.
|
||||
COMMENT;
|
||||
|
||||
$fixer = new PhpCsFixer\Config();
|
||||
$fixer->setUnsupportedPhpVersionAllowed(true);
|
||||
$fixer
|
||||
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
|
||||
->setRiskyAllowed(true)
|
||||
@@ -142,7 +143,7 @@ $fixer
|
||||
'whitespace_after_comma_in_array' => true,
|
||||
'yoda_style' => false,
|
||||
'ternary_to_null_coalescing' => true,
|
||||
'visibility_required' => ['elements' => [
|
||||
'modifier_keywords' => ['elements' => [
|
||||
'const',
|
||||
'method',
|
||||
'property',
|
||||
|
||||
@@ -10,7 +10,7 @@ Send your ideas, code reviews, pull requests and feature requests to help to imp
|
||||
- Make your changes in a new git branch, based on the latest code in `main`
|
||||
- Apply our code-style by running `composer codestyle-fix`
|
||||
- Run the static code analysis with `composer phpstan`
|
||||
- Verify everything still works with `composer tests-unit`
|
||||
- Verify everything still works with `composer tests`
|
||||
- Add tests for your changes
|
||||
|
||||
Further documentation can be found in the [developer documentation](https://www.kimai.org/documentation/developers.html).
|
||||
|
||||
11
README.md
11
README.md
@@ -21,13 +21,6 @@ authentication via SAML/LDAP/Database, two-factor authentication (2FA) with TOTP
|
||||
user/customer/project specific rates, advanced search & filtering, money and time budgets, advanced reporting, support for [plugins](https://www.kimai.org/store/)
|
||||
and so much more.
|
||||
|
||||
### Versions
|
||||
|
||||
There are two [versions](https://www.kimai.org/documentation/versions.html) of Kimai existing:
|
||||
|
||||
- [Version 2](https://github.com/kimai/kimai) — the current stable release (PHP 8.1+)
|
||||
- [Version 1](https://github.com/kimai/kimai/tree/1.x) — do **NOT** use, EOL since mid of 2023 (PHP 7.4)
|
||||
|
||||
### Links
|
||||
|
||||
- [Home](https://www.kimai.org) — Kimai project homepage
|
||||
@@ -36,7 +29,7 @@ There are two [versions](https://www.kimai.org/documentation/versions.html) of K
|
||||
|
||||
### Requirements
|
||||
|
||||
- PHP 8.1.3 minimum (support for PHP 8.2 and 8.3)
|
||||
- PHP 8.1.3 minimum with support for 8.2, 8.3, 8.4, 8.5
|
||||
- MariaDB or MySQL
|
||||
- A webserver and subdomain (subdirectory is not supported)
|
||||
- PHP extensions: `gd`, `intl`, `json`, `mbstring`, `pdo`, `tokenizer`, `xml`, `xsl`, `zip`
|
||||
@@ -51,7 +44,7 @@ There are two [versions](https://www.kimai.org/documentation/versions.html) of K
|
||||
|
||||
There are more documented ways for [on-premise hosting](https://www.kimai.org/documentation/chapter-on-premise.html).
|
||||
|
||||
And if you don't want to host Kimai, you can use [the Cloud version](https://www.kimai.cloud/) of it.
|
||||
And if you don't want to host Kimai, you can use the [Cloud version](https://www.kimai.cloud/) of it.
|
||||
|
||||
### Updating Kimai
|
||||
|
||||
|
||||
50
UPGRADING-2.md
Normal file
50
UPGRADING-2.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Upgrading Kimai - Version 2.x
|
||||
|
||||
_Make sure to create a backup before you start!_
|
||||
|
||||
Read the [updates documentation](https://www.kimai.org/documentation/updates.html) to find out how
|
||||
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.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)
|
||||
now requires the `charset` and `serverVersion` params, e.g.: `DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.5.8-MariaDB` (examples in `.env`).
|
||||
|
||||
## [2.0](https://github.com/kimai/kimai/releases/tag/2.0)
|
||||
|
||||
**!! This release requires minimum PHP version to 8.1 !!**
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- All plugins need to be updated: delete all previous version from your installation (`rm -r var/plugins/*`) before updating!
|
||||
- The `local.yaml` is not compatible with old version, remove it before the update and then re-create it after everything works
|
||||
- removed: configuring the `dashboard` is not supported any longer
|
||||
- removed: custom translation files via `theme.branding.translation`
|
||||
- removed: changing the plugin directory via `kimai.plugin_dir`
|
||||
|
||||
### Developer
|
||||
|
||||
Developer read the full documentation at [https://www.kimai.org/documentation/migration-v2.html](https://www.kimai.org/documentation/migration-v2.html).
|
||||
|
||||
- Invoice renderer and templates for XML, JSON and TEXT were moved to the [Extended invoicing plugin](https://www.kimai.org/store/invoice-bundle.html) (install if you use one of those)
|
||||
- Moved `company.docx` to [external repo](https://github.com/kimai/invoice-templates/tree/main/docx-company) (needs to be re-uploaded if you want to keep on using it!)
|
||||
- Role names are forced to be uppercase
|
||||
- Removed unused `public/avatars/` directory
|
||||
- Time-tracking mode `duration_only` was removed, existing installations will be switched to `duration_fixed_begin`
|
||||
- Removed Twig filters. You might have to replace them in your custom export/invoice templates:
|
||||
- `date_full` => `date_time`
|
||||
- `duration_decimal` => `duration(true)`
|
||||
- `currency` => `currency_name`
|
||||
- `country` => `country_name`
|
||||
- `language` => `language_name`
|
||||
- Removed support for custom translation files (use [TranslationBundle](https://www.kimai.org/store/translation-bundle.html) instead or write your own plugin)
|
||||
- Removed all 3rd party mailer packages, you need to install them manually (ONLY if you used a short syntax to configure the `MAILER_URL` in `.env`):
|
||||
- `composer require symfony/amazon-mailer`
|
||||
- `composer require symfony/google-mailer`
|
||||
- `composer require symfony/mailchimp-mailer`
|
||||
- `composer require symfony/mailgun-mailer`
|
||||
- `composer require symfony/postmark-mailer`
|
||||
- `composer require symfony/sendgrid-mailer`
|
||||
@@ -11,14 +11,22 @@ Perform EACH version specific task between your version and the new one, otherwi
|
||||
|
||||
**!! This release requires minimum PHP version 8.4 !!**
|
||||
|
||||
### Rename .env
|
||||
|
||||
Rename your file `.env` to `.env.local` or even better: move all variables to your webserver/container environment.
|
||||
|
||||
### Developer
|
||||
|
||||
Removed translations:
|
||||
- `action.edit`: use `edit` instead
|
||||
- `my.profile`: use `user_profile` instead
|
||||
- `stats.userAmountToday`: use `` instead
|
||||
- `stats.userAmountWeek`: use `` instead
|
||||
- `stats.userAmountMonth`: use `` instead
|
||||
- `stats.userAmountYear`: use `` instead
|
||||
- `stats.userAmountTotal`: use `` instead
|
||||
- `update_multiple`
|
||||
Do not use method chaining: all fluent interface, especially in Entities, are no longer supported.
|
||||
|
||||
- Require PHP 8.4
|
||||
- Bump to Symfony 7.4
|
||||
- Removed old API token `X-AUTH-USER` and `X-AUTH-TOKEN`
|
||||
- Removed `TimesheetConstraint` - use a normal `Constraint` as base class and attach the `#[App\Validator\Attribute\TimesheetConstraint]` attribute
|
||||
- Removed `ProjectConstraint` - use a `FormExtension` and attach your custom constraints
|
||||
- Interface `MetaTableTypeInterface` has new methods: `getSection()`, `setSection()`
|
||||
- Interface `ExportRendererInterface` has new methods: `getType()`, `isInternal()`
|
||||
- Interface `ExportableItem` has new methods: `getTags()`, `getBreak()`
|
||||
- Removed and renamed translations, most important `action.edit` => `edit`, `my.profile` => `user_profile`
|
||||
- Removed `User::isExportDecimal()`
|
||||
- Use duration format `HH:mm` in default PDF exports
|
||||
|
||||
51
UPGRADING.md
51
UPGRADING.md
@@ -1,50 +1,3 @@
|
||||
# Upgrading Kimai - Version 2.x
|
||||
# Upgrading Kimai
|
||||
|
||||
_Make sure to create a backup before you start!_
|
||||
|
||||
Read the [updates documentation](https://www.kimai.org/documentation/updates.html) to find out how
|
||||
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.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)
|
||||
now requires the `charset` and `serverVersion` params, e.g.: `DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.5.8-MariaDB` (examples in `.env`).
|
||||
|
||||
## [2.0](https://github.com/kimai/kimai/releases/tag/2.0)
|
||||
|
||||
**!! This release requires minimum PHP version to 8.1 !!**
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- All plugins need to be updated: delete all previous version from your installation (`rm -r var/plugins/*`) before updating!
|
||||
- The `local.yaml` is not compatible with old version, remove it before the update and then re-create it after everything works
|
||||
- removed: configuring the `dashboard` is not supported any longer
|
||||
- removed: custom translation files via `theme.branding.translation`
|
||||
- removed: changing the plugin directory via `kimai.plugin_dir`
|
||||
|
||||
### Developer
|
||||
|
||||
Developer read the full documentation at [https://www.kimai.org/documentation/migration-v2.html](https://www.kimai.org/documentation/migration-v2.html).
|
||||
|
||||
- Invoice renderer and templates for XML, JSON and TEXT were moved to the [Extended invoicing plugin](https://www.kimai.org/store/invoice-bundle.html) (install if you use one of those)
|
||||
- Moved `company.docx` to [external repo](https://github.com/kimai/invoice-templates/tree/main/docx-company) (needs to be re-uploaded if you want to keep on using it!)
|
||||
- Role names are forced to be uppercase
|
||||
- Removed unused `public/avatars/` directory
|
||||
- Time-tracking mode `duration_only` was removed, existing installations will be switched to `duration_fixed_begin`
|
||||
- Removed Twig filters. You might have to replace them in your custom export/invoice templates:
|
||||
- `date_full` => `date_time`
|
||||
- `duration_decimal` => `duration(true)`
|
||||
- `currency` => `currency_name`
|
||||
- `country` => `country_name`
|
||||
- `language` => `language_name`
|
||||
- Removed support for custom translation files (use [TranslationBundle](https://www.kimai.org/store/translation-bundle.html) instead or write your own plugin)
|
||||
- Removed all 3rd party mailer packages, you need to install them manually (ONLY if you used a short syntax to configure the `MAILER_URL` in `.env`):
|
||||
- `composer require symfony/amazon-mailer`
|
||||
- `composer require symfony/google-mailer`
|
||||
- `composer require symfony/mailchimp-mailer`
|
||||
- `composer require symfony/mailgun-mailer`
|
||||
- `composer require symfony/postmark-mailer`
|
||||
- `composer require symfony/sendgrid-mailer`
|
||||
The most recent version is Kimai 3, please read the changelog at [UPGRADING-3.md](UPGRADING-3.md).
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* https://gridstackjs.com
|
||||
* https://github.com/gridstack/gridstack.js/tree/master/doc
|
||||
*/
|
||||
require('gridstack/dist/gridstack.min.css');
|
||||
require('gridstack/dist/gridstack-extra.min.css');
|
||||
import { GridStack } from 'gridstack';
|
||||
global.GridStack = GridStack;
|
||||
5
assets/highlight.js
Normal file
5
assets/highlight.js
Normal file
@@ -0,0 +1,5 @@
|
||||
require('highlight.js/styles/github-dark.css');
|
||||
|
||||
const hljs = require('highlight.js/lib/common');
|
||||
|
||||
global.hljs = hljs;
|
||||
@@ -1,2 +1,4 @@
|
||||
|
||||
/**
|
||||
* @deprecated use invoice-pdf instead
|
||||
*/
|
||||
require('./sass/_invoice.scss');
|
||||
|
||||
@@ -43,6 +43,8 @@ import KimaiHotkeys from "./plugins/KimaiHotkeys";
|
||||
import KimaiRemoteModal from "./plugins/KimaiRemoteModal";
|
||||
import KimaiUser from "./plugins/KimaiUser";
|
||||
import KimaiAutocompleteTags from "./forms/KimaiAutocompleteTags";
|
||||
import KimaiMonthPicker from "./forms/KimaiMonthPicker";
|
||||
import KimaiSortable from "./plugins/KimaiSortable";
|
||||
|
||||
export default class KimaiLoader {
|
||||
|
||||
@@ -76,6 +78,7 @@ export default class KimaiLoader {
|
||||
kimai.registerPlugin(new KimaiTeamForm());
|
||||
kimai.registerPlugin(new KimaiCopyDataForm());
|
||||
kimai.registerPlugin(new KimaiDateNowForm());
|
||||
kimai.registerPlugin(new KimaiMonthPicker());
|
||||
kimai.registerPlugin(new KimaiForm());
|
||||
kimai.registerPlugin(new KimaiHotkeys());
|
||||
|
||||
@@ -90,6 +93,7 @@ export default class KimaiLoader {
|
||||
kimai.registerPlugin(new KimaiActiveRecords());
|
||||
kimai.registerPlugin(new KimaiAPILink('api-link'));
|
||||
kimai.registerPlugin(new KimaiMultiUpdateTable());
|
||||
kimai.registerPlugin(new KimaiSortable());
|
||||
kimai.registerPlugin(new KimaiThemeInitializer());
|
||||
|
||||
// notify all listeners that Kimai plugins can now be registered
|
||||
|
||||
@@ -43,7 +43,10 @@ export default class KimaiDateNowForm extends KimaiFormPlugin {
|
||||
const formElement = document.getElementById(linkTarget.dataset.target);
|
||||
if (!formElement.disabled) {
|
||||
formElement.value = this.getDateUtils().format(linkTarget.dataset.format, null);
|
||||
// this should usually work
|
||||
formElement.dispatchEvent(new Event('change', {bubbles: true}));
|
||||
// this is required for Litepicker to pick the new date (with autoRefresh option)
|
||||
formElement.dispatchEvent(new Event('keyup', {bubbles: true}));
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
@@ -62,13 +62,10 @@ export default class KimaiFormSelect extends KimaiFormTomselectPlugin {
|
||||
plugins.push('remove_button');
|
||||
}
|
||||
|
||||
/*
|
||||
const isOrdering = false;
|
||||
if (isOrdering) {
|
||||
plugins.push('caret_position');
|
||||
if (node.dataset['order'] !== undefined && node.dataset['order'] === '1') {
|
||||
//plugins.push('caret_position');
|
||||
plugins.push('drag_drop');
|
||||
}
|
||||
*/
|
||||
|
||||
let options = {
|
||||
// see https://github.com/orchidjs/tom-select/issues/543#issuecomment-1664342257
|
||||
@@ -83,8 +80,11 @@ export default class KimaiFormSelect extends KimaiFormTomselectPlugin {
|
||||
// if there are more than X entries, the other ones are hidden and can only be found
|
||||
// by typing some characters to trigger the internal option search
|
||||
// see App\Form\Type\TagsType::MAX_AMOUNT_SELECT
|
||||
// TODO make this value configurable with a data attribute
|
||||
maxOptions: 500,
|
||||
sortField:[{field: '$order'}, {field: '$score'}],
|
||||
// required so it works in table.responsive, but requires z-index 1056, because bootstrap modal would otherwise hide it
|
||||
dropdownParent: 'body',
|
||||
};
|
||||
|
||||
let render = {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
/*!
|
||||
* [KIMAI] KimaiFormPlugin: base class for all none ID plugin that handle forms
|
||||
* [KIMAI] KimaiFormTomselectPlugin: base form plugin for everyone using tomselect
|
||||
*/
|
||||
|
||||
import KimaiFormPlugin from './KimaiFormPlugin';
|
||||
|
||||
44
assets/js/forms/KimaiMonthPicker.js
Normal file
44
assets/js/forms/KimaiMonthPicker.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* [KIMAI] KimaiDatePicker: single date selects (currently unused)
|
||||
*/
|
||||
|
||||
import KimaiFormPlugin from "./KimaiFormPlugin";
|
||||
|
||||
export default class KimaiMonthPicker extends KimaiFormPlugin {
|
||||
|
||||
/**
|
||||
* @param {HTMLFormElement} form
|
||||
* @return boolean
|
||||
*/
|
||||
supportsForm(form) // eslint-disable-line no-unused-vars
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLFormElement} form
|
||||
*/
|
||||
activateForm(form)
|
||||
{
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type','month');
|
||||
|
||||
const notADateValue = 'not-a-month';
|
||||
input.setAttribute('value', notADateValue);
|
||||
|
||||
if (input.value === notADateValue) {
|
||||
const polyfills = form.querySelectorAll('a.input-month-polyfill');
|
||||
polyfills.forEach(i => i.classList.toggle('d-none'));
|
||||
const inputs = form.querySelectorAll('input[type="month"]');
|
||||
inputs.forEach(i => i.classList.toggle('d-none'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -144,6 +144,10 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
|
||||
|
||||
_parseBeginTime()
|
||||
{
|
||||
if (this._beginTime.value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
let newBeginTime = this._formatTimeForParsing(this._beginTime.value, this._beginTime.dataset['format']);
|
||||
if (newBeginTime !== this._beginTime.value) {
|
||||
this._beginTime.value = newBeginTime;
|
||||
@@ -153,6 +157,10 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
|
||||
|
||||
_parseEndTime()
|
||||
{
|
||||
if (this._endTime.value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
let newEndTime = this._formatTimeForParsing(this._endTime.value, this._endTime.dataset['format']);
|
||||
if (newEndTime !== this._endTime.value) {
|
||||
this._endTime.value = newEndTime;
|
||||
@@ -162,6 +170,10 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
|
||||
|
||||
_parseDuration()
|
||||
{
|
||||
if (this._duration.value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setDurationAsString(this._getParsedDuration());
|
||||
}
|
||||
|
||||
@@ -190,8 +202,8 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
|
||||
{
|
||||
let formatted = time.trim();
|
||||
|
||||
// replace dot with colon
|
||||
formatted = formatted.replace(/\./g, ':');
|
||||
// replace invalid separators with colon
|
||||
formatted = formatted.replace(/\.|;|,/g, ':');
|
||||
// uppercase 12-hour format
|
||||
formatted = formatted.replace(/am/i, 'AM');
|
||||
formatted = formatted.replace(/pm/i, 'PM');
|
||||
@@ -446,14 +458,14 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
|
||||
|
||||
/**
|
||||
* Ruleset:
|
||||
* - invalid duration => skip
|
||||
* - invalid or empty duration => skip
|
||||
* - if begin and end are empty: set begin to now and end to duration
|
||||
* - if begin is empty and end is not empty: set begin to end minus duration
|
||||
* - if begin is not empty and end is empty and duration is > 0 (running records = 0): set end to begin plus duration
|
||||
*/
|
||||
_changedDuration()
|
||||
{
|
||||
if (!this._isDurationConnected()) {
|
||||
if (!this._isDurationConnected() || this._duration.value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,17 @@
|
||||
*/
|
||||
|
||||
/*!
|
||||
* [KIMAI] KimaiRecentActivities: responsible to reload the users recent activities
|
||||
* [KIMAI] KimaiRemoteModal: load remote content (without forms) into a modal
|
||||
*/
|
||||
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
import { Modal } from 'bootstrap';
|
||||
|
||||
/**
|
||||
* Use like this:
|
||||
* <a href="{{ path('your-route') }}" class="remote-modal-load" data-modal-id="remote_modal" data-modal-class="p-0" data-modal-title="Some title" title="Some title">Modal</a>
|
||||
* <a href="{{ path('your-route') }}" class="remote-modal-load" data-modal-title="Some title" title="Some title">Modal</a>
|
||||
*/
|
||||
export default class KimaiRemoteModal extends KimaiPlugin {
|
||||
|
||||
constructor()
|
||||
@@ -39,38 +44,6 @@ export default class KimaiRemoteModal extends KimaiPlugin {
|
||||
for (let link of document.querySelectorAll(this._selector)) {
|
||||
link.addEventListener('click', this.handle);
|
||||
}
|
||||
|
||||
document.addEventListener('kimai.closeRemoteModal', () => { this._hide(); });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} element
|
||||
* @private
|
||||
*/
|
||||
_initElement(element)
|
||||
{
|
||||
for (let link of element.querySelectorAll('a.remote-modal-reload')) {
|
||||
link.addEventListener('click', this.handle);
|
||||
}
|
||||
}
|
||||
|
||||
_hide()
|
||||
{
|
||||
this._getModal().hide();
|
||||
}
|
||||
|
||||
_getModalElement()
|
||||
{
|
||||
return document.getElementById('remote_modal');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Modal}
|
||||
* @private
|
||||
*/
|
||||
_getModal()
|
||||
{
|
||||
return Modal.getOrCreateInstance(this._getModalElement());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,21 +58,33 @@ export default class KimaiRemoteModal extends KimaiPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
let modalSelector = 'remote_modal';
|
||||
if (element.dataset['modalId'] !== undefined) {
|
||||
modalSelector = element.dataset['modalId'];
|
||||
}
|
||||
const modalElement = document.getElementById(modalSelector);
|
||||
if (modalElement === null) {
|
||||
console.log('Could not find modal with ID: ' + modalSelector);
|
||||
}
|
||||
|
||||
return response.text().then(html => {
|
||||
const newFormHtml = document.createElement('div');
|
||||
newFormHtml.classList.add('modal-body');
|
||||
newFormHtml.classList.add('p-0');
|
||||
newFormHtml.innerHTML = html;
|
||||
const modalBody = document.createElement('div');
|
||||
modalBody.classList.add('modal-body');
|
||||
if (element.dataset['modalClass'] !== undefined) {
|
||||
modalBody.classList.add(element.dataset['modalClass']);
|
||||
}
|
||||
modalBody.innerHTML = html;
|
||||
|
||||
this._initElement(newFormHtml);
|
||||
|
||||
const modal = this._getModalElement();
|
||||
modal.querySelector('.modal-body').replaceWith(newFormHtml);
|
||||
if (element.dataset['modalTitle'] !== undefined) {
|
||||
modal.querySelector('.modal-title').textContent = element.dataset['modalTitle'];
|
||||
for (let link of modalBody.querySelectorAll('a.remote-modal-reload')) {
|
||||
link.addEventListener('click', this.handle);
|
||||
}
|
||||
|
||||
this._getModal().show();
|
||||
modalElement.querySelector('.modal-body').replaceWith(modalBody);
|
||||
if (element.dataset['modalTitle'] !== undefined) {
|
||||
modalElement.querySelector('.modal-title').textContent = element.dataset['modalTitle'];
|
||||
}
|
||||
|
||||
Modal.getOrCreateInstance(modalElement).show();
|
||||
});
|
||||
})
|
||||
.catch((reason) => {
|
||||
|
||||
51
assets/js/plugins/KimaiSortable.js
Normal file
51
assets/js/plugins/KimaiSortable.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
* [KIMAI] KimaiSortable: allow sorting of HTML elements
|
||||
*/
|
||||
|
||||
import KimaiPlugin from "../KimaiPlugin";
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
export default class KimaiSortable extends KimaiPlugin {
|
||||
|
||||
getId() {
|
||||
return 'sortable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable sorting on the given selector
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @returns {true}
|
||||
*/
|
||||
toggle(element) {
|
||||
if (!element instanceof HTMLElement) {
|
||||
console.error('Given element is invalid');
|
||||
return false;
|
||||
}
|
||||
|
||||
let sortable = Sortable.get(element);
|
||||
if (sortable === undefined || sortable === null) {
|
||||
Sortable.create(element);
|
||||
return true;
|
||||
}
|
||||
|
||||
sortable.destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
toArray(element) {
|
||||
let sortable = Sortable.get(element);
|
||||
if (sortable === undefined || sortable === null) {
|
||||
return null;
|
||||
}
|
||||
return sortable.toArray();
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export default class KimaiThemeInitializer extends KimaiPlugin {
|
||||
}
|
||||
|
||||
const temp = document.createElement('div');
|
||||
temp.innerHTML = '<div class="overlay"><div class="fas fa-sync fa-spin"></div></div>';
|
||||
temp.innerHTML = '<div class="overlay"><div class="progress progress-sm"><div class="progress-bar progress-bar-indeterminate"></div></div></div>';
|
||||
this.overlay = temp.firstElementChild;
|
||||
document.querySelector(container).append(this.overlay);
|
||||
});
|
||||
|
||||
@@ -49,6 +49,7 @@ import enGbLocale from '@fullcalendar/core/locales/en-gb';
|
||||
import enUsLocale from '@fullcalendar/core/locales/en-gb';
|
||||
import KimaiColor from './KimaiColor';
|
||||
import KimaiContextMenu from "./KimaiContextMenu";
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export default class KimaiCalendar {
|
||||
|
||||
@@ -72,21 +73,6 @@ export default class KimaiCalendar {
|
||||
/** @type {KimaiAlert} ALERT */
|
||||
const ALERT = this.kimai.getPlugin('alert');
|
||||
|
||||
let initialView = 'dayGridMonth';
|
||||
switch (options['initialView']) {
|
||||
case 'month':
|
||||
initialView = 'dayGridMonth';
|
||||
break;
|
||||
case 'agendaWeek':
|
||||
case 'week':
|
||||
initialView = 'timeGridWeek';
|
||||
break;
|
||||
case 'agendaDay':
|
||||
case 'day':
|
||||
initialView = 'timeGridDay';
|
||||
break;
|
||||
}
|
||||
|
||||
// Instead of using "buttonIcons" the theme needs to be adjusted directly
|
||||
// https://fullcalendar.io/docs/buttonIcons
|
||||
BootstrapTheme.prototype.classes = {
|
||||
@@ -119,7 +105,8 @@ export default class KimaiCalendar {
|
||||
esLocale, euLocale, faLocale, fiLocale, frLocale, heLocale, hrLocale, huLocale, itLocale, jaLocale, koLocale,
|
||||
nbLocale, nlLocale, plLocale, ptLocale, ptBrLocale, roLocale, ruLocale, skLocale, svLocale, trLocale, zhLocale, viLocale ],
|
||||
plugins: [ bootstrap5Plugin, dayGridPlugin, timeGridPlugin, googlePlugin, iCalendarPlugin, interactionPlugin ],
|
||||
initialView: initialView,
|
||||
initialView: this.toInternalViewName(this.options['initialView']),
|
||||
initialDate: this.options['initialDate'],
|
||||
// https://fullcalendar.io/docs/theming
|
||||
themeSystem: 'bootstrap5',
|
||||
// https://fullcalendar.io/docs/headerToolbar
|
||||
@@ -154,8 +141,9 @@ export default class KimaiCalendar {
|
||||
slotMinTime: this.options['timeframeBegin'] + ':00',
|
||||
slotMaxTime: this.options['timeframeEnd'] === '23:59' ? '24:00:00' : (this.options['timeframeEnd'] + ':59'),
|
||||
|
||||
// auto calculation seems to do the better job, therefor deactivated
|
||||
//slotLabelInterval: this.options['slotDuration'],
|
||||
// deactivate for auto calculation, which does a good job.
|
||||
// but 1h seems to be a "normal distance" for calendar apps (like Google and Apple)
|
||||
slotLabelInterval: '1:00',
|
||||
|
||||
// how long should entries look like when they don't have an end
|
||||
defaultTimedEventDuration: this.options['slotDuration'],
|
||||
@@ -171,14 +159,20 @@ export default class KimaiCalendar {
|
||||
// once we can configure working days
|
||||
// hiddenDays: [ 2, 4 ]
|
||||
|
||||
// when we support holidays and other full day events
|
||||
// allDaySlot: false,
|
||||
// dropAccept
|
||||
|
||||
dayMaxEventRows: true,
|
||||
eventMaxStack: this.options['dayLimit'],
|
||||
dayMaxEvents: this.options['dayLimit'],
|
||||
|
||||
// the callbacks "viewDidMount" and "viewWillUnmount" are only called when switching between month and others, not between week and day
|
||||
datesSet: (dateInfo) => {
|
||||
document.dispatchEvent(new CustomEvent('kimai.calendar.changeDate', {detail: {
|
||||
view: this.toExternalViewName(dateInfo.view.type),
|
||||
date: dateInfo.start.toISOString().split('T')[0],
|
||||
}}));
|
||||
},
|
||||
|
||||
views: {
|
||||
dayGrid: {
|
||||
dayMaxEventRows: this.options['dayLimit']
|
||||
@@ -266,6 +260,12 @@ export default class KimaiCalendar {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// called after all events of one source were set, so this can
|
||||
// and will be called multiple times before the calendar is initialized
|
||||
eventsSet: (events) => {
|
||||
this._renderDayAndWeekSum(this.getCalendar().getCurrentData().viewSpec.type, events);
|
||||
}
|
||||
};
|
||||
|
||||
// ============= DRAG & DROP =============
|
||||
@@ -528,6 +528,44 @@ export default class KimaiCalendar {
|
||||
return (event.source.id.indexOf('kimai-') === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} viewName
|
||||
* @returns {string}
|
||||
*/
|
||||
toExternalViewName(viewName) {
|
||||
switch(viewName) {
|
||||
case 'timeGridDay':
|
||||
return 'day';
|
||||
case 'timeGridWeek':
|
||||
return 'week';
|
||||
case 'dayGridMonth':
|
||||
default:
|
||||
return 'month';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} viewName
|
||||
* @returns {string}
|
||||
*/
|
||||
toInternalViewName(viewName) {
|
||||
switch(viewName) {
|
||||
case 'day':
|
||||
case 'agendaDay':
|
||||
case 'timeGridDay':
|
||||
return 'timeGridDay';
|
||||
case 'week':
|
||||
case 'agendaWeek':
|
||||
case 'timeGridWeek':
|
||||
return 'timeGridWeek';
|
||||
case 'month':
|
||||
case 'agendaMonth':
|
||||
case 'dayGridMonth':
|
||||
default:
|
||||
return 'dayGridMonth';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @return {boolean}
|
||||
@@ -687,4 +725,73 @@ export default class KimaiCalendar {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* @param {string} view
|
||||
* @param {EventApi[]} events
|
||||
* @private
|
||||
*/
|
||||
_renderDayAndWeekSum(view, events) {
|
||||
if (view === 'dayGridMonth') {
|
||||
// currently we do not display totals in month view
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {KimaiDateUtils} DATES */
|
||||
const DATES = this.kimai.getPlugin('date');
|
||||
|
||||
const durations = {};
|
||||
|
||||
if (view === 'timeGridWeek') {
|
||||
// make sure we have an entry for every day of the week, even days without timesheets
|
||||
document.querySelectorAll(`th.fc-col-header-cell[data-date]`).forEach(cell => {
|
||||
durations[cell.dataset.date] = 0;
|
||||
});
|
||||
}
|
||||
|
||||
events.forEach(item => {
|
||||
const start = DateTime.fromJSDate(item.start).toUTC();
|
||||
const dateStr = start.toFormat('yyyy-MM-dd');
|
||||
|
||||
if (!durations[dateStr]) {
|
||||
durations[dateStr] = 0;
|
||||
}
|
||||
|
||||
// absences or public holidays are all day
|
||||
if (item.end !== null) {
|
||||
const end = DateTime.fromJSDate(item.end).toUTC();
|
||||
const duration = end.diff(start, 'hours').as('seconds');
|
||||
durations[dateStr] += duration;
|
||||
}
|
||||
});
|
||||
|
||||
const dailyTotals = document.querySelectorAll('.fc-dailytotal');
|
||||
dailyTotals.forEach(element => element.remove());
|
||||
for (const dateValue in durations) {
|
||||
const durationValue = durations[dateValue];
|
||||
|
||||
if (view === 'timeGridWeek') { // this is the week view
|
||||
const headerCells = document.querySelectorAll(`th.fc-col-header-cell[data-date="${dateValue}"]`);
|
||||
|
||||
headerCells.forEach(cell => {
|
||||
const newElement = document.createElement('div');
|
||||
newElement.classList.add('fc-dailytotal');
|
||||
newElement.textContent = DATES.formatSeconds(durationValue);
|
||||
cell.appendChild(newElement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// this is the day view
|
||||
if (view === 'timeGridDay') {
|
||||
const dayEl = document.querySelector('th.fc-day');
|
||||
const dayDate = dayEl.dataset.date;
|
||||
const dayTotal = document.querySelectorAll('.fc-dailytotal');
|
||||
dayTotal.forEach(element => element.remove());
|
||||
|
||||
const newElement = document.createElement('div');
|
||||
newElement.classList.add('fc-dailytotal');
|
||||
newElement.textContent = DATES.formatSeconds(durations[dayDate]);
|
||||
dayEl.appendChild(newElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,20 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
$modal-backdrop-opacity: 0.6 !default;
|
||||
$modal-backdrop-opacity: 0.7 !default;
|
||||
|
||||
@import "~@tabler/core/scss/config";
|
||||
|
||||
/*
|
||||
THIS DOES NOT WORK AS EXPECTED, AS TABLER IS NOT REGENERATED HERE.
|
||||
SO WE CANNOT CHANGE VARIABLES, LIKE THE ABOVE...
|
||||
*/
|
||||
@import '~@tabler/core/dist/css/tabler.rtl.css';
|
||||
@import '~@tabler/core/dist/css/tabler-themes.rtl.css';
|
||||
|
||||
@import '~tom-select/dist/scss/tom-select';
|
||||
@import '~tom-select/dist/scss/tom-select.bootstrap5';
|
||||
|
||||
@import '~@tabler/core/dist/css/tabler-vendors.rtl.css';
|
||||
|
||||
/**
|
||||
@@ -23,21 +30,6 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts/";
|
||||
@import '~@fortawesome/fontawesome-free/scss/solid';
|
||||
@import '~@fortawesome/fontawesome-free/scss/brands';
|
||||
|
||||
/**
|
||||
* We need that, to compile the rest of the file
|
||||
*/
|
||||
@import "~bootstrap/scss/functions";
|
||||
|
||||
@import "~@tabler/core/src/scss/variables";
|
||||
@import "~@tabler/core/src/scss/variables-dark";
|
||||
@import "~@tabler/core/src/scss/mixins";
|
||||
|
||||
@import "~bootstrap/scss/variables";
|
||||
@import "~bootstrap/scss/variables-dark";
|
||||
@import "~bootstrap/scss/maps";
|
||||
@import "~bootstrap/scss/mixins";
|
||||
@import "~bootstrap/scss/utilities";
|
||||
|
||||
/**
|
||||
* Kimai related stuff
|
||||
*/
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
$modal-backdrop-opacity: 0.6 !default;
|
||||
$modal-backdrop-opacity: 0.7 !default;
|
||||
|
||||
@import '~@tabler/core/src/scss/tabler';
|
||||
@import '~@tabler/core/src/scss/vendor/litepicker';
|
||||
@import '~@tabler/core/src/scss/vendor/tom-select';
|
||||
@import '~@tabler/core/scss/tabler';
|
||||
@import '~@tabler/core/scss/tabler-themes';
|
||||
@import '~@tabler/core/scss/vendor/litepicker';
|
||||
|
||||
@import '~tom-select/dist/scss/tom-select.bootstrap5';
|
||||
@import '~@tabler/core/scss/vendor/tom-select';
|
||||
|
||||
/**
|
||||
* Everyone needs icons
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
@import "modal";
|
||||
@import "progressbar";
|
||||
@import "avatar";
|
||||
@import "theme-dark";
|
||||
@import "pages";
|
||||
@import "weekly-hours";
|
||||
@import "tabler-fixes";
|
||||
@import "help";
|
||||
@import "chart";
|
||||
@import "rtl";
|
||||
@import "dashboard";
|
||||
|
||||
7
assets/sass/chart.scss
Normal file
7
assets/sass/chart.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
div.chart-container {
|
||||
position: relative;
|
||||
canvas.chartjs {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
@@ -93,3 +93,19 @@ table.dataTable thead > tr > th.hw-min {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown {
|
||||
blockquote {
|
||||
p {
|
||||
/* Parsedown wraps content in a <p> like <blockquote><p> */
|
||||
margin: 0;
|
||||
}
|
||||
/* Tabler doesn't style blockquotes */
|
||||
border-left: 15px var(--tblr-border-style) var(--tblr-border-color);
|
||||
background-color: var(--tblr-bg-surface-secondary);
|
||||
}
|
||||
pre code.hljs {
|
||||
padding: 0;
|
||||
background-color: var(--tblr-bg-surface-dark);
|
||||
}
|
||||
}
|
||||
3
assets/sass/dashboard.scss
Normal file
3
assets/sass/dashboard.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
section.dashboard {
|
||||
.widget { max-height: 600px; }
|
||||
}
|
||||
@@ -55,4 +55,19 @@ fieldset > .mb-3.row:last-child {
|
||||
|
||||
.form-fieldset-light:last-child {
|
||||
border:none;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
.dropdown-action {
|
||||
visibility: hidden;
|
||||
opacity: 0.5;
|
||||
}
|
||||
&:hover {
|
||||
.dropdown-action {
|
||||
visibility: visible;
|
||||
&:hover {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,6 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1021;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
> .fas {
|
||||
position: absolute;
|
||||
top: 50vh;
|
||||
left: 50vw;
|
||||
margin-left: -15px;
|
||||
margin-top: -15px;
|
||||
color: #000;
|
||||
font-size: 30px;
|
||||
}
|
||||
background-color: var(--tblr-backdrop-bg);
|
||||
opacity: $modal-backdrop-opacity;
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
----------- This file contains rules that only page to certain pages -----------
|
||||
*/
|
||||
|
||||
/* weekly working times AKA quick-entries form */
|
||||
section.quick-entry-page {
|
||||
|
||||
#quick_entry_form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* shrinks the dropdown elements for project and activity to fit into the table, instead of adjusting to the longest option element */
|
||||
.ts-wrapper {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
/* make the duration field and dropdown smaller */
|
||||
#quick_entry_box {
|
||||
.duration-widget {
|
||||
.input-group {
|
||||
min-width: 85px;
|
||||
}
|
||||
.btn-duration-preset {
|
||||
padding: 7px 8px 7px 5px;
|
||||
}
|
||||
input {
|
||||
padding: 7px 3px 7px 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,11 @@
|
||||
.content-wrapper {
|
||||
margin: 0!important;
|
||||
}
|
||||
section.content {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
span i.dot {
|
||||
display: none;
|
||||
margin-right: 0!important;
|
||||
}
|
||||
.float-help {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
|
||||
/* bootstrap thead.sticky-top has z-index 1020 and tomselect dropdowns often hide behind that */
|
||||
/*
|
||||
1. bootstrap thead.sticky-top has z-index 1020 and tomselect dropdowns often hide behind that: use 1021
|
||||
2. bootstrap modal uses 1055, tomselect with dropdownParent: 'body' (required for use in table.responsive, e.g quick.entry on small devices) needs 1056, so it works in modals
|
||||
*/
|
||||
.ts-dropdown {
|
||||
z-index: 1021;
|
||||
z-index: 1056;
|
||||
|
||||
}
|
||||
|
||||
/* Tabler defines a min-width of 7rem, what makes many input fields become a two-line field
|
||||
|
||||
@@ -17,16 +17,6 @@ fieldset.form-fieldset > legend {
|
||||
--tblr-dropdown-divider-margin-y: .5rem;
|
||||
}
|
||||
|
||||
/* fix translucent scroll border (e.g. timesheet modal duration dropdown) - see https://github.com/tabler/tabler/issues/1606 */
|
||||
.dropdown-menu {
|
||||
background-clip: border-box;
|
||||
}
|
||||
|
||||
/* hide empty fieldset - see https://github.com/tabler/tabler/issues/1650 */
|
||||
fieldset:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host,:root,[data-bs-theme=light] {
|
||||
/* remove the shadow from all form element groups */
|
||||
--tblr-box-shadow-input: none;
|
||||
@@ -43,20 +33,19 @@ fieldset:empty {
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
/* fixes https://github.com/tabler/tabler/issues/1974 */
|
||||
.litepicker .container__days .day-item.is-in-range {
|
||||
--litepicker-is-in-range-color: var(--tblr-primary-text-emphasis);
|
||||
}
|
||||
}
|
||||
|
||||
/* fixes contrast of batch-update checkboxes - https://github.com/kimai/kimai/issues/5146 */
|
||||
.multiupdater[type=checkbox]
|
||||
{
|
||||
.multiupdater[type=checkbox] {
|
||||
--tblr-border-color-translucent: rgba(4, 32, 69, .2);
|
||||
}
|
||||
|
||||
/* fixes height for long tags https://github.com/kimai/kimai/issues/5169 */
|
||||
.tag {
|
||||
--tblr-tag-height: unset;
|
||||
}
|
||||
|
||||
/* Page title (e.g. Dashboard) does not work properly in navbar, just outside */
|
||||
.navbar {
|
||||
.page-title {
|
||||
color: var(--tblr-body-color);
|
||||
}
|
||||
}
|
||||
@@ -17,22 +17,18 @@ table.dataTable {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
thead .sorting_asc,
|
||||
thead .sorting_desc {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
thead .sorting:after,
|
||||
thead .sorting_asc:after,
|
||||
thead .sorting_desc:after {
|
||||
padding-left: 5px;
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-family: 'Font Awesome\ 6 Free';
|
||||
opacity: 0.5;
|
||||
font-size: 12px;
|
||||
font-weight: 700
|
||||
}
|
||||
thead .sorting:after {
|
||||
opacity: 0.2;
|
||||
content: "\f0dc"; /* sort */
|
||||
content: "\f0dc";
|
||||
}
|
||||
thead .sorting_asc:after {
|
||||
content: "\f077";
|
||||
@@ -42,7 +38,7 @@ table.dataTable {
|
||||
}
|
||||
|
||||
thead > tr > th {
|
||||
vertical-align: top;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -92,7 +88,7 @@ table.dataTable {
|
||||
&.summary td {
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
background-color: var(--tblr-bg-surface-secondary);
|
||||
background-color: var(--tblr-secondary-lt);
|
||||
}
|
||||
th.multiCheckbox {
|
||||
width: 15px;
|
||||
@@ -135,14 +131,6 @@ table.dataTable {
|
||||
}
|
||||
}
|
||||
|
||||
/* Quick entry form */
|
||||
.form-dataTable {
|
||||
table.dataTable {
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table.table-hover {
|
||||
tr {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
@include color-mode(dark, true) {
|
||||
$dark-font-opacity: 0.6;
|
||||
|
||||
/*
|
||||
--tblr-body-bg: #1e222b;
|
||||
--tblr-bg-surface-tertiary: #1e222b;
|
||||
--tblr-bg-surface: #1e222b;
|
||||
--tblr-bg-surface-secondary: #17202b;
|
||||
//--tblr-muted: #d9dcdf;
|
||||
*/
|
||||
|
||||
.table thead th {
|
||||
color: inherit;
|
||||
opacity: $dark-font-opacity;
|
||||
}
|
||||
table.dataTable {
|
||||
thead .sorting:after {
|
||||
opacity: $dark-font-opacity;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
|
||||
> .fas {
|
||||
color: var(--tblr-white);
|
||||
}
|
||||
}
|
||||
/* Fix tabs pills (e.g. user profile) */
|
||||
.nav {
|
||||
--tblr-nav-link-color: inherit;
|
||||
opacity: $dark-font-opacity;
|
||||
}
|
||||
.datagrid-title {
|
||||
color: inherit;
|
||||
opacity: $dark-font-opacity;
|
||||
}
|
||||
.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
178
assets/sass/weekly-hours.scss
Normal file
178
assets/sass/weekly-hours.scss
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* This file contains rules for the "weekly-hours" AKA "quick-entries" form */
|
||||
|
||||
.quick-entry-page {
|
||||
|
||||
.form-dataTable {
|
||||
table.dataTable {
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#quick_entry_form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ts-wrapper {
|
||||
min-height: 36px;
|
||||
max-height: 36px;
|
||||
max-width: 150px;
|
||||
|
||||
.ts-control {
|
||||
white-space: nowrap;
|
||||
.item {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table tbody td:not(.total) {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.duration-widget input.duration-input {
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
/* make the duration field and dropdown smaller */
|
||||
#quick_entry_box {
|
||||
.duration-widget {
|
||||
.input-group {
|
||||
max-width: 50px;
|
||||
min-width: 50px;
|
||||
}
|
||||
.btn-duration-preset {
|
||||
display: none;
|
||||
min-width: 30px;
|
||||
}
|
||||
input.duration-input {
|
||||
padding: 7px 5px;
|
||||
max-width: 50px;
|
||||
border-bottom-right-radius: var(--tblr-border-radius);
|
||||
border-top-right-radius: var(--tblr-border-radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* make sure dropdown never hides behind table responsive */
|
||||
.dropdown-menu {
|
||||
z-index: 1056;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
.quick-entry-page {
|
||||
.ts-wrapper {
|
||||
max-width: 170px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.quick-entry-page {
|
||||
.ts-wrapper {
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1100px) {
|
||||
.quick-entry-page {
|
||||
.ts-wrapper {
|
||||
max-width: 160px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.quick-entry-page {
|
||||
.ts-wrapper {
|
||||
max-width: 165px;
|
||||
}
|
||||
.duration-widget {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1250px) {
|
||||
.quick-entry-page {
|
||||
#quick_entry_box {
|
||||
.duration-widget {
|
||||
.input-group {
|
||||
max-width: 85px;
|
||||
min-width: 75px;
|
||||
}
|
||||
.btn-duration-preset {
|
||||
display: flex;
|
||||
}
|
||||
input.duration-input {
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1300px) {
|
||||
.quick-entry-page {
|
||||
#quick_entry_box {
|
||||
.duration-widget {
|
||||
input.duration-input {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ts-wrapper {
|
||||
max-width: 190px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1400px) {
|
||||
.quick-entry-page {
|
||||
.ts-wrapper {
|
||||
max-width: 235px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1500px) {
|
||||
.quick-entry-page {
|
||||
.ts-wrapper {
|
||||
max-width: 290px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) {
|
||||
.quick-entry-page {
|
||||
.ts-wrapper {
|
||||
max-width: 340px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1800px) {
|
||||
.quick-entry-page {
|
||||
.ts-wrapper {
|
||||
max-width: 450px;
|
||||
}
|
||||
#quick_entry_box {
|
||||
.duration-widget {
|
||||
.input-group {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ use App\ConsoleApplication;
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||
if (!is_dir(dirname(__DIR__).'/vendor')) {
|
||||
throw new LogicException('Dependencies are missing. Try running "composer install".');
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "8.1.*||8.2.*||8.3.*||8.4.*",
|
||||
"php": "8.4.*||8.5.*",
|
||||
"ext-gd": "*",
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
@@ -29,51 +29,54 @@
|
||||
"composer/semver": "^3.3",
|
||||
"doctrine/doctrine-bundle": "^2.7",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.3",
|
||||
"doctrine/orm": "^2.8",
|
||||
"doctrine/orm": "^3.0",
|
||||
"easybill/zugferd-php": "^2.1",
|
||||
"endroid/qr-code": "^4.8",
|
||||
"erusev/parsedown": "^1.6",
|
||||
"friendsofsymfony/rest-bundle": "^3.0",
|
||||
"endroid/qr-code": "^6.0",
|
||||
"parsedown/parsedown": "^1.6",
|
||||
"friendsofsymfony/rest-bundle": "3.9.0-beta1",
|
||||
"gedmo/doctrine-extensions": "^3.6",
|
||||
"horstoeko/zugferd": "^1.0",
|
||||
"horstoeko/zugferdublbridge": "^1.0",
|
||||
"jms/serializer-bundle": "^5.0",
|
||||
"kevinpapst/tabler-bundle": "^1.4",
|
||||
"kevinpapst/tabler-bundle": "^2.0",
|
||||
"league/csv": "^9.4",
|
||||
"mpdf/mpdf": "^8.0",
|
||||
"nelmio/api-doc-bundle": "^5.0",
|
||||
"nelmio/cors-bundle": "^2.0",
|
||||
"onelogin/php-saml": "^4.0",
|
||||
"openspout/openspout": "^4.0",
|
||||
"pagerfanta/pagerfanta": "^3.0",
|
||||
"pagerfanta/pagerfanta": "^4.0",
|
||||
"phpoffice/phpspreadsheet": "^2.0",
|
||||
"phpoffice/phpword": "^1.0",
|
||||
"psr/container": "^2.0",
|
||||
"psr/log": "^3.0",
|
||||
"scheb/2fa-backup-code": "^6.2",
|
||||
"scheb/2fa-bundle": "^6.2",
|
||||
"scheb/2fa-totp": "^6.2",
|
||||
"symfony/asset": "^6.0",
|
||||
"symfony/console": "^6.0",
|
||||
"symfony/dotenv": "^6.0",
|
||||
"symfony/expression-language": "^6.0",
|
||||
"scheb/2fa-backup-code": "^7.11",
|
||||
"scheb/2fa-bundle": "^7.11",
|
||||
"scheb/2fa-totp": "^7.11",
|
||||
"symfony/asset": "^7.0",
|
||||
"symfony/console": "^7.0",
|
||||
"symfony/dependency-injection": "^7.0",
|
||||
"symfony/dotenv": "^7.0",
|
||||
"symfony/expression-language": "^7.0",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "^6.0",
|
||||
"symfony/framework-bundle": "^6.0",
|
||||
"symfony/http-client": "^6.0",
|
||||
"symfony/intl": "^6.0",
|
||||
"symfony/mailer": "^6.0",
|
||||
"symfony/mime": "^6.0",
|
||||
"symfony/form": "^7.0",
|
||||
"symfony/framework-bundle": "^7.0",
|
||||
"symfony/http-client": "^7.0",
|
||||
"symfony/intl": "^7.0",
|
||||
"symfony/mailer": "^7.0",
|
||||
"symfony/mime": "^7.0",
|
||||
"symfony/monolog-bundle": "^3.4",
|
||||
"symfony/process": "^6.0",
|
||||
"symfony/rate-limiter": "^6.0",
|
||||
"symfony/runtime": "^6.0",
|
||||
"symfony/security-bundle": "^6.0",
|
||||
"symfony/security-csrf": "^6.0",
|
||||
"symfony/serializer": "^6.0",
|
||||
"symfony/translation": "^6.0",
|
||||
"symfony/twig-bundle": "^6.0",
|
||||
"symfony/validator": "^6.0",
|
||||
"symfony/process": "^7.0",
|
||||
"symfony/rate-limiter": "^7.0",
|
||||
"symfony/runtime": "^7.0",
|
||||
"symfony/security-bundle": "^7.0",
|
||||
"symfony/security-csrf": "^7.0",
|
||||
"symfony/serializer": "^7.0",
|
||||
"symfony/translation": "^7.0",
|
||||
"symfony/twig-bundle": "^7.0",
|
||||
"symfony/validator": "^7.0",
|
||||
"symfony/webpack-encore-bundle": "^2.0",
|
||||
"symfony/yaml": "^6.0",
|
||||
"symfony/yaml": "^7.0",
|
||||
"twig/cssinliner-extra": "^3.0",
|
||||
"twig/extra-bundle": "^3.0",
|
||||
"twig/inky-extra": "^3.0",
|
||||
@@ -93,14 +96,14 @@
|
||||
"phpstan/phpstan-strict-rules": "^2.0",
|
||||
"phpstan/phpstan-symfony": "^2.0",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"symfony/browser-kit": "^6.0",
|
||||
"symfony/css-selector": "^6.0",
|
||||
"symfony/debug-bundle": "^6.0",
|
||||
"symfony/dom-crawler": "^6.0",
|
||||
"symfony/phpunit-bridge": "^6.0",
|
||||
"symfony/stopwatch": "^6.0",
|
||||
"symfony/var-dumper": "^6.0",
|
||||
"symfony/web-profiler-bundle": "^6.0"
|
||||
"symfony/browser-kit": "^7.0",
|
||||
"symfony/css-selector": "^7.0",
|
||||
"symfony/debug-bundle": "^7.0",
|
||||
"symfony/dom-crawler": "^7.0",
|
||||
"symfony/phpunit-bridge": "^7.0",
|
||||
"symfony/stopwatch": "^7.0",
|
||||
"symfony/var-dumper": "^7.0",
|
||||
"symfony/web-profiler-bundle": "^7.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
@@ -115,7 +118,7 @@
|
||||
},
|
||||
"optimize-autoloader": true,
|
||||
"platform": {
|
||||
"php": "8.1.3"
|
||||
"php": "8.4.4"
|
||||
},
|
||||
"preferred-install": {
|
||||
"*": "dist"
|
||||
@@ -142,6 +145,9 @@
|
||||
"symfony/polyfill-intl-idn": "*",
|
||||
"symfony/polyfill-intl-normalizer": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-php84": "*",
|
||||
"symfony/polyfill-php83": "*",
|
||||
"symfony/polyfill-php82": "*",
|
||||
"symfony/polyfill-php81": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
@@ -207,7 +213,7 @@
|
||||
"symfony": {
|
||||
"id": "01C3FWRDJJEX9K6Y3A4XDFXPBR",
|
||||
"allow-contrib": true,
|
||||
"require": "6.4.*"
|
||||
"require": "7.4.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4857
composer.lock
generated
4857
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ return [
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
|
||||
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
|
||||
JMS\SerializerBundle\JMSSerializerBundle::class => ['all' => true],
|
||||
FOS\RestBundle\FOSRestBundle::class => ['all' => true],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
framework:
|
||||
assets:
|
||||
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
|
||||
packages:
|
||||
avatars:
|
||||
base_path: 'avatars'
|
||||
@@ -1,17 +0,0 @@
|
||||
framework:
|
||||
cache:
|
||||
#app: cache.adapter.redis
|
||||
#default_redis_provider: redis://127.0.0.1:6379
|
||||
|
||||
#app: cache.adapter.memcached
|
||||
#default_memcached_provider: 'memcached://localhost'
|
||||
|
||||
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||
#app: cache.adapter.apcu
|
||||
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
||||
5
config/packages/dama_doctrine_test_bundle.yaml
Normal file
5
config/packages/dama_doctrine_test_bundle.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
when@test:
|
||||
dama_doctrine_test:
|
||||
enable_static_connection: true
|
||||
enable_static_meta_data_cache: true
|
||||
enable_static_query_cache: true
|
||||
5
config/packages/debug.yaml
Normal file
5
config/packages/debug.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
when@dev:
|
||||
debug:
|
||||
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
|
||||
# See the "server:dump" command to start a new server.
|
||||
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
|
||||
@@ -10,29 +10,31 @@ doctrine:
|
||||
default_connection: default
|
||||
connections:
|
||||
default:
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
use_savepoints: true
|
||||
# existing migrations will fail if the schema filter is activated
|
||||
#schema_filter: ~^(?!(bundle_migration_|kimai2_sessions))~
|
||||
url: '%env(DATABASE_URL)%'
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
driver: 'pdo_mysql'
|
||||
charset: utf8mb4
|
||||
default_table_options:
|
||||
charset: utf8mb4
|
||||
collation: utf8mb4_unicode_ci
|
||||
schema_manager_factory: doctrine.dbal.default_schema_manager_factory
|
||||
|
||||
types:
|
||||
datetime: App\Doctrine\UTCDateTimeType
|
||||
datetime_immutable: App\Doctrine\UTCDateTimeImmutableType
|
||||
orm:
|
||||
controller_resolver:
|
||||
# FIXME this is causing a deprecation and needs to be changed
|
||||
# auto_mapping: false
|
||||
auto_mapping: true
|
||||
auto_generate_proxy_classes: '%kernel.debug%'
|
||||
auto_mapping: false
|
||||
auto_generate_proxy_classes: true
|
||||
default_entity_manager: default
|
||||
enable_lazy_ghost_objects: true
|
||||
enable_native_lazy_objects: true
|
||||
entity_managers:
|
||||
default:
|
||||
identity_generation_preferences:
|
||||
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
||||
validate_xml_mapping: true
|
||||
report_fields_where_declared: true
|
||||
connection: default
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
@@ -56,17 +58,23 @@ when@test:
|
||||
connections:
|
||||
default:
|
||||
logging: false
|
||||
use_savepoints: true
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
orm:
|
||||
metadata_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
auto_generate_proxy_classes: false
|
||||
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
result_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.result_cache_pool
|
||||
|
||||
framework:
|
||||
cache:
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
# namespace is arbitrary but should be different from App\Migrations
|
||||
# as migrations classes should NOT be autoloaded
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
enable_profiler: false
|
||||
transactional: false
|
||||
storage:
|
||||
table_storage:
|
||||
table_name: 'migration_versions'
|
||||
migrations_paths:
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
custom_template: '%kernel.project_dir%/migrations/MigrationTemplate.txt'
|
||||
custom_template: '%kernel.project_dir%/migrations/MigrationTemplate.txt'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Read the documentation: https://fosrestbundle.readthedocs.io/en/3.x/
|
||||
fos_rest:
|
||||
param_fetcher_listener:
|
||||
enabled: true
|
||||
@@ -39,3 +40,5 @@ fos_rest:
|
||||
- { path: ^/api, prefer_extension: true, fallback_format: json, priorities: [ json ] }
|
||||
zone:
|
||||
- { path: ^/api/* }
|
||||
service:
|
||||
view_handler: App\API\ViewHandler
|
||||
@@ -1,6 +1,7 @@
|
||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
parameters:
|
||||
env(TRUSTED_PROXIES): ''
|
||||
env(TRUSTED_HOSTS): ''
|
||||
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
@@ -14,6 +15,7 @@ framework:
|
||||
enable_attributes: true
|
||||
|
||||
trusted_proxies: '%env(string:TRUSTED_PROXIES)%'
|
||||
trusted_hosts: '%env(string:TRUSTED_HOSTS)%'
|
||||
|
||||
exceptions:
|
||||
App\Validator\ValidationFailedException:
|
||||
@@ -25,6 +27,7 @@ framework:
|
||||
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||
# Remove or comment this section to explicitly disable session support.
|
||||
session:
|
||||
name: KIMAI_SESSION
|
||||
handler_id: App\Security\SessionHandler
|
||||
cookie_secure: auto
|
||||
cookie_samesite: lax
|
||||
@@ -34,6 +37,9 @@ framework:
|
||||
php_errors:
|
||||
log: true
|
||||
|
||||
property_info:
|
||||
with_constructor_extractor: true
|
||||
|
||||
mailer:
|
||||
dsn: '%env(MAILER_URL)%'
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@ jms_serializer:
|
||||
datetime:
|
||||
default_format: 'Y-m-d\TH:i:sO' # DATE_ISO8601
|
||||
visitors:
|
||||
json_serialization:
|
||||
options:
|
||||
- JSON_UNESCAPED_SLASHES
|
||||
- JSON_PRESERVE_ZERO_FRACTION
|
||||
xml_serialization:
|
||||
format_output: '%kernel.debug%'
|
||||
metadata:
|
||||
@@ -18,3 +14,20 @@ jms_serializer:
|
||||
excluded: []
|
||||
property_naming:
|
||||
id: 'jms_serializer.identical_property_naming_strategy'
|
||||
|
||||
when@prod:
|
||||
jms_serializer:
|
||||
visitors:
|
||||
json_serialization:
|
||||
options:
|
||||
- JSON_UNESCAPED_SLASHES
|
||||
- JSON_PRESERVE_ZERO_FRACTION
|
||||
|
||||
when@dev:
|
||||
jms_serializer:
|
||||
visitors:
|
||||
json_serialization:
|
||||
options:
|
||||
- JSON_PRETTY_PRINT
|
||||
- JSON_UNESCAPED_SLASHES
|
||||
- JSON_PRESERVE_ZERO_FRACTION
|
||||
|
||||
@@ -119,8 +119,8 @@ kimai:
|
||||
roles:
|
||||
ROLE_USER: ['view_team_member','time_team_project','create_tag','view_reporting']
|
||||
ROLE_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile','view_team_member','hours_other_profile']
|
||||
ROLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member','view_all_data','contract_other_profile','hours_other_profile']
|
||||
ROLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','supervisor_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member','upload_invoice_template','view_all_data','contract_other_profile','hours_other_profile']
|
||||
ROLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member','view_all_data','contract_other_profile','hours_other_profile','create_export_template']
|
||||
ROLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','supervisor_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member','upload_invoice_template','view_all_data','contract_other_profile','hours_other_profile','create_export_template']
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,50 @@
|
||||
monolog:
|
||||
channels: ["deprecation"]
|
||||
channels:
|
||||
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: notice
|
||||
handler: file_log
|
||||
excluded_http_codes:
|
||||
- {400: ['^/api/']}
|
||||
- {401: ['^/api/']}
|
||||
- 403
|
||||
- 404
|
||||
- 405
|
||||
channels: ["!event", "!deprecation"]
|
||||
file_log:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
formatter: monolog.formatter.kimai
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!deprecation"]
|
||||
deprecation:
|
||||
type: stream
|
||||
channels: ["deprecation"]
|
||||
path: "%kernel.logs_dir%/deprecations.log"
|
||||
formatter: monolog.formatter.deprecation
|
||||
|
||||
when@test:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
channels: ["!event"]
|
||||
nested:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
formatter: monolog.formatter.kimai
|
||||
|
||||
when@prod:
|
||||
monolog:
|
||||
@@ -24,42 +69,3 @@ when@prod:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!deprecation"]
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: notice
|
||||
handler: file_log
|
||||
excluded_http_codes:
|
||||
- {400: ['^/api/']}
|
||||
- {401: ['^/api/']}
|
||||
- 403
|
||||
- 404
|
||||
- {405: ['/homepage$', '/login_check$', '/export/data$']}
|
||||
channels: ["!event", "!deprecation"]
|
||||
file_log:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
formatter: monolog.formatter.kimai
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!deprecation"]
|
||||
deprecation:
|
||||
type: stream
|
||||
channels: ["deprecation"]
|
||||
path: "%kernel.logs_dir%/deprecations.log"
|
||||
formatter: monolog.formatter.deprecation
|
||||
|
||||
when@test:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: info
|
||||
channels: ["!event"]
|
||||
formatter: monolog.formatter.kimai
|
||||
|
||||
@@ -3,20 +3,20 @@ nelmio_api_doc:
|
||||
models:
|
||||
use_jms: true
|
||||
names:
|
||||
- { alias: CustomerEditForm, type: App\Form\API\CustomerApiEditForm, groups: [Default, Entity, Customer] }
|
||||
- { alias: CustomerEditForm, type: App\Form\API\CustomerApiEditForm, groups: [Default, Entity, Customer, Customer_Entity] }
|
||||
- { alias: CustomerEntity, type: App\Entity\Customer, groups: [Default, Entity, Customer, Customer_Entity, Not_Expanded] }
|
||||
- { alias: Customer, type: App\Entity\Customer, groups: [Default, Not_Expanded] }
|
||||
- { alias: CustomerRate, type: App\Entity\CustomerRate, groups: [Default, Entity, Customer_Rate] }
|
||||
- { alias: CustomerRateForm, type: App\Form\API\CustomerRateApiForm, groups: [Default, Entity, Customer_Rate] }
|
||||
- { alias: CustomerCollection, type: App\Entity\Customer, groups: [Default, Collection, Customer] }
|
||||
- { alias: ProjectEditForm, type: App\Form\API\ProjectApiEditForm, groups: [Default, Entity, Project] }
|
||||
- { alias: ProjectEditForm, type: App\Form\API\ProjectApiEditForm, groups: [Default, Entity, Project, Project_Entity] }
|
||||
- { alias: ProjectEntity, type: App\Entity\Project, groups: [Default, Entity, Project, Project_Entity, Not_Expanded] }
|
||||
- { alias: Project, type: App\Entity\Project, groups: [Default, Not_Expanded] }
|
||||
- { alias: ProjectExpanded, type: App\Entity\Project, groups: [Default, Expanded] }
|
||||
- { alias: ProjectRate, type: App\Entity\ProjectRate, groups: [Default, Entity, Project_Rate] }
|
||||
- { alias: ProjectRateForm, type: App\Form\API\ProjectRateApiForm, groups: [Default, Entity, Project_Rate] }
|
||||
- { alias: ProjectCollection, type: App\Entity\Project, groups: [Default, Collection, Project] }
|
||||
- { alias: ActivityEditForm, type: App\Form\API\ActivityApiEditForm, groups: [Default, Entity, Activity] }
|
||||
- { alias: ActivityEditForm, type: App\Form\API\ActivityApiEditForm, groups: [Default, Entity, Activity, Activity_Entity] }
|
||||
- { alias: ActivityEntity, type: App\Entity\Activity, groups: [Default, Entity, Activity, Activity_Entity, Not_Expanded] }
|
||||
- { alias: Activity, type: App\Entity\Activity, groups: [Default, Not_Expanded] }
|
||||
- { alias: ActivityExpanded, type: App\Entity\Activity, groups: [Default, Expanded] }
|
||||
@@ -29,7 +29,7 @@ nelmio_api_doc:
|
||||
- { alias: TimesheetEntity, type: App\Entity\Timesheet, groups: [Default, Entity, Timesheet, Timesheet_Entity, Not_Expanded] }
|
||||
- { alias: TimesheetExpanded, type: App\Entity\Timesheet, groups: [Default, Entity, Timesheet, Timesheet_Entity, Expanded] }
|
||||
- { alias: TimesheetCollection, type: App\Entity\Timesheet, groups: [Default, Collection, Timesheet, Not_Expanded] }
|
||||
- { alias: TimesheetCollectionExpanded, type: App\Entity\Timesheet, groups: [Default, Collection, Timesheet, Subresource, Expanded] }
|
||||
- { alias: TimesheetCollectionExpanded, type: App\Entity\Timesheet, groups: [Default, Collection, Timesheet, Expanded] }
|
||||
- { alias: UserCreateForm, type: App\Form\API\UserApiCreateForm, groups: [Default, Entity, User, User_Entity] }
|
||||
- { alias: UserEditForm, type: App\Form\API\UserApiEditForm, groups: [Default, Entity, User, User_Entity] }
|
||||
- { alias: User, type: App\Entity\User, groups: [Default] }
|
||||
|
||||
@@ -17,7 +17,7 @@ nelmio_cors:
|
||||
'^/api/':
|
||||
# allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
|
||||
allow_origin: ['*']
|
||||
allow_headers: ['Content-Type', 'Authorization', 'X-AUTH-USER', 'X-AUTH-TOKEN']
|
||||
allow_headers: ['Content-Type', 'Authorization']
|
||||
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
# expose_headers: ['Link']
|
||||
max_age: 3600
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
parameters:
|
||||
env(default_uri): 'http://localhost'
|
||||
|
||||
framework:
|
||||
router:
|
||||
utf8: true
|
||||
@@ -5,7 +8,7 @@ framework:
|
||||
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
#default_uri: http://localhost
|
||||
default_uri: '%env(DEFAULT_URI)%'
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
security:
|
||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||
password_hashers:
|
||||
App\Entity\User: auto
|
||||
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
chain_provider:
|
||||
chain:
|
||||
@@ -11,12 +12,11 @@ security:
|
||||
class: App\Entity\User
|
||||
kimai_ldap:
|
||||
id: App\Ldap\LdapUserProvider
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
# Ensure dev tools and static assets are always allowed
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
|
||||
api:
|
||||
access_token:
|
||||
token_handler: App\API\Authentication\AccessTokenHandler
|
||||
@@ -27,84 +27,70 @@ security:
|
||||
stateless: true
|
||||
remember_me: false
|
||||
provider: chain_provider
|
||||
custom_authenticators:
|
||||
- App\API\Authentication\TokenAuthenticator
|
||||
|
||||
secured_area:
|
||||
kimai_ldap: ~
|
||||
pattern: ^/
|
||||
user_checker: App\Security\UserChecker
|
||||
stateless: false
|
||||
|
||||
entry_point: form_login
|
||||
|
||||
custom_authenticators:
|
||||
- App\Saml\SamlAuthenticator
|
||||
|
||||
remember_me:
|
||||
name: KIMAI_REMEMBER
|
||||
secret: '%kernel.secret%'
|
||||
lifetime: 604800
|
||||
path: /
|
||||
always_remember_me: true
|
||||
|
||||
# activate all configured user provider
|
||||
provider: chain_provider
|
||||
|
||||
form_login:
|
||||
check_path: security_check
|
||||
login_path: login
|
||||
enable_csrf: true
|
||||
|
||||
two_factor:
|
||||
auth_form_path: 2fa_login
|
||||
check_path: 2fa_login_check
|
||||
remember_me_sets_trusted: true
|
||||
|
||||
logout:
|
||||
path: logout
|
||||
target: homepage
|
||||
enable_csrf: false
|
||||
|
||||
login_throttling:
|
||||
max_attempts: 5
|
||||
interval: '5 minutes'
|
||||
|
||||
login_link:
|
||||
check_route: link_login_check
|
||||
signature_properties: ['id']
|
||||
lifetime: 900
|
||||
max_uses: 3
|
||||
|
||||
access_decision_manager:
|
||||
# only grants access if there is no voter denying access
|
||||
strategy: unanimous
|
||||
allow_if_all_abstain: false
|
||||
|
||||
role_hierarchy:
|
||||
ROLE_USER: ~
|
||||
ROLE_TEAMLEAD: ROLE_USER
|
||||
ROLE_ADMIN: ROLE_TEAMLEAD
|
||||
ROLE_USER: ~
|
||||
ROLE_TEAMLEAD: ROLE_USER
|
||||
ROLE_ADMIN: ROLE_TEAMLEAD
|
||||
ROLE_SUPER_ADMIN: ROLE_ADMIN
|
||||
|
||||
# Note: Only the *first* matching rule is applied
|
||||
access_control:
|
||||
- { path: '^/auth/2fa', role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
|
||||
- { path: '^/auth', roles: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}$', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/auth', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/login', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/register', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/resetting', role: PUBLIC_ACCESS }
|
||||
- { path: '^/{_locale}/', roles: ROLE_USER }
|
||||
- { path: '^/api', roles: IS_AUTHENTICATED }
|
||||
- {path: '^/auth/2fa', role: IS_AUTHENTICATED_2FA_IN_PROGRESS}
|
||||
- {path: '^/auth', roles: PUBLIC_ACCESS}
|
||||
- {path: '^/{_locale}$', role: PUBLIC_ACCESS}
|
||||
- {path: '^/{_locale}/auth', role: PUBLIC_ACCESS}
|
||||
- {path: '^/{_locale}/login', role: PUBLIC_ACCESS}
|
||||
- {path: '^/{_locale}/register', role: PUBLIC_ACCESS}
|
||||
- {path: '^/{_locale}/resetting', role: PUBLIC_ACCESS}
|
||||
- {path: '^/{_locale}/', roles: ROLE_USER}
|
||||
- {path: '^/api', roles: IS_AUTHENTICATED}
|
||||
|
||||
when@test:
|
||||
# this configuration simplifies testing URLs protected by the security mechanism
|
||||
# See https://symfony.com/doc/current/cookbook/testing/http_authentication.html
|
||||
security:
|
||||
password_hashers:
|
||||
# Password hashers are resource-intensive by design to ensure security.
|
||||
# In tests, it's safe to reduce their cost to improve performance.
|
||||
App\Entity\User:
|
||||
algorithm: auto
|
||||
# see https://github.com/symfony/recipes/pull/1026
|
||||
cost: 4 # Lowest possible value for bcrypt
|
||||
time_cost: 3 # Lowest possible value for argon
|
||||
memory_cost: 10 # Lowest possible value for argon
|
||||
|
||||
@@ -6,20 +6,18 @@
|
||||
#
|
||||
tabler:
|
||||
options:
|
||||
# dark or normal layout
|
||||
theme_auto: true
|
||||
dark_mode: false
|
||||
# condensed = one nav-bar, false = two nav-bars
|
||||
navbar_condensed: true
|
||||
# overlap = https://preview.tabler.io/layout-navbar-overlap.html
|
||||
navbar_overlap: true
|
||||
# boxed layout (true) or full-screen (false)
|
||||
navbar_overlap: false
|
||||
navbar_dark: true
|
||||
boxed_layout: false
|
||||
# right to left = true
|
||||
rtl_mode: false
|
||||
# true = only avatar, false = show username and title as well
|
||||
user_menu_condensed: true
|
||||
# url to your company logo
|
||||
user_menu_condensed: false
|
||||
logo_url: "touch-icon-192x192.png"
|
||||
theme_base: "neutral"
|
||||
theme_radius: 0.5
|
||||
theme_primary: "blue"
|
||||
|
||||
knp_menu:
|
||||
enable: false
|
||||
@@ -47,6 +45,7 @@ tabler:
|
||||
barcode: fas fa-barcode
|
||||
bookmark: far fa-star
|
||||
bookmarked: fas fa-star
|
||||
break: fas fa-utensils
|
||||
calendar: far fa-calendar-alt
|
||||
cancel: fas fa-times
|
||||
clock: far fa-clock
|
||||
@@ -68,10 +67,10 @@ tabler:
|
||||
documentation: fas fa-book
|
||||
dot: fas fa-circle
|
||||
download: fas fa-download
|
||||
duration: far fa-hourglass
|
||||
duration: fas fa-stopwatch
|
||||
edit: far fa-edit
|
||||
end: fas fa-stopwatch
|
||||
export: fas fa-file-export
|
||||
end: fas fa-hourglass-end
|
||||
export: fas fa-download
|
||||
failure: fas fa-times
|
||||
fax: fas fa-fax
|
||||
filter: fas fa-filter
|
||||
@@ -93,6 +92,7 @@ tabler:
|
||||
manual: fas fa-book
|
||||
mobile: fas fa-mobile
|
||||
money: fas fa-coins
|
||||
move: fas fa-up-down-left-right
|
||||
ods: fas fa-table
|
||||
off: fas fa-toggle-off
|
||||
on: fas fa-toggle-on
|
||||
@@ -116,6 +116,7 @@ tabler:
|
||||
rejected: fas fa-ban
|
||||
right: fas fa-chevron-right
|
||||
right2: fas fa-angle-double-right
|
||||
remove: fas fa-xmark
|
||||
roles: fas fa-user-shield
|
||||
save: far fa-save
|
||||
search: fas fa-search
|
||||
@@ -139,7 +140,5 @@ tabler:
|
||||
visibility: far fa-eye
|
||||
warning: fas fa-exclamation
|
||||
weekly-times: fas fa-th
|
||||
widget_add: fas fa-folder-plus
|
||||
widget_remove: fas fa-folder-minus
|
||||
work_times: fas fa-calculator
|
||||
xlsx: fas fa-file-excel
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
twig:
|
||||
debug: '%kernel.debug%'
|
||||
strict_variables: '%kernel.debug%'
|
||||
file_name_pattern: '*.twig'
|
||||
form_themes:
|
||||
- 'form/vertical.html.twig'
|
||||
exception_controller: null
|
||||
paths:
|
||||
'%kernel.project_dir%/templates/bundles/TablerBundle': theme
|
||||
'%kernel.project_dir%/vendor/kevinpapst/tabler-bundle/templates': theme
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { only_exceptions: false }
|
||||
profiler:
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
web_profiler:
|
||||
toolbar: false
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { collect: false }
|
||||
profiler:
|
||||
collect: false
|
||||
collect_serializer_data: true
|
||||
|
||||
@@ -1,17 +1,45 @@
|
||||
webpack_encore:
|
||||
# The path where Encore is building the assets.
|
||||
# This should match Encore.setOutputPath() in webpack.config.js.
|
||||
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
|
||||
output_path: '%kernel.project_dir%/public/build'
|
||||
# If multiple builds are defined (as shown below), you can disable the default build:
|
||||
# output_path: false
|
||||
|
||||
# if using Encore.enableIntegrityHashes() specify the crossorigin attribute value (default: false, or use 'anonymous' or 'use-credentials')
|
||||
# crossorigin: 'anonymous'
|
||||
|
||||
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||
# Available in version 1.2
|
||||
cache: '%kernel.debug%'
|
||||
# Set attributes that will be rendered on all script and link tags
|
||||
script_attributes:
|
||||
defer: true
|
||||
# Uncomment (also under link_attributes) if using Turbo Drive
|
||||
# https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change
|
||||
# 'data-turbo-track': reload
|
||||
# link_attributes:
|
||||
# Uncomment if using Turbo Drive
|
||||
# 'data-turbo-track': reload
|
||||
|
||||
when@dev:
|
||||
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
|
||||
# crossorigin: 'anonymous'
|
||||
|
||||
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
|
||||
# preload: true
|
||||
|
||||
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
|
||||
# strict_mode: false
|
||||
|
||||
# If you have multiple builds:
|
||||
# builds:
|
||||
# frontend: '%kernel.project_dir%/public/frontend/build'
|
||||
|
||||
# pass the build name as the 3rd argument to the Twig functions
|
||||
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
|
||||
|
||||
framework:
|
||||
assets:
|
||||
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
|
||||
|
||||
when@prod:
|
||||
webpack_encore:
|
||||
cache: false
|
||||
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||
# Available in version 1.2
|
||||
cache: true
|
||||
|
||||
#when@test:
|
||||
# webpack_encore:
|
||||
# strict_mode: false
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json
|
||||
|
||||
# This file is the entry point to configure the routes of your app.
|
||||
# Methods with the #[Route] attribute are automatically imported.
|
||||
# See also https://symfony.com/doc/current/routing.html
|
||||
|
||||
# To list all registered routes, run the following command:
|
||||
# bin/console debug:router
|
||||
|
||||
controllers:
|
||||
# resource: routing.controllers
|
||||
resource: ../src/Controller/
|
||||
type: attribute
|
||||
prefix: /{_locale}
|
||||
@@ -8,7 +18,8 @@ controllers:
|
||||
api.swagger_ui:
|
||||
path: /api/doc
|
||||
methods: GET
|
||||
defaults: { _controller: nelmio_api_doc.controller.stoplight }
|
||||
defaults:
|
||||
_controller: nelmio_api_doc.controller.stoplight
|
||||
|
||||
api:
|
||||
resource: ../src/API/
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||
prefix: /{_locale}/_error
|
||||
defaults:
|
||||
_locale: '%locale%'
|
||||
@@ -1,7 +0,0 @@
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
prefix: /_profiler
|
||||
6
config/routes/framework.yaml
Normal file
6
config/routes/framework.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||
prefix: /{_locale}/_error
|
||||
defaults:
|
||||
_locale: '%locale%'
|
||||
3
config/routes/security.yaml
Normal file
3
config/routes/security.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
||||
8
config/routes/web_profiler.yaml
Normal file
8
config/routes/web_profiler.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
||||
prefix: /_profiler
|
||||
@@ -1,16 +1,22 @@
|
||||
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
|
||||
|
||||
# This file is the entry point to configure your own services.
|
||||
# Files in the packages/ subdirectory configure your dependencies.
|
||||
# See also https://symfony.com/doc/current/service_container/import.html
|
||||
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
locale: en
|
||||
# can be regenerated with: bin/console kimai:reset:locales
|
||||
kimai_locales: ['ar', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'ta', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_CZ', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_ES', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_FR', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GS', 'en_GU', 'en_GY', 'en_HK', 'en_HU', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_IT', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NO', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PL', 'en_PN', 'en_PR', 'en_PT', 'en_PW', 'en_RH', 'en_RO', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SK', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN']
|
||||
kimai_locales: ['ar', 'bg', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'ta', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_CZ', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_ES', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_FR', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GS', 'en_GU', 'en_GY', 'en_HK', 'en_HU', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_IT', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NO', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PL', 'en_PN', 'en_PR', 'en_PT', 'en_PW', 'en_RH', 'en_RO', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SK', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN']
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
public: false # Allows optimizing the container by removing unused services; this also means
|
||||
# fetching services directly from the container via $container->get() won't work.
|
||||
# The best practice is to be explicit about your dependencies anyway.
|
||||
public: false
|
||||
bind:
|
||||
$projectDirectory: '%kernel.project_dir%'
|
||||
$kernelEnvironment: '%kernel.environment%'
|
||||
@@ -18,14 +24,14 @@ services:
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
resource: '../src/*'
|
||||
resource: '../src/'
|
||||
exclude:
|
||||
- '../src/API/Model/'
|
||||
- '../src/DependencyInjection/'
|
||||
- '../src/Doctrine/Extensions/'
|
||||
- '../src/Entity/'
|
||||
- '../src/Event/'
|
||||
- '../src/Export/Package/'
|
||||
- '../src/Export/Base/'
|
||||
- '../src/Form/Model/'
|
||||
- '../src/Model/'
|
||||
- '../src/Repository/Loader/'
|
||||
@@ -36,6 +42,9 @@ services:
|
||||
- '../src/Kernel.php'
|
||||
- '../src/Constants.php'
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
||||
# controllers are imported separately to make sure services can be injected
|
||||
# as action arguments even if you don't extend any base controller class
|
||||
App\Controller\:
|
||||
@@ -87,6 +96,9 @@ services:
|
||||
App\Timesheet\RateService:
|
||||
arguments: ['%kimai.timesheet.rates%']
|
||||
|
||||
App\Timesheet\RateCalculator\RateCalculatorMode:
|
||||
factory: ['@App\Timesheet\RateCalculator\RateCalculatorFactory', getRateCalculatorMode]
|
||||
|
||||
# ================================================================================
|
||||
# SECURITY & VOTER
|
||||
# ================================================================================
|
||||
@@ -113,99 +125,104 @@ services:
|
||||
# ================================================================================
|
||||
|
||||
App\Repository\TimesheetRepository:
|
||||
class: App\Repository\TimesheetRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\TimesheetRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Timesheet']
|
||||
|
||||
App\Repository\UserRepository:
|
||||
class: App\Repository\UserRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\UserRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\User']
|
||||
|
||||
App\Repository\TeamRepository:
|
||||
class: App\Repository\TeamRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\TeamRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Team']
|
||||
|
||||
App\Repository\ActivityRepository:
|
||||
class: App\Repository\ActivityRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\ActivityRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Activity']
|
||||
|
||||
App\Repository\ProjectRepository:
|
||||
class: App\Repository\ProjectRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\ProjectRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Project']
|
||||
|
||||
App\Repository\TagRepository:
|
||||
class: App\Repository\TagRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\TagRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Tag']
|
||||
|
||||
App\Repository\CustomerRepository:
|
||||
class: App\Repository\CustomerRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\CustomerRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Customer']
|
||||
|
||||
App\Repository\InvoiceTemplateRepository:
|
||||
class: App\Repository\InvoiceTemplateRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\InvoiceTemplateRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\InvoiceTemplate']
|
||||
|
||||
App\Repository\ConfigurationRepository:
|
||||
class: App\Repository\ConfigurationRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\ConfigurationRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Configuration']
|
||||
|
||||
App\Repository\RoleRepository:
|
||||
class: App\Repository\RoleRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\RoleRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Role']
|
||||
|
||||
App\Repository\RolePermissionRepository:
|
||||
class: App\Repository\RolePermissionRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\RolePermissionRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\RolePermission']
|
||||
|
||||
|
||||
App\Repository\InvoiceDocumentRepository:
|
||||
class: App\Repository\InvoiceDocumentRepository
|
||||
arguments: ['%kimai.invoice.documents%']
|
||||
|
||||
App\Repository\CustomerRateRepository:
|
||||
class: App\Repository\CustomerRateRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\CustomerRateRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\CustomerRate']
|
||||
|
||||
App\Repository\ActivityRateRepository:
|
||||
class: App\Repository\ActivityRateRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\ActivityRateRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\ActivityRate']
|
||||
|
||||
App\Repository\ProjectRateRepository:
|
||||
class: App\Repository\ProjectRateRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\ProjectRateRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\ProjectRate']
|
||||
|
||||
App\Repository\InvoiceRepository:
|
||||
class: App\Repository\InvoiceRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\InvoiceRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Invoice']
|
||||
|
||||
App\Repository\BookmarkRepository:
|
||||
class: App\Repository\BookmarkRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\BookmarkRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\Bookmark']
|
||||
|
||||
App\Repository\WorkingTimeRepository:
|
||||
class: App\Repository\WorkingTimeRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\WorkingTimeRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\WorkingTime']
|
||||
|
||||
App\Repository\AccessTokenRepository:
|
||||
class: App\Repository\AccessTokenRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
class: App\Repository\AccessTokenRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\AccessToken']
|
||||
|
||||
App\Repository\ExportTemplateRepository:
|
||||
class: App\Repository\ExportTemplateRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\ExportTemplate']
|
||||
|
||||
monolog.formatter.kimai:
|
||||
class: Monolog\Formatter\LineFormatter
|
||||
arguments:
|
||||
@@ -217,3 +234,12 @@ services:
|
||||
arguments:
|
||||
- "[%%datetime%%] %%message%% %%context%%\n"
|
||||
- "Y-m-d H:i:s"
|
||||
|
||||
App\API\ViewHandler:
|
||||
arguments: ['@fos_rest.view_handler.default']
|
||||
|
||||
App\Validator\Constraints\TimesheetAllValidator:
|
||||
arguments: ['%kimai.validator_timesheet%']
|
||||
|
||||
App\Validator\Constraints\QuickEntryTimesheetValidator:
|
||||
arguments: ['%kimai.validator_timesheet%']
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
parameters:
|
||||
locale: en
|
||||
kimai_locales: ['ar', 'cs', 'de', 'de_CH', 'da', 'en', 'eo', 'es', 'eu', 'fr', 'he', 'hu', 'it', 'ja', 'ko', 'nl', 'pl', 'pt_BR', 'ro', 'ru', 'sk', 'sv', 'tr', 'vi', 'zh_CN']
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
public: true
|
||||
|
||||
App\Configuration\SystemConfiguration:
|
||||
arguments: ['@App\Configuration\ConfigurationService', "%kimai.config%"]
|
||||
|
||||
# required for the importer command test
|
||||
App\Repository\UserRepository:
|
||||
class: Doctrine\ORM\EntityRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments: ['App\Entity\User']
|
||||
|
||||
# required for the importer command test
|
||||
App\Importer\ImporterService:
|
||||
public: true
|
||||
arguments: ['@App\Customer\CustomerService', '@App\Project\ProjectService']
|
||||
|
||||
App\User\UserService:
|
||||
public: true
|
||||
arguments: ['@App\Repository\UserRepository', '@event_dispatcher', '@validator', '@App\Configuration\SystemConfiguration', '@security.user_password_hasher']
|
||||
1
kimai.sh
1
kimai.sh
@@ -22,6 +22,7 @@ function update_kimai() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf var/sessions/ 2>&1
|
||||
rm -rf var/cache/* 2>&1
|
||||
git fetch --tags
|
||||
git checkout "$VERSION"
|
||||
|
||||
@@ -28,13 +28,13 @@ class Version20190510205245 extends AbstractMigration
|
||||
$timesheetTags->addColumn('tag_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||
$timesheetTags->addIndex(['timesheet_id'], 'IDX_E3284EFEABDD46BE');
|
||||
$timesheetTags->addIndex(['tag_id'], 'IDX_E3284EFEBAD26311');
|
||||
$timesheetTags->setPrimaryKey(['timesheet_id', 'tag_id']);
|
||||
$this->addPrimaryKeyConstraint($timesheetTags, ['timesheet_id', 'tag_id']);
|
||||
|
||||
$tags = $schema->createTable('kimai2_tags');
|
||||
$tags->addColumn('id', 'integer', ['length' => 11, 'autoincrement' => true, 'notnull' => true]);
|
||||
$tags->addColumn('name', 'string', ['length' => 100, 'notnull' => true]);
|
||||
$tags->addUniqueIndex(['name'], 'UNIQ_27CAF54C5E237E06');
|
||||
$tags->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($tags, ['id']);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
||||
@@ -34,7 +34,7 @@ final class Version20190617100845 extends AbstractMigration
|
||||
$timesheetMeta->addColumn('name', 'string', ['notnull' => true, 'length' => 50]);
|
||||
$timesheetMeta->addColumn('value', 'string', ['notnull' => false, 'length' => 255]);
|
||||
$timesheetMeta->addColumn('visible', 'boolean', ['notnull' => false, 'default' => false]);
|
||||
$timesheetMeta->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($timesheetMeta, ['id']);
|
||||
$timesheetMeta->addIndex(['timesheet_id'], 'IDX_CB606CBAABDD46BE');
|
||||
$timesheetMeta->addUniqueIndex(['timesheet_id', 'name'], 'UNIQ_CB606CBAABDD46BE5E237E06');
|
||||
$timesheetMeta->addForeignKeyConstraint('kimai2_timesheet', ['timesheet_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_CB606CBAABDD46BE');
|
||||
@@ -45,7 +45,7 @@ final class Version20190617100845 extends AbstractMigration
|
||||
$customerMeta->addColumn('name', 'string', ['notnull' => true, 'length' => 50]);
|
||||
$customerMeta->addColumn('value', 'string', ['notnull' => false, 'length' => 255]);
|
||||
$customerMeta->addColumn('visible', 'boolean', ['notnull' => false, 'default' => false]);
|
||||
$customerMeta->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($customerMeta, ['id']);
|
||||
$customerMeta->addIndex(['customer_id'], 'IDX_A48A760F9395C3F3');
|
||||
$customerMeta->addUniqueIndex(['customer_id', 'name'], 'UNIQ_A48A760F9395C3F35E237E06');
|
||||
$customerMeta->addForeignKeyConstraint('kimai2_customers', ['customer_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_A48A760F9395C3F3');
|
||||
@@ -56,7 +56,7 @@ final class Version20190617100845 extends AbstractMigration
|
||||
$projectMeta->addColumn('name', 'string', ['notnull' => true, 'length' => 50]);
|
||||
$projectMeta->addColumn('value', 'string', ['notnull' => false, 'length' => 255]);
|
||||
$projectMeta->addColumn('visible', 'boolean', ['notnull' => false, 'default' => false]);
|
||||
$projectMeta->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($projectMeta, ['id']);
|
||||
$projectMeta->addIndex(['project_id'], 'IDX_50536EF2166D1F9C');
|
||||
$projectMeta->addUniqueIndex(['project_id', 'name'], 'UNIQ_50536EF2166D1F9C5E237E06');
|
||||
$projectMeta->addForeignKeyConstraint('kimai2_projects', ['project_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_50536EF2166D1F9C');
|
||||
@@ -67,7 +67,7 @@ final class Version20190617100845 extends AbstractMigration
|
||||
$activityMeta->addColumn('name', 'string', ['notnull' => true, 'length' => 50]);
|
||||
$activityMeta->addColumn('value', 'string', ['notnull' => false, 'length' => 255]);
|
||||
$activityMeta->addColumn('visible', 'boolean', ['notnull' => false, 'default' => false]);
|
||||
$activityMeta->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($activityMeta, ['id']);
|
||||
$activityMeta->addIndex(['activity_id'], 'IDX_A7C0A43D81C06096');
|
||||
$activityMeta->addUniqueIndex(['activity_id', 'name'], 'UNIQ_A7C0A43D81C060965E237E06');
|
||||
$activityMeta->addForeignKeyConstraint('kimai2_activities', ['activity_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_A7C0A43D81C06096');
|
||||
|
||||
@@ -32,7 +32,7 @@ final class Version20190730123324 extends AbstractMigration
|
||||
$teams->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]);
|
||||
$teams->addColumn('name', 'string', ['notnull' => true, 'length' => 100]);
|
||||
$teams->addColumn('teamlead_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||
$teams->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($teams, ['id']);
|
||||
$teams->addUniqueIndex(['name'], 'UNIQ_3BEDDC7F5E237E06');
|
||||
$teams->addForeignKeyConstraint('kimai2_users', ['teamlead_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_3BEDDC7F8F7DE5D7');
|
||||
|
||||
@@ -41,21 +41,21 @@ final class Version20190730123324 extends AbstractMigration
|
||||
$userTeams->addColumn('team_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||
$userTeams->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_B5E92CF8A76ED395');
|
||||
$userTeams->addForeignKeyConstraint('kimai2_teams', ['team_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_B5E92CF8296CD8AE');
|
||||
$userTeams->setPrimaryKey(['user_id', 'team_id']);
|
||||
$this->addPrimaryKeyConstraint($userTeams, ['user_id', 'team_id']);
|
||||
|
||||
$customerTeams = $schema->createTable('kimai2_customers_teams');
|
||||
$customerTeams->addColumn('customer_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||
$customerTeams->addColumn('team_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||
$customerTeams->addForeignKeyConstraint('kimai2_customers', ['customer_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_50BD83889395C3F3');
|
||||
$customerTeams->addForeignKeyConstraint('kimai2_teams', ['team_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_50BD8388296CD8AE');
|
||||
$customerTeams->setPrimaryKey(['customer_id', 'team_id']);
|
||||
$this->addPrimaryKeyConstraint($customerTeams, ['customer_id', 'team_id']);
|
||||
|
||||
$projectTeams = $schema->createTable('kimai2_projects_teams');
|
||||
$projectTeams->addColumn('project_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||
$projectTeams->addColumn('team_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||
$projectTeams->addForeignKeyConstraint('kimai2_projects', ['project_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_9345D431166D1F9C');
|
||||
$projectTeams->addForeignKeyConstraint('kimai2_teams', ['team_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_9345D431296CD8AE');
|
||||
$projectTeams->setPrimaryKey(['project_id', 'team_id']);
|
||||
$this->addPrimaryKeyConstraint($projectTeams, ['project_id', 'team_id']);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
||||
@@ -31,7 +31,7 @@ final class Version20191108151534 extends AbstractMigration
|
||||
$roles = $schema->createTable('kimai2_roles');
|
||||
$roles->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]);
|
||||
$roles->addColumn('name', 'string', ['notnull' => true, 'length' => 50]);
|
||||
$roles->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($roles, ['id']);
|
||||
$roles->addUniqueIndex(['name'], 'roles_name');
|
||||
|
||||
$rolePermissions = $schema->createTable('kimai2_roles_permissions');
|
||||
@@ -39,7 +39,7 @@ final class Version20191108151534 extends AbstractMigration
|
||||
$rolePermissions->addColumn('role_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||
$rolePermissions->addColumn('permission', 'string', ['notnull' => true, 'length' => 50]);
|
||||
$rolePermissions->addColumn('allowed', 'boolean', ['notnull' => true, 'default' => false]);
|
||||
$rolePermissions->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($rolePermissions, ['id']);
|
||||
$rolePermissions->addUniqueIndex(['role_id', 'permission'], 'role_permission');
|
||||
$rolePermissions->addForeignKeyConstraint('kimai2_roles', ['role_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_D263A3B8D60322AC');
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ final class Version20200109102138 extends AbstractMigration
|
||||
$customerComment->addColumn('created_by_id', 'integer', ['notnull' => true]);
|
||||
$customerComment->addColumn('created_at', 'datetime', ['notnull' => true]);
|
||||
$customerComment->addColumn('pinned', 'boolean', ['notnull' => true, 'default' => false]);
|
||||
$customerComment->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($customerComment, ['id']);
|
||||
$customerComment->addIndex(['customer_id'], 'IDX_A5B142D99395C3F3');
|
||||
$customerComment->addForeignKeyConstraint('kimai2_customers', ['customer_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_A5B142D99395C3F3');
|
||||
$customerComment->addForeignKeyConstraint('kimai2_users', ['created_by_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_A5B142D9B03A8386');
|
||||
@@ -47,7 +47,7 @@ final class Version20200109102138 extends AbstractMigration
|
||||
$projectComment->addColumn('created_by_id', 'integer', ['notnull' => true]);
|
||||
$projectComment->addColumn('created_at', 'datetime', ['notnull' => true]);
|
||||
$projectComment->addColumn('pinned', 'boolean', ['notnull' => true, 'default' => false]);
|
||||
$projectComment->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($projectComment, ['id']);
|
||||
$projectComment->addIndex(['project_id'], 'IDX_29A23638166D1F9C');
|
||||
$projectComment->addForeignKeyConstraint('kimai2_projects', ['project_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_29A23638166D1F9C');
|
||||
$projectComment->addForeignKeyConstraint('kimai2_users', ['created_by_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_29A23638B03A8386');
|
||||
|
||||
@@ -37,7 +37,7 @@ final class Version20200205115243 extends AbstractMigration
|
||||
$customerRates->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_82AB0AECA76ED395');
|
||||
$customerRates->addForeignKeyConstraint('kimai2_customers', ['customer_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_82AB0AEC9395C3F3');
|
||||
$customerRates->addUniqueIndex(['user_id', 'customer_id'], 'UNIQ_82AB0AECA76ED3959395C3F3');
|
||||
$customerRates->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($customerRates, ['id']);
|
||||
|
||||
$projectRates = $schema->createTable('kimai2_projects_rates');
|
||||
$projectRates->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]);
|
||||
@@ -48,7 +48,7 @@ final class Version20200205115243 extends AbstractMigration
|
||||
$projectRates->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_41535D55A76ED395');
|
||||
$projectRates->addForeignKeyConstraint('kimai2_projects', ['project_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_41535D55166D1F9C');
|
||||
$projectRates->addUniqueIndex(['user_id', 'project_id'], 'UNIQ_41535D55A76ED395166D1F9C');
|
||||
$projectRates->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($projectRates, ['id']);
|
||||
|
||||
$activityRates = $schema->createTable('kimai2_activities_rates');
|
||||
$activityRates->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]);
|
||||
@@ -59,7 +59,7 @@ final class Version20200205115243 extends AbstractMigration
|
||||
$activityRates->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_4A7F11BEA76ED395');
|
||||
$activityRates->addForeignKeyConstraint('kimai2_activities', ['activity_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_4A7F11BE81C06096');
|
||||
$activityRates->addUniqueIndex(['user_id', 'activity_id'], 'UNIQ_4A7F11BEA76ED39581C06096');
|
||||
$activityRates->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($activityRates, ['id']);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
||||
@@ -45,7 +45,7 @@ final class Version20200308171950 extends AbstractMigration
|
||||
$invoices->addUniqueIndex(['invoice_filename'], 'UNIQ_76C38E372323B33D');
|
||||
$invoices->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_76C38E37A76ED395');
|
||||
$invoices->addForeignKeyConstraint('kimai2_customers', ['customer_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_76C38E379395C3F3');
|
||||
$invoices->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($invoices, ['id']);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
||||
@@ -31,7 +31,7 @@ final class Version20200524142042 extends AbstractMigration
|
||||
$sessions->addColumn('data', 'blob', ['length' => 65535, 'notnull' => true]);
|
||||
$sessions->addColumn('time', 'integer', ['unsigned' => true, 'notnull' => true]);
|
||||
$sessions->addColumn('lifetime', 'integer', ['unsigned' => true, 'notnull' => true]);
|
||||
$sessions->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($sessions, ['id']);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
||||
@@ -33,7 +33,7 @@ final class Version20200725213424 extends AbstractMigration
|
||||
$activityTeams->addColumn('team_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||
$activityTeams->addForeignKeyConstraint('kimai2_activities', ['activity_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_986998DA81C06096');
|
||||
$activityTeams->addForeignKeyConstraint('kimai2_teams', ['team_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_986998DA296CD8AE');
|
||||
$activityTeams->setPrimaryKey(['activity_id', 'team_id']);
|
||||
$this->addPrimaryKeyConstraint($activityTeams, ['activity_id', 'team_id']);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
||||
@@ -36,7 +36,7 @@ final class Version20210316224358 extends AbstractMigration
|
||||
$bookmarks->addColumn('content', 'text', ['notnull' => true]);
|
||||
$bookmarks->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_4016EF25A76ED395');
|
||||
$bookmarks->addUniqueIndex(['user_id', 'name'], 'UNIQ_4016EF25A76ED3955E237E06');
|
||||
$bookmarks->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($bookmarks, ['id']);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
||||
@@ -30,7 +30,7 @@ final class Version20210802174318 extends AbstractMigration
|
||||
$teamMember->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]);
|
||||
$teamMember->addColumn('teamlead', 'boolean', ['notnull' => true, 'default' => false]);
|
||||
$teamMember->dropPrimaryKey();
|
||||
$teamMember->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($teamMember, ['id']);
|
||||
$teamMember->addUniqueIndex(['user_id', 'team_id'], 'UNIQ_B5E92CF8A76ED395296CD8AE');
|
||||
}
|
||||
|
||||
@@ -41,6 +41,6 @@ final class Version20210802174318 extends AbstractMigration
|
||||
$teamMember->dropPrimaryKey();
|
||||
$teamMember->dropColumn('teamlead');
|
||||
$teamMember->dropColumn('id');
|
||||
$teamMember->setPrimaryKey(['user_id', 'team_id']);
|
||||
$this->addPrimaryKeyConstraint($teamMember, ['user_id', 'team_id']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ final class Version20220101204501 extends AbstractMigration
|
||||
$table->addColumn('name', 'string', ['notnull' => true, 'length' => 50]);
|
||||
$table->addColumn('value', 'text', ['notnull' => false, 'length' => 65535]);
|
||||
$table->addColumn('visible', 'boolean', ['notnull' => true, 'default' => false]);
|
||||
$table->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($table, ['id']);
|
||||
$table->addIndex(['invoice_id'], 'IDX_7EDC37D92989F1FD');
|
||||
$table->addUniqueIndex(['invoice_id', 'name'], 'UNIQ_7EDC37D92989F1FD5E237E06');
|
||||
$table->addForeignKeyConstraint('kimai2_invoices', ['invoice_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_7EDC37D92989F1FD');
|
||||
|
||||
@@ -35,7 +35,7 @@ final class Version20230327143628 extends AbstractMigration
|
||||
$workingTimes->addColumn('expected', 'integer', ['notnull' => true]);
|
||||
$workingTimes->addColumn('actual', 'integer', ['notnull' => true]);
|
||||
$workingTimes->addColumn('approved_at', 'datetime', ['notnull' => false, 'default' => null]);
|
||||
$workingTimes->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($workingTimes, ['id']);
|
||||
$workingTimes->addIndex(['user_id'], 'IDX_F95E4933A76ED395');
|
||||
$workingTimes->addIndex(['approved_by'], 'IDX_F95E49334EA3CB3D');
|
||||
$workingTimes->addUniqueIndex(['user_id', 'date'], 'UNIQ_F95E4933A76ED395AA9E377A');
|
||||
|
||||
@@ -33,7 +33,7 @@ final class Version20240214061246 extends AbstractMigration
|
||||
$accessTokens->addColumn('last_usage', 'datetime_immutable', ['notnull' => false, 'default' => null]);
|
||||
$accessTokens->addColumn('expires_at', 'datetime_immutable', ['notnull' => false, 'default' => null]);
|
||||
|
||||
$accessTokens->setPrimaryKey(['id']);
|
||||
$this->addPrimaryKeyConstraint($accessTokens, ['id']);
|
||||
|
||||
$accessTokens->addIndex(['user_id'], 'IDX_6FB0DB1EA76ED395');
|
||||
$accessTokens->addUniqueIndex(['token'], 'UNIQ_6FB0DB1E5F37A13B');
|
||||
|
||||
51
migrations/Version20250608143244.php
Normal file
51
migrations/Version20250608143244.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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 DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.36.0
|
||||
*/
|
||||
final class Version20250608143244 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create the export template table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$table = $schema->createTable('kimai2_export_templates');
|
||||
|
||||
$table->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]);
|
||||
$table->addColumn('title', 'string', ['notnull' => true, 'length' => 100]);
|
||||
$table->addColumn('renderer', 'string', ['notnull' => true, 'length' => 20]);
|
||||
$table->addColumn('language', 'string', ['notnull' => false, 'length' => 6]);
|
||||
$table->addColumn('columns', 'json', ['notnull' => true]);
|
||||
$table->addColumn('options', 'json', ['notnull' => true]);
|
||||
|
||||
$this->addPrimaryKeyConstraint($table, ['id']);
|
||||
$table->addUniqueIndex(['title'], 'UNIQ_2F0CA26F2B36786B');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
if ($schema->hasTable('kimai2_export_templates')) {
|
||||
$schema->dropTable('kimai2_export_templates');
|
||||
}
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
46
migrations/Version20251031142000.php
Normal file
46
migrations/Version20251031142000.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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 DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.41
|
||||
*/
|
||||
final class Version20251031142000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add dedicated address fields for customer and customer-field for invoice template';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE kimai2_customers ADD address_line1 VARCHAR(150) DEFAULT NULL, ADD address_line2 VARCHAR(150) DEFAULT NULL, ADD address_line3 VARCHAR(150) DEFAULT NULL, ADD postcode VARCHAR(20) DEFAULT NULL, ADD city VARCHAR(50) DEFAULT NULL, ADD buyer_reference VARCHAR(50) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE kimai2_invoice_templates ADD customer_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE kimai2_invoice_templates MODIFY company VARCHAR(255) NULL');
|
||||
$this->addSql('ALTER TABLE kimai2_invoice_templates ADD CONSTRAINT FK_1626CFE99395C3F3 FOREIGN KEY (customer_id) REFERENCES kimai2_customers (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_1626CFE99395C3F3 ON kimai2_invoice_templates (customer_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE kimai2_invoice_templates DROP FOREIGN KEY FK_1626CFE99395C3F3');
|
||||
$this->addSql('DROP INDEX IDX_1626CFE99395C3F3 ON kimai2_invoice_templates');
|
||||
$this->addSql('ALTER TABLE kimai2_invoice_templates DROP customer_id');
|
||||
$this->addSql('ALTER TABLE kimai2_customers DROP address_line1, DROP address_line2, DROP address_line3, DROP postcode, DROP city, DROP buyer_reference');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
43
migrations/Version20251031143000.php
Normal file
43
migrations/Version20251031143000.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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 DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.41
|
||||
*/
|
||||
final class Version20251031143000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create the invoice template meta table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE kimai2_invoice_templates_meta (id INT AUTO_INCREMENT NOT NULL, template_id INT NOT NULL, name VARCHAR(50) NOT NULL, value TEXT DEFAULT NULL, visible TINYINT(1) DEFAULT 0 NOT NULL, INDEX IDX_A165B0555DA0FB8 (template_id), UNIQUE INDEX UNIQ_A165B0555DA0FB85E237E06 (template_id, name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE kimai2_invoice_templates_meta ADD CONSTRAINT FK_A165B0555DA0FB8 FOREIGN KEY (template_id) REFERENCES kimai2_invoice_templates (id) ON DELETE CASCADE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$table = $schema->getTable('kimai2_invoice_templates_meta');
|
||||
$table->removeForeignKey('FK_A165B0555DA0FB8');
|
||||
|
||||
$schema->dropTable('kimai2_invoice_templates_meta');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
50
migrations/Version20251214160001.php
Normal file
50
migrations/Version20251214160001.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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 DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* @version 2.45
|
||||
*/
|
||||
final class Version20251214160001 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add index for a query called on every timesheet page';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$table = $schema->getTable('kimai2_tags');
|
||||
|
||||
if (!$table->hasIndex('IDX_27CAF54C7AB0E859')) {
|
||||
// used to count the tags for the dropdown (filter and timesheet edit)
|
||||
$table->addIndex(['visible'], 'IDX_27CAF54C7AB0E859');
|
||||
}
|
||||
// deleted in this release
|
||||
$this->addSql("DELETE from kimai2_configuration where name = 'defaults.user.language'");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$table = $schema->getTable('kimai2_tags');
|
||||
|
||||
if ($table->hasIndex('IDX_27CAF54C7AB0E859')) {
|
||||
$table->dropIndex('IDX_27CAF54C7AB0E859');
|
||||
}
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
109
migrations/Version20260101010000.php
Normal file
109
migrations/Version20260101010000.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?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 DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
/**
|
||||
* @version 3.0
|
||||
*/
|
||||
final class Version20260101010000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Kimai 3.0 update';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$connection = $this->connection;
|
||||
|
||||
$rows = $connection->fetchAllAssociative('SELECT id, roles FROM kimai2_users');
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$id = $row['id'];
|
||||
$value = $row['roles'];
|
||||
$data = [];
|
||||
|
||||
if (\is_string($value) && str_starts_with($value, 'a:')) {
|
||||
$data = unserialize($value);
|
||||
}
|
||||
|
||||
if (!\is_array($data)) {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$json = json_encode($data);
|
||||
|
||||
$connection->executeStatement('UPDATE kimai2_users SET roles = :data WHERE id = :id', [
|
||||
'data' => $json,
|
||||
'id' => $id,
|
||||
]);
|
||||
}
|
||||
|
||||
$table = $schema->getTable('kimai2_users');
|
||||
$table->dropColumn('api_token');
|
||||
|
||||
$column = $table->getColumn('roles');
|
||||
$column->setType(Type::getType(Types::JSON));
|
||||
$column->setComment('(DC2Type:json)');
|
||||
$column->setNotnull(true);
|
||||
|
||||
$table = $schema->getTable('kimai2_timesheet');
|
||||
$table->dropColumn('category');
|
||||
|
||||
$this->addSql("DELETE FROM kimai2_user_preferences WHERE `name` = 'export_decimal'");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$connection = $this->connection;
|
||||
|
||||
// this will leave behind a json_valid() check in mariadb
|
||||
$connection->executeStatement('ALTER TABLE kimai2_users CHANGE roles roles LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\'');
|
||||
|
||||
// Fetch the existing rows from the table
|
||||
$rows = $connection->fetchAllAssociative('SELECT id, roles FROM kimai2_users');
|
||||
|
||||
// Iterate over each row
|
||||
foreach ($rows as $row) {
|
||||
$id = $row['id'];
|
||||
$roles = $row['roles'];
|
||||
$data = [];
|
||||
|
||||
if (json_validate($roles)) {
|
||||
$data = json_decode($roles, true);
|
||||
}
|
||||
|
||||
if (!\is_array($data)) {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$connection->executeStatement('UPDATE kimai2_users SET roles = :roles WHERE id = :id', [
|
||||
'roles' => serialize($data),
|
||||
'id' => $id,
|
||||
]);
|
||||
}
|
||||
|
||||
$timesheetTable = $schema->getTable('kimai2_timesheet');
|
||||
$timesheetTable->addColumn('category', 'string', ['length' => 10, 'notnull' => true, 'default' => 'work']);
|
||||
|
||||
$usersTable = $schema->getTable('kimai2_users');
|
||||
$usersTable->addColumn('api_token', 'string', ['length' => 255, 'notnull' => false, 'default' => null]);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
112
package.json
112
package.json
@@ -1,57 +1,59 @@
|
||||
{
|
||||
"name": "kimai",
|
||||
"homepage": "https://github.com/kimai/kimai",
|
||||
"license": "AGPL",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Kevin Papst",
|
||||
"url": "https://www.kevinpapst.de"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/kimai/kimai.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev-server": "yarn encore dev-server",
|
||||
"dev": "yarn encore dev",
|
||||
"watch": "yarn encore dev --watch",
|
||||
"lint": "yarn eslint assets/js/",
|
||||
"build": "yarn encore production"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7",
|
||||
"@babel/eslint-parser": "^7",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7",
|
||||
"@babel/preset-env": "^7",
|
||||
"@eslint/js": "^9",
|
||||
"@fortawesome/fontawesome-free": "^6",
|
||||
"@fullcalendar/bootstrap5": "^5",
|
||||
"@fullcalendar/core": "^5",
|
||||
"@fullcalendar/daygrid": "^5",
|
||||
"@fullcalendar/google-calendar": "^5",
|
||||
"@fullcalendar/icalendar": "^5",
|
||||
"@fullcalendar/interaction": "^5",
|
||||
"@fullcalendar/timegrid": "^5",
|
||||
"@popperjs/core": "^2",
|
||||
"@symfony/webpack-encore": "^5",
|
||||
"@tabler/core": "1.0.0-beta21",
|
||||
"bootstrap": "^5.3",
|
||||
"chart.js": "^4",
|
||||
"core-js": "^3",
|
||||
"eslint": "^9",
|
||||
"globals": "^15",
|
||||
"gridstack": "^7",
|
||||
"litepicker": "^2",
|
||||
"luxon": "^3",
|
||||
"sass": "^1",
|
||||
"sass-loader": "^16",
|
||||
"tom-select": "^2",
|
||||
"webpack": "^5",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"packageManager": "yarn@4.5.3"
|
||||
"name": "kimai",
|
||||
"homepage": "https://github.com/kimai/kimai",
|
||||
"license": "AGPL",
|
||||
"private": true,
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Kevin Papst",
|
||||
"url": "https://www.kevinpapst.de"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/kimai/kimai.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev-server": "yarn encore dev-server",
|
||||
"dev": "yarn encore dev",
|
||||
"watch": "yarn encore dev --watch",
|
||||
"lint": "yarn eslint assets/js/",
|
||||
"build": "yarn encore production"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7",
|
||||
"@babel/eslint-parser": "^7",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7",
|
||||
"@babel/preset-env": "^7",
|
||||
"@eslint/js": "^9",
|
||||
"@fortawesome/fontawesome-free": "^6",
|
||||
"@fullcalendar/bootstrap5": "^5",
|
||||
"@fullcalendar/core": "^5",
|
||||
"@fullcalendar/daygrid": "^5",
|
||||
"@fullcalendar/google-calendar": "^5",
|
||||
"@fullcalendar/icalendar": "^5",
|
||||
"@fullcalendar/interaction": "^5",
|
||||
"@fullcalendar/timegrid": "^5",
|
||||
"@popperjs/core": "^2",
|
||||
"@symfony/webpack-encore": "^5",
|
||||
"@tabler/core": "1.4",
|
||||
"bootstrap": "^5.3",
|
||||
"chart.js": "^4",
|
||||
"core-js": "^3",
|
||||
"eslint": "^9",
|
||||
"globals": "^15",
|
||||
"highlight.js": "^11",
|
||||
"litepicker": "^2",
|
||||
"luxon": "^3",
|
||||
"sass": "^1",
|
||||
"sass-loader": "^16",
|
||||
"sortablejs": "^1.15",
|
||||
"tom-select": "^2",
|
||||
"webpack": "^5",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"packageManager": "yarn@4.5.3"
|
||||
}
|
||||
|
||||
637
phpstan.neon
637
phpstan.neon
@@ -41,6 +41,7 @@ parameters:
|
||||
symfony:
|
||||
containerXmlPath: %rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml
|
||||
ignoreErrors:
|
||||
- identifier: offsetAccess.notFound
|
||||
- '#^Method .*\(\) has parameter \$builder with generic interface Symfony\\Component\\Form\\FormBuilderInterface but does not specify its types\: TData$#'
|
||||
|
||||
-
|
||||
@@ -48,16 +49,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/API/ActivityController.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\API\\\\Authentication\\\\TokenAuthenticator\\:\\:getCredentials\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/API/Authentication/TokenAuthenticator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$plainPassword of method Symfony\\\\Component\\\\PasswordHasher\\\\PasswordHasherInterface\\:\\:verify\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/API/Authentication/TokenAuthenticator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$name of method App\\\\Entity\\\\Customer\\:\\:getMetaField\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
@@ -123,11 +114,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/API/TagController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$datetime of method App\\\\Timesheet\\\\DateTimeFactory\\:\\:createDateTime\\(\\) expects string, mixed given\\.$#"
|
||||
count: 3
|
||||
path: src/API/TimesheetController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$statistic of class App\\\\Event\\\\ActivityStatisticEvent constructor expects App\\\\Model\\\\ActivityStatistic, App\\\\Model\\\\ActivityStatistic\\|null given\\.$#"
|
||||
count: 1
|
||||
@@ -183,11 +169,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Command/AbstractUserCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$string of function trim expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/AbstractUserCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$username of method App\\\\User\\\\UserService\\:\\:findUserByUsernameOrThrowException\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
@@ -408,11 +389,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Command/PromoteUserCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\<0, max\\>\\|false given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/RegenerateLocalesCommand.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method find\\(\\) on Symfony\\\\Component\\\\Console\\\\Application\\|null\\.$#"
|
||||
count: 4
|
||||
@@ -423,11 +399,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Command/ReloadCommand.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property SimpleXMLElement\\|false\\:\\:\\$file\\.$#"
|
||||
count: 1
|
||||
path: src/Command/TranslationCommand.php
|
||||
|
||||
-
|
||||
message: "#^Argument of an invalid type list\\<string\\>\\|false supplied for foreach, only iterables are supported\\.$#"
|
||||
count: 4
|
||||
@@ -588,11 +559,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Configuration\\\\SystemConfiguration\\:\\:getCustomerDefaultCurrency\\(\\) should return string but returns bool\\|float\\|int\\|string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Configuration\\\\SystemConfiguration\\:\\:getCustomerDefaultTimezone\\(\\) should return string\\|null but returns bool\\|float\\|int\\|string\\|null\\.$#"
|
||||
count: 1
|
||||
@@ -603,41 +569,11 @@ parameters:
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Configuration\\\\SystemConfiguration\\:\\:getUserDefaultCurrency\\(\\) should return string but returns bool\\|float\\|int\\|string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Configuration\\\\SystemConfiguration\\:\\:getUserDefaultLanguage\\(\\) should return string but returns bool\\|float\\|int\\|string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Configuration\\\\SystemConfiguration\\:\\:getUserDefaultTheme\\(\\) should return string\\|null but returns bool\\|float\\|int\\|string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Configuration\\\\SystemConfiguration\\:\\:getUserDefaultTimezone\\(\\) should return string\\|null but returns bool\\|float\\|int\\|string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Configuration\\\\SystemConfiguration\\:\\:offsetExists\\(\\) has parameter \\$offset with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Configuration\\\\SystemConfiguration\\:\\:offsetGet\\(\\) has parameter \\$offset with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$key of method App\\\\Configuration\\\\SystemConfiguration\\:\\:set\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Configuration/SystemConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$string of function trim expects string, bool\\|float\\|int\\|string given\\.$#"
|
||||
count: 1
|
||||
@@ -653,34 +589,9 @@ parameters:
|
||||
count: 1
|
||||
path: src/Controller/ActivityController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$name of class App\\\\Entity\\\\Team constructor expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/ActivityController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$replace of method App\\\\Repository\\\\ActivityRepository\\:\\:deleteActivity\\(\\) expects App\\\\Entity\\\\Activity\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/ActivityController.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'user' on mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/CalendarController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$name of class App\\\\Entity\\\\Team constructor expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/CustomerController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$replace of method App\\\\Repository\\\\CustomerRepository\\:\\:deleteCustomer\\(\\) expects App\\\\Entity\\\\Customer\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/CustomerController.php
|
||||
|
||||
-
|
||||
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
|
||||
count: 2
|
||||
count: 1
|
||||
path: src/Controller/DashboardController.php
|
||||
|
||||
-
|
||||
@@ -698,16 +609,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Controller/DashboardController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$name of method App\\\\Widget\\\\WidgetInterface\\:\\:setOption\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/DashboardController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$value of method App\\\\Widget\\\\WidgetInterface\\:\\:setOption\\(\\) expects bool\\|int\\|string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/DashboardController.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Controller\\\\DoctorController\\:\\:getComposerPackages\\(\\) should return array\\<string, string\\> but returns array\\<string, string\\|null\\>\\.$#"
|
||||
count: 1
|
||||
@@ -763,11 +664,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Controller/InvoiceController.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/InvoiceController.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getName\\(\\) on Symfony\\\\Component\\\\Form\\\\FormInterface\\|null\\.$#"
|
||||
count: 1
|
||||
@@ -803,21 +699,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Controller/ProjectController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$name of class App\\\\Entity\\\\Team constructor expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/ProjectController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$replace of method App\\\\Repository\\\\ProjectRepository\\:\\:deleteProject\\(\\) expects App\\\\Entity\\\\Project\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/ProjectController.php
|
||||
|
||||
-
|
||||
message: "#^Call to function array_key_exists\\(\\) with 'entry' and array\\{day\\: DateTime, entry\\: App\\\\Entity\\\\Timesheet\\} will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/QuickEntryController.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method format\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 1
|
||||
@@ -993,36 +874,16 @@ parameters:
|
||||
count: 1
|
||||
path: src/Customer/CustomerStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\DataFixtures\\\\InvoiceFixtures\\:\\:getInvoiceConfigs\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/DataFixtures/InvoiceFixtures.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\DataFixtures\\\\TeamFixtures\\:\\:getGroups\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/DataFixtures/TeamFixtures.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method setTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/DataFixtures/TimesheetFixtures.php
|
||||
|
||||
-
|
||||
message: "#^Cannot clone DateTime\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/DataFixtures/TimesheetFixtures.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$begin of method App\\\\Entity\\\\Timesheet\\:\\:setBegin\\(\\) expects DateTime, DateTime\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/DataFixtures/TimesheetFixtures.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$user of method App\\\\DataFixtures\\\\TimesheetFixtures\\:\\:createTimesheetEntry\\(\\) expects App\\\\Entity\\\\User, App\\\\Entity\\\\User\\|null given\\.$#"
|
||||
count: 2
|
||||
path: src/DataFixtures/TimesheetFixtures.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\DependencyInjection\\\\AppExtension\\:\\:createPermissionParameter\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -1268,21 +1129,11 @@ parameters:
|
||||
count: 1
|
||||
path: src/Entity/Team.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\Timesheet\\:\\:getCategory\\(\\) should return string but returns string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Entity/Timesheet.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\Timesheet\\:\\:getMetaFields\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#"
|
||||
count: 1
|
||||
path: src/Entity/Timesheet.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\Timesheet\\:\\:getTags\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#"
|
||||
count: 1
|
||||
path: src/Entity/Timesheet.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$name of method App\\\\Entity\\\\Timesheet\\:\\:getMetaField\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
@@ -1358,21 +1209,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Entity/User.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\User\\:\\:setConfirmationToken\\(\\) has parameter \\$confirmationToken with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Entity/User.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\User\\:\\:setPassword\\(\\) has parameter \\$password with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Entity/User.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\User\\:\\:setPlainPassword\\(\\) has parameter \\$password with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Entity/User.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\User\\:\\:setRoles\\(\\) has parameter \\$roles with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -1458,11 +1294,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Event/RevenueStatisticEvent.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Event\\\\ThemeJavascriptTranslationsEvent\\:\\:getTranslations\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Event/ThemeJavascriptTranslationsEvent.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$string of function strtolower expects string, string\\|null given\\.$#"
|
||||
count: 2
|
||||
@@ -1523,136 +1354,11 @@ parameters:
|
||||
count: 1
|
||||
path: src/Export/Annotation/Order.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getBudget\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getCurrency\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getCustomer\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 5
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getName\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimeBudget\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method jsonSerialize\\(\\) on App\\\\Model\\\\Statistic\\\\BudgetStatistic\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Base\\\\HtmlRenderer\\:\\:calculateActivityBudget\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Base\\\\HtmlRenderer\\:\\:calculateProjectBudget\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Base\\\\HtmlRenderer\\:\\:calculateSummary\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Base\\\\HtmlRenderer\\:\\:getOptions\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/HtmlRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getBudget\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getCurrency\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getCustomer\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 5
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getName\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimeBudget\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method jsonSerialize\\(\\) on App\\\\Model\\\\Statistic\\\\BudgetStatistic\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Base\\\\PDFRenderer\\:\\:calculateActivityBudget\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Base\\\\PDFRenderer\\:\\:calculateProjectBudget\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Base\\\\PDFRenderer\\:\\:calculateSummary\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Base\\\\PDFRenderer\\:\\:getOptions\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Base\\\\PDFRenderer\\:\\:getPdfOptions\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$filename of static method App\\\\Utils\\\\FileHelper\\:\\:convertToAsciiFilename\\(\\) expects string, array\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Base/PDFRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Property App\\\\Export\\\\Base\\\\PDFRenderer\\:\\:\\$pdfOptions type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -2010,7 +1716,7 @@ parameters:
|
||||
|
||||
-
|
||||
message: "#^Cannot clone mixed\\.$#"
|
||||
count: 2
|
||||
count: 1
|
||||
path: src/Form/QuickEntryForm.php
|
||||
|
||||
-
|
||||
@@ -2048,11 +1754,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Form/TimesheetEditForm.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method modify\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Form/TimesheetEditForm.php
|
||||
|
||||
-
|
||||
message: "#^Cannot clone DateTime\\|null\\.$#"
|
||||
count: 1
|
||||
@@ -2153,61 +1854,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Form/TimesheetPreCreateForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addActivityMultiChoice\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addActivitySelect\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addCustomerMultiChoice\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addCustomerSelect\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addDateRange\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addOrderBy\\(\\) has parameter \\$allowedColumns with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addProjectMultiChoice\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addProjectSelect\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addTeamsChoice\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\AbstractToolbarForm\\:\\:addUsersChoice\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$user of method App\\\\Repository\\\\Query\\\\BaseFormTypeQuery\\:\\:setUser\\(\\) expects App\\\\Entity\\\\User, mixed given\\.$#"
|
||||
count: 2
|
||||
path: src/Form/Toolbar/AbstractToolbarForm.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Toolbar\\\\ActivityToolbarForm\\:\\:addActivityMultiChoice\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -2823,11 +2469,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Form/Type/MetaFieldsCollectionType.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method modify\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Type/QuickEntryTimesheetType.php
|
||||
|
||||
-
|
||||
message: "#^Cannot cast mixed to int\\.$#"
|
||||
count: 2
|
||||
@@ -2838,21 +2479,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Form/Type/QuickEntryTimesheetType.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'activity' on mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Type/QuickEntryWeekType.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'project' on mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Type/QuickEntryWeekType.php
|
||||
|
||||
-
|
||||
message: "#^Cannot clone mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Type/QuickEntryWeekType.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Form\\\\Type\\\\SearchTermType\\:\\:buildForm\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -2903,11 +2529,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Form/UserTwoFactorType.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getVat\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/Calculator/AbstractCalculator.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
@@ -2958,31 +2579,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getSubtotal\\(\\) on App\\\\Invoice\\\\CalculatorInterface\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTax\\(\\) on App\\\\Invoice\\\\CalculatorInterface\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimeWorked\\(\\) on App\\\\Invoice\\\\CalculatorInterface\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTotal\\(\\) on App\\\\Invoice\\\\CalculatorInterface\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getVat\\(\\) on App\\\\Invoice\\\\CalculatorInterface\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Invoice\\\\Hydrator\\\\InvoiceModelDefaultHydrator\\:\\:hydrate\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -3013,16 +2609,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Invoice/Hydrator/InvoiceModelUserHydrator.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getCompany\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/InvoiceFilename.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getName\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/InvoiceFilename.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$filename of method App\\\\Invoice\\\\InvoiceFilename\\:\\:convert\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
@@ -3068,11 +2654,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Invoice/Renderer/AbstractSpreadsheetRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTitle\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/Renderer/AbstractSpreadsheetRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$haystack of function stripos expects string, mixed given\\.$#"
|
||||
count: 5
|
||||
@@ -3093,11 +2674,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Invoice/Renderer/AbstractTwigRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getLanguage\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/Renderer/AbstractTwigRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Invoice\\\\Renderer\\\\AbstractTwigRenderer\\:\\:renderTwigTemplate\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -3118,21 +2694,11 @@ parameters:
|
||||
count: 2
|
||||
path: src/Invoice/Renderer/DocxRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$filename of static method App\\\\Utils\\\\FileHelper\\:\\:convertToAsciiFilename\\(\\) expects string, array\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/Renderer/PdfRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getCustomer\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/ServiceInvoice.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getRenderer\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#"
|
||||
count: 6
|
||||
path: src/Invoice/ServiceInvoice.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method hasInvoiceTemplate\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 1
|
||||
@@ -3338,21 +2904,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Plugin/PluginMetadata.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'duration' on mixed\\.$#"
|
||||
count: 3
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'id' on mixed\\.$#"
|
||||
count: 4
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'lastRecord' on mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getEnd\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 2
|
||||
@@ -3383,31 +2934,11 @@ parameters:
|
||||
count: 2
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$datetime of method App\\\\Timesheet\\\\DateTimeFactory\\:\\:createDateTime\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$duration of method App\\\\Model\\\\TimesheetCountedStatistic\\:\\:setDuration\\(\\) expects int, float\\|int given\\.$#"
|
||||
count: 1
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$durationDay of method App\\\\Reporting\\\\ProjectView\\\\ProjectViewModel\\:\\:setDurationDay\\(\\) expects int, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$durationMonth of method App\\\\Reporting\\\\ProjectView\\\\ProjectViewModel\\:\\:setDurationMonth\\(\\) expects int, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$durationWeek of method App\\\\Reporting\\\\ProjectView\\\\ProjectViewModel\\:\\:setDurationWeek\\(\\) expects int, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Project/ProjectStatisticService.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$project of class App\\\\Reporting\\\\ProjectDetails\\\\ProjectDetailsModel constructor expects App\\\\Entity\\\\Project, App\\\\Entity\\\\Project\\|null given\\.$#"
|
||||
count: 1
|
||||
@@ -3558,11 +3089,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Repository/TimesheetRepository.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method add\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/TimesheetRepository.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getCustomer\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
||||
count: 1
|
||||
@@ -3665,7 +3191,7 @@ parameters:
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$identifier of method App\\\\Entity\\\\User\\:\\:setUserIdentifier\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 2
|
||||
count: 1
|
||||
path: src/Saml/SamlProvider.php
|
||||
|
||||
-
|
||||
@@ -3673,16 +3199,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Saml/SamlProvider.php
|
||||
|
||||
-
|
||||
message: "#^Property App\\\\Saml\\\\Security\\\\SamlAuthenticationFailureHandler\\:\\:\\$defaultOptions has no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Saml/Security/SamlAuthenticationFailureHandler.php
|
||||
|
||||
-
|
||||
message: "#^Property App\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandler\\:\\:\\$defaultOptions has no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Saml/Security/SamlAuthenticationSuccessHandler.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Security\\\\KimaiUserProvider\\:\\:getProviders\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -3723,91 +3239,36 @@ parameters:
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/CeilRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method setTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/CeilRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot clone DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/CeilRounding.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$begin of method App\\\\Entity\\\\Timesheet\\:\\:setBegin\\(\\) expects DateTime, DateTime\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Timesheet/Rounding/CeilRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/ClosestRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method setTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 4
|
||||
path: src/Timesheet/Rounding/ClosestRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot clone DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/ClosestRounding.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$begin of method App\\\\Entity\\\\Timesheet\\:\\:setBegin\\(\\) expects DateTime, DateTime\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Timesheet/Rounding/ClosestRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/DefaultRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method setTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/DefaultRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot clone DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/DefaultRounding.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$begin of method App\\\\Entity\\\\Timesheet\\:\\:setBegin\\(\\) expects DateTime, DateTime\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Timesheet/Rounding/DefaultRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/FloorRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method setTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/FloorRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot clone DateTime\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/Rounding/FloorRounding.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$begin of method App\\\\Entity\\\\Timesheet\\:\\:setBegin\\(\\) expects DateTime, DateTime\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Timesheet/Rounding/FloorRounding.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimezone\\(\\) on App\\\\Entity\\\\User\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Timesheet/TimesheetService.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getTimezone\\(\\) on DateTime\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Timesheet/TimesheetService.php
|
||||
|
||||
-
|
||||
message: "#^Cannot clone DateTime\\|null\\.$#"
|
||||
count: 1
|
||||
@@ -3833,11 +3294,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Timesheet/TimesheetService.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$begin of method App\\\\Entity\\\\Timesheet\\:\\:setBegin\\(\\) expects DateTime, DateTime\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Timesheet/TimesheetService.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Timesheet\\\\TimesheetStatisticService\\:\\:getDailyStatisticsGrouped\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -3943,21 +3399,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Twig/LocaleFormatExtensions.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Twig\\\\PaginationExtension\\:\\:createRouteGenerator\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Twig/PaginationExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Twig\\\\PaginationExtension\\:\\:renderPagination\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Twig/PaginationExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Twig\\\\PaginationExtension\\:\\:renderPagination\\(\\) has parameter \\$pager with generic class Pagerfanta\\\\Pagerfanta but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Twig/PaginationExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Twig\\\\Runtime\\\\ThemeExtension\\:\\:actions\\(\\) has parameter \\$payload with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -3973,16 +3414,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Twig/Runtime/ThemeExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Twig\\\\Runtime\\\\WidgetExtension\\:\\:renderWidget\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Twig/Runtime/WidgetExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\User\\\\LoginManager\\:\\:logInUser\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/User/LoginManager.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\User\\\\UserService\\:\\:updateUser\\(\\) has parameter \\$groups with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -4003,11 +3434,6 @@ parameters:
|
||||
count: 3
|
||||
path: src/Utils/Color.php
|
||||
|
||||
-
|
||||
message: "#^Class App\\\\Utils\\\\DataTable implements generic interface IteratorAggregate but does not specify its types\\: TKey, TValue$#"
|
||||
count: 1
|
||||
path: src/Utils/DataTable.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\DataTable\\:\\:addColumn\\(\\) has parameter \\$column with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -4018,11 +3444,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Utils/DataTable.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\DataTable\\:\\:getIterator\\(\\) should return Traversable\\<mixed, mixed\\> but returns Traversable\\<\\(int\\|string\\), mixed\\>\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/DataTable.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\DataTable\\:\\:getOptions\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -4163,11 +3584,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Utils/PageSetup.php
|
||||
|
||||
-
|
||||
message: "#^Class App\\\\Utils\\\\Pagination extends generic class Pagerfanta\\\\Pagerfanta but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Utils/Pagination.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$class of method App\\\\Utils\\\\PaginationTemplate\\:\\:linkLi\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
@@ -4179,29 +3595,14 @@ parameters:
|
||||
path: src/Utils/PaginationTemplate.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\ParsedownExtension\\:\\:blockHeader\\(\\) has no return type specified\\.$#"
|
||||
message: "#^Method App\\\\Utils\\\\Parsedown\\:\\:blockHeader\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/ParsedownExtension.php
|
||||
path: src/Utils/Parsedown.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\ParsedownExtension\\:\\:blockHeader\\(\\) has parameter \\$Line with no type specified\\.$#"
|
||||
message: "#^Method App\\\\Utils\\\\Parsedown\\:\\:blockHeader\\(\\) has parameter \\$Line with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/ParsedownExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\ParsedownExtension\\:\\:blockTable\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/ParsedownExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\ParsedownExtension\\:\\:blockTable\\(\\) has parameter \\$Block with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/ParsedownExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\ParsedownExtension\\:\\:blockTable\\(\\) has parameter \\$Line with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/ParsedownExtension.php
|
||||
path: src/Utils/Parsedown.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\ParsedownExtension\\:\\:inlineUrl\\(\\) has parameter \\$Excerpt with no type specified\\.$#"
|
||||
@@ -4216,7 +3617,7 @@ parameters:
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$str of function strtr expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/ParsedownExtension.php
|
||||
path: src/Utils/Parsedown.php
|
||||
|
||||
-
|
||||
message: "#^Property App\\\\Utils\\\\ParsedownExtension\\:\\:\\$BlockTypes has no type specified\\.$#"
|
||||
@@ -4288,16 +3689,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Utils/ReleaseVersion.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'pattern' on mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Validator/Constraints/Duration.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$pattern of method Symfony\\\\Component\\\\Validator\\\\Constraints\\\\Regex\\:\\:__construct\\(\\) expects array\\|string\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Validator/Constraints/Duration.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$value \\(App\\\\Entity\\\\Team\\) of method App\\\\Validator\\\\Constraints\\\\TeamValidator\\:\\:validate\\(\\) should be contravariant with parameter \\$value \\(mixed\\) of method Symfony\\\\Component\\\\Validator\\\\ConstraintValidatorInterface\\:\\:validate\\(\\)$#"
|
||||
count: 2
|
||||
@@ -4388,11 +3779,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Widget/Type/AbstractWidgetType.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Widget\\\\Type\\\\AbstractWidgetType\\:\\:setPermissions\\(\\) has parameter \\$permissions with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Widget/Type/AbstractWidgetType.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Widget\\\\Type\\\\PaginatedWorkingTimeChart\\:\\:getLastWeekInYear\\(\\) has parameter \\$year with no type specified\\.$#"
|
||||
count: 1
|
||||
@@ -4422,3 +3808,8 @@ parameters:
|
||||
message: "#^Method App\\\\Form\\\\MultiUpdate\\\\MultiUpdateTableDTO\\:\\:setEntities\\(\\) has parameter \\$entities with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection but does not specify its types\\: TKey, T$#"
|
||||
count: 1
|
||||
path: src/Form/MultiUpdate/MultiUpdateTableDTO.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\InvoiceTemplate\\:\\:getMetaFields\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#"
|
||||
count: 1
|
||||
path: src/Entity/InvoiceTemplate.php
|
||||
|
||||
144
phpunit.xml.dist
144
phpunit.xml.dist
@@ -1,67 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
failOnNotice="true"
|
||||
failOnWarning="true"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
cacheDirectory="var/cache/phpunit/">
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1"/>
|
||||
<ini name="max_execution_time" value="-1"/>
|
||||
<ini name="date.timezone" value="UTC"/>
|
||||
<ini name="intl.default_locale" value="en_US"/>
|
||||
<ini name="date.timezone" value="Europe/Vienna"/>
|
||||
<env name="KERNEL_CLASS" value="App\Kernel" force="true"/>
|
||||
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
|
||||
<env name="APP_ENV" value="test" force="true"/>
|
||||
<env name="APP_DEBUG" value="0" force="true"/>
|
||||
<env name="APP_SECRET" value="5a79a1c866efef9ca1800f971d689f3e" force="true"/>
|
||||
<env name="DATABASE_URL" value="mysql://kimai2_test:kimai2_test@127.0.0.1:3306/kimai2_test?charset=utf8mb4&serverVersion=10.5.8-MariaDB"/>
|
||||
<env name="CORS_ALLOW_ORIGIN" value="^https?://localhost(:[0-9]+)?$"/>
|
||||
<env name="MAILER_URL" value="null://null"/>
|
||||
<env name="MAILER_FROM" value="kimai@example.com"/>
|
||||
<!--
|
||||
REINSTALL THE TEST DATABASE, eg. AFTER CHANGING STRUCTURE!
|
||||
cacheDirectory="var/cache/phpunit/"
|
||||
displayDetailsOnTestsThatTriggerDeprecations="true"
|
||||
displayDetailsOnTestsThatTriggerErrors="true"
|
||||
displayDetailsOnTestsThatTriggerNotices="true"
|
||||
displayDetailsOnTestsThatTriggerWarnings="true"
|
||||
displayDetailsOnPhpunitDeprecations="true">
|
||||
<php>
|
||||
<ini name="display_errors" value="1" />
|
||||
<ini name="error_reporting" value="-1"/>
|
||||
<ini name="max_execution_time" value="-1"/>
|
||||
<ini name="date.timezone" value="UTC"/>
|
||||
<ini name="intl.default_locale" value="en_US"/>
|
||||
<ini name="date.timezone" value="Europe/Vienna"/>
|
||||
<env name="KERNEL_CLASS" value="App\Kernel" force="true"/>
|
||||
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
|
||||
<server name="SHELL_VERBOSITY" value="-1" />
|
||||
<env name="APP_DEBUG" value="0" force="true"/>
|
||||
<env name="DATABASE_URL" value="mysql://kimai2_test:kimai2_test@127.0.0.1:3306/kimai2_test?charset=utf8mb4&serverVersion=10.11.15-MariaDB"/>
|
||||
<!-- ###+ nelmio/cors-bundle ### -->
|
||||
<env name="CORS_ALLOW_ORIGIN" value="'^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'"/>
|
||||
<!-- ###- nelmio/cors-bundle ### -->
|
||||
<!--
|
||||
REINSTALL THE TEST DATABASE, eg. AFTER CHANGING STRUCTURE!
|
||||
|
||||
- change to "true"
|
||||
- composer tests
|
||||
- change back to "false"
|
||||
- change to "true"
|
||||
- composer tests
|
||||
- change back to "false"
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS `kimai2_test`;
|
||||
CREATE USER IF NOT EXISTS `kimai2_test`@127.0.0.1;
|
||||
ALTER USER `kimai2_test`@127.0.0.1 IDENTIFIED BY "kimai2_test";
|
||||
GRANT execute,select,insert,update,delete,create,alter,drop,index,references ON `kimai2_test`.* TO kimai2_test@127.0.0.1;
|
||||
-->
|
||||
<env name="BOOTSTRAP_RESET_DATABASE" value="true"/>
|
||||
</php>
|
||||
CREATE DATABASE IF NOT EXISTS `kimai2_test`;
|
||||
CREATE USER IF NOT EXISTS `kimai2_test`@127.0.0.1;
|
||||
ALTER USER `kimai2_test`@127.0.0.1 IDENTIFIED BY "kimai2_test";
|
||||
GRANT execute,select,insert,update,delete,create,alter,drop,index,references ON `kimai2_test`.* TO kimai2_test@127.0.0.1;
|
||||
-->
|
||||
<env name="BOOTSTRAP_RESET_DATABASE" value="true"/>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Kimai">
|
||||
<directory>tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<!-- ###+ symfony/framework-bundle ### -->
|
||||
<env name="APP_ENV" value="test" force="true"/>
|
||||
<env name="APP_SECRET" value="5a79a1c866efef9ca1800f971d689f3e" force="true"/>
|
||||
<env name="APP_SHARE_DIR" value="var/share"/>
|
||||
<!-- ###- symfony/framework-bundle ### -->
|
||||
<!-- ###+ symfony/mailer ### -->
|
||||
<env name="MAILER_URL" value="null://null"/>
|
||||
<env name="MAILER_FROM" value="kimai@example.com"/>
|
||||
<!-- ###- symfony/mailer ### -->
|
||||
|
||||
<coverage>
|
||||
<include>
|
||||
<directory suffix=".php">src/</directory>
|
||||
<directory suffix=".php">templates/</directory>
|
||||
</include>
|
||||
<exclude>
|
||||
<directory suffix=".php">migrations/</directory>
|
||||
<directory suffix=".php">assets/</directory>
|
||||
<directory suffix=".php">bin/</directory>
|
||||
<directory suffix=".php">config/</directory>
|
||||
<directory suffix=".php">node_modules/</directory>
|
||||
<directory suffix=".php">public/</directory>
|
||||
<directory suffix=".php">tests/</directory>
|
||||
<directory suffix=".php">translations/</directory>
|
||||
<directory suffix=".php">var/</directory>
|
||||
<directory suffix=".php">vendor/</directory>
|
||||
</exclude>
|
||||
</coverage>
|
||||
|
||||
<extensions>
|
||||
<bootstrap class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"/>
|
||||
</extensions>
|
||||
<!-- ###+ symfony/routing ### -->
|
||||
<!-- Configure how to generate URLs in non-HTTP contexts, such as CLI commands. -->
|
||||
<!-- See https://symfony.com/doc/current/routing.html#generating-urls-in-commands -->
|
||||
<env name="DEFAULT_URI" value="http://localhost"/>
|
||||
<!-- ###- symfony/routing ### -->
|
||||
|
||||
<!-- ###+ symfony/messenger ### -->
|
||||
<!-- Choose one of the transports below -->
|
||||
<!-- MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages -->
|
||||
<!-- MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages -->
|
||||
<env name="MESSENGER_TRANSPORT_DSN" value="doctrine://default?auto_setup=0"/>
|
||||
<!-- ###- symfony/messenger ### -->
|
||||
</php>
|
||||
<testsuites>
|
||||
<testsuite name="Kimai">
|
||||
<directory>tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<extensions>
|
||||
<bootstrap class="Symfony\Bridge\PhpUnit\SymfonyExtension">
|
||||
<parameter name="clock-mock-namespaces" value="App" />
|
||||
<parameter name="dns-mock-namespaces" value="App" />
|
||||
</bootstrap>
|
||||
<bootstrap class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"/>
|
||||
</extensions>
|
||||
<source ignoreSuppressionOfDeprecations="true" restrictNotices="true" restrictWarnings="true">
|
||||
<include>
|
||||
<directory suffix=".php">src/</directory>
|
||||
<directory suffix=".php">templates/</directory>
|
||||
</include>
|
||||
<exclude>
|
||||
<directory suffix=".php">migrations/</directory>
|
||||
<directory suffix=".php">assets/</directory>
|
||||
<directory suffix=".php">bin/</directory>
|
||||
<directory suffix=".php">config/</directory>
|
||||
<directory suffix=".php">node_modules/</directory>
|
||||
<directory suffix=".php">public/</directory>
|
||||
<directory suffix=".php">tests/</directory>
|
||||
<directory suffix=".php">translations/</directory>
|
||||
<directory suffix=".php">var/</directory>
|
||||
<directory suffix=".php">vendor/</directory>
|
||||
</exclude>
|
||||
</source>
|
||||
</phpunit>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<tile>
|
||||
<square150x150logo src="favicon/mstile-150x150.png"/>
|
||||
<square310x310logo src="favicon/mstile-large.jpg"/>
|
||||
<TileColor>#00a300</TileColor>
|
||||
<TileColor>#ffffff</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user