Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2ce42523a | ||
|
|
b2dc5b1035 | ||
|
|
752d0cfbf3 | ||
|
|
32c8ec2aaf | ||
|
|
2ffdaa088f | ||
|
|
2fafaabe40 | ||
|
|
b1d86b4836 | ||
|
|
384fe8b8ab | ||
|
|
d429c56687 | ||
|
|
8c52eac9f0 | ||
|
|
59ecbbb591 | ||
|
|
2e54a56996 | ||
|
|
6b2a56f492 | ||
|
|
5d48d463d9 | ||
|
|
f295d6e30e | ||
|
|
162acf37a8 | ||
|
|
37c741c48d | ||
|
|
bbdcf16a5d | ||
|
|
dfb0134137 | ||
|
|
6a86afb5fd | ||
|
|
677f8d776c | ||
|
|
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 |
@@ -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
|
||||
|
||||
|
||||
5
.github/workflows/docker.yaml
vendored
5
.github/workflows/docker.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: Determine version
|
||||
run: |
|
||||
input="${{ github.event.inputs.kimai_tag }}"
|
||||
|
||||
|
||||
# Determine between manual trigger and release event
|
||||
if [ -z "$input" ]; then
|
||||
echo "Using release tag: ${{ github.event.release.tag_name }}"
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
echo "Invalid version number: $version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo "kimai_version=$version" >> $GITHUB_ENV
|
||||
|
||||
- name: FPM image
|
||||
@@ -60,7 +60,6 @@ jobs:
|
||||
tags: |
|
||||
kimai/kimai2:latest
|
||||
kimai/kimai2:fpm
|
||||
kimai/kimai2:fpm-${{ env.kimai_version }}
|
||||
push: true
|
||||
|
||||
- name: Apache image
|
||||
|
||||
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,27 @@ 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
|
||||
- Replace Twig `AppVariable` with custom implementation
|
||||
- You need to adjust your templates if you access anything else then `app.locale`, `app.user`. `app.current_route`.
|
||||
- Most often used:
|
||||
- Replace `app.request.locale` with `app.locale`
|
||||
- Replace `app.request.attributes.get('_route')` with `app.current_route`
|
||||
|
||||
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).
|
||||
|
||||
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,7 @@ 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";
|
||||
|
||||
export default class KimaiLoader {
|
||||
|
||||
@@ -76,6 +77,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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ export default class KimaiAPILink extends KimaiPlugin {
|
||||
const ALERT = this.getContainer().getPlugin('alert');
|
||||
const successHandle = () => {
|
||||
EVENTS.trigger(eventName);
|
||||
document.dispatchEvent(new CustomEvent('kimai.reloadedContent'));
|
||||
if (attributes['msgSuccess'] !== undefined) {
|
||||
ALERT.success(attributes['msgSuccess']);
|
||||
}
|
||||
@@ -82,6 +83,7 @@ export default class KimaiAPILink extends KimaiPlugin {
|
||||
if (attributes['msgError'] !== undefined) {
|
||||
message = attributes['msgError'];
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent('kimai.reloadedContent'));
|
||||
API.handleError(message, error);
|
||||
};
|
||||
|
||||
@@ -90,6 +92,8 @@ export default class KimaiAPILink extends KimaiPlugin {
|
||||
data = attributes['payload'];
|
||||
}
|
||||
|
||||
document.dispatchEvent(new CustomEvent('kimai.reloadContent'));
|
||||
|
||||
if (method === 'PATCH') {
|
||||
API.patch(url, data, successHandle, errorHandle);
|
||||
} else if (method === 'POST') {
|
||||
|
||||
@@ -217,8 +217,6 @@ export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
|
||||
const eventName = form.dataset['formEvent'];
|
||||
/** @type {KimaiEvent} alert */
|
||||
const events = this.getContainer().getPlugin('event');
|
||||
/** @type {KimaiAlert} alert */
|
||||
const alert = this.getContainer().getPlugin('alert');
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -259,15 +257,8 @@ export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
|
||||
} else {
|
||||
events.trigger(eventName);
|
||||
|
||||
// try to find form defined message first, but
|
||||
let msg = form.dataset['msgSuccess'];
|
||||
// if that is not available: use a generic fallback message
|
||||
if (msg === null || msg === undefined || msg === '') {
|
||||
msg = 'action.update.success';
|
||||
}
|
||||
this._isDirty = false;
|
||||
this._getModal().hide();
|
||||
alert.success(msg);
|
||||
}
|
||||
});
|
||||
})
|
||||
@@ -277,6 +268,8 @@ export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
|
||||
message = 'action.update.error';
|
||||
}
|
||||
|
||||
/** @type {KimaiAlert} alert */
|
||||
const alert = this.getContainer().getPlugin('alert');
|
||||
alert.error(message, error.message);
|
||||
|
||||
// this is useful for changing form fields and retrying to save (and in development to test form changes)
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -42,13 +42,13 @@ export default class KimaiThemeInitializer extends KimaiPlugin {
|
||||
}
|
||||
|
||||
// at which element we append the loading screen
|
||||
let container = 'body';
|
||||
let container = 'div.page-wrapper';
|
||||
if (event.detail !== undefined && event.detail !== null) {
|
||||
container = event.detail;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -69,23 +70,6 @@ export default class KimaiCalendar {
|
||||
const DATES = this.kimai.getPlugin('date');
|
||||
/** @type {KimaiAjaxModalForm} MODAL */
|
||||
const MODAL = this.kimai.getPlugin('modal');
|
||||
/** @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
|
||||
@@ -119,7 +103,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 +139,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 +157,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 +258,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 =============
|
||||
@@ -282,6 +280,7 @@ export default class KimaiCalendar {
|
||||
droppable: true,
|
||||
// drop function handles external draggable events
|
||||
drop: (dropInfo) => {
|
||||
document.dispatchEvent(new CustomEvent('kimai.reloadContent'));
|
||||
const entry = dropInfo.draggedEl;
|
||||
const source = entry.parentElement;
|
||||
let data = JSON.parse(entry.dataset.entry);
|
||||
@@ -324,7 +323,7 @@ export default class KimaiCalendar {
|
||||
(result) => {
|
||||
const newItem = this.convertSourceForCalendar(result);
|
||||
this.getCalendar().addEvent(newItem, true);
|
||||
ALERT.success('action.update.success');
|
||||
document.dispatchEvent(new CustomEvent('kimai.reloadedContent'));
|
||||
}
|
||||
);
|
||||
} else {
|
||||
@@ -334,7 +333,7 @@ export default class KimaiCalendar {
|
||||
(result) => {
|
||||
const newItem = this.convertSourceForCalendar(result);
|
||||
this.getCalendar().addEvent(newItem, true);
|
||||
ALERT.success('action.update.success');
|
||||
document.dispatchEvent(new CustomEvent('kimai.reloadedContent'));
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -528,6 +527,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}
|
||||
@@ -665,8 +702,6 @@ export default class KimaiCalendar {
|
||||
|
||||
/** @type {KimaiAPI} API */
|
||||
const API = this.kimai.getPlugin('api');
|
||||
/** @type {KimaiAlert} ALERT */
|
||||
const ALERT = this.kimai.getPlugin('alert');
|
||||
/** @type {KimaiDateUtils} DATE */
|
||||
const DATES = this.kimai.getPlugin('date');
|
||||
|
||||
@@ -678,13 +713,85 @@ export default class KimaiCalendar {
|
||||
payload.end = null;
|
||||
}
|
||||
|
||||
document.dispatchEvent(new CustomEvent('kimai.reloadContent'));
|
||||
|
||||
const updateUrl = this.options.url.update(event.id);
|
||||
API.patch(updateUrl, JSON.stringify(payload), () => {
|
||||
ALERT.success('action.update.success');
|
||||
document.dispatchEvent(new CustomEvent('kimai.reloadedContent'));
|
||||
}, (error) => {
|
||||
eventArg.revert();
|
||||
document.dispatchEvent(new CustomEvent('kimai.reloadedContent'));
|
||||
API.handleError('action.update.error', error);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* @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,7 @@
|
||||
@import "modal";
|
||||
@import "progressbar";
|
||||
@import "avatar";
|
||||
@import "theme-dark";
|
||||
@import "pages";
|
||||
@import "weekly-hours";
|
||||
@import "tabler-fixes";
|
||||
@import "help";
|
||||
@import "rtl";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,27 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/* Highlighted text is not visible - https://github.com/tabler/tabler/issues/2603 */
|
||||
[data-bs-theme=dark] {
|
||||
::selection,
|
||||
.text-selected {
|
||||
background-color: var(--#{$prefix}primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
"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",
|
||||
"parsedown/parsedown": "^1.6",
|
||||
"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.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5083
composer.lock
generated
5083
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
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
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
|
||||
globals:
|
||||
app: '@App\Twig\AppVariable'
|
||||
|
||||
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,16 @@ 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%']
|
||||
|
||||
App\Twig\AppVariable:
|
||||
tags:
|
||||
- {name: twig.global, alias: app}
|
||||
|
||||
@@ -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",
|
||||
"gridstack": "^7",
|
||||
"highlight.js": "^11",
|
||||
"litepicker": "^2",
|
||||
"luxon": "^3",
|
||||
"sass": "^1",
|
||||
"sass-loader": "^16",
|
||||
"tom-select": "^2",
|
||||
"webpack": "^5",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"packageManager": "yarn@4.5.3"
|
||||
}
|
||||
|
||||
691
phpstan.neon
691
phpstan.neon
File diff suppressed because it is too large
Load Diff
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"/>
|
||||
<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>
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user